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