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