Partial Fix #5560 - Gitlive - branching wip
[gitlive] / GitMonitor.vala
1 /**
2
3 The monitor suffers from various issues - basically event flows while it's running...
4
5 normall operations
6
7 - monitors for file changes
8  -- adds to QUEUE when occurs.
9  
10 - queue runs in background.
11  - if it's got stuff in it..
12  - COMMIT (normally)
13  - now?? - if on master - try and branch
14    == do user selection, until we have branched
15    == then start monitoring again..
16    
17
18
19
20 */
21
22
23
24
25 public class GitMonitor : Monitor
26 {
27
28         public static GitMonitor gitmonitor;
29         /**
30          * @property {String} the "gitlive" directory, normally ~/gitlive
31          *  dset by OWNER... - we should do this as a CTOR.
32          *  
33          */
34         public static string gitlive;
35         
36         
37         private Gee.ArrayList<GitMonitorQueue> queue ;
38         public bool queueRunning = false;
39         
40         public DateTime lastAdd;
41
42  
43
44         public GitMonitor ()
45         {
46         
47                 this.queue = new Gee.ArrayList<GitMonitorQueue>();
48                 GitMonitor.gitmonitor = this;
49                 Timeout.add_full(Priority.LOW, 500, () => {
50
51                         //GLib.debug("TIMEOUT queue length = %d, is_runing = %s\n", (int)this.queue.length , this.queueRunning ? "Y" : "N");
52
53                         //stdout.printf("QL %u: QR: %d\n", this.queue.length, this.queueRunning ? 1 : 0);
54                         if (this.queue.size < 1  || this.queueRunning) {
55                                 return true;
56                         }
57                         
58                         var last = -1 * this.lastAdd.difference(new DateTime.now(new TimeZone.local()));
59  
60                         // stdout.printf("LAST RUN: %s (expect %s) \n" ,
61                         //         last.to_string(),   (5 * TimeSpan.SECOND).to_string() );
62  
63                         if (last < 5 * TimeSpan.SECOND) { // wait 5 seconds before running. ????
64                                 return true;
65                         }
66                         //_this.lastAdd = new Date();
67                         //return 1;
68                 
69                         this.runQueue();
70                         return true; //
71                 });
72                 
73         }
74
75         public new void pauseError(string failure) 
76         {
77         
78                 var notification = new Notify.Notification(
79                           "Git Live ERROR!!",
80                         failure,
81                         "dialog-information"
82                         
83                 );
84                         
85                 notification.set_timeout(60); // show errros for longer
86                 notification.show();
87                 
88         Canberra.Context context;
89         Canberra.Proplist props;
90
91         Canberra.Context.create (out context);
92         Canberra.Proplist.create (out props);
93
94         props.sets (Canberra.PROP_EVENT_ID, "phone-outgoing-busy");
95         props.sets (Canberra.PROP_EVENT_DESCRIPTION, "Gitlive stopped on error");
96  
97
98         context.play_full (0, props, null);
99
100
101                         
102         
103                 this.paused = true;
104                 this.queueRunning = false;
105                 // what does this do to the old one...
106                 //this.queue = new Gee.ArrayList<GitMonitorQueue> ();
107                 this.stop();
108                 StatusIconA.statusicon.pauseError();
109                 
110                 var m = new Gtk.MessageDialog(null, Gtk.DialogFlags.MODAL,Gtk.MessageType.ERROR,Gtk.ButtonsType.CLOSE,
111                         "A Serious problem occured running git, you will probably have to resolve this manually\n" + 
112                         "Committing is paused now, so fix the problem, close this window, then press start again\n\n\n" + 
113                         failure
114                 );
115                 m.set_keep_above(true);
116                 m.show();
117                 m.set_position(Gtk.WindowPosition.CENTER);
118                 m.response.connect( (id_pressed) => {
119                         m.hide();
120                 });
121                 
122         }
123  
124         public new void pause() {
125                 this.paused = true;
126                 // what does this do to the old one...
127                 //this.queue = new Gee.ArrayList<GitMonitorQueue> ();
128                 StatusIconA.statusicon.pause();
129
130         }
131         
132         public void restoreQueue( Gee.ArrayList<GitMonitorQueue> queue)
133         {
134                 //used to restore teh queue after switch branches?/ - breaks our privte queue idea..
135                 this.queue = queue;
136         }
137         
138         /*
139         public new void resume () {
140                 this.paused = false;
141                 this.queue = new Array<GitMonitorQueue> ();
142                 StatusIconA.statusicon.resume();
143                 
144                 
145         }
146         */
147         /**
148          * Start the monitoring
149          * and run the queue every 500 milliseconds..
150          *
151          */
152         public new void start() 
153         {
154                 StatusIconA.statusicon.refreshing();
155                 
156                  
157                 this.lastAdd = new DateTime.now(new TimeZone.local()); 
158                 
159                 Timeout.add_full(Priority.LOW, 500, () => {
160                         //stdout.printf("GitMonitor.start :: top.length = %u\n", this.top.length);
161                         // call this.monitor on each of 'top'
162                         for(int i = 0; i < this.top.length ; i++) {
163
164                                 this.monitor(this.top.index(i) );
165                         }
166                         StatusIconA.statusicon.resume();
167                     this.paused = false;
168                    
169                         
170                         try {
171
172                           
173                                 var notification = new Notify.Notification(
174                                         "Git Live",
175                                         "%s\nMonitoring %u Directories".printf(GitMonitor.gitlive, this.monitors.length), 
176                                          "dialog-information"
177                                 );
178                 
179                                 notification.set_timeout(5);
180                                 notification.show();
181                         } catch(Error e) {
182                                 GLib.debug("Error sending notification to screen: %s",e.message);
183                         }
184                         return false; // do not keep doing this..
185
186                 });
187                 
188
189                 
190                  
191         }
192
193
194         public new void stop() {
195                 StatusIconA.statusicon.pause();
196                 base.stop();
197         }
198         
199         
200         public override void monitor (string path,  int depth = 0)
201         {
202                 
203                 //GLib.debug("GitMonitor : monitor %d %s", depth, path);
204                 //var depth = typeof(depth) == 'number'  ? depth *1 : 0;
205                 
206                  
207                 // if we are not at top level.. and there is a .git directory  (it's a submodule .. ignore) 
208                 if (depth > 1 && FileUtils.test(path + "/.git" , FileTest.IS_DIR)) {
209                         return;
210                 }
211                 
212                 if (depth == 1) {
213                 
214                         if (!FileUtils.test(path + "/.git" , FileTest.IS_DIR)) {
215                                 return; // skip non-git directories..
216                         }
217                 
218                         GitRepo.get(path);
219                 
220                         // FIXME - check if repo is flagged as not autocommit..
221                         //var repo = imports.Scm.Repo.Repo.get(path);
222                         //if (!repo || !repo.autocommit()) {
223                         //    return;
224                         //} 
225                 }
226                 
227                 
228                 // check if the repo is to be monitored.
229                 //print("PATH : " + path);
230                 
231                 
232                 base.monitor(path, depth);
233         }
234
235         
236
237         /**
238          * run the queue.
239          * - pulls the items off the queue 
240          *    (as commands run concurrently and new items may get added while it's running)
241          * - runs the queue items
242          * - pushes upstream.
243          * 
244          */
245         public void runQueue()
246         {
247                 
248                 if (this.paused || this.queue.size < 1 ) {
249                         return;
250                 }
251                 
252                 foreach(var q in this.queue) {
253                         if (!q.shouldIgnore() && !q.repo.is_wip_branch() && q.repo.is_auto_branch()) {
254                                 var oldq = this.queue;
255                                 this.queue =  new Gee.ArrayList<GitMonitorQueue>(); 
256                                 NewBranch.singleton().show(q.repo, oldq);
257                                 
258                                 return;
259                         }
260                 
261                 }
262                 
263                  
264                 
265                 GLib.debug("GitMonitor.runQueue size =%d\n", this.queue.size);
266
267                 this.queueRunning = true;
268
269                 var cmds = new Gee.ArrayList<GitMonitorQueue>();
270
271                 for(var i = 0; i < this.queue.size; i++) {
272                         cmds.add(this.queue.get(i));
273                 }
274
275                 this.queue = new Gee.ArrayList<GitMonitorQueue>();// empty queue!
276  
277                 string[] success = {};
278                 string[] failure = {};
279            //var repos = new Array<GitRepo>(); //??
280                 //var done = new Array<GitMonitorQueue>();
281                 
282                 // first build a array of repo's to work with
283                 var repo_list = new Array<GitRepo>();
284                 
285                 // pull and group.
286                 
287                 //print(JSON.stringify(cmds));
288                 // make sure nothing get's added to the queue where we are doing this..
289
290                 this.paused = true;
291
292                 var leave_queued = new Gee.ArrayList<GitMonitorQueue>();
293                 GLib.debug("GitMonitor.runQueue - creating repos");
294                 
295                 for(var i = 0; i < cmds.size; i++) {
296                    
297                         var cmd = cmds.get(i);
298                 
299                         var gitpath = cmd.gitpath; 
300                         
301                         var repo = GitRepo.get( gitpath );
302                         if ( !repo.is_wip_branch() && repo.is_auto_branch()) {
303                                 leave_queued.add(cmd);
304                                 continue;
305                         }
306                         
307                         GLib.debug("GitMonitor.runQueue - finding %s", cmd.gitpath);
308                 
309                         var ix  = GitRepo.indexOf(repo_list,   gitpath);
310                         if (ix < 0) {
311                                 repo_list.append_val( GitRepo.get( gitpath ));
312                                 ix = GitRepo.indexOf(repo_list,  cmd.gitpath);
313                         }
314                         GLib.debug("GitMonitor.runQueue - adding to repolist %d", ix);
315
316                         //if (typeof(repo_list[gitpath]) == 'undefined') {
317                         //    repo_list[gitpath] = new imports.Scm.Git.Repo.Repo( { repopath : gitpath });
318                         //    repo_list[gitpath].cmds = [];
319                          //   repo_list[gitpath].pull();
320                         //}
321                         repo_list.index(ix).cmds.add(cmd);
322
323                 }
324                 this.queue = leave_queued;
325                 
326                 this.paused = false;
327                 // build add, remove and commit message list..
328
329                 GLib.debug("GitMonitor.runQueue - creating actions");
330                 
331                 for(var i = 0;i < repo_list.length;i++) {
332          
333                         var repo = repo_list.index(i);
334
335                         var add_files = new Gee.ArrayList<GitMonitorQueue>();
336                         var add_files_f = new Gee.ArrayList<GitMonitorQueue>();
337                         var remove_files = new Gee.ArrayList<GitMonitorQueue>();
338                         var messages = new Gee.ArrayList<GitMonitorQueue>();
339                         //print(JSON.stringify(repo.cmds,null,4));
340                         
341                         for(var ii = 0;ii < repo.cmds.size;ii++) {
342                                 var cmd = repo.cmds.get(ii);
343         
344                                 if (repo.is_ignore(cmd.vname)) {
345                                         continue;
346                                 }
347         
348                                 
349                                 switch(cmd.action) {
350                                         case "add" :
351                                                 
352                                                 if (GitMonitorQueue.indexOfAdd(add_files, cmd.vname) > -1) {
353                                                    break;
354                                                 }
355                                                 
356                                                 add_files.add(cmd);
357                                                 break;
358                                         
359                                         case "rm":
360                                                 if (GitMonitorQueue.indexOfAdd(remove_files, cmd.vname) > -1 ) {
361                                                    break;
362                                                 }
363                                                 
364                                                 // if file exists, do not try and delete it.
365                                                 if (FileUtils.test(cmd.fullpath(), FileTest.EXISTS)) {
366                                                         break;
367                                                 }
368                                                 
369                                                 remove_files.add(cmd);
370                                                 break;
371                                         
372                                         case "commit" :
373                                                 if (GitMonitorQueue.indexOfMessage(messages, cmd.message) > -1 ) {
374                                                    break;
375                                                 }
376                                                 messages.add(cmd);
377                                                 
378                                                 break;
379                                         default:
380                                                 stdout.printf("Opps unmatched action %s\n", cmd.action);
381                                                 break;
382                                 } 
383                         }
384                         
385                         repo.cmds.clear(); // reset the repo's command list..
386                         
387                         GLib.debug( "ADD : %s", GitMonitorQueue.queueArrayToString(add_files));
388                         GLib.debug( "REMOVE FILES: %s", GitMonitorQueue.queueArrayToString(remove_files));
389                         
390                         //repo.debug = 1;
391                         // these can fail... at present... as we wildcard stuff.
392                    
393                         // make sure added files do not get removed.. ?? 
394                         /*
395                         var remove_files_f = new Array<GitMonitorQueue>();
396                         for(var ii = 0;ii < remove_files.length;ii++) {
397                                 if (GitMonitorQueue.indexOfAdd(add_files,  remove_files.index(ii).vname) > -1 ) {
398                                          continue;
399                                 }
400                                 remove_files_f.append_val(remove_files.index(ii));
401                         };
402                         stdout.printf("REMOVE : %u files\n"  , remove_files_f.length);
403                         */
404                         
405                         // if file was added, then removed, 
406                         var remove_files_f = new Gee.ArrayList<GitMonitorQueue>();
407                         for(var ii = 0;ii < remove_files.size;ii++) {
408                                 
409                                 
410                                 
411                                 if (GitMonitorQueue.indexOfAdd(add_files,  remove_files.get(ii).vname) > -1 ) {
412                                         // in add and remove - do not remvove
413                                         continue;
414                                 }
415                                 remove_files_f.add(remove_files.get(ii));
416                         };
417                         for(var ii = 0;ii < add_files.size;ii++) {
418                                 if (GitMonitorQueue.indexOfAdd(remove_files,  add_files.get(ii).vname) > -1 ) {
419                                         // the add file is in the remove list, and it does not exist - do not add it..
420                                         print("check exists ? %s\n",add_files.get(ii).fullpath());
421                                         
422                                         if (!FileUtils.test(add_files.get(ii).fullpath(), FileTest.EXISTS)) {
423                                                         continue;
424                                         }
425                                          
426                                 } 
427                                 
428                                 add_files_f.add(add_files.get(ii));
429                         };
430                         
431                         GLib.debug( "ADD : %s", GitMonitorQueue.queueArrayToString(add_files_f));
432                         GLib.debug( "REMOVE FILES: %s", GitMonitorQueue.queueArrayToString(remove_files_f));
433                     
434                     if (add_files_f.size < 1 && remove_files_f.size < 1) {
435                                 continue;
436                     }
437                     
438                         // make sure monitoring is paused so it does not recursively pick up
439                         // deletions
440                         this.paused = true; 
441                           
442                           
443                         try {
444                                 
445                                 repo.pull();
446                         } catch(Error e) {
447                                 this.pauseError(e.message);
448                                 //failure +=  e.message;
449                                 //print("Pull failed:\n");
450                                 return;
451                         }
452                                                                         
453                         // -- DO STUFF..            
454                         try {
455                                 repo.add(add_files_f);
456                         } catch(Error e) {
457                                 this.pauseError(e.message);
458                                 return;
459                                 failure +=  e.message;
460                                 GLib.debug("Add failed:");
461                         }  
462                         try {
463                                  repo.remove(remove_files_f);
464                         } catch(Error e) {
465                                 this.pauseError(e.message);
466                                 return;
467                                 failure +=  e.message;
468                                 GLib.debug("Remove failed:");
469                         }  
470  
471                         try { 
472                                 success += repo.commit(
473                                         GitMonitorQueue.messageToString(messages),
474                                         add_files_f
475                                 );
476                                 success += repo.push();
477
478
479                         } catch(Error e) {
480                         
481                                 // if the error is 'nothing to commit, working tree clean'
482                                 // then it's not an error, - just continue;
483                                 if (/nothing to commit, working tree clean/.match(e.message)) {
484                                         GLib.debug("%s",e.message);
485                                         success += e.message;
486                                         this.paused = false; 
487                                         continue;
488                                 }
489                                 
490                         
491                         
492                                 this.paused = false;                    
493                                 this.pauseError(e.message);
494                                 
495                                 return;
496                                 //failure += e.message;
497                                 //print("Push failed:\n");
498                                 
499                         }   
500                         this.paused = false;                                            
501                 }
502                 
503                 
504                 // finally merge all the commit messages.
505                  
506                 try {
507                         // catch notification failures.. so we can carry on..
508                         if (success.length > 0) {
509
510                                 
511                                 var notification = new Notify.Notification(
512                                         "Git Live Commited",
513                                         string.joinv("\n",success),
514                                          "dialog-information"
515                                         
516                                 );
517         
518                                 notification.set_timeout(5);
519                                 notification.show();   
520                         }
521                         
522                         //if (failure.length > 0) {
523
524                                 // should never get this far...
525                         //      this.pauseError();   
526                         //}
527                 } catch(Error e) {
528                         GLib.debug(e.message);
529                         
530                 }
531                 this.queueRunning = false;
532         }
533         
534
535
536         
537
538         //string[] just_created;
539  
540  
541
542
543
544    
545
546
547         public override  void onChanged(MonitorNamePathDir src) 
548         { 
549                 //print("GitMonitor.onChanged\n");        
550                 return; // always ignore this..?
551                 //this.parsePath(src);
552         }
553         
554
555  
556         /**
557          *  results in  git add  + git commit..
558          *
559          */
560         public override void onChangesDoneHint(MonitorNamePathDir src)  
561         { 
562
563                 if (this.paused) {
564                         return;
565                 }
566                 GLib.debug("GitMonitor.onChangedHint");                         
567
568                 this.lastAdd = new DateTime.now(new TimeZone.local()); 
569                 var cmd = new GitMonitorQueue(src);
570                 if (cmd.shouldIgnore()) {
571                         GLib.debug("GitMonitor.onChangedHint - ignored");
572                         return;
573                 }
574                 
575                 //var add_it = false;
576                 /*
577                 if (this.is_just_created(cmd.path)) {
578                         
579                 if (typeof(this.just_created[src.path]) !='undefined') {
580                         delete this.just_created[src.path];
581                         
582                         this.queue.push( 
583                                 [ src.gitpath,  'add', src.vpath ],
584                                 [ src.gitpath,  'commit',    { message: src.vpath} ] 
585                                 
586                         );
587                  
588                         return;
589                 }
590                 */
591                 cmd.action = "add";
592                 this.queue.add(cmd);
593
594                 cmd = new GitMonitorQueue(src);
595                 cmd.action = "commit";
596                 cmd.message = cmd.vname;
597                 this.queue.add(cmd);
598  
599                  
600         }
601         public override  void onDeleted(MonitorNamePathDir src) 
602    { 
603
604                 if (this.paused) {
605                         return;
606                 }
607                 GLib.debug("GitMonitor.onDeleted");                     
608                 this.lastAdd = new DateTime.now(new TimeZone.local()); 
609                 var cmd = new GitMonitorQueue(src);
610                 if (cmd.shouldIgnore()) {
611                         return;
612                 }
613                 // should check if monitor needs removing..
614                 // it should also check if it was a directory.. - so we dont have to commit all..
615                 cmd.action = "rm";
616                 this.queue.add(cmd);
617
618                 cmd = new GitMonitorQueue(src);
619                 cmd.action = "commit";
620                 cmd.message = cmd.vname;
621                 cmd.commit_all = true;
622
623                 this.queue.add(cmd);
624  
625         }
626         public override  void onCreated(MonitorNamePathDir src) {
627
628                 if (this.paused) {
629                         return;
630                 }
631                 GLib.debug("GitMonitor.onCreated");                     
632                 this.lastAdd = new DateTime.now(new TimeZone.local()); 
633                 var cmd = new GitMonitorQueue(src);
634                 if (cmd.shouldIgnore()) {
635                         return;
636                 }
637
638                 if (!FileUtils.test(src.path, GLib.FileTest.IS_DIR)) {
639                    // this.just_created[src.path] = true;
640                         return; // we do not handle file create flags... - use done hint.
641                 }
642                 // directory has bee created
643                 this.monitor(src.path);
644                 //this.top.append_val(src.path);
645                 //this.monitor(src.path );
646
647
648 // -- no point in adding a dir.. as git does not handle them...
649 //        this.queue.push( 
650   //          [ src.gitpath, 'add' , src.vpath,  { all: true } ],
651  //           [ src.gitpath, 'commit' , { all: true, message: src.vpath} ]
652   //          
653    //     );
654
655         }
656
657         public  override void onAttributeChanged(MonitorNamePathDir src) 
658         { 
659
660                 if (this.paused) {
661                         return;
662                 }
663                 GLib.debug("GitMonitor.onAttributeChanged %s", src.name);                       
664                 if (src.dir == GitMonitor.gitlive) {
665                    return; // attribute on top level..
666                 }
667                 
668                 this.lastAdd = new DateTime.now(new TimeZone.local()); 
669                 var cmd = new GitMonitorQueue(src);
670                 if (cmd.shouldIgnore()) {
671                         return;
672                 }
673                 cmd.action = "add";
674                 this.queue.add(cmd);
675
676                 cmd = new GitMonitorQueue(src);
677                 cmd.action = "commit";
678                 cmd.message = "Attribute changed " + cmd.vname;
679                 this.queue.add(cmd);
680         }
681  
682    public  override void onMoved(MonitorNamePathDir src,MonitorNamePathDir dest)  
683         { 
684
685                 if (this.paused) {
686                         return;
687                 }
688                 GLib.debug("GitMonitor.onMoved");                       
689                 this.lastAdd = new DateTime.now(new TimeZone.local()); 
690                 var cmd_s = new GitMonitorQueue(src);
691
692                 var cmd_d = new GitMonitorQueue(dest);
693    
694                 
695                 if (cmd_d.gitpath != cmd_s.gitpath) {
696                         this.onDeleted(src);
697                         this.onCreated(dest);
698                         this.onChangesDoneHint(dest);
699                         return;
700                 }
701                 // needs to handle move to/from unsupported types..
702                 
703                 if (cmd_s.shouldIgnore()) {
704                         this.onCreated(dest);
705                         this.onChangesDoneHint(dest);
706                         return;
707
708                 }
709                 if (cmd_d.shouldIgnore()) {
710                         
711                         this.onDeleted(src);
712                         return;
713                 }
714                  
715                 GLib.debug("RM: %s", cmd_s.vname);
716                 cmd_s.action = "rm";
717                 this.queue.add(cmd_s);
718
719                 
720                 
721                 GLib.debug("ADD: %s", cmd_d.vname);
722                 cmd_d.action = "add";
723                 this.queue.add(cmd_d);
724
725
726                 var cmd = new GitMonitorQueue(dest);
727                 cmd.action = "commit";
728                 cmd.message = "MOVED " + cmd_s.vname + " to " + cmd_d.vname;
729                 if (GitMonitorQueue.queueHas(this.queue, cmd_s, "add")) {
730                         cmd.message = cmd_d.vname;
731                 }
732                 
733                 this.queue.add(cmd);
734
735
736                  
737         }
738            
739 }