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