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