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