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