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