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