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