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