gitlive.js
[gitlive] / gitlive.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
19 * --pretty=format:%H BRANCHNAME
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 pull origin gitlive
52 *  cd gitlive
53 *    git push
54 *    
55 *  cd featuredir
56 *     git push origin origin:refs/heads/feature_XXX
57 *     git checkout feature_XXX
58 *   cd gitlive
59 *     commit etc. (with new bug number) 
60 *    cd featuredir
61 *     git pull origin gitlive
62 *     git push
63 *   cd gitlive
64 *     git push
65
66 */
67
68 GI      = imports.gi.GIRepository
69 GLib        = imports.gi.GLib;
70
71 // we add this in, as it appears to get lost sometimes if we set it using the ENV. variable in builder.sh
72 GI.IRepository.prepend_search_path(GLib.get_home_dir() + '/.Builder/girepository-1.1');
73
74 Gio         = imports.gi.Gio;
75 Gtk         = imports.gi.Gtk;
76 Notify      = imports.gi.Notify;
77
78 Spawn       = imports.Spawn;
79 Git         = imports.Git;
80 StatusIcon  = imports.StatusIcon.StatusIcon;
81 Monitor     = imports.Monitor.Monitor;
82
83
84 //File = imports[__script_path__+'/../introspection-doc-generator/File.js'].File
85 Gtk.init (null, null);
86
87 var gitlive = GLib.get_home_dir() + "/gitlive";
88
89 if (!GLib.file_test(gitlive, GLib.FileTest.IS_DIR)) {
90     var msg = new Gtk.MessageDialog({message_type:
91         Gtk.MessageType.INFO, buttons : Gtk.ButtonsType.OK, text: "GIT Live - ~/gitlive does not exist."});
92     msg.run();
93     msg.destroy();
94     
95     Seed.quit();
96 }
97
98  
99 var monitor = new Monitor({
100     /**
101      *
102      * queue objects
103      *  action: 'add' | rm | update
104      *  repo : 'gitlive'
105      *  file : XXXXX
106      *
107      * 
108      *
109      */
110     action_queue : [],
111     queueRunning : false,
112      
113     start: function()
114     {
115         var _this = this;
116         this.lastAdd = new Date();
117          
118         // start monitoring first..
119         Monitor.prototype.start.call(this);
120         
121         // then start our queue runner..
122         GLib.timeout_add(GLib.PRIORITY_LOW, 500, function() {
123             //TIMEOUT", _this.queue.length , _this.queueRunning].join(', '));
124             if (!_this.queue.length || _this.queueRunning) {
125                 return 1;
126             }
127             var last = Math.floor(((new Date()) - this.lastAdd) / 100);
128             if (last < 4) { // wait 1/2 a seconnd before running.
129                 return 1;
130             }
131             _this.runQueue();
132             return 1;
133         },null,null);
134         
135         
136         var notification = new Notify.Notification({
137             summary: "Git Live",
138             body : gitlive + "\nMonitoring " + this.monitors.length + " Directories"
139         });
140
141         notification.set_timeout(2000);
142         notification.show();   
143     },
144     /**
145      * run the queue.
146      * - pulls the items off the queue 
147      *    (as commands run concurrently and new items may get added while it's running)
148      * - runs the queue items
149      * - pushes upstream.
150      * 
151      */
152     runQueue: function()
153     {
154         this.queueRunning = true;
155         var cmds = [];
156         //this.queue.forEach(function (q) {
157         //    cmds.push(q);
158         //});
159         
160         this.action_queue.forEach(function (q) {
161             cmds.push(q);
162         });
163         //this.queue = []; // empty queue!
164         this.action_queue = [];
165         var success = [];
166         var failure = [];
167         var repos = [];
168         var done = [];
169         
170         function readResult(sp) {
171             switch (sp.result * 1) {
172                 case 0: // success:
173                     success.push(sp.args.join(' '));
174                     if (sp.output.length) success.push(sp.output + '');
175                   // if (sp.stderr.length) success.push(sp.stderr + '');
176                     break;
177                 default: 
178                     failure.push(sp.args.join(' '));
179                     if (sp.output.length) failure.push(sp.output);
180                     if (sp.stderr.length) failure.push(sp.stderr);
181                     break;
182             }
183         }
184             
185         cmds.forEach(function(cmd) {
186             // prevent duplicate calls..
187             if (done.indexOf(JSON.stringify(cmd)) > -1) {
188                 return;
189             }
190             done.push(JSON.stringify(cmd));
191             // --- we keep a list of repositories that will be pushed to at the end..
192             
193             if (repos.indexOf(cmd.repos) < 0) {
194                 repos.push(cmd.repos);
195                 //    Git.run(cmd.repos , 'pull'); // pull before we push!
196             }
197             
198             var gp  = gitlive + '/' + cmd.repo;
199             
200             switch( cmd.action ) {
201                 case 'add':
202                     readResult(Git.run(gp, 'add',  cmd.file ));
203                     readResult(Git.run(gp, 'commit',  src.file, { message: cmd.file}  ));
204                     break;
205                     
206                 case 'rm':
207                     readResult(Git.run(gp, 'rm',  cmd.file ));
208                     readResult(Git.run(gp, 'commit',  { all: true, message: cmd.file}  ));
209                     break;
210                      
211                 case 'update':
212                     readResult(Git.run(gp, 'commit', cmd.file  , {   message: cmd.file}  ));
213                     break;
214                     
215                 case 'mv':
216                     readResult(Git.run(gp, 'mv', cmd.file , cmd.target));
217                     readResult(Git.run(gp, 'commit', cmd.file  , cmd.target,
218                             {   message: 'MOVED ' + src.file +' to ' + dest.target }  ));
219                     break; 
220             }
221             
222              
223             
224         });
225          
226         // push upstream.
227         repos.forEach(function(r) {
228             var sp = Git.run(gitlive + '/' +r , 'push', { all: true } );
229             if (sp.length) {
230                 success.push(sp);
231             }
232             
233         });
234         
235         if (success.length) {
236             print(success.join("\n"));
237             var notification = new Notify.Notification({
238                 summary: "Git Live Commited",
239                 body : success.join("\n")
240                 
241             });
242
243             notification.set_timeout(2000);
244             notification.show();   
245         }
246         if (failure.length) {
247         
248             var notification = new Notify.Notification({
249                 summary: "Git Live ERROR!!",
250                 body : failure.join("\n")
251                 
252             });
253
254             notification.set_timeout(5000); // show errros for longer
255             notification.show();   
256         }
257         this.queueRunning = false;
258     },
259     
260     shouldIgnore: function(f)
261     {
262         if (f.name[0] == '.') {
263             // except!
264             if (f.name == '.htaccess') {
265                 return false;
266             }
267             
268             return true;
269         }
270         if (f.name.match(/~$/)) {
271             return true;
272         }
273         // ignore anything in top level!!!!
274         if (!f.vpath.length) {
275             return true;
276         }
277         
278         return false;
279         
280     },
281     
282     /**
283      * set gitpath and vpath
284      * 
285      * 
286      */
287     
288     parsePath: function(f)
289     {
290            
291         var vpath_ar = f.path.substring(gitlive.length +1).split('/');
292         f.repo = vpath_ar.shift();
293         f.gitpath = gitlive + '/' + f.repo;
294         f.vpath =  vpath_ar.join('/');
295         
296         
297     },
298     
299     just_created : {},
300       
301     onChanged : function(src) 
302     { 
303         return; // always ignore this..?
304         //this.parsePath(src);
305     },
306     
307     /**
308      *  results in  git add  + git commit..
309      *  
310      *
311      *
312      */
313     
314     onChangesDoneHint : function(src) 
315     { 
316         this.parsePath(src);
317         if (this.shouldIgnore(src)) {
318             return;
319         }
320         
321         var add_it = false;
322         if (typeof(this.just_created[src.path]) !='undefined') {
323             delete this.just_created[src.path];
324             this.lastAdd = new Date();
325             //this.queue.push( 
326             //    [ src.gitpath,  'add', src.vpath ],
327             //    [ src.gitpath,  'commit',  src.vpath, { message: src.vpath} ] 
328             //    
329             //);
330             this.action_queue.push({
331                 action: 'add',
332                 repo : src.repo,
333                 file : src.vpath
334             });
335             
336             
337          
338             return;
339         }
340         this.lastAdd = new Date();
341         //this.queue.push( 
342         //    [ src.gitpath,  'add', src.vpath ],
343         //    [ src.gitpath,  'commit', src.vpath, {  message: src.vpath} ]
344         //
345         //);
346         
347         this.action_queue.push({
348             action: 'add',
349             repo : src.repo,
350             file : src.vpath
351         });
352         
353
354     },
355     onDeleted : function(src) 
356     { 
357         this.parsePath(src);
358         if (this.shouldIgnore(src)) {
359             return;
360         }
361         // should check if monitor needs removing..
362         // it should also check if it was a directory.. - so we dont have to commit all..
363         
364         this.lastAdd = new Date();
365         //this.queue.push( 
366         //    [ src.gitpath, 'rm' , src.vpath ],
367         //    [ src.gitpath, 'commit', { all: true, message: src.vpath} ]
368         //    
369         //);
370         this.action_queue.push({
371             action: 'rm',
372             repo : src.repo,
373             file : src.vpath
374         });
375         
376     },
377     onCreated : function(src) 
378     { 
379         this.parsePath(src);
380         if (this.shouldIgnore(src)) {
381             return;
382         }
383         
384         if (!GLib.file_test(src.path, GLib.FileTest.IS_DIR)) {
385             this.just_created[src.path] = true;
386             return; // we do not handle file create flags... - use done hint.
387         }
388         // director has bee created
389         this.monitor(src.path);
390         
391         /*
392           since git does not really handle directory adds...
393          
394         this.lastAdd = new Date();
395         this.action_queue.push({
396             action: 'add',
397             repo : src.repo,
398             file : src.vpath
399         });
400         
401         this.queue.push( 
402             [ src.gitpath, 'add' , src.vpath,  { all: true } ],
403             [ src.gitpath, 'commit' , { all: true, message: src.vpath} ]
404             
405         );
406         */
407         
408         
409     },
410     onAttributeChanged : function(src) { 
411         this.parsePath(src);
412         if (this.shouldIgnore(src)) {
413             return;
414         }
415         this.lastAdd = new Date();
416         
417         
418         //this.queue.push( 
419        //     [ src.gitpath, 'commit' ,  src.vpath, { message: src.vpath} ]
420        // );
421         this.action_queue.push({
422             action: 'update',
423             repo : src.repo,
424             file : src.vpath
425         });
426  
427     
428     },
429     
430     onMoved : function(src,dest)
431     { 
432         this.parsePath(src);
433         this.parsePath(dest);
434         
435         if (src.gitpath != dest.gitpath) {
436             this.onDeleted(src);
437             this.onCreated(dest);
438             this.onChangedDoneHint(dest);
439             return;
440         }
441         // needs to handle move to/from unsupported types..
442         
443         if (this.shouldIgnore(src)) {
444             return;
445         }
446         if (this.shouldIgnore(dest)) {
447             return;
448         }
449         this.lastAdd = new Date();
450        // this.queue.push( 
451        //     [ src.gitpath, 'mv',  '-k', src.vpath, dest.vpath ],
452        //     [ src.gitpath, 'commit' ,  src.vpath, dest.vpath ,
453        //         { message:   'MOVED ' + src.vpath +' to ' + dest.vpath} ]
454        // );
455         
456         this.action_queue.push({
457             action: 'mv',
458             repo : src.repo,
459             file : src.vpath,
460             target : dest.vpath
461             
462         });
463         
464     }
465           
466     
467 });
468  
469  
470   
471
472 function errorDialog(data) {
473     var msg = new Gtk.MessageDialog({
474             message_type: Gtk.MessageType.ERROR, 
475             buttons : Gtk.ButtonsType.OK, 
476             text: data
477     });
478     msg.run();
479     msg.destroy();
480 }
481
482  
483
484
485
486 //
487 // need a better icon...
488
489
490 StatusIcon.init();   
491
492
493 Notify.init("gitlive");
494
495 monitor.add(GLib.get_home_dir() + "/gitlive");
496 monitor.start();
497 Gtk.main();
498 //icon.signal["activate"].connect(on_left_click);
499