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