GitRepo.vala
[gitlive] / GitRepo.vala
1
2 /**
3  * @class Scm.Git.Repo
4  *
5  * @extends Scm.Repo
6  * 
7  *
8  *
9  */
10 public class GitRepo : Object
11 {
12     
13     public Array<GitMonitorQueue> cmds;
14
15     public string name;
16     public string gitdir;
17     public string git_working_dir;
18     public bool debug = false;
19     
20     public Gee.HashMap<string,bool> ignore_files;
21
22     /**
23     * index of.. matching gitpath..
24     */
25     public static int indexOf( Array<GitRepo> repos, string gitpath) {
26         // make a fake object to compare against..
27         var test_repo = new GitRepo(gitpath);
28         
29         for(var i =0; i < repos.length; i++) {
30             if (repos.index(i).gitdir == test_repo.gitdir) {
31                 return i;
32             }
33         }
34         return -1;
35     
36     }
37     
38     
39     public static   Array<GitRepo> list()
40     {
41         
42         //if (GitRepo.list_cache !=  null) {
43         //    unowned  Array<GitRepo>    ret = GitRepo.list_cache;
44          //   return ret;
45         //}
46         
47         var list_cache = new Array<GitRepo>();
48         
49         var dir = Environment.get_home_dir() + "/gitlive";
50         
51         var f = File.new_for_path(dir);
52         FileEnumerator file_enum;
53         try {
54             file_enum = f.enumerate_children(
55                 FileAttribute.STANDARD_DISPLAY_NAME + ","+ 
56                 FileAttribute.STANDARD_TYPE,
57                 FileQueryInfoFlags.NONE,
58                 null);
59         } catch (Error e) {
60             
61             return list_cache;
62             
63         }
64         
65         FileInfo next_file; 
66         
67         while (true) {
68             
69             try {
70                 next_file = file_enum.next_file(null);
71                 if (next_file == null) {
72                     break;
73                 }
74                 
75             } catch (Error e) {
76                 GLib.debug("Error: %s",e.message);
77                 break;
78             }
79          
80             //print("got a file " + next_file.sudo () + '?=' + Gio.FileType.DIRECTORY);
81             
82             if (next_file.get_file_type() !=  FileType.DIRECTORY) {
83                 next_file = null;
84                 continue;
85             }
86             
87             if (next_file.get_file_type() ==  FileType.SYMBOLIC_LINK) {
88                 next_file = null;
89                 continue;
90             }
91             
92             if (next_file.get_display_name()[0] == '.') {
93                 next_file = null;
94                 continue;
95             }
96             var sp = dir+"/"+next_file.get_display_name();
97            
98             var gitdir = dir + "/" + next_file.get_display_name() + "/.git";
99             
100             if (!FileUtils.test(gitdir, FileTest.IS_DIR)) {
101                 continue;
102             }
103             
104              list_cache.append_val(new GitRepo(  sp )) ;
105              
106             
107         }
108     
109         return list_cache;
110         
111          
112           
113 }
114     
115  
116    
117     /**
118      * constructor:
119      * 
120      * @param {Object} cfg - Configuration
121      *     (basically repopath is currently only critical one.)
122      *
123      */
124      
125     public GitRepo(string path) {
126         // cal parent?
127         this.name =   File.new_for_path(path).get_basename();
128         this.ignore_files = new Gee.HashMap<string,bool>();
129         
130         this.git_working_dir = path;
131         this.gitdir = path + "/.git";
132         if (!FileUtils.test(this.gitdir , FileTest.IS_DIR)) {
133             this.gitdir = path; // naked...
134         }
135         this.cmds = new  Array<GitMonitorQueue> ();
136         //Repo.superclass.constructor.call(this,cfg);
137         
138     } 
139     
140     
141     public bool is_autocommit ()
142     {
143         return !FileUtils.test(this.gitdir + "/.gitlive-disable-autocommit" , FileTest.EXISTS);
144     }
145     public bool is_autopush ()
146     {
147         return !FileUtils.test(this.gitdir + "/.gitlive-disable-autopush" , FileTest.EXISTS);
148     }
149     
150     Gee.HashMap<string,GitBranch> branches;
151     
152     public void loadBranches()
153     {
154         string[] cmd = { "branch",   "--no-color", "--verbose", "--no-abbrev" , "-a"  };
155         var res = this.git( cmd );
156         var lines = res.split("\n");
157         for (var i = 0; i < lines.length ; i++) {
158                 var br = new GitBranch(this);
159                 if (!br.parseBranchListItem(lines[i])) {
160                         continue;
161                 }
162                 branches.set(br.realName(), br);
163         }
164     
165     }
166     
167     /**
168      * add:
169      * add files to track.
170      *
171      * @argument {Array} files the files to add.
172      */
173     public string add ( Array<GitMonitorQueue> files ) throws Error, SpawnError
174     {
175         // should really find out if these are untracked files each..
176         // we run multiple versions to make sure that if one failes, it does not ignore the whole lot..
177         // not sure if that is how git works.. but just be certian.
178         var ret = "";
179         for (var i = 0; i < files.length;i++) {
180             var f = files.index(i).vname;
181             try {
182                 string[] cmd = { "add",    f  };
183                 this.git( cmd );
184             } catch (Error e) {
185                 ret += e.message  + "\n";
186             }        
187
188         }
189         return ret;
190     }
191         
192     public bool is_ignore(string fname) throws Error, SpawnError
193     {
194                 if (fname == ".gitignore") {
195                         this.ignore_files.clear();
196                 }
197                 
198                 if (this.ignore_files.has_key(fname)) {
199                         return this.ignore_files.get(fname);
200                 }
201                 
202                 try {
203                         var ret = this.git( { "check-ignore" , fname } );
204                         this.ignore_files.set(fname, ret.length >  0);
205                         return ret.length > 0;
206                 } catch (SpawnError e) {
207                         this.ignore_files.set(fname, false);
208                         return false;
209                 }
210                  
211     } 
212     
213     
214       /**
215      * remove:
216      * remove files to track.
217      *
218      * @argument {Array} files the files to add.
219      */
220     public string remove  ( Array<GitMonitorQueue> files ) throws Error, SpawnError
221     {
222         // this may fail if files do not exist..
223         // should really find out if these are untracked files each..
224         // we run multiple versions to make sure that if one failes, it does not ignore the whole lot..
225         // not sure if that is how git works.. but just be certian.
226         var ret = "";
227
228         for (var i = 0; i < files.length;i++) {
229             var f = files.index(i).vname;
230             try {
231                 string[] cmd = { "rm",  "-f" ,  f  };
232                 this.git( cmd );
233             } catch (Error e) {
234                 ret += e.message  + "\n";
235             }        
236         }
237
238         return ret;
239
240     }
241     
242     
243     /**
244      * commit:
245      * perform a commit.
246      *
247      * @argument {Object} cfg commit configuration
248      * 
249      * @property {String} name (optional)
250      * @property {String} email (optional)
251      * @property {String} changed (date) (optional)
252      * @property {String} reason (optional)
253      * @property {Array} files - the files that have changed. 
254      * 
255      */
256      
257     public string commit ( string message, Array<GitMonitorQueue> files  ) throws Error, SpawnError
258     {
259         
260
261         /*
262         var env = [];
263
264         if (typeof(cfg.name) != 'undefined') {
265             args.push( {
266                 'author' : cfg.name + ' <' + cfg.email + '>'
267             });
268             env.push(
269                 "GIT_COMMITTER_NAME" + cfg.name,
270                 "GIT_COMMITTER_EMAIL" + cfg.email
271             );
272         }
273
274         if (typeof(cfg.changed) != 'undefined') {
275             env.push("GIT_AUTHOR_DATE= " + cfg.changed )
276             
277         }
278         */
279         string[] args = { "commit", "-m" };
280         args +=  (message.length > 0  ? message : "Changed" );
281         for (var i = 0; i< files.length ; i++ ) {
282             args += files.index(i).vname; // full path?
283         }
284          
285         return this.git(args);
286     }
287     
288     /**
289      * pull:
290      * Fetch and merge remote repo changes into current branch..
291      *
292      * At present we just need this to update the current working branch..
293      * -- maybe later it will have a few options and do more stuff..
294      *
295      */
296     public string pull () throws Error, SpawnError
297     {
298         // should probably hand error conditions better... 
299         string[] cmd = { "pull" , "--no-edit" };
300         return this.git( cmd );
301
302         
303     }
304     
305     public delegate void GitAsyncCallback (GitRepo repo, int err, string str);
306     public void pull_async(GitAsyncCallback cb) 
307     {
308     
309         string[] cmd = { "pull" , "--no-edit" };
310          this.git_async( cmd , cb);
311          
312     
313     }
314     
315     /**
316      * push:
317      * Send local changes to remote repo(s)
318      *
319      * At present we just need this to push the current branch.
320      * -- maybe later it will have a few options and do more stuff..
321      *
322      */
323     public string push () throws Error, SpawnError
324     {
325         // should 
326         return this.git({ "push", "origin", "HEAD" });
327         
328     }
329     
330     
331     
332      /**
333      * git:
334      * The meaty part.. run spawn.. with git..
335      *
336      *
337      */
338     
339     public string git(string[] args_in ) throws Error, SpawnError
340     {
341         // convert arguments.
342         
343         string[]  args = { "git" };
344         //args +=  "--git-dir";
345         //args +=  this.gitdir;
346         args +=  "--no-pager";
347  
348  
349         //if (this.gitdir != this.repopath) {
350         //    args +=   "--work-tree";
351          //   args += this.repopath; 
352         //}
353         for (var i = 0; i < args_in.length;i++) {
354             args += args_in[i];
355         }            
356
357         //this.lastCmd = args.join(" ");
358         //if(this.debug) {
359             GLib.debug( "CWD=%s",  this.git_working_dir ); 
360             GLib.debug( "cmd: %s", string.joinv (" ", args)); 
361         //}
362
363         string[]   env = {};
364         string  home = "HOME=" + Environment.get_home_dir() ;
365         env +=  home ;
366         // do not need to set gitpath..
367         //if (File.exists(this.repo + '/.git/config')) {
368             //env.push("GITPATH=" + this.repo );
369         //}
370         
371
372         var cfg = new SpawnConfig(this.git_working_dir , args , env);
373         
374
375        // may throw error...
376         var sp = new Spawn(cfg);
377       
378
379         GLib.debug( "GOT: %s" , sp.output);
380         // parse output for some commands ?
381         return sp.output;
382     }
383         
384    unowned GitAsyncCallback git_async_on_callback;
385         public void  git_async( string[] args_in,   GitAsyncCallback cb ) throws Error, SpawnError
386     {
387         // convert arguments.
388        this.git_async_on_callback = cb;
389         string[]  args = { "git" };
390         //args +=  "--git-dir";
391         //args +=  this.gitdir;
392         args +=  "--no-pager";
393  
394  
395         //if (this.gitdir != this.repopath) {
396         //    args +=   "--work-tree";
397          //   args += this.repopath; 
398         //}
399         for (var i = 0; i < args_in.length;i++) {
400             args += args_in[i];
401         }            
402
403         //this.lastCmd = args.join(" ");
404         //if(this.debug) {
405             GLib.debug( "CWD=%s",  this.git_working_dir ); 
406             //print( "cmd: %s\n", string.joinv (" ", args)); 
407         //}
408
409         string[]   env = {};
410         string  home = "HOME=" + Environment.get_home_dir() ;
411         env +=  home ;
412         // do not need to set gitpath..
413         //if (File.exists(this.repo + '/.git/config')) {
414             //env.push("GITPATH=" + this.repo );
415         //}
416         
417
418         var cfg = new SpawnConfig(this.git_working_dir , args , env);
419         cfg.async = true;
420        
421
422        // may throw error...
423         var sp = new Spawn(cfg);
424                 //sp.ref();
425         //this.ref();
426         sp.run(this.git_async_on_complete); 
427          
428     }
429     
430     void git_async_on_complete(int err, string output)
431     {
432                 GLib.debug("GOT %d : %s", err, output);
433                 this.git_async_on_callback(this, err, output);
434 //              this.unref();   
435         //      sp.unref();             
436     
437     
438     }
439     
440 }