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