Scm/Git/Repo.js
[gitlive] / Scm / Git / Repo.js
1
2 const GLib   = imports.gi.GLib;
3
4 const XObject = imports.XObject.XObject;
5 const Event  = imports.Scm.Git.Event.Event;
6 const Spawn  = imports.Spawn.Spawn;
7 const File  = imports.File.File;
8
9
10 Repo = XObject.define(
11     function(cfg) {
12         // cal parent?
13         XObject.extend(this,cfg);
14         this.gitdir = cfg.repopath + "/.git";
15         
16         if (!File.isDirectory(this.gitdir)) {
17             this.gitdir = this.repopath;
18         }
19         //Repo.superclass.constructor.call(this,cfg);
20         
21     },
22     imports.Scm.Repo.Repo, // or Object
23     {
24         branches : false,
25         tags : false,
26         gitdir : false,
27         debug : false,
28         lastCmd : false, 
29         
30         getMetaData : function() {
31             return {
32               name :  'Git',
33               tool : ['git' ]  
34                 
35             };
36         },
37         getBranches : function()
38         {
39             if (this.branches !== false) {
40                 return this.branches;
41             }
42             this.branches = {};
43             
44             var bl = this.git([ 'branch', {
45                 'no-color' : true,
46                 'verbose' : true,
47                 'a' : true
48             }]).split("\n");
49             
50             bl.forEach(function(line) {
51                   // * master 61e7e7d oneliner
52                 var active = line[0]=='*';
53                 line = line.substring(2);
54                 
55                 var parts = line.split(/\s+/);
56                 if (parts[0] == '->') {
57                     return; // it's an alias like  remotes/origin/HEAD    -> origin/master
58                 }
59                 this.branches[parts[0]] = parts[1];
60                 if (active) {
61                     this.curBranch = parts[0];
62                 }
63             });
64             return this.branches;
65           },
66           /*
67         
68           public function getTags()
69           {
70             if ($this->tags !== null) {
71               return $this->tags;
72             }
73             $this->tags = array();
74             $fp = $this->git('tag');
75             while ($line = fgets($fp)) {
76               $line = trim($line);
77               $this->tags[$line] = $line;
78             }
79             $fp = null;
80             return $this->tags;
81           }
82         
83           public function readdir($path, $object = null, $ident = null)
84           {
85             $res = array();
86         
87             if ($object === null) {
88               $object = 'branch';
89               $ident = 'master';
90             }
91             $rev = $this->resolveRevision(null, $object, $ident);
92         
93             if (strlen($path)) {
94               $path = rtrim($path, '/') . '/';
95             }
96         
97             $fp = $this->git('ls-tree', $rev, $path);
98         
99             $dirs = array();
100             require_once 'MTrack/SCM/Git/File.php';
101             while ($line = fgets($fp)) {
102                 // blob = file, tree = dir..
103                 list($mode, $type, $hash, $name) = preg_split("/\s+/", $line);
104                 //echo '<PRE>';echo $line ."\n</PRE>";
105                 $res[] = new MTrack_SCM_Git_File($this, "$name", $rev, $type == 'tree', $hash);
106             }
107             return $res;
108           }
109         
110           public function file($path, $object = null, $ident = null)
111           {
112             if ($object == null) {
113               $branches = $this->getBranches();
114               if (isset($branches['master'])) {
115                 $object = 'branch';
116                 $ident = 'master';
117               } else {
118                 // fresh/empty repo
119                 return null;
120               }
121             }
122             $rev = $this->resolveRevision(null, $object, $ident);
123             require_once 'MTrack/SCM/Git/File.php';
124             return new MTrack_SCM_Git_File($this, $path, $rev);
125           }
126        */
127         /**
128          * 
129          * @param  string path (can be empty - eg. '')
130          * @param  {number|date} limit  how many to fetch
131          * @param  {string} object = eg. rev|tag|branch  (use 'rev' here and ident=HASH to retrieve a speific revision
132          * @param  {string} ident = 
133          *
134          * Example:
135          *   - fetch on revision?:  '.',1,'rev','xxxxxxxxxx'
136          *
137          *
138          * range... object ='rev' ident ='master..release'
139          * 
140          */
141         history: function(path, limit , object, ident)
142         {
143             limit = limit || false;
144             object = object || false;
145             ident = ident || false; 
146             var res = [];
147             var args = [ 'log' ];
148              
149             
150             if (object !== false) {
151                 rev = this.resolveRevision(false, object, ident); // from scm...
152                 args.push( '' + rev);  
153             } else {
154                 args.push( "master" );
155             }
156            
157             var arg = {
158                 "no-color" : true, 
159             //args.push("--name-status";
160                 "raw" : true,
161                 "no-abbrev" : true,
162                 "numstat" : true,
163                 "date" : 'iso8601'
164             };
165             if (limit !== false) {
166                 if (typeof(limit) == 'number') {
167                     arg['max-count'] =  limit;
168                 } else if (typeof(limit) == 'object') {
169                     arg.skip=  limit[0];
170                     arg['max-count']=  limit[1];
171      
172                 } else {
173                     arg.since = limit;
174                 }
175             }
176             
177             args.push(arg);
178             
179             
180             
181        
182             
183             //echo '<PRE>';print_r($args);echo '</PRE>';
184             path = path[0] == '/' ? path.substring(1) : path; 
185             
186             args.push({ '' : true });
187             args.push(path);
188             
189             //   print_R(array($args, '--' ,$path));exit;
190             var fp = this.git(args).split("\n");
191     
192             var commits = [];
193             var commit = false;
194             while (fp.length) {
195                 var line = fp.shift() + "\n";
196                 
197                 if (line.match(/^commit/)) {
198                     if (commit !== false) {
199                         commits.push( commit );
200                     }
201                     commit = line;
202                     continue;
203                 }
204                 commit += line ;
205             }
206              
207             if (commit !== false) {
208                 commits.push( commit );
209             }
210             var res = [];
211             //print(JSON.stringify(commits,null,4));
212             
213             var _t = this;
214             commits.forEach( function(c) {
215               //       print(typeof(Event)); print(JSON.stringify(c));
216                 var ev = new Event( {commit : c, repo: _t });
217                 res.push(ev);
218                   
219             });
220             // close 'fp'
221             return res;
222         },
223         diff : function(path, from, to)
224         {
225             to = to || false;
226             from = from || false;
227             
228              
229             
230             
231             /*
232             if ($path instanceof MTrackSCMFile) {
233                 if ($from === null) {
234                     $from = $path->rev;
235                 }
236                 $path = $path->name;
237                 
238             }
239             // if it's a file event.. we are even lucker..
240             if ($path instanceof MTrackSCMFileEvent) {
241                 return $this->git('log', '--max-count=1', '--format=format:', '--patch', $from, '--', $path->name);
242                 
243             }
244             */
245             // diff ignoring white space..
246             args = [ 'diff' , { 'w' : true} ]
247         
248             if (to == false) {
249                 to = from;
250                 from = from + '^';
251             }
252             
253             args.push(from+'..'+to);
254             args.push( { '' : true });
255             if (typeof(path) != 'string') {
256                 path.forEach(function(p) { args.push(p); })
257             }
258             return this.git(args); 
259         },
260         
261         
262         
263         dayTree: function (path, limit , object, ident)
264         {
265             
266             var ar = this.history(path, limit , object, ident);
267             
268             // the point of this is to extract all the revisions, and group them.
269             
270             
271             
272             //echo '<PRE>';print_R($ar);
273             
274             // need to get a 2 dimensional array of
275             // files along top, and commints down.
276             var cfiles = []
277             var rows = [];
278             
279             var days = {};
280             
281             ar.forEach(function( commit) {
282                 
283                 var files = commit.files;
284                 var day = commit.cday;
285                 if (typeof(days[day]) == 'undefined') { 
286                     days[day] = {
287                         'text' : day,
288                         'rev' : day,
289                         'children' : {}
290                     }
291                 }
292                 var time= commit.ctime;
293                 if (typeof(days[day]['children'][time]) == 'undefined' ) { 
294                     days[day]['children'][time] = {
295                         'text' : time,
296                         'rev' : day + ' ' + time,
297                         'children' : [] 
298                     };
299                 }
300                 days[day]['children'][time]['children'].push( {
301                     'text' : commit.changelog,
302                     'rev' : commit.rev,
303                     'leaf' :  true
304                 });
305             });
306             var out = [];
307             
308             for(var d in days) {
309                 var dr = days[d];
310                 dcn = dr['children'];
311                 dr['children'] = [];
312                 for(var t in dcn) {
313                     var to = dcn[t];
314                     to['rev']  = to['children'][0]['rev'];
315                     dr['children'].push( to);
316                 }
317                 dr['rev'] = dr['children'][0]['rev'];
318                 out.push( dr);
319             }
320             
321             return out;            
322              
323             
324         },
325         
326         
327     
328     
329         changedFiles :function(path,  object, ident)
330         {
331              object = object || false;
332             ident = ident || false; 
333             var res = [];
334             var args = [ 'diff', { 'numstat' : true}  , { 'w' : true } ];
335              
336             
337             if (object !== false) {
338                 rev = this.resolveRevision(false, object, ident); // from scm...
339                 args.push( '' + rev);  
340             } else {
341                 args.push( "master" );
342             }
343             path = path[0] == '/' ? path.substring(1) : path; 
344             
345             args.push({ '' : true });
346             args.push(path);
347               // in theory you could click a number of them and only merge those changes..
348             // need to do a git diff.. and just get a list of files..
349             var rows = [];
350             var res = this.git(args).split("\n"); 
351             res.forEach( function(line) {
352                 
353                 
354                 
355                 var ar = line.split("\t"); 
356                 if (ar.length !=3 ) {
357                     return;
358                 }
359                 rows.push({ 
360                     'added' : ar[0],
361                     'removed' : ar[1],
362                     'filename' : ar[2]
363                 });
364                 
365                 
366             });
367             return rows;  
368     
369         },
370         
371         
372         
373         
374         
375         
376         
377     merge : function(branch_from, branch_to, rev, files, use_merge)
378     {
379         
380         var mi = this.history("/", 1, "rev", rev);
381        // echo '<PRE>';print_R($mi);exit;
382         
383         // pause gitlive!!!!
384         this.git([ 'checkout', { 'b': true }, to]);
385         //$wd->git('checkout', '-b', $this->release, 'remotes/origin/'. $this->release);
386         
387         
388         
389         //$patchfile = $this->tempName('txt');
390          
391         
392         if (files !== false) {
393             
394             
395             
396         }
397         if (is_array($files)) { 
398             
399             var diff = this.diff(files, from, to);
400              
401              
402                var sp = new Spawn({
403                 cwd : this.repopath,
404                 args : [ 'patch' , '-p1' ] ,
405                 env :  [  "HOME=" + GLib.get_home_dir() ],
406                 debug: false,
407                 exceptions : false,
408                 async : false,
409                 listeners : {
410                     input : function() {
411                         return diff;
412                     }
413                 }
414             });
415             sp.run(); 
416              
417               ;  //eg . patch -p1 < /var/lib/php5/MTrackTMPgZFeAN.txt
418         } else {
419             // if no files -- it means all?/
420             // although we should check to see if this is valid..
421             this.git([' merge', { 'squash' : true }, rev ]);
422        }
423           
424         //echo $cmd;
425         /*
426         $commit = (object) array(
427             'when' =>  $mi[0]->ctime,
428             'reason' => $_REQUEST['message'],
429             'name'  => $this->authUser->name,
430             'email' => $this->authUser->email,
431         );
432         
433         $res = $wd->commit($commit);
434         if (!is_array($files)) {
435             // we do an actually merge commit seperatly from the merge diff, so that
436             // our logs show a nice history in each of those commits.
437             // not sure if this is a good idea or not..
438             $wd->git('merge', '-m', "Merge Commit with working branch (no code changed)" , $rev);
439         }
440         
441         
442         $res .= $wd->push();
443         $this->jok($res);
444         
445        // $wd->checkout($this->release);
446         // generate the patch
447         // apply the patch
448         // commit with message..
449         // push
450         
451         
452         */
453         
454         
455         
456         
457         
458         
459     },
460         
461         
462         
463         
464     /*
465         public function getWorkingCopy()
466         {
467             require_once 'MTrack/SCM/Git/WorkingCopy.php';
468             return new MTrack_SCM_Git_WorkingCopy($this);
469         }
470     
471       public function getRelatedChanges($revision) // pretty nasty.. could end up with 1000's of changes..
472       {
473         $parents = array();
474         $kids = array();
475     
476         $fp = $this->git('rev-parse', "$revision^");
477         while (($line = fgets($fp)) !== false) {
478           $parents[] = trim($line);
479         }
480     
481         // Ugh!: http://stackoverflow.com/questions/1761825/referencing-the-child-of-a-commit-in-git
482         $fp = $this->git('rev-list', '--all', '--parents');
483         while (($line = fgets($fp)) !== false) {
484           $hashes = preg_split("/\s+/", $line);
485           $kid = array_shift($hashes);
486           if (in_array($revision, $hashes)) {
487             $kids[] = $kid;
488           }
489         }
490     
491         return array($parents, $kids);
492       } */
493     
494         
495          
496         
497         git: function(args_in)
498         {
499             // convert arguments.
500             
501             //print(JSON.stringify(args_in,null,4));
502             args_in.unshift( {
503                 'git-dir' : this.gitdir,
504                 'no-pager' : true 
505             });
506             var args = ['git' ];
507             
508             if (this.gitdir != this.repopath) {
509                 args_in.unshift( { "work-tree" :  this.gitdir } ); 
510             }
511             
512             args_in.forEach(function(arg) { 
513                  if (typeof(arg) == 'string') {
514                     args.push(arg);
515                     return;
516                 }
517                 if (typeof(arg) == 'object') {
518                     for(var k in arg) {
519                         var v = arg[k];
520                         
521                         args.push(k.length != 1 ? ('--' + k) : ('-' + k));
522                         
523                         if (v === true) {
524                             continue;;
525                         }
526                         args.push(v);
527                     }
528                 }
529             });
530             this.lastCmd = args.join(" ");
531             if(this.debug) {
532                
533                 print( args.join(" ")); 
534             }
535             
536             var env =  [  "HOME=" + GLib.get_home_dir() ];
537             // do not need to set gitpath..
538             //if (File.exists(this.repo + '/.git/config')) {
539                 //env.push("GITPATH=" + this.repo );
540             //}
541             
542             //print(JSON.stringify(args,null,4));  Seed.quit();
543             var sp = new Spawn({
544                 cwd : this.repopath,
545                 args : args,
546                 env : env, // optional
547                 debug: false,
548                 exceptions : false,
549                 async : false
550             });
551             sp.run();
552             //print(JSON.stringify(sp,null,4));  Seed.quit();
553
554             //print("GOT: " + output)
555             // parse output for some commands ?
556             return sp.output;
557         }
558   
559    
560 });
561