sync
[gitlive] / old_seed_version / Scm / Git / Repo.js
diff --git a/old_seed_version/Scm/Git/Repo.js b/old_seed_version/Scm/Git/Repo.js
new file mode 100644 (file)
index 0000000..a946bfe
--- /dev/null
@@ -0,0 +1,939 @@
+
+const GLib   = imports.gi.GLib;
+
+const XObject = imports.XObject.XObject;
+const Event  = imports.Scm.Git.Event.Event;
+const Spawn  = imports.Spawn.Spawn;
+const File  = imports.File.File;
+
+/**
+ * @class Scm.Git.Repo
+ *
+ * @extends Scm.Repo
+ * 
+ *
+ *
+ */
+Repo = XObject.define(
+    /**
+     * constructor:
+     * 
+     * @param {Object} cfg - Configuration
+     *     (basically repopath is currently only critical one.)
+     *
+     */
+     
+    function(cfg) {
+        // cal parent?
+        XObject.extend(this,cfg);
+        this.gitdir = cfg.repopath + "/.git";
+        
+        if (!File.isDirectory(this.gitdir)) {
+            this.gitdir = this.repopath;
+        }
+        //Repo.superclass.constructor.call(this,cfg);
+        
+    },
+    imports.Scm.Repo.Repo, // or Object
+    {
+        branches : false,
+        currentBranch : false,
+        tags : false,
+        gitdir : false,
+        debug : true,
+        lastCmd : false,
+        hasLocalChanges : false,
+        localChanges : false,
+        
+        getMetaData : function() {
+            return {
+              name :  'Git',
+              tool : ['git' ]  
+                
+            };
+        },
+        
+        
+        getStatus : function()
+        {
+            //git status --porcelain
+            // find out if we are up-to-date.
+            //git ls-remote origin -h refs/heads/master
+            var bl = this.git([ 'status', {
+                'porcelain' : true
+            }]);
+            print(JSON.stringify(bl.length));
+            this.hasLocalChanges = bl.length > 0 ? true : false; 
+            this.localChanges = bl;
+            
+            
+        },
+        
+        
+        getBranches : function()
+        {
+            if (this.branches !== false) {
+                return this.branches;
+            }
+            this.branches = [];
+            
+            var bl = this.git([ 'branch', {
+                'no-color' : true,
+                'verbose' : true,
+                'no-abbrev'  : true,
+                'a' : true
+            }]).split("\n");
+            var bmap = {};
+            var _this=this;
+            
+            var local = [];
+            var remotes = [];
+            
+            bl.forEach(function(line) {
+                  // * master 61e7e7d oneliner
+                var active = line[0]=='*';
+                if (!line.length) {
+                    return;
+                }
+                line = line.substring(2);
+                
+                //print(JSON.stringify(line));
+                var parts = line.split(/\s+/);
+                if (parts[1] == '->') {
+                    return; // it's an alias like  remotes/origin/HEAD    -> origin/master
+                }
+                var br = {
+                    lastrev :  parts[1],
+                    name :      '',
+                    remote :    '',
+                    remoterev : ''
+                };
+                if (parts[0].match(/^remotes\//)) {
+                    br.remote = parts[0];
+                    bmap[br.remote] = br;
+                    remotes.push(br);
+                } else { 
+                    br.name = parts[0];
+                    bmap[br.name] = br;
+                    local.push(br);
+                }
+                
+                if (active) {
+                    _this.currentBranch = br;
+                }
+            });
+            
+            //print(JSON.stringify(local));
+            //print(JSON.stringify(remotes));
+            //print(JSON.stringify(bmap,null,4));
+
+            
+            // overlay tracking informaion
+            bl = this.git([
+                'for-each-ref',
+                { format :  '%(refname:short):remotes/%(upstream:short)' },
+                'refs/heads'
+            ]).split("\n");
+            //print(this.lastCmd);
+            
+            //print(JSON.stringify(bl,null,4));
+            
+            bl.forEach(function(line) {
+                if (!line.length) {
+                    return;
+                }
+                var ar = line.split(':remotes/');
+                
+                var lname= ar[0];
+                var rname = 'remotes/' + ar[1];
+                //print(rname);
+                // we should always have a local version of it.
+                bmap[lname].remote = rname;
+                if (typeof(bmap[rname]) == 'undefined') {
+                    return;
+                }
+                bmap[lname].remoterev =  bmap[rname].lastrev;
+                // flag it for not adding..
+                
+                bmap[rname].name = lname;
+            });
+            var _this =this;
+            
+            
+            
+            // add any remotes that do not have name..
+            remotes.forEach(function(r) {
+                if (r.name.length) {
+                    return;
+                }
+                
+                // create a tracking branch..
+                var name = r.remote.replace(/^remotes\//, '' ).replace(/^origin\//,'').replace('/', '.');
+                
+                 
+              
+                if (typeof(bmap[name]) != 'undefined') {
+                    // already got aremote of that name.
+                    // skip it...
+                    r.remoterev = r.lastrev;
+                    r.lastrev = '';
+                    local.push(r);
+                    return;
+                }
+                
+                
+                _this.git([ 
+                    'branch',
+                    {
+                        f : true,
+                        track : true
+                    },
+                    name,
+                    r.remote
+                ]);
+                
+                r.remoterev = r.lastrev;
+                r.name = name;
+                local.push(r);
+            });
+            
+            this.branches = local;
+         //    print(JSON.stringify(local,null,4));
+            
+            
+            return this.branches;
+        },
+        _remotes : false,
+        
+        remotes: function(cfg)
+        {
+            
+            if (cfg) {
+                this._remotes = false; // reset so we can query it..
+                
+                var url = Repo.parseURL(cfg.url);
+                if ((url.scheme == 'http://' || url.scheme == 'https://' )
+                    && url.user.length) {
+                    // remove username - as it confuses netrc..
+                    cfg.url = url.scheme + url.host + '/' +url.path;
+                    
+                }
+                
+                
+                this.git([
+                    'remote',
+                    'add',
+                    cfg.name,
+                    cfg.url
+                ]);
+                this.branches = false;
+                
+                this.git([
+                    'fetch',
+                    cfg.name,
+                    { 'a' : true }
+                ]);
+                
+                
+                
+                //print(this.lastCmd);
+
+            }
+            
+            
+            if (this._remotes!== false) {
+                return this._remotes;
+            }
+            this._remotes = [];
+            
+            var bl = this.git([ 'remote', {
+            //    'no-color' : true,
+                'verbose' : true
+            }]).split("\n");
+            
+            var _this=this;
+            bl.forEach(function(line) {
+                if (!line.length) {
+                    return;
+                  // * master 61e7e7d oneliner
+                }
+               // print(JSON.stringify( line));
+                var parts = line.split(/\t/);
+                var type = false;
+                parts[1] = parts[1].replace(/\([a-z]+\)$/, function( a) {
+                    type = a.substr(1,a.length-2);
+                    return '';
+                });
+                
+                _this._remotes.push( {
+                    name: parts[0],
+                    url : parts[1],
+                    type:  type
+                });
+                
+            });
+            return this._remotes;
+        },
+        
+        
+        
+        lastupdated: function() {
+            return 'tbc';
+        },
+        autocommit: function(val) {
+            if (typeof(val) == 'undefined') {
+                
+                if (File.exists(this.gitdir + '/.gitlive-disable-autocommit')) {
+                    return false;
+                }
+                
+                return true;
+            }
+            if (val == this.autocommit()) {
+                return val;
+            }
+            if (!val) { // turn off
+                File.write(this.gitdir + '/.gitlive-disable-autocommit', '.');
+            } else {
+                File.remove(this.gitdir + '/.gitlive-disable-autocommit');
+            }
+            return val;
+            
+        },
+        
+        
+        
+        autopush: function(val) {
+            
+            if (typeof(val) == 'undefined') {
+                
+                if (File.exists(this.gitdir + '/.gitlive-disable-autopush')) {
+                    return false;
+                }
+                
+                return true;
+            }
+            if (val == this.autocommit()) {
+                return val;
+            }
+            if (!val) { // turn off
+                File.write(this.gitdir + '/.gitlive-disable-autopush', '.');
+            } else {
+                File.remove(this.gitdir + '/.gitlive-disable-autopush');
+            }
+            return val; 
+        },
+          /*
+        
+          public function getTags()
+          {
+            if ($this->tags !== null) {
+              return $this->tags;
+            }
+            $this->tags = array();
+            $fp = $this->git('tag');
+            while ($line = fgets($fp)) {
+              $line = trim($line);
+              $this->tags[$line] = $line;
+            }
+            $fp = null;
+            return $this->tags;
+          }
+        
+          public function readdir($path, $object = null, $ident = null)
+          {
+            $res = array();
+        
+            if ($object === null) {
+              $object = 'branch';
+              $ident = 'master';
+            }
+            $rev = $this->resolveRevision(null, $object, $ident);
+        
+            if (strlen($path)) {
+              $path = rtrim($path, '/') . '/';
+            }
+        
+            $fp = $this->git('ls-tree', $rev, $path);
+        
+            $dirs = array();
+            require_once 'MTrack/SCM/Git/File.php';
+            while ($line = fgets($fp)) {
+                // blob = file, tree = dir..
+                list($mode, $type, $hash, $name) = preg_split("/\s+/", $line);
+                //echo '<PRE>';echo $line ."\n</PRE>";
+                $res[] = new MTrack_SCM_Git_File($this, "$name", $rev, $type == 'tree', $hash);
+            }
+            return $res;
+          }
+        
+          public function file($path, $object = null, $ident = null)
+          {
+            if ($object == null) {
+              $branches = $this->getBranches();
+              if (isset($branches['master'])) {
+                $object = 'branch';
+                $ident = 'master';
+              } else {
+                // fresh/empty repo
+                return null;
+              }
+            }
+            $rev = $this->resolveRevision(null, $object, $ident);
+            require_once 'MTrack/SCM/Git/File.php';
+            return new MTrack_SCM_Git_File($this, $path, $rev);
+          }
+       */
+        /**
+         * history:
+         * Get the log/history about repo or specific file(s)
+         * 
+         * @param  string path (can be empty - eg. '')
+         * @param  {number|date} limit  how many to fetch
+         * @param  {string} object = eg. rev|tag|branch  (use 'rev' here and ident=HASH to retrieve a speific revision
+         * @param  {string} ident = 
+         *
+         * Example:
+         *   - fetch on revision?:  '.',1,'rev','xxxxxxxxxx'
+         *
+         *
+         * range... object ='rev' ident ='master..release'
+         * 
+         */
+        history: function(path, limit , object, ident)
+        {
+            limit = limit || false;
+            object = object || false;
+            ident = ident || false; 
+            var res = [];
+            var args = [ 'log' ];
+             
+            
+            if (object !== false) {
+                rev = this.resolveRevision(false, object, ident); // from scm...
+                args.push( '' + rev);  
+            } else {
+                args.push( "master" );
+            }
+           
+            var arg = {
+                "no-color" : true, 
+            //args.push("--name-status";
+                "raw" : true,
+                 
+                "numstat" : true,
+                "date" : 'iso8601'
+            };
+            if (limit !== false) {
+                if (typeof(limit) == 'number') {
+                    arg['max-count'] =  limit;
+                } else if (typeof(limit) == 'object') {
+                    arg.skip=  limit[0];
+                    arg['max-count']=  limit[1];
+     
+                } else {
+                    arg.since = limit;
+                }
+            }
+            
+            args.push(arg);
+            
+            
+            
+       
+            
+            //echo '<PRE>';print_r($args);echo '</PRE>';
+            
+            
+            
+            args.push({ '' : true });
+            if (typeof(path) == 'string') {
+                path = path[0] == '/' ? path.substring(1) : path;
+                args.push(path);
+            } else {
+                path.forEach(function(p) {
+                     args.push(p);
+                })
+            }
+            
+            
+            //   print_R(array($args, '--' ,$path));exit;
+            var fp = this.git(args).split("\n");
+    
+            var commits = [];
+            var commit = false;
+            while (fp.length) {
+                var line = fp.shift() + "\n";
+                
+                if (line.match(/^commit/)) {
+                    if (commit !== false) {
+                        commits.push( commit );
+                    }
+                    commit = line;
+                    continue;
+                }
+                commit += line ;
+            }
+             
+            if (commit !== false) {
+                commits.push( commit );
+            }
+            var res = [];
+            //print(JSON.stringify(commits,null,4));
+            
+            var _t = this;
+            commits.forEach( function(c) {
+              //       print(typeof(Event)); print(JSON.stringify(c));
+                var ev = new Event( {commit : c, repo: _t });
+                res.push(ev);
+                  
+            });
+            // close 'fp'
+            return res;
+        },
+        diff : function(path, from, to)
+        {
+            to = to || false;
+            from = from || false;
+            
+             
+            
+            
+            /*
+            if ($path instanceof MTrackSCMFile) {
+                if ($from === null) {
+                    $from = $path->rev;
+                }
+                $path = $path->name;
+                
+            }
+            // if it's a file event.. we are even lucker..
+            if ($path instanceof MTrackSCMFileEvent) {
+                return $this->git('log', '--max-count=1', '--format=format:', '--patch', $from, '--', $path->name);
+                
+            }
+            */
+            // diff ignoring white space..
+            args = [ 'diff' , { 'w' : true} ]
+        
+            if (to == false) {
+                to = from;
+                from = from + '^';
+            }
+            
+            args.push(from+'..'+to);
+            args.push( { '' : true });
+            if (typeof(path) != 'string') {
+                path.forEach(function(p) { args.push(p); })
+            }
+            return this.git(args); 
+        },
+        
+        
+        
+        dayTree: function (path, limit , object, ident)
+        {
+            
+            var ar = this.history(path, limit , object, ident);
+            
+            // the point of this is to extract all the revisions, and group them.
+            
+            
+            
+            //echo '<PRE>';print_R($ar);
+            
+            // need to get a 2 dimensional array of
+            // files along top, and commints down.
+            var cfiles = []
+            var rows = [];
+            
+            var days = {};
+            
+            ar.forEach(function( commit) {
+                
+                var files = commit.files;
+                var day = commit.cday;
+                if (typeof(days[day]) == 'undefined') { 
+                    days[day] = {
+                        'text' : day,
+                        'rev' : day,
+                        'children' : {}
+                    }
+                }
+                var time= commit.ctime;
+                if (typeof(days[day]['children'][time]) == 'undefined' ) { 
+                    days[day]['children'][time] = {
+                        'text' : time,
+                        'rev' : day + ' ' + time,
+                        'children' : [] 
+                    };
+                }
+                days[day]['children'][time]['children'].push( {
+                    'text' : commit.changelog,
+                    'rev' : commit.rev,
+                    'leaf' :  true
+                });
+            });
+            var out = [];
+            
+            for(var d in days) {
+                var dr = days[d];
+                dcn = dr['children'];
+                dr['children'] = [];
+                for(var t in dcn) {
+                    var to = dcn[t];
+                    to['rev']  = to['children'][0]['rev'];
+                    dr['children'].push( to);
+                }
+                dr['rev'] = dr['children'][0]['rev'];
+                out.push( dr);
+            }
+            
+            return out;            
+             
+            
+        },
+        
+        
+    
+    
+        changedFiles :function(path,  object, ident)
+        {
+             object = object || false;
+            ident = ident || false; 
+            var res = [];
+            var args = [ 'diff', { 'numstat' : true}  , { 'w' : true } ];
+             
+            
+            if (object !== false) {
+                rev = this.resolveRevision(false, object, ident); // from scm...
+                args.push( '' + rev);  
+            } else {
+                args.push( "master" );
+            }
+            path = path[0] == '/' ? path.substring(1) : path; 
+            
+            args.push({ '' : true });
+            args.push(path);
+              // in theory you could click a number of them and only merge those changes..
+            // need to do a git diff.. and just get a list of files..
+            var rows = [];
+            var res = this.git(args).split("\n"); 
+            res.forEach( function(line) {
+                
+                
+                
+                var ar = line.split("\t"); 
+                if (ar.length !=3 ) {
+                    return;
+                }
+                rows.push({ 
+                    'added' : ar[0],
+                    'removed' : ar[1],
+                    'filename' : ar[2]
+                });
+                
+                
+            });
+            return rows;  
+    
+        },
+        
+        
+        checkout : function(branch)
+        {
+            this.git(['checkout', branch ]);
+        },
+        /**
+         * stash:
+         * Very basic stash the current changes (normally so you can checkout
+         * another branch..)
+         */
+        stash: function()
+        {
+            this.git(['stash' ]);
+        },
+        
+        
+            
+        applyPatch : function( diff )
+        {
+            var sp = new Spawn({
+                cwd : this.repopath,
+                args : [ 'patch' , '-p1' , '-f' ] ,
+                env :  [  "HOME=" + GLib.get_home_dir() ],
+                debug: true,
+                exceptions : false,
+                async : false,
+                listeners : {
+                    input : function() {
+                        print("sedning patch!");
+                        return diff;
+                    }
+                }
+            });
+            sp.run();
+            return sp.output;
+        } ,
+        /**
+         * add:
+         * add files to track.
+         *
+         * @argument {Array} files the files to add.
+         */
+        add : function ( files )
+        {
+            // should really find out if these are untracked files each..
+            // we run multiple versions to make sure that if one failes, it does not ignore the whole lot..
+            // not sure if that is how git works.. but just be certian.
+            var _t = this;
+            files.forEach(function(f) {
+                try { 
+                    _t.git([ 'add', { '': true }, f ]);
+                } catch(e) {} // ignore errors..
+            });  
+        },
+        
+          /**
+         * remove:
+         * remove files to track.
+         *
+         * @argument {Array} files the files to add.
+         */
+        remove : function ( files )
+        {
+            // this may fail if files do not exist..
+            // should really find out if these are untracked files each..
+            // we run multiple versions to make sure that if one failes, it does not ignore the whole lot..
+            // not sure if that is how git works.. but just be certian.
+            var _t = this;
+            files.forEach(function(f) {
+                try {
+                    _t.git([ 'rm', { f: true } , { '': true }, f ]);
+                } catch(e) {} // ignore errors..
+            });  
+        },
+        
+        /**
+         * commit:
+         * perform a commit.
+         *
+         * @argument {Object} cfg commit configuration
+         * 
+         * @property {String} name (optional)
+         * @property {String} email (optional)
+         * @property {String} changed (date) (optional)
+         * @property {String} reason (optional)
+         * @property {Array} files - the files that have changed. 
+         * 
+         */
+         
+        commit : function( cfg )
+        {
+            
+            var args= [  'commit'  ];
+            var env = [];
+            
+            if (typeof(cfg.name) != 'undefined') {
+                args.push( {
+                    'author' : cfg.name + ' <' + cfg.email + '>'
+                });
+                env.push(
+                    "GIT_COMMITTER_NAME" + cfg.name,
+                    "GIT_COMMITTER_EMAIL" + cfg.email
+                );
+            }
+            if (typeof(cfg.changed) != 'undefined') {
+                env.push("GIT_AUTHOR_DATE= " + cfg.changed )
+                
+            }
+            args.push( 
+                { 'm' : (cfg.reason ? cfg.reason : 'Changed') },
+                { '': true }
+            );
+            
+            cfg.files.forEach(function(f) { args.push(f); })
+             
+            return this.git(args, env);
+        },
+        
+        /**
+         * pull:
+         * Fetch and merge remote repo changes into current branch..
+         *
+         * At present we just need this to update the current working branch..
+         * -- maybe later it will have a few options and do more stuff..
+         *
+         */
+        pull : function ()
+        {
+            // should probably hand error conditions better... 
+            return this.git([ 'pull' ]);
+            
+            
+        },
+        /**
+         * push:
+         * Send local changes to remote repo(s)
+         *
+         * At present we just need this to push the current branch.
+         * -- maybe later it will have a few options and do more stuff..
+         *
+         */
+        push : function ()
+        {
+            // should 
+            return this.git([ 'push' ]);
+            
+        },
+        
+    /*
+        public function getWorkingCopy()
+        {
+            require_once 'MTrack/SCM/Git/WorkingCopy.php';
+            return new MTrack_SCM_Git_WorkingCopy($this);
+        }
+    
+      public function getRelatedChanges($revision) // pretty nasty.. could end up with 1000's of changes..
+      {
+        $parents = array();
+        $kids = array();
+    
+        $fp = $this->git('rev-parse', "$revision^");
+        while (($line = fgets($fp)) !== false) {
+          $parents[] = trim($line);
+        }
+    
+        // Ugh!: http://stackoverflow.com/questions/1761825/referencing-the-child-of-a-commit-in-git
+        $fp = $this->git('rev-list', '--all', '--parents');
+        while (($line = fgets($fp)) !== false) {
+          $hashes = preg_split("/\s+/", $line);
+          $kid = array_shift($hashes);
+          if (in_array($revision, $hashes)) {
+            $kids[] = $kid;
+          }
+        }
+    
+        return array($parents, $kids);
+      } */
+    
+        
+         
+        
+        git: function(args_in,env)
+        {
+            // convert arguments.
+            
+            //print(JSON.stringify(args_in,null,4));
+            args_in.unshift( {
+                'git-dir' : this.gitdir,
+                'no-pager' : true 
+            });
+            var args = ['git' ];
+            
+            if (this.gitdir != this.repopath) {
+                args_in.unshift( { "work-tree" :  this.repopath } ); 
+            }
+            
+            args_in.forEach(function(arg) { 
+                 if (typeof(arg) == 'string') {
+                    args.push(arg);
+                    return;
+                }
+                if (typeof(arg) == 'object') {
+                    for(var k in arg) {
+                        var v = arg[k];
+                        
+                        args.push(k.length != 1 ? ('--' + k) : ('-' + k));
+                        
+                        if (v === true) {
+                            continue;;
+                        }
+                        args.push(v);
+                    }
+                }
+            });
+            this.lastCmd = args.join(" ");
+            if(this.debug) {
+               
+                print( args.join(" ")); 
+            }
+            env = env || [];
+            env.push(  "HOME=" + GLib.get_home_dir() );
+            // do not need to set gitpath..
+            //if (File.exists(this.repo + '/.git/config')) {
+                //env.push("GITPATH=" + this.repo );
+            //}
+            
+            //print(JSON.stringify(args,null,4));  Seed.quit();
+            var sp = new Spawn({
+                cwd : this.repopath,
+                args : args,
+                env : env, // optional
+                debug: this.debug,
+                exceptions : false,
+                async : false
+            });
+            sp.run();
+            
+            if (sp.result) {
+                print(JSON.stringify(sp.result));
+                
+                print(JSON.stringify(sp.args));
+                print(JSON.stringify(sp.stderr));
+                
+                throw {
+                    name    : "RepoSpawnError",
+                    message : sp.stderr,
+                    spawn   : sp
+                };                
+            }
+             
+            //print("GOT: " + output)
+            // parse output for some commands ?
+            return sp.output;
+        },
+        /**
+         * parseAuthor:
+         * break author string with name and email into parts
+         * @argument {String} author
+         * @returns {Object} with 'name' and 'email' properties.
+         */ 
+        parseAuthor : function(author)
+        {
+            var lp = author.indexOf('<');
+             
+            return {
+                name : author.substring(0, lp-1).replace(/\s+$/,''),
+                email : author.substring(0, author.length-1).substring(lp+1)
+            };
+            
+        }
+          
+});
+
+Repo.parseURL = function(url)
+{
+    
+    // git : git://github.com/roojs/roojs1.git
+    // http https://roojs@github.com/roojs/roojs1.git
+    // ssh  git@github.com:roojs/roojs1.git
+    var ret = {
+       scheme : 'ssh://'
+    }
+    url = url.replace(/^[a-z]+:\/\//, function(v) {
+        ret.scheme = v;
+        return '';
+    });
+    var parts = url.split(ret.scheme == 'ssh://' ? ':' : "/");
+    var login_host  = parts.shift().split('@');
+    var user_pass  = (login_host.length == 2 ? login_host.shift() : '').split(':');
+    ret.user = user_pass[0];
+    ret.pass = user_pass.length == 2 ? user_pass[1] : ''; 
+    
+    ret.host = login_host.shift();
+    ret.path = parts.join('/');
+    return ret;
+    
+    
+}
\ No newline at end of file