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