You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ag...@apache.org on 2014/04/24 18:07:01 UTC

git commit: repo-status & repo-update modules

Repository: cordova-coho
Updated Branches:
  refs/heads/master 6c10b474d -> 29a3289fe


repo-status & repo-update modules


Project: http://git-wip-us.apache.org/repos/asf/cordova-coho/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-coho/commit/29a3289f
Tree: http://git-wip-us.apache.org/repos/asf/cordova-coho/tree/29a3289f
Diff: http://git-wip-us.apache.org/repos/asf/cordova-coho/diff/29a3289f

Branch: refs/heads/master
Commit: 29a3289fe96d2d5050af2df754272e0571da45f0
Parents: 6c10b47
Author: Andrew Grieve <ag...@chromium.org>
Authored: Thu Apr 24 12:06:44 2014 -0400
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Thu Apr 24 12:06:44 2014 -0400

----------------------------------------------------------------------
 src/gitutil.js     |  51 ++++++++
 src/main.js        | 323 ++++++------------------------------------------
 src/repo-status.js | 111 +++++++++++++++++
 src/repo-update.js | 145 ++++++++++++++++++++++
 4 files changed, 344 insertions(+), 286 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-coho/blob/29a3289f/src/gitutil.js
----------------------------------------------------------------------
diff --git a/src/gitutil.js b/src/gitutil.js
index 3058905..eff1513 100644
--- a/src/gitutil.js
+++ b/src/gitutil.js
@@ -19,6 +19,7 @@ under the License.
 
 var path = require('path');
 var executil = require('./executil');
+var gitutil = exports;
 
 exports.findMostRecentTag = function() {
     return executil.execHelper(executil.ARGS('git describe --tags --abbrev=0 HEAD'), true);
@@ -28,3 +29,53 @@ exports.tagExists = function*(tagName) {
     return !!(yield executil.execHelper(executil.ARGS('git tag --list ' + tagName), true));
 }
 
+exports.retrieveCurrentBranchName = function*(allowDetached) {
+    var ref = yield executil.execHelper(executil.ARGS('git symbolic-ref HEAD'), true, true);
+    if (!ref) {
+        if (allowDetached) {
+            return null;
+        }
+        throw new Error('Aborted due to repo ' + shjs.pwd() + ' not being on a named branch');
+    }
+    var match = /refs\/heads\/(.*)/.exec(ref);
+    if (!match) {
+        throw new Error('Could not parse branch name from: ' + ref);
+    }
+    return match[1];
+}
+
+exports.remoteBranchExists = function*(repo, name) {
+    return !!(yield executil.execHelper(executil.ARGS('git branch -r --list ' + repo.remoteName + '/' + name), true));
+}
+
+exports.stashAndPop = function*(repo, func) {
+    var requiresStash = yield gitutil.pendingChangesExist();
+    var branchName = yield gitutil.retrieveCurrentBranchName();
+
+    if (requiresStash) {
+        yield executil.execHelper(executil.ARGS('git stash save --all --quiet', 'coho stash'));
+    }
+
+    yield func();
+
+    yield gitutil.gitCheckout(branchName);
+    if (requiresStash) {
+        yield executil.execHelper(executil.ARGS('git stash pop'));
+    }
+}
+
+exports.pendingChangesExist = function*() {
+    return !!(yield executil.execHelper(executil.ARGS('git status --porcelain'), true));
+}
+
+exports.gitCheckout = function*(branchName) {
+    var curBranch = yield gitutil.retrieveCurrentBranchName(true);
+    if (curBranch != branchName) {
+        return yield executil.execHelper(executil.ARGS('git checkout -q ', branchName));
+    }
+}
+
+exports.localBranchExists = function*(name) {
+    return !!(yield executil.execHelper(executil.ARGS('git branch --list ' + name), true));
+}
+

http://git-wip-us.apache.org/repos/asf/cordova-coho/blob/29a3289f/src/main.js
----------------------------------------------------------------------
diff --git a/src/main.js b/src/main.js
index db2fff7..a2a7d2d 100644
--- a/src/main.js
+++ b/src/main.js
@@ -25,6 +25,7 @@ var apputil = require('./apputil');
 var executil = require('./executil');
 var gitutil = require('./gitutil');
 var repoutil = require('./repoutil');
+var repoupdate = require('./repo-update');
 var print = apputil.print;
 try {
     var optimist = require('optimist');
@@ -63,36 +64,6 @@ function cpAndLog(src, dest) {
     }
 }
 
-function *gitCheckout(branchName) {
-    var curBranch = yield retrieveCurrentBranchName(true);
-    if (curBranch != branchName) {
-        return yield executil.execHelper(executil.ARGS('git checkout -q ', branchName));
-    }
-}
-
-function *localBranchExists(name) {
-    return !!(yield executil.execHelper(executil.ARGS('git branch --list ' + name), true));
-}
-
-function *remoteBranchExists(repo, name) {
-    return !!(yield executil.execHelper(executil.ARGS('git branch -r --list ' + repo.remoteName + '/' + name), true));
-}
-
-function *retrieveCurrentBranchName(allowDetached) {
-    var ref = yield executil.execHelper(executil.ARGS('git symbolic-ref HEAD'), true, true);
-    if (!ref) {
-        if (allowDetached) {
-            return null;
-        }
-        throw new Error('Aborted due to repo ' + shjs.pwd() + ' not being on a named branch');
-    }
-    var match = /refs\/heads\/(.*)/.exec(ref);
-    if (!match) {
-        throw new Error('Could not parse branch name from: ' + ref);
-    }
-    return match[1];
-}
-
 function retrieveCurrentTagName() {
     // This will return the tag name plus commit info it not directly at a tag.
     // That's fine since all users of this function are meant to use the result
@@ -100,88 +71,6 @@ function retrieveCurrentTagName() {
     return executil.execHelper(executil.ARGS('git describe --tags HEAD'), true, true);
 }
 
-function *repoStatusCommand(argv) {
-    var opt = flagutil.registerRepoFlag(optimist)
-    var opt = optimist
-        .options('b', {
-            alias: 'branch',
-            desc: 'The name of the branch to report on. Can be specified multiple times to specify multiple branches. The local version of the branch is compared with the origin\'s version unless --b2 is specified.'
-         })
-        .options('branch2', {
-            desc: 'The name of the branch to diff against. This is origin/$branch by default.'
-         })
-        .options('diff', {
-            desc: 'Show a diff of the changes.',
-            default: false
-         })
-    opt = flagutil.registerHelpFlag(opt);
-    var argv = opt
-        .usage('Reports what changes exist locally that are not yet pushed.\n' +
-               '\n' +
-               'Example usage: $0 repo-status -r auto -b master -b 2.9.x\n' +
-               'Example usage: $0 repo-status -r plugins -b dev --branch2 master --diff')
-        .argv;
-
-    if (argv.h) {
-        optimist.showHelp();
-        process.exit(1);
-    }
-    var branches = argv.b && (Array.isArray(argv.b) ? argv.b : [argv.b]);
-    var branches2 = branches && argv.branch2 && (Array.isArray(argv.branch2) ? argv.branch2 : [argv.branch2]);
-    var repos = flagutil.computeReposFromFlag(argv.r);
-
-    if (branches2 && branches && branches.length != branches2.length) {
-        apputil.fatal('Must specify the same number of --branch and --branch2 flags');
-    }
-
-    yield repoutil.forEachRepo(repos, function*(repo) {
-        if (repo.svn) {
-            print('repo-status not implemented for svn repos');
-            return;
-        }
-        // Determine remote name.
-        yield updateRepos([repo], [], true);
-        var actualBranches = branches ? branches : /^plugin/.test(repo.id) ? ['dev', 'master'] : ['master'];
-        for (var i = 0; i < actualBranches.length; ++i) {
-            var branchName = actualBranches[i];
-            if (!(yield localBranchExists(branchName))) {
-                continue;
-            }
-            var targetBranch = branches2 ? branches2[i] : ((yield remoteBranchExists(repo, branchName)) ? repo.remoteName + '/' + branchName : 'master');
-            var changes = yield executil.execHelper(executil.ARGS('git log --no-merges --oneline ' + targetBranch + '..' + branchName), true);
-            if (changes) {
-                print('Local commits exist on ' + branchName + ':');
-                console.log(changes);
-            }
-        }
-        var gitStatus = yield executil.execHelper(executil.ARGS('git status --short'), true);
-        if (gitStatus) {
-            print('Uncommitted changes:');
-            console.log(gitStatus);
-        }
-    });
-    if (argv.diff) {
-        yield repoutil.forEachRepo(repos, function*(repo) {
-            var actualBranches = branches ? branches : [/^plugin/.test(repo.id) ? 'dev' : 'master'];
-            for (var i = 0; i < actualBranches.length; ++i) {
-                var branchName = actualBranches[i];
-                if (!(yield localBranchExists(branchName))) {
-                    return;
-                }
-                var targetBranch = branches2 ? branches2[i] : ((yield remoteBranchExists(repo, branchName)) ? repo.remoteName + '/' + branchName : 'master');
-                var diff = yield executil.execHelper(executil.ARGS('git diff ' + targetBranch + '...' + branchName), true);
-                if (diff) {
-                    print('------------------------------------------------------------------------------');
-                    print('Diff for ' + repo.repoName + ' on branch ' + branchName + ' (vs ' + targetBranch + ')');
-                    print('------------------------------------------------------------------------------');
-                    console.log(diff);
-                    console.log('\n');
-                }
-            }
-        });
-    }
-}
-
 function *repoResetCommand(argv) {
     var opt = flagutil.registerRepoFlag(optimist)
     var opt = optimist
@@ -212,11 +101,11 @@ function *repoResetCommand(argv) {
     function *cleanRepo(repo) {
         for (var i = 0; i < branches.length; ++i) {
             var branchName = branches[i];
-            if (!(yield localBranchExists(branchName))) {
+            if (!(yield gitutil.localBranchExists(branchName))) {
                 continue;
             }
-            if (yield remoteBranchExists(repo, branchName)) {
-                yield gitCheckout(branchName);
+            if (yield gitutil.remoteBranchExists(repo, branchName)) {
+                yield gitutil.gitCheckout(branchName);
                 var changes = yield executil.execHelper(executil.ARGS('git log --oneline ' + repo.remoteName + '/' + branchName + '..' + branchName));
                 if (changes) {
                     print(repo.repoName + ' on branch ' + branchName + ': Local commits exist. Resetting.');
@@ -225,8 +114,8 @@ function *repoResetCommand(argv) {
                     print(repo.repoName + ' on branch ' + branchName + ': No local commits to reset.');
                 }
             } else {
-                if ((yield retrieveCurrentBranchName()) == branchName) {
-                    yield gitCheckout('master');
+                if ((yield gitutil.retrieveCurrentBranchName()) == branchName) {
+                    yield gitutil.gitCheckout('master');
                 }
                 print(repo.repoName + ' deleting local-only branch ' + branchName + '.');
                 yield executil.execHelper(executil.ARGS('git log --oneline -3 ' + branchName));
@@ -236,10 +125,10 @@ function *repoResetCommand(argv) {
     }
     yield repoutil.forEachRepo(repos, function*(repo) {
         // Determine remote name.
-        yield updateRepos([repo], [], true);
-        var branchName = yield retrieveCurrentBranchName();
+        yield repoupdate.updateRepos([repo], [], true);
+        var branchName = yield gitutil.retrieveCurrentBranchName();
         if (branches.indexOf(branchName) == -1) {
-            yield stashAndPop(repo, function*() {
+            yield gitutil.stashAndPop(repo, function*() {
                 yield cleanRepo(repo);
             });
         } else {
@@ -273,15 +162,15 @@ function *repoPushCommand(argv) {
 
     yield repoutil.forEachRepo(repos, function*(repo) {
         // Update first.
-        yield updateRepos([repo], branches, false);
+        yield repoupdate.updateRepos([repo], branches, false);
         for (var i = 0; i < branches.length; ++i) {
             var branchName = branches[i];
-            if (!(yield localBranchExists(branchName))) {
+            if (!(yield gitutil.localBranchExists(branchName))) {
                 continue;
             }
-            var isNewBranch = !(yield remoteBranchExists(repo, branchName));
+            var isNewBranch = !(yield gitutil.remoteBranchExists(repo, branchName));
 
-            yield gitCheckout(branchName);
+            yield gitutil.gitCheckout(branchName);
 
             if (isNewBranch) {
                 yield executil.execHelper(executil.ARGS('git push --set-upstream ' + repo.remoteName + ' ' + branchName));
@@ -297,144 +186,6 @@ function *repoPushCommand(argv) {
     });
 }
 
-function *repoUpdateCommand(argv) {
-    var opt = flagutil.registerRepoFlag(optimist)
-    var opt = opt
-        .options('b', {
-            alias: 'branch',
-            desc: 'The name of the branch to update. Can be specified multiple times to update multiple branches.',
-            default: ['master', 'dev']
-         })
-        .options('fetch', {
-            type: 'boolean',
-            desc: 'Use --no-fetch to skip the "git fetch" step.',
-            default: true
-         });
-    opt = flagutil.registerHelpFlag(opt);
-    var argv = opt
-        .usage('Updates git repositories by performing the following commands:\n' +
-               '    save active branch\n' +
-               '    git fetch $REMOTE \n' +
-               '    git stash\n' +
-               '    for each specified branch:\n' +
-               '        git checkout $BRANCH\n' +
-               '        git rebase $REMOTE/$BRANCH\n' +
-               '        git checkout -\n' +
-               '    git checkout $SAVED_ACTIVE_BRANCH\n' +
-               '    git stash pop\n' +
-               '\n' +
-               'Usage: $0 repo-update')
-        .argv;
-
-    if (argv.h) {
-        optimist.showHelp();
-        process.exit(1);
-    }
-    var branches = Array.isArray(argv.b) ? argv.b : [argv.b];
-    var repos = flagutil.computeReposFromFlag(argv.r);
-
-    // ensure that any missing repos are cloned
-    yield require('./repo-clone').cloneRepos(repos,true);
-    yield updateRepos(repos, branches, !argv.fetch);
-}
-
-function *determineApacheRemote(repo) {
-    var fields = (yield executil.execHelper(executil.ARGS('git remote -v'), true)).split(/\s+/);
-    var ret = null;
-    for (var i = 1; i < fields.length; i += 3) {
-        ['git-wip-us.apache.org/repos/asf/', 'git.apache.org/'].forEach(function(validRepo) {
-            if (fields[i].indexOf(validRepo + repo.repoName) != -1) {
-                ret = fields[i - 1];
-            }
-        });
-    }
-    if (ret)
-        return ret;
-    apputil.fatal('Could not find an apache remote for repo ' + repo.repoName);
-}
-
-function *pendingChangesExist() {
-    return !!(yield executil.execHelper(executil.ARGS('git status --porcelain'), true));
-}
-
-function *stashAndPop(repo, func) {
-    var requiresStash = yield pendingChangesExist();
-    var branchName = yield retrieveCurrentBranchName();
-
-    if (requiresStash) {
-        yield executil.execHelper(executil.ARGS('git stash save --all --quiet', 'coho stash'));
-    }
-
-    yield func();
-
-    yield gitCheckout(branchName);
-    if (requiresStash) {
-        yield executil.execHelper(executil.ARGS('git stash pop'));
-    }
-}
-
-function *updateRepos(repos, branches, noFetch) {
-    // Pre-fetch checks.
-    yield repoutil.forEachRepo(repos, function*(repo) {
-        if (repo.svn) {
-            return;
-        }
-        // Ensure it's on a named branch.
-        yield retrieveCurrentBranchName();
-        // Find the apache remote.
-        if (!repo.remoteName) {
-            repo.remoteName = yield determineApacheRemote(repo);
-        }
-    });
-
-    if (!noFetch) {
-        yield repoutil.forEachRepo(repos, function*(repo) {
-            if (repo.svn) {
-                return;
-            }
-            // TODO - can these be combined? Fetching with --tags seems to not pull in changes...
-            yield executil.execHelper(executil.ARGS('git fetch --progress ' + repo.remoteName));
-            yield executil.execHelper(executil.ARGS('git fetch --progress --tags ' + repo.remoteName));
-        });
-    }
-
-    if (branches && branches.length) {
-        yield repoutil.forEachRepo(repos, function*(repo) {
-            if (repo.svn) {
-                yield executil.execHelper(executil.ARGS('svn up'));
-                return;
-            }
-            var staleBranches = {};
-            for (var i = 0; i < branches.length; ++i) {
-                var branchName = branches[i];
-                if (yield remoteBranchExists(repo, branches[i])) {
-                    var changes = yield executil.execHelper(executil.ARGS('git log --oneline ' + branchName + '..' + repo.remoteName + '/' + branchName), true, true);
-                    staleBranches[branchName] = !!changes;
-                }
-            }
-            var staleBranches = branches.filter(function(branchName) {
-                return !!staleBranches[branchName];
-            });
-            if (!staleBranches.length) {
-                print('Confirmed already up-to-date: ' + repo.repoName);
-            } else {
-                print('Updating ' + repo.repoName);
-                yield stashAndPop(repo, function*() {
-                    for (var i = 0; i < staleBranches.length; ++i) {
-                        var branchName = staleBranches[i];
-                        yield gitCheckout(branchName);
-                        var ret = yield executil.execHelper(executil.ARGS('git rebase ' + repo.remoteName + '/' + branchName), false, true);
-                        if (ret === null) {
-                            console.log('\n\nUpdate failed. Run again with --no-fetch to try again without re-fetching.');
-                            process.exit(1);
-                        }
-                    }
-                });
-            }
-        });
-    }
-}
-
 function configureReleaseCommandFlags(opt) {
     var opt = flagutil.registerRepoFlag(opt)
     opt = opt
@@ -460,11 +211,11 @@ function *updateJsSnapshot(repo, version) {
         var cordovaJsRepo = repoutil.getRepoById('js');
         if (hasBuiltJs != version) {
             yield repoutil.forEachRepo([cordovaJsRepo], function*() {
-                yield stashAndPop(cordovaJsRepo, function*() {
+                yield gitutil.stashAndPop(cordovaJsRepo, function*() {
                     if (getVersionBranchName(version) == 'master') {
-                        yield gitCheckout('master');
+                        yield gitutil.gitCheckout('master');
                     } else {
-                        yield gitCheckout(version);
+                        yield gitutil.gitCheckout(version);
                     }
                     yield executil.execHelper(executil.ARGS('grunt'));
                     hasBuiltJs = version;
@@ -483,7 +234,7 @@ function *updateJsSnapshot(repo, version) {
             var src = path.join('..', 'cordova-js', 'pkg', repo.cordovaJsSrcName || ('cordova.' + repo.id + '.js'));
             cpAndLog(src, jsPath);
         });
-        if (yield pendingChangesExist()) {
+        if (yield gitutil.pendingChangesExist()) {
             yield executil.execHelper(executil.ARGS('git commit -am', 'Update JS snapshot to version ' + version + ' (via coho)'));
         }
     } else if (repoutil.repoGroups.all.indexOf(repo) != -1) {
@@ -504,14 +255,14 @@ function *updateRepoVersion(repo, version) {
             shjs.sed('-i', /VERSION.*=.*;/, 'VERSION = "' + version + '";', path.join('bin', 'templates', 'cordova', 'version'));
         }
         shjs.config.apputil.fatal = false;
-        if (!(yield pendingChangesExist())) {
+        if (!(yield gitutil.pendingChangesExist())) {
             print('VERSION file was already up-to-date.');
         }
     } else {
         console.warn('No VERSION file exists in repo ' + repo.repoName);
     }
 
-    if (yield pendingChangesExist()) {
+    if (yield gitutil.pendingChangesExist()) {
         yield executil.execHelper(executil.ARGS('git commit -am', 'Set VERSION to ' + version + ' (via coho)'));
     }
 }
@@ -535,7 +286,7 @@ function *prepareReleaseBranchCommand() {
     var branchName = getVersionBranchName(version);
 
     // First - perform precondition checks.
-    yield updateRepos(repos, [], true);
+    yield repoupdate.updateRepos(repos, [], true);
 
     var cordovaJsRepo = repoutil.getRepoById('js');
 
@@ -547,32 +298,32 @@ function *prepareReleaseBranchCommand() {
     }
 
     yield repoutil.forEachRepo(repos, function*(repo) {
-        yield stashAndPop(repo, function*() {
+        yield gitutil.stashAndPop(repo, function*() {
             // git fetch + update master
-            yield updateRepos([repo], ['master'], false);
+            yield repoupdate.updateRepos([repo], ['master'], false);
 
             // Either create or pull down the branch.
-            if (yield remoteBranchExists(repo, branchName)) {
+            if (yield gitutil.remoteBranchExists(repo, branchName)) {
                 print('Remote branch already exists for repo: ' + repo.repoName);
                 // Check out and rebase.
-                yield updateRepos([repo], [branchName], true);
-                yield gitCheckout(branchName);
-            } else if (yield localBranchExists(branchName)) {
+                yield repoupdate.updateRepos([repo], [branchName], true);
+                yield gitutil.gitCheckout(branchName);
+            } else if (yield gitutil.localBranchExists(branchName)) {
                 yield executil.execHelper(executil.ARGS('git checkout ' + branchName));
             } else {
-                yield gitCheckout('master');
+                yield gitutil.gitCheckout('master');
                 yield executil.execHelper(executil.ARGS('git checkout -b ' + branchName));
             }
             yield updateJsSnapshot(repo, version);
             print(repo.repoName + ': ' + 'Setting VERSION to "' + version + '" on branch + "' + branchName + '".');
             yield updateRepoVersion(repo, version);
 
-            yield gitCheckout('master');
+            yield gitutil.gitCheckout('master');
             var devVersion = createPlatformDevVersion(version);
             print(repo.repoName + ': ' + 'Setting VERSION to "' + devVersion + '" on branch + "master".');
             yield updateRepoVersion(repo, devVersion);
             yield updateJsSnapshot(repo, devVersion);
-            yield gitCheckout(branchName);
+            yield gitutil.gitCheckout(branchName);
         });
     });
 
@@ -594,7 +345,7 @@ function *tagReleaseBranchCommand(argv) {
     var branchName = getVersionBranchName(version);
 
     // First - perform precondition checks.
-    yield updateRepos(repos, [], true);
+    yield repoupdate.updateRepos(repos, [], true);
 
     function *execOrPretend(cmd) {
         if (pretend) {
@@ -604,19 +355,19 @@ function *tagReleaseBranchCommand(argv) {
         }
     }
     yield repoutil.forEachRepo(repos, function*(repo) {
-        yield stashAndPop(repo, function*() {
+        yield gitutil.stashAndPop(repo, function*() {
             // git fetch.
-            yield updateRepos([repo], [], false);
+            yield repoupdate.updateRepos([repo], [], false);
 
-            if (yield remoteBranchExists(repo, branchName)) {
+            if (yield gitutil.remoteBranchExists(repo, branchName)) {
                 print('Remote branch already exists for repo: ' + repo.repoName);
-                yield gitCheckout(branchName);
+                yield gitutil.gitCheckout(branchName);
             } else {
                 apputil.fatal('Release branch does not exist for repo ' + repo.repoName);
             }
 
             // git merge
-            yield updateRepos([repo], [branchName], true);
+            yield repoupdate.updateRepos([repo], [branchName], true);
 
             // Create/update the tag.
             var tagName = yield retrieveCurrentTagName();
@@ -646,7 +397,7 @@ function main() {
         }, {
             name: 'repo-update',
             desc: 'Performs git pull --rebase on all specified repositories.',
-            entryPoint: repoUpdateCommand
+            entryPoint: require('./repo-update')
         }, {
             name: 'repo-reset',
             desc: 'Performs git reset --hard origin/$BRANCH and git clean -f -d on all specified repositories.',
@@ -654,7 +405,7 @@ function main() {
         }, {
             name: 'repo-status',
             desc: 'Lists changes that exist locally but have not yet been pushed.',
-            entryPoint: repoStatusCommand
+            entryPoint: require('./repo-status')
         }, {
             name: 'repo-push',
             desc: 'Push changes that exist locally but have not yet been pushed.',

http://git-wip-us.apache.org/repos/asf/cordova-coho/blob/29a3289f/src/repo-status.js
----------------------------------------------------------------------
diff --git a/src/repo-status.js b/src/repo-status.js
new file mode 100644
index 0000000..bd1ff69
--- /dev/null
+++ b/src/repo-status.js
@@ -0,0 +1,111 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+var fs = require('fs');
+var optimist = require('optimist');
+var apputil = require('./apputil');
+var executil = require('./executil');
+var flagutil = require('./flagutil');
+var gitutil = require('./gitutil');
+var repoutil = require('./repoutil');
+var repoupdate = require('./repo-update');
+var print = apputil.print;
+
+module.exports = function*(argv) {
+    var opt = flagutil.registerRepoFlag(optimist)
+    var opt = optimist
+        .options('b', {
+            alias: 'branch',
+            desc: 'The name of the branch to report on. Can be specified multiple times to specify multiple branches. The local version of the branch is compared with the origin\'s version unless --b2 is specified.'
+         })
+        .options('branch2', {
+            desc: 'The name of the branch to diff against. This is origin/$branch by default.'
+         })
+        .options('diff', {
+            desc: 'Show a diff of the changes.',
+            default: false
+         })
+    opt = flagutil.registerHelpFlag(opt);
+    var argv = opt
+        .usage('Reports what changes exist locally that are not yet pushed.\n' +
+               '\n' +
+               'Example usage: $0 repo-status -r auto -b master -b 2.9.x\n' +
+               'Example usage: $0 repo-status -r plugins -b dev --branch2 master --diff')
+        .argv;
+
+    if (argv.h) {
+        optimist.showHelp();
+        process.exit(1);
+    }
+    var branches = argv.b && (Array.isArray(argv.b) ? argv.b : [argv.b]);
+    var branches2 = branches && argv.branch2 && (Array.isArray(argv.branch2) ? argv.branch2 : [argv.branch2]);
+    var repos = flagutil.computeReposFromFlag(argv.r);
+
+    if (branches2 && branches && branches.length != branches2.length) {
+        apputil.fatal('Must specify the same number of --branch and --branch2 flags');
+    }
+
+    yield repoutil.forEachRepo(repos, function*(repo) {
+        if (repo.svn) {
+            print('repo-status not implemented for svn repos');
+            return;
+        }
+        // Determine remote name.
+        yield repoupdate.updateRepos([repo], [], true);
+        var actualBranches = branches ? branches : /^plugin/.test(repo.id) ? ['dev', 'master'] : ['master'];
+        for (var i = 0; i < actualBranches.length; ++i) {
+            var branchName = actualBranches[i];
+            if (!(yield gitutil.localBranchExists(branchName))) {
+                continue;
+            }
+            var targetBranch = branches2 ? branches2[i] : ((yield gitutil.remoteBranchExists(repo, branchName)) ? repo.remoteName + '/' + branchName : 'master');
+            var changes = yield executil.execHelper(executil.ARGS('git log --no-merges --oneline ' + targetBranch + '..' + branchName), true);
+            if (changes) {
+                print('Local commits exist on ' + branchName + ':');
+                console.log(changes);
+            }
+        }
+        var gitStatus = yield executil.execHelper(executil.ARGS('git status --short'), true);
+        if (gitStatus) {
+            print('Uncommitted changes:');
+            console.log(gitStatus);
+        }
+    });
+    if (argv.diff) {
+        yield repoutil.forEachRepo(repos, function*(repo) {
+            var actualBranches = branches ? branches : [/^plugin/.test(repo.id) ? 'dev' : 'master'];
+            for (var i = 0; i < actualBranches.length; ++i) {
+                var branchName = actualBranches[i];
+                if (!(yield gitutil.localBranchExists(branchName))) {
+                    return;
+                }
+                var targetBranch = branches2 ? branches2[i] : ((yield gitutil.remoteBranchExists(repo, branchName)) ? repo.remoteName + '/' + branchName : 'master');
+                var diff = yield executil.execHelper(executil.ARGS('git diff ' + targetBranch + '...' + branchName), true);
+                if (diff) {
+                    print('------------------------------------------------------------------------------');
+                    print('Diff for ' + repo.repoName + ' on branch ' + branchName + ' (vs ' + targetBranch + ')');
+                    print('------------------------------------------------------------------------------');
+                    console.log(diff);
+                    console.log('\n');
+                }
+            }
+        });
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/cordova-coho/blob/29a3289f/src/repo-update.js
----------------------------------------------------------------------
diff --git a/src/repo-update.js b/src/repo-update.js
new file mode 100644
index 0000000..d887e4f
--- /dev/null
+++ b/src/repo-update.js
@@ -0,0 +1,145 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+
+var optimist = require('optimist');
+var apputil = require('./apputil');
+var executil = require('./executil');
+var flagutil = require('./flagutil');
+var gitutil = require('./gitutil');
+var repoutil = require('./repoutil');
+var print = apputil.print;
+
+module.exports = function*(argv) {
+    var opt = flagutil.registerRepoFlag(optimist)
+    var opt = opt
+        .options('b', {
+            alias: 'branch',
+            desc: 'The name of the branch to update. Can be specified multiple times to update multiple branches.',
+            default: ['master', 'dev']
+         })
+        .options('fetch', {
+            type: 'boolean',
+            desc: 'Use --no-fetch to skip the "git fetch" step.',
+            default: true
+         });
+    opt = flagutil.registerHelpFlag(opt);
+    var argv = opt
+        .usage('Updates git repositories by performing the following commands:\n' +
+               '    save active branch\n' +
+               '    git fetch $REMOTE \n' +
+               '    git stash\n' +
+               '    for each specified branch:\n' +
+               '        git checkout $BRANCH\n' +
+               '        git rebase $REMOTE/$BRANCH\n' +
+               '        git checkout -\n' +
+               '    git checkout $SAVED_ACTIVE_BRANCH\n' +
+               '    git stash pop\n' +
+               '\n' +
+               'Usage: $0 repo-update')
+        .argv;
+
+    if (argv.h) {
+        optimist.showHelp();
+        process.exit(1);
+    }
+    var branches = Array.isArray(argv.b) ? argv.b : [argv.b];
+    var repos = flagutil.computeReposFromFlag(argv.r);
+
+    // ensure that any missing repos are cloned
+    yield require('./repo-clone').cloneRepos(repos,true);
+    yield updateRepos(repos, branches, !argv.fetch);
+}
+
+function *updateRepos(repos, branches, noFetch) {
+    // Pre-fetch checks.
+    yield repoutil.forEachRepo(repos, function*(repo) {
+        if (repo.svn) {
+            return;
+        }
+        // Ensure it's on a named branch.
+        yield gitutil.retrieveCurrentBranchName();
+        // Find the apache remote.
+        if (!repo.remoteName) {
+            repo.remoteName = yield determineApacheRemote(repo);
+        }
+    });
+
+    if (!noFetch) {
+        yield repoutil.forEachRepo(repos, function*(repo) {
+            if (repo.svn) {
+                return;
+            }
+            // TODO - can these be combined? Fetching with --tags seems to not pull in changes...
+            yield executil.execHelper(executil.ARGS('git fetch --progress ' + repo.remoteName));
+            yield executil.execHelper(executil.ARGS('git fetch --progress --tags ' + repo.remoteName));
+        });
+    }
+
+    if (branches && branches.length) {
+        yield repoutil.forEachRepo(repos, function*(repo) {
+            if (repo.svn) {
+                yield executil.execHelper(executil.ARGS('svn up'));
+                return;
+            }
+            var staleBranches = {};
+            for (var i = 0; i < branches.length; ++i) {
+                var branchName = branches[i];
+                if (yield gitutil.remoteBranchExists(repo, branches[i])) {
+                    var changes = yield executil.execHelper(executil.ARGS('git log --oneline ' + branchName + '..' + repo.remoteName + '/' + branchName), true, true);
+                    staleBranches[branchName] = !!changes;
+                }
+            }
+            var staleBranches = branches.filter(function(branchName) {
+                return !!staleBranches[branchName];
+            });
+            if (!staleBranches.length) {
+                print('Confirmed already up-to-date: ' + repo.repoName);
+            } else {
+                print('Updating ' + repo.repoName);
+                yield gitutil.stashAndPop(repo, function*() {
+                    for (var i = 0; i < staleBranches.length; ++i) {
+                        var branchName = staleBranches[i];
+                        yield gitutil.gitCheckout(branchName);
+                        var ret = yield executil.execHelper(executil.ARGS('git rebase ' + repo.remoteName + '/' + branchName), false, true);
+                        if (ret === null) {
+                            apputil.fatal('\n\nUpdate failed. Run again with --no-fetch to try again without re-fetching.');
+                        }
+                    }
+                });
+            }
+        });
+    }
+}
+module.exports.updateRepos = updateRepos;
+
+function *determineApacheRemote(repo) {
+    var fields = (yield executil.execHelper(executil.ARGS('git remote -v'), true)).split(/\s+/);
+    var ret = null;
+    for (var i = 1; i < fields.length; i += 3) {
+        ['git-wip-us.apache.org/repos/asf/', 'git.apache.org/'].forEach(function(validRepo) {
+            if (fields[i].indexOf(validRepo + repo.repoName) != -1) {
+                ret = fields[i - 1];
+            }
+        });
+    }
+    if (ret)
+        return ret;
+    apputil.fatal('Could not find an apache remote for repo ' + repo.repoName);
+}
+