Fix variable scope in StatusIcon.activate
[gitlive] / gitlive2.js
1 #!/usr/bin/seed
2 ///<script type="text/javascript">
3 /**
4 * Git Live
5
6 * inotify hooks for ~/gitlive
7 * that commit and push any changes made.
8 * Bit like a revision controled backed up file system!?
9 *
10 *
11 * The aims of this
12 * A) have a gitlive branch - where all our commits go.. - so they can be replicated on the server 
13 * B) HEAD branch - where things get merged to..
14 *    -- eventually on closing issues..
15 *    -- currently when we switch from one feature to another..
16 *
17 * CURRENT HEAD?   
18 * git log -n 1 --pretty=format:%H BRANCHNAME
19
20
21 *
22 * Notes on feature branch implementation
23 * We need to add a gitlive branch on the remote server..
24 *   git push origin origin:refs/heads/gitlive 
25 *   git checkout --track -b gitlive origin/gitlive << means pull will use both branches..
26 *
27 *
28 * On our feature tree..  
29 *   git push origin origin:refs/heads/feature_2
30
31 * we clone directory into gitlive_feature/XXXXX
32 *     git branch issue_XXX
33 *     git checkout issue_XXX
34 *    
35 * run this on the feature branch it merges and commits..
36 *  git pull origin master << or gitlive..
37 *  
38 *
39 * Standard change file (same bug as before..)
40 *   cd gitlive
41 *     commit etc.. (with bug no..)
42 *   cd featuredir
43 *     git pull origin gitlive
44 *     git push
45 *   cd gitlive
46 *     git push
47 *     
48 *  Change to new bug number..
49 *  cd featuredir
50 *    git checkout -b master origin/master
51 *    git checkout master <<< make sure
52 *    git pull --squash origin gitlive
53 *    git commit -m 'done with old bug number'
54 *    git push
55 *  cd gitlive
56 *    git push
57 *  cd featuredir
58 *     git push origin origin:refs/heads/feature_XXX
59 *     git checkout feature_XXX
60 *   cd gitlive
61 *     commit etc. (with new bug number) 
62 *    cd featuredir
63 *     git pull origin gitlive
64 *     git push
65 *   cd gitlive
66 *     git push
67
68 */
69
70 GI      = imports.gi.GIRepository
71 GLib        = imports.gi.GLib;
72
73 // we add this in, as it appears to get lost sometimes if we set it using the ENV. variable in builder.sh
74 GI.IRepository.prepend_search_path(GLib.get_home_dir() + '/.Builder/girepository-1.1');
75
76 Gio         = imports.gi.Gio;
77 Gtk         = imports.gi.Gtk;
78 Notify      = imports.gi.Notify;
79
80 Spawn       = imports.Spawn;
81 Git         = imports.Git;
82 StatusIcon  = imports.StatusIcon.StatusIcon;
83 Monitor     = imports.Monitor.Monitor;
84 File        = imports.File.File;
85
86
87
88 //File = imports[__script_path__+'/../introspection-doc-generator/File.js'].File
89 Gtk.init (null, null);
90
91 var gitlive = GLib.get_home_dir() + "/gitlive";
92
93 if (!GLib.file_test(gitlive, GLib.FileTest.IS_DIR)) {
94     var msg = new Gtk.MessageDialog({message_type:
95         Gtk.MessageType.INFO, buttons : Gtk.ButtonsType.OK, text: "GIT Live - ~/gitlive does not exist."});
96     msg.run();
97     msg.destroy();
98     
99     Seed.quit();
100 }
101
102  
103 var monitor = new Monitor({
104     /**
105      *
106      * queue objects
107      *  action: 'add' | rm | update
108      *  repo : 'gitlive'
109      *  file : XXXXX
110      *
111      * 
112      *
113      */
114     action_queue : [],
115     queueRunning : false,
116      
117     start: function()
118     {
119         
120         
121         
122         this.dot_gitlive = GLib.get_home_dir() + "/.gitlive";
123         if (!File.exists(this.dot_gitlive)) {
124             File.mkdir(this.dot_gitlive);
125         }
126         
127         
128         var _this = this;
129         this.lastAdd = new Date();
130          
131         // start monitoring first..
132         Monitor.prototype.start.call(this);
133         
134         // then start our queue runner..
135         GLib.timeout_add(GLib.PRIORITY_LOW, 500, function() {
136             //TIMEOUT", _this.queue.length , _this.queueRunning].join(', '));
137             if (!_this.queue.length || _this.queueRunning) {
138                 return 1;
139             }
140             var last = Math.floor(((new Date()) - this.lastAdd) / 100);
141             if (last < 4) { // wait 1/2 a seconnd before running.
142                 return 1;
143             }
144             _this.runQueue();
145             return 1;
146         },null,null);
147         
148         
149         var notification = new Notify.Notification({
150             summary: "Git Live",
151             body : gitlive + "\nMonitoring " + this.monitors.length + " Directories"
152         });
153
154         notification.set_timeout(2000);
155         notification.show();   
156     },
157     
158     initRepo : function(src)
159     {
160         print("INIT REPO " + src);
161         
162         function logrun(sp) {
163             print("LOGRUN?" + typeof(sp));
164             switch (sp.result * 1) {
165                 case 0: // success:
166                     print(sp.args.join(' '));
167                     if (sp.output.length) print(sp.output + '');
168                   // if (sp.stderr.length) success.push(sp.stderr + '');
169                     break;
170                 default: 
171                     print(sp.args.join(' '));
172                     if (sp.output.length) print(sp.output);
173                     if (sp.stderr.length) print(sp.stderr);
174                     break;
175             }
176         }
177         
178         // make hour mirrored directory
179         var rname = src.split('/').pop();
180         var dotdir = this.dot_gitlive + '/' + rname;
181         
182         print("CHECK WORKING CACHE " + dotdir);
183
184         if (!File.isDirectory(dotdir)) {
185            // print("Not a dir?");
186             logrun(Git.run(this.dot_gitlive, 'clone', src  , {   shared :  true }  ));
187         }
188         // refresh - always..
189         logrun(Git.run(src, 'pull'));
190         
191         // create and checkout gitlive branch.
192         logrun(Git.run(src, 'push', 'origin', 'origin:refs/heads/gitlive'));
193         logrun(Git.run(src, 'checkout', { track : true ,  'b'  : 'gitlive' } , 'origin/gitlive'));
194         
195         
196         
197          
198         
199         
200         
201         
202     },
203     
204     
205     /**
206      * run the queue.
207      * - pulls the items off the queue 
208      *    (as commands run concurrently and new items may get added while it's running)
209      * - runs the queue items
210      * - pushes upstream.
211      * 
212      */
213     runQueue: function()
214     {
215         this.queueRunning = true;
216         var cmds = [];
217         //this.queue.forEach(function (q) {
218         //    cmds.push(q);
219         //});
220         
221         this.action_queue.forEach(function (q) {
222             cmds.push(q);
223         });
224         //this.queue = []; // empty queue!
225         this.action_queue = [];
226         var success = [];
227         var failure = [];
228         var repos = [];
229         var done = [];
230         
231         function readResult(sp) {
232             switch (sp.result * 1) {
233                 case 0: // success:
234                     success.push(sp.args.join(' '));
235                     if (sp.output.length) success.push(sp.output + '');
236                   // if (sp.stderr.length) success.push(sp.stderr + '');
237                     break;
238                 default: 
239                     failure.push(sp.args.join(' '));
240                     if (sp.output.length) failure.push(sp.output);
241                     if (sp.stderr.length) failure.push(sp.stderr);
242                     break;
243             }
244         }
245             
246         cmds.forEach(function(cmd) {
247             // prevent duplicate calls..
248             if (done.indexOf(JSON.stringify(cmd)) > -1) {
249                 return;
250             }
251             done.push(JSON.stringify(cmd));
252             // --- we keep a list of repositories that will be pushed to at the end..
253             
254             if (repos.indexOf(cmd.repos) < 0) {
255                 repos.push(cmd.repos);
256                 //    Git.run(cmd.repos , 'pull'); // pull before we push!
257             }
258             
259             
260             // at this point we need to know the issue number...
261             
262             
263             var gp  = gitlive + '/' + cmd.repo;
264             var dotdir = this.dot_gitlive + '/' + rname;
265
266             // create feature branch. - if it's a new feature..
267             readResult(Git.run(dotdir, 'push', 'origin', 'origin:refs/heads/feature_XXX'));
268             readResult(Git.run(dotdir, 'checkout', 'feature_XXX'));
269
270             switch( cmd.action ) {
271                 case 'add':
272                     readResult(Git.run(gp, 'add',  cmd.file ));
273                     readResult(Git.run(gp, 'commit',  src.file, { message: cmd.file}  ));
274                     
275                     break;
276                     
277                 case 'rm':
278                     readResult(Git.run(gp, 'rm',  cmd.file ));
279                     readResult(Git.run(gp, 'commit',  { all: true, message: cmd.file}  ));
280                     break;
281                      
282                 case 'update':
283                     readResult(Git.run(gp, 'commit', cmd.file  , {   message: cmd.file}  ));
284                     break;
285                     
286                 case 'mv':
287                     readResult(Git.run(gp, 'mv', cmd.file , cmd.target));
288                     readResult(Git.run(gp, 'commit', cmd.file  , cmd.target,
289                             {   message: 'MOVED ' + src.file +' to ' + dest.target }  ));
290                     break; 
291             }
292             // duplicate the changes into the feature dir, and push it back into the data..
293             readResult(Git.run(dotdir, 'pull',  'origin', 'gitlive'));
294             readResult(Git.run(dotdir, 'push'));
295              
296             
297         });
298          
299         // push upstream.
300         repos.forEach(function(r) {
301             var sp = Git.run(gitlive + '/' +r , 'push', { all: true } );
302             if (sp.length) {
303                 success.push(sp);
304             }
305             
306         });
307         
308         if (success.length) {
309             print(success.join("\n"));
310             var notification = new Notify.Notification({
311                 summary: "Git Live Commited",
312                 body : success.join("\n")
313                 
314             });
315
316             notification.set_timeout(2000);
317             notification.show();   
318         }
319         if (failure.length) {
320         
321             var notification = new Notify.Notification({
322                 summary: "Git Live ERROR!!",
323                 body : failure.join("\n")
324                 
325             });
326
327             notification.set_timeout(5000); // show errros for longer
328             notification.show();   
329         }
330         this.queueRunning = false;
331     },
332     
333     shouldIgnore: function(f)
334     {
335         if (f.name[0] == '.') {
336             // except!
337             if (f.name == '.htaccess') {
338                 return false;
339             }
340             
341             return true;
342         }
343         if (f.name.match(/~$/)) {
344             return true;
345         }
346         // ignore anything in top level!!!!
347         if (!f.vpath.length) {
348             return true;
349         }
350         
351         return false;
352         
353     },
354     
355     /**
356      * set gitpath and vpath
357      * 
358      * 
359      */
360     
361     parsePath: function(f)
362     {
363            
364         var vpath_ar = f.path.substring(gitlive.length +1).split('/');
365         f.repo = vpath_ar.shift();
366         f.gitpath = gitlive + '/' + f.repo;
367         f.vpath =  vpath_ar.join('/');
368         
369         
370     },
371     
372     just_created : {},
373       
374     onChanged : function(src) 
375     { 
376         return; // always ignore this..?
377         //this.parsePath(src);
378     },
379     
380     /**
381      *  results in  git add  + git commit..
382      *  
383      *
384      *
385      */
386     
387     onChangesDoneHint : function(src) 
388     { 
389         this.parsePath(src);
390         if (this.shouldIgnore(src)) {
391             return;
392         }
393         
394         var add_it = false;
395         if (typeof(this.just_created[src.path]) !='undefined') {
396             delete this.just_created[src.path];
397             this.lastAdd = new Date();
398             //this.queue.push( 
399             //    [ src.gitpath,  'add', src.vpath ],
400             //    [ src.gitpath,  'commit',  src.vpath, { message: src.vpath} ] 
401             //    
402             //);
403             this.action_queue.push({
404                 action: 'add',
405                 repo : src.repo,
406                 file : src.vpath
407             });
408             
409             
410          
411             return;
412         }
413         this.lastAdd = new Date();
414         //this.queue.push( 
415         //    [ src.gitpath,  'add', src.vpath ],
416         //    [ src.gitpath,  'commit', src.vpath, {  message: src.vpath} ]
417         //
418         //);
419         
420         this.action_queue.push({
421             action: 'add',
422             repo : src.repo,
423             file : src.vpath
424         });
425         
426
427     },
428     onDeleted : function(src) 
429     { 
430         this.parsePath(src);
431         if (this.shouldIgnore(src)) {
432             return;
433         }
434         // should check if monitor needs removing..
435         // it should also check if it was a directory.. - so we dont have to commit all..
436         
437         this.lastAdd = new Date();
438         //this.queue.push( 
439         //    [ src.gitpath, 'rm' , src.vpath ],
440         //    [ src.gitpath, 'commit', { all: true, message: src.vpath} ]
441         //    
442         //);
443         this.action_queue.push({
444             action: 'rm',
445             repo : src.repo,
446             file : src.vpath
447         });
448         
449     },
450     onCreated : function(src) 
451     { 
452         this.parsePath(src);
453         if (this.shouldIgnore(src)) {
454             return;
455         }
456         
457         if (!GLib.file_test(src.path, GLib.FileTest.IS_DIR)) {
458             this.just_created[src.path] = true;
459             return; // we do not handle file create flags... - use done hint.
460         }
461         // director has bee created
462         this.monitor(src.path);
463         
464         /*
465           since git does not really handle directory adds...
466          
467         this.lastAdd = new Date();
468         this.action_queue.push({
469             action: 'add',
470             repo : src.repo,
471             file : src.vpath
472         });
473         
474         this.queue.push( 
475             [ src.gitpath, 'add' , src.vpath,  { all: true } ],
476             [ src.gitpath, 'commit' , { all: true, message: src.vpath} ]
477             
478         );
479         */
480         
481         
482     },
483     onAttributeChanged : function(src) { 
484         this.parsePath(src);
485         if (this.shouldIgnore(src)) {
486             return;
487         }
488         this.lastAdd = new Date();
489         
490         
491         //this.queue.push( 
492        //     [ src.gitpath, 'commit' ,  src.vpath, { message: src.vpath} ]
493        // );
494         this.action_queue.push({
495             action: 'update',
496             repo : src.repo,
497             file : src.vpath
498         });
499  
500     
501     },
502     
503     onMoved : function(src,dest)
504     { 
505         this.parsePath(src);
506         this.parsePath(dest);
507         
508         if (src.gitpath != dest.gitpath) {
509             this.onDeleted(src);
510             this.onCreated(dest);
511             this.onChangedDoneHint(dest);
512             return;
513         }
514         // needs to handle move to/from unsupported types..
515         
516         if (this.shouldIgnore(src)) {
517             return;
518         }
519         if (this.shouldIgnore(dest)) {
520             return;
521         }
522         this.lastAdd = new Date();
523        // this.queue.push( 
524        //     [ src.gitpath, 'mv',  '-k', src.vpath, dest.vpath ],
525        //     [ src.gitpath, 'commit' ,  src.vpath, dest.vpath ,
526        //         { message:   'MOVED ' + src.vpath +' to ' + dest.vpath} ]
527        // );
528         
529         this.action_queue.push({
530             action: 'mv',
531             repo : src.repo,
532             file : src.vpath,
533             target : dest.vpath
534             
535         });
536         
537     }
538           
539     
540 });
541  
542  
543   
544
545 function errorDialog(data) {
546     var msg = new Gtk.MessageDialog({
547             message_type: Gtk.MessageType.ERROR, 
548             buttons : Gtk.ButtonsType.OK, 
549             text: data
550     });
551     msg.run();
552     msg.destroy();
553 }
554
555  
556
557
558
559 //
560 // need a better icon...
561
562
563 StatusIcon.init();   
564
565
566 Notify.init("gitlive");
567
568 monitor.add(GLib.get_home_dir() + "/gitlive");
569 monitor.start();
570 Gtk.main();
571 //icon.signal["activate"].connect(on_left_click);
572