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