e02ae7b50aa34c667263c68e804a93a384afb55f
[gitlive] / GitRepo.vala
1
2 /**
3  * @class Scm.Git.Repo
4  *
5  * @extends Scm.Repo
6  * 
7  *
8  *
9  */
10 static GitRepo  _GitRepo; 
11  
12 public class GitRepo : Object
13 {
14      
15     public Gee.ArrayList<GitMonitorQueue> cmds;
16
17     public string name;
18     public string gitdir;
19     public string git_working_dir;
20     public bool debug = false;
21     public bool has_local_changes = false;
22     public string host = "";
23     public string git_status;    
24     public string git_diff;        
25     public string ahead_or_behind = "";
26     
27     public Gee.HashMap<string,bool> ignore_files;
28     public GitBranch currentBranch;
29     public Gee.HashMap<string,GitBranch> branches; // accessed in GitBranch..
30         public RooTicket? activeTicket;
31     public  Gee.HashMap<string,GitRepo> cache;
32     
33     
34     
35         public static GitRepo singleton()
36     {
37         if (_GitRepo == null) {
38             _GitRepo = new GitRepo.single();
39             _GitRepo.cache = new Gee.HashMap<string,GitRepo>();
40         }
41         return _GitRepo;
42     }
43  
44     /**
45     * index of.. matching gitpath..
46     */
47     public static int indexOf( Array<GitRepo> repos, string gitpath) {
48         // make a fake object to compare against..
49         var test_repo = GitRepo.get(gitpath);
50         
51         for(var i =0; i < repos.length; i++) {
52             if (repos.index(i).gitdir == test_repo.gitdir) {
53                 return i;
54             }
55         }
56         return -1;
57     
58     }
59     
60
61     
62     
63     
64     public static   Array<GitRepo> list()
65     {
66
67         //if (GitRepo.list_cache !=  null) {
68         //    unowned  Array<GitRepo>    ret = GitRepo.list_cache;
69          //   return ret;
70         //}
71         var cache = GitRepo.singleton().cache;
72         var list_cache = new Array<GitRepo>();
73         
74         var dir = Environment.get_home_dir() + "/gitlive";
75         
76         var f = File.new_for_path(dir);
77         FileEnumerator file_enum;
78         try {
79             file_enum = f.enumerate_children(
80                 FileAttribute.STANDARD_DISPLAY_NAME + ","+ 
81                 FileAttribute.STANDARD_TYPE,
82                 FileQueryInfoFlags.NONE,
83                 null);
84         } catch (Error e) {
85             
86             return list_cache;
87             
88         }
89         
90         FileInfo next_file; 
91         
92         while (true) {
93             
94             try {
95                 next_file = file_enum.next_file(null);
96                 if (next_file == null) {
97                     break;
98                 }
99                 
100             } catch (Error e) {
101                 GLib.debug("Error: %s",e.message);
102                 break;
103             }
104          
105             //print("got a file " + next_file.sudo () + '?=' + Gio.FileType.DIRECTORY);
106             
107             if (next_file.get_file_type() !=  FileType.DIRECTORY) {
108                 next_file = null;
109                 continue;
110             }
111             
112             if (next_file.get_file_type() ==  FileType.SYMBOLIC_LINK) {
113                 next_file = null;
114                 continue;
115             }
116             
117             if (next_file.get_display_name()[0] == '.') {
118                 next_file = null;
119                 continue;
120             }
121             var sp = dir+"/"+next_file.get_display_name();
122            
123             var gitdir = dir + "/" + next_file.get_display_name() + "/.git";
124             
125             if (!FileUtils.test(gitdir, FileTest.IS_DIR)) {
126                 continue;
127             }
128             
129                 var rep =  GitRepo.get(  sp );
130                 list_cache.append_val(rep);             
131             
132         }
133     
134         return list_cache;
135          
136         }
137         
138         public static GitRepo get(string path) 
139         {
140                 var cache = GitRepo.singleton().cache;
141                 if (cache.has_key(path)) {
142                         return cache.get(path);
143                 }
144                 return new GitRepo(path);
145         }
146         
147     private GitRepo.single() {
148                 // used to create the signleton
149         }
150     /**
151      * constructor:
152      * 
153      * @param {Object} cfg - Configuration
154      *     (basically repopath is currently only critical one.)
155      *
156      */
157      
158     private GitRepo(string path) {
159         // cal parent?
160         this.name =   File.new_for_path(path).get_basename();
161         this.ignore_files = new Gee.HashMap<string,bool>();
162         
163         this.git_working_dir = path;
164         this.gitdir = path + "/.git";
165         if (!FileUtils.test(this.gitdir , FileTest.IS_DIR)) {
166             this.gitdir = path; // naked...
167         }
168         this.cmds = new  Gee.ArrayList<GitMonitorQueue> ();
169         
170                 var cache = GitRepo.singleton().cache;
171         //Repo.superclass.constructor.call(this,cfg);
172                 if ( !cache.has_key(path) ) {
173                         cache.set( path, this);
174         }
175         
176         var r = this.git({ "remote" , "get-url" , "--push" , "origin"});
177         var uri = new Soup.URI(r);      
178         this.host = uri.get_host();
179
180         
181         this.loadBranches();
182         this.loadActiveTicket();
183         this.loadStatus();
184     } 
185     
186     public bool is_master_branch()
187     {
188         // special branches that do not allow autopushing now...
189         return this.currentBranch.name == "master" || this.currentBranch.name == "roojs";
190                 
191     }
192     public void init_config()
193     {
194         // managed = 
195         if (this.get_config("managed") == "") {
196                 this.set_config("managed", this.host != "git.roojs.com" ? "1" : "0";
197                 }
198     
199     }
200     
201     
202     
203     public string get_config(string key) {
204         return this.git({ "config" , "gitlive." + key);
205         }
206     public string set_config(string key, string value) {
207         return this.git({ "config" , "gitlive." + key, value);
208         }
209     
210     public bool is_managed()
211     {
212         // is it a roojs origin?
213          
214         if (this.host != "git.roojs.com") { // we can only push to this url. -- unless we have forced it to be managed.
215                 var r = this.git({ "config" , "gitlive.managed");
216                 
217                 return FileUtils.test(this.gitdir + "/.gitlive-managed" , FileTest.EXISTS);
218         }
219         // otherwise see if unmanaged is set to disable it..
220         return !FileUtils.test(this.gitdir + "/.gitlive-unmanaged" , FileTest.EXISTS);  
221
222     }
223     
224     
225     public bool is_autocommit ()
226     {           
227         return !FileUtils.test(this.gitdir + "/.gitlive-disable-autocommit" , FileTest.EXISTS);
228     }
229     public void set_autocommit(bool val)
230     {
231
232                 var cur = this.is_autocommit();
233                 GLib.debug("SET auto commit : %s <= %s", val ? "ON" : "OFF",  cur  ? "ON" : "OFF");
234                 if (cur == val) {
235                         return; // no change..
236                 }
237                 if (!val) {
238                         FileUtils.set_contents(this.gitdir + "/.gitlive-disable-autocommit" , "x");
239                 } else {
240                         // it exists...
241                         FileUtils.remove(this.gitdir + "/.gitlive-disable-autocommit" ); 
242                 }
243     
244     }
245     
246     public bool is_auto_branch ()
247     {
248         if (this.name == "gitlog") {
249                 return false;
250                 }
251                 // check remote...
252         if (this.is_managed()) {
253                 return true;
254                 }
255         return false;
256         
257  
258     }
259     
260     public void set_auto_branch(bool val)
261     {
262
263                 var cur = this.is_auto_branch();
264                 GLib.debug("SET auto branch : %s <= %s", val ? "ON" : "OFF",  cur  ? "ON" : "OFF");
265                 
266                 if (cur == val) {
267                         return; // no change..
268                 }
269         if (this.host != "git.roojs.com") { // we can only push to this url. -- unless we have forced it to be managed.
270                 // remote is not our server..
271                 if (val) {
272                         FileUtils.set_contents(this.gitdir + "/.gitlive-managed" , "x");
273                 } else {
274                         FileUtils.remove(this.gitdir + "/.gitlive-managed" ); 
275                         }
276                 return;
277
278         }
279         
280         if (val) {
281                         FileUtils.remove(this.gitdir + "/.gitlive-unmanaged" ); 
282                 } else {
283                 FileUtils.set_contents(this.gitdir + "/.gitlive-unmanaged" , "x");              
284
285                 }
286           
287     
288     }
289     public bool is_autopush ()
290     {
291         return !FileUtils.test(this.gitdir + "/.gitlive-disable-autopush" , FileTest.EXISTS);
292     }
293     public void set_autopush(bool val)
294     {
295
296                 var cur = this.is_autopush();
297                 GLib.debug("SET auto push : %s <= %s", val ? "ON" : "OFF",  cur  ? "ON" : "OFF");
298                 if (cur == val) {
299                         return; // no change..
300                 }
301                 if (!val) {
302                         FileUtils.set_contents(this.gitdir + "/.gitlive-disable-autopush" , "");
303                 } else {
304                         // it exists...
305                         FileUtils.remove(this.gitdir + "/.gitlive-disable-autopush" ); 
306                 }
307     
308     }
309     
310     
311         public void loadStatus()
312         {
313                 var r = this.git({ "status" , "--porcelain" });
314                 this.git_status = r;
315                 this.has_local_changes = r.length > 0;
316                 
317                 var rs = this.git({ "status" , "-sb" });
318
319                 this.ahead_or_behind = rs.contains("[ahead") ? "A" : (rs.contains("[behind") ? "B" : "");
320                 
321                 
322                 this.git_diff  = this.git({ "diff" , "HEAD", "--no-color" });
323         }    
324
325     
326     public void loadBranches()
327     {
328
329         GitBranch.loadBranches(this);
330     }
331      
332     
333     
334     
335     public string branchesToString()
336     {
337         var ret = "";
338                 foreach( var br in this.branches.values) {
339                         if (br.name == "") {
340                                 continue; 
341                         }
342                         ret += ret.length > 0 ? "\n"  : "";
343                         ret += br.name;
344                 
345                 }
346                 return ret;
347         
348     }
349      public static void doMerges(string action, string ticket_id, string commit_message)
350     {
351        GitMonitor.gitmonitor.stop();
352        
353        var commitrevs = "";
354        var sucess = true;
355        foreach(var  repo in GitRepo.singleton().cache.values) {
356                if (repo.activeTicket != null && repo.activeTicket.id == ticket_id) {
357                        var res = repo.doMerge(action,ticket_id, commit_message);
358                        if (!res) {
359                                sucess = false;
360                                continue;
361                        }
362                        commitrevs += commitrevs.length > 0 ? " " : "";
363                        commitrevs += repo.currentBranch.lastrev;
364                }
365        }
366        if (sucess && action == "CLOSE") {
367                RooTicket.singleton().getById(ticket_id).close(commitrevs);
368        }
369        GitMonitor.gitmonitor.start();
370     }
371      
372
373     public bool doMerge(string action, string ticket_id, string commit_message)
374     {
375        // in theory we should check to see if other repo's have got the same branch and merge all them at the same time.
376        // also need to decide which branch we will merge into?
377                    var ret = "";
378                    if (action == "CLOSE" || action == "LEAVE") {
379                                    
380                try {
381                    var oldbranch = this.currentBranch.name;
382                    this.setActiveTicket(null, "master");
383                            string [] cmd = { "merge",   "--squash",  oldbranch };
384                            this.git( cmd );
385                            cmd = { "commit",   "-a" , "-m",  commit_message };
386                            this.git( cmd );
387                            this.push();
388                            this.loadBranches(); // updates lastrev..
389                
390                        var notification = new Notify.Notification(
391                                "Merged branch %s to master".printf(oldbranch),
392                                "",
393                                 "dialog-information"
394                                
395                        );
396
397                        notification.set_timeout(5);
398                        notification.show();   
399                
400                // close ticket..
401                return true; 
402                
403            } catch (Error e) {
404
405                GitMonitor.gitmonitor.pauseError(e.message);
406                return false;
407            }
408            // error~?? -- show the error dialog...
409                    return false;
410        }
411        if (action == "MASTER") {
412                // merge master into ours..
413                        try {
414                        string[] cmd = { "merge",  "master" };
415                        this.git( cmd );
416                        var notification = new Notify.Notification(
417                                        "Merged code from master to %s".printf(this.currentBranch.name),
418                                        "",
419                                         "dialog-information"
420                                        
421                                );
422                                notification.set_timeout(5);
423                                notification.show();   
424                       
425                        return true;
426                        } catch (Error e) {
427                        GitMonitor.gitmonitor.pauseError(e.message);
428                        return false;
429                    }
430            }
431        if (action == "EXIT") {
432                        try {
433                        var oldbranch  = this.currentBranch.name;
434                          this.setActiveTicket(null, "master");
435                        this.loadBranches();
436                        var notification = new Notify.Notification(
437                                        "Left branch %s".printf(oldbranch),
438                                        "",
439                                         "dialog-information"
440                                        
441                                );
442                                notification.set_timeout(5);
443                                notification.show();   
444                        
445                        return true;
446                    } catch (Error e) {
447                        GitMonitor.gitmonitor.pauseError(e.message);
448
449                        return false;                   
450                    }
451                    // error~?? -- show the error dialog...
452
453        }
454        return false;
455     }
456         
457     public void loadActiveTicket()
458     {
459         this.activeTicket = null;
460         if (!FileUtils.test(this.gitdir + "/.gitlive-active-ticket" , FileTest.EXISTS)) {
461                 return;
462         }
463         string ticket_id;
464         FileUtils.get_contents(this.gitdir + "/.gitlive-active-ticket" , out ticket_id);  
465         if (ticket_id.length < 1) {
466                 return;
467                 }
468                 this.activeTicket = RooTicket.singleton().getById(ticket_id.strip());
469         
470         
471     }
472     
473     
474     
475     public bool setActiveTicket(RooTicket? ticket, string branchname)
476     {
477         if (!this.createBranchNamed(branchname)) {
478                 return false;
479                 }
480                 if (ticket != null) {
481                 FileUtils.set_contents(this.gitdir + "/.gitlive-active-ticket" , ticket.id);
482         } else {
483                 FileUtils.remove(this.gitdir + "/.gitlive-active-ticket" );
484         }
485         this.activeTicket = ticket;
486         return true;
487     }
488     
489     public bool createBranchNamed(string branchname)
490     {   
491                 
492
493                      if (this.branches.has_key(branchname)) {
494                         this.switchToExistingBranchNamed(branchname);
495                     
496                     } else {
497                                  this.createNewBranchNamed(branchname); 
498                             
499                     }
500                        var notification = new Notify.Notification(
501                        "Changed to branch %s".printf(branchname),
502                        "",
503                         "dialog-information"
504                        
505                );
506
507                notification.set_timeout(5);
508                notification.show();   
509        
510          
511          this.loadBranches(); // update branch list...
512          //GitMonitor.gitmonitor.runQueue(); // no point - we have hidden the queue..
513          return true;
514     }
515      bool switchToExistingBranchNamed(string branchname)
516      {
517                 var stash = false;
518                                          // this is where it get's tricky...
519                 string files = "";
520                 try {                   
521                                 string[] cmd = { "ls-files" ,  "-m" };                   // list the modified files..
522                                 files = this.git(cmd);
523                                 stash = files.length> 1 ;
524                                 
525                                 
526                                 cmd = { "stash" };                      
527                                 if (stash) { this.git(cmd); }
528                                 
529                                 this.pull();
530                                 
531                                 cmd = { "checkout", branchname  };
532                                 this.git(cmd);
533                   } catch(Error e) {
534                                 GitMonitor.gitmonitor.pauseError(e.message);
535                                 return false;           
536                   }
537                 try {
538                    if (branchname != "master") {
539                        string[] cmd = { "merge", "master"  };
540                             this.git(cmd);
541                             this.push();
542                        
543                     }
544                     
545                 } catch(Error e) {
546                     string[] cmd = { "checkout", "master"  };
547                     this.git(cmd);
548                         GitMonitor.gitmonitor.pauseError(
549                                 "Use\n\na) git checkout %s\nb) git mergetool\nc) git commit\nd) git push\n d) stash pop \ne) start gitlive again\n".printf(
550                                         branchname)
551                                  + e.message
552                         );
553                         return false;           
554                  
555                 }
556                 try {                                   
557                     string[]  cmd = { "stash", "pop"  };
558                     if (stash) { 
559                         this.git(cmd); 
560                         var fl = files.split("\n");
561                         cmd = { "commit", "-m" , "Changed " + string.joinv("",fl) };
562                         foreach(var f in fl) {
563                                 if (f.length < 1) continue;
564                                 cmd += f;
565                         }
566                         this.git(cmd);                              
567                 }
568              
569
570                    
571                 } catch(Error ee) {
572                         GitMonitor.gitmonitor.pauseError(ee.message);
573                         return false;           
574                 }
575        this.push();
576        return true;                             
577                  
578      }
579     
580     
581     
582      bool createNewBranchNamed(string branchname)
583      {
584                 var stash = false;
585                  try {                                  
586                                 string[] cmd = { "ls-files" ,  "-m" };                   // list the modified files..
587                                 var files = this.git(cmd);
588                                 stash = files.length> 1 ;
589                         
590                          cmd = { "checkout", "-b" , branchname  };
591                         this.git(cmd);
592
593                cmd = { "push", "-u" , "origin" ,"HEAD"  };
594                         this.git(cmd);
595                                 if (stash) { 
596
597                                 var fl = files.split("\n");
598                                 cmd = { "commit", "-m" , "Changed " + string.joinv("",fl) };
599                                 foreach(var f in fl) {
600                                         if (f.length < 1) continue;
601                                         cmd += f;
602                                 }
603                                 this.git(cmd);  
604                                 this.push();                        
605                         }
606
607              
608                 } catch(Error ee) {
609                                 GitMonitor.gitmonitor.pauseError(ee.message);
610                                 return false;           
611                         }
612                         return true;
613      
614      }
615     
616     
617     
618     /**
619      * add:
620      * add files to track.
621      *
622      * @argument {Array} files the files to add.
623      */
624     public string add ( Gee.ArrayList<GitMonitorQueue> files ) throws Error, SpawnError
625     {
626         // should really find out if these are untracked files each..
627         // we run multiple versions to make sure that if one failes, it does not ignore the whole lot..
628         // not sure if that is how git works.. but just be certian.
629         var ret = "";
630         for (var i = 0; i < files.size;i++) {
631             var f = files.get(i).vname;
632             try {
633                 string[] cmd = { "add",    f  };
634                 this.git( cmd );
635             } catch (Error e) {
636                 ret += e.message  + "\n";
637             }        
638
639         }
640         return ret;
641     }
642         
643     public bool is_ignore(string fname) throws Error, SpawnError
644     {
645                 if (fname == ".gitignore") {
646                         this.ignore_files.clear();
647                 }
648                 
649                 if (this.ignore_files.has_key(fname)) {
650                         return this.ignore_files.get(fname);
651                 }
652                 
653                 try {
654                         var ret = this.git( { "check-ignore" , fname } );
655                         this.ignore_files.set(fname, ret.length >  0);
656                         return ret.length > 0;
657                 } catch (SpawnError e) {
658                         this.ignore_files.set(fname, false);
659                         return false;
660                 }
661                  
662     } 
663     
664     
665       /**
666      * remove:
667      * remove files to track.
668      *
669      * @argument {Array} files the files to add.
670      */
671     public string remove  ( Gee.ArrayList<GitMonitorQueue> files ) throws Error, SpawnError
672     {
673         // this may fail if files do not exist..
674         // should really find out if these are untracked files each..
675         // we run multiple versions to make sure that if one failes, it does not ignore the whole lot..
676         // not sure if that is how git works.. but just be certian.
677         var ret = "";
678
679         for (var i = 0; i < files.size;i++) {
680             var f = files.get(i).vname;
681             try {
682                 string[] cmd = { "rm",  "-f" ,  f  };
683                 this.git( cmd );
684             } catch (Error e) {
685                 ret += e.message  + "\n";
686             }        
687         }
688
689         return ret;
690
691     }
692     
693     
694     /**
695      * commit:
696      * perform a commit.
697      *
698      * @argument {Object} cfg commit configuration
699      * 
700      * @property {String} name (optional)
701      * @property {String} email (optional)
702      * @property {String} changed (date) (optional)
703      * @property {String} reason (optional)
704      * @property {Array} files - the files that have changed. 
705      * 
706      */
707      
708     public string commit ( string message, Gee.ArrayList<GitMonitorQueue> files  ) throws Error, SpawnError
709     {
710         
711
712         /*
713         var env = [];
714
715         if (typeof(cfg.name) != 'undefined') {
716             args.push( {
717                 'author' : cfg.name + ' <' + cfg.email + '>'
718             });
719             env.push(
720                 "GIT_COMMITTER_NAME" + cfg.name,
721                 "GIT_COMMITTER_EMAIL" + cfg.email
722             );
723         }
724
725         if (typeof(cfg.changed) != 'undefined') {
726             env.push("GIT_AUTHOR_DATE= " + cfg.changed )
727             
728         }
729         */
730         string[] args = { "commit", "-m" };
731         args +=  (message.length > 0  ? message : "Changed" );
732         for (var i = 0; i< files.size ; i++ ) {
733             args += files.get(i).vname; // full path?
734         }
735          
736         return this.git(args);
737     }
738     
739     /**
740      * pull:
741      * Fetch and merge remote repo changes into current branch..
742      *
743      * At present we just need this to update the current working branch..
744      * -- maybe later it will have a few options and do more stuff..
745      *
746      */
747     public string pull () throws Error, SpawnError
748     {
749         // should probably hand error conditions better... 
750         string[] cmd = { "pull" , "--no-edit" };
751         return this.git( cmd );
752
753         
754     }
755     
756     public delegate void GitAsyncCallback (GitRepo repo, int err, string str);
757     public void pull_async(GitAsyncCallback cb) 
758     {
759     
760          string[] cmd = { "pull" , "--no-edit" };
761          this.git_async( cmd , cb);
762          
763     
764     }
765     
766     /**
767      * push:
768      * Send local changes to remote repo(s)
769      *
770      * At present we just need this to push the current branch.
771      * -- maybe later it will have a few options and do more stuff..
772      *
773      */
774     public string push () throws Error, SpawnError
775     {
776         // should 
777         return this.git({ "push"  });
778         
779     }
780     
781     
782     
783      /**
784      * git:
785      * The meaty part.. run spawn.. with git..
786      *
787      *
788      */
789     
790     public string git(string[] args_in ) throws Error, SpawnError
791     {
792         // convert arguments.
793         
794         string[]  args = { "git" };
795         //args +=  "--git-dir";
796         //args +=  this.gitdir;
797         args +=  "--no-pager";
798  
799  
800         //if (this.gitdir != this.repopath) {
801         //    args +=   "--work-tree";
802          //   args += this.repopath; 
803         //}
804         for (var i = 0; i < args_in.length;i++) {
805             args += args_in[i];
806         }            
807
808         //this.lastCmd = args.join(" ");
809         //if(this.debug) {
810             GLib.debug( "CWD=%s",  this.git_working_dir ); 
811             GLib.debug( "cmd: %s", string.joinv (" ", args)); 
812         //}
813
814         string[]   env = {};
815         string  home = "HOME=" + Environment.get_home_dir() ;
816         env +=  home ;
817         // do not need to set gitpath..
818         //if (File.exists(this.repo + '/.git/config')) {
819             //env.push("GITPATH=" + this.repo );
820         //}
821           
822         var cfg = new SpawnConfig(this.git_working_dir , args , env);
823         //cfg.debug = true;
824
825        // may throw error...
826         var sp = new Spawn(cfg);
827       
828         // diff output is a bit big..
829                 if (args_in[0] != "diff") {
830                 GLib.debug( "GOT: %s" , sp.output);
831         }
832         // parse output for some commands ?
833         return sp.output;
834     }
835         
836    unowned GitAsyncCallback git_async_on_callback;
837         public void  git_async( string[] args_in,   GitAsyncCallback cb ) throws Error, SpawnError
838     {
839         // convert arguments.
840        this.git_async_on_callback = cb;
841         string[]  args = { "git" };
842         //args +=  "--git-dir";
843         //args +=  this.gitdir;
844         args +=  "--no-pager";
845  
846  
847         //if (this.gitdir != this.repopath) {
848         //    args +=   "--work-tree";
849          //   args += this.repopath; 
850         //}
851         for (var i = 0; i < args_in.length;i++) {
852             args += args_in[i];
853         }            
854
855         //this.lastCmd = args.join(" ");
856         //if(this.debug) {
857             GLib.debug( "CWD=%s",  this.git_working_dir ); 
858             //print( "cmd: %s\n", string.joinv (" ", args)); 
859         //}
860
861         string[]   env = {};
862         string  home = "HOME=" + Environment.get_home_dir() ;
863         env +=  home ;
864         // do not need to set gitpath..
865         //if (File.exists(this.repo + '/.git/config')) {
866             //env.push("GITPATH=" + this.repo );
867         //}
868         
869
870         var cfg = new SpawnConfig(this.git_working_dir , args , env);
871         cfg.async = true;
872        
873
874        // may throw error...
875         var sp = new Spawn(cfg);
876                 //sp.ref();
877         //this.ref();
878         sp.run(this.git_async_on_complete); 
879          
880     }
881     
882     void git_async_on_complete(int err, string output)
883     {
884                 GLib.debug("GOT %d : %s", err, output);
885                 this.git_async_on_callback(this, err, output);
886 //              this.unref();   
887         //      sp.unref();             
888     
889     
890     }
891     
892  
893          
894     
895  
896     public void update_async(GitAsyncCallback cb) 
897     {
898          string[] cmd = { "fetch" , "--all" };
899          this.git_async( cmd , cb);
900          
901     }
902     
903     
904     static uint update_all_total = 0;
905     static string update_all_after = "";
906      
907     public static void updateAll(string after)
908     {
909                 update_all_after = after;
910                 var tr =  GitRepo.singleton().cache;
911             
912         
913        update_all_total = tr.size;
914        foreach(var repo  in tr.values) {
915                 if (!repo.is_managed()) {
916                         update_all_total--;                     
917                         continue;
918                 }
919            repo.update_async(updateAllCallback); 
920         } 
921
922     }
923     public static void  updateAllCallback(GitRepo repo, int err, string res)
924     {
925         repo.loadBranches();
926         repo.loadStatus();
927         
928         update_all_total--;
929         if (update_all_total > 0 ) {
930                 return;
931                 }
932                 switch (update_all_after) {
933                         case "show_clones":
934                                 Clones.singleton().show();
935                                 break;
936                         default:
937                                 break;
938                 }
939                 return;
940     }
941     
942     
943     
944 }