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