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