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