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