Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,21 @@ Implemented

Planned
* List tags for repos

## Developing

1. Make a secrets file:

```npm
cp example.config.json config.json
```

2. Make an [new personal access token](https://github.com/settings/tokens/new) on GitHub.
3. Paste your access token into `config.json`.
4. Run:

```bash
npm install
```


20 changes: 13 additions & 7 deletions bin/polymanage-github
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,38 @@ var myPackage = require('../package.json');
var config = require('../config.json');

var GithubManager = require('../src/githubManager');
GithubManager.auth({
type: "oauth",
token: config.github.token
});

program
.version(myPackage.version)
.command('issues <repo> [otherRepos...]')
.option('-l, --list', 'List all issues across repos')
.option('-a, --assign-unassigned-to <assignee>', 'Assign all unassigned issues across repos to a user')
.option('-v, --verbose', 'Verbose')
.action(function (repoRegex, otherRepos) {
console.log(repoRegex, otherRepos);
})
var manager = GithubManager.getGithubManager(repoRegex, otherRepos);
if (this.assignUnassignedTo) {
manager.assignAll(this.assignUnassignedTo, this.verbose);
}
});

program
.command('list <repoRegex> [otherRepos...]')
.option('-p, --properties <properties>', 'List of properties to emit', function(val) { return val.split(' ')})
.option('-v, --verbose', 'Verbose')
.action(function (repoRegex, otherRepos) {
GithubManager.auth({
type: "oauth",
token: config.github.token
});

var manager = GithubManager.getGithubManager(repoRegex, otherRepos);
manager.list(this.properties, this.verbose);
});

program
.command('labels [command]', 'act on github labels for one or more repos');

program
.command('assign [command]', 'act on github labels for one or more repos');


program.parse(process.argv);
5 changes: 5 additions & 0 deletions example.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"github": {
"token": ""
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.0.1",
"description": "A command-line tool for managing polymer repositories",
"author": "The Polymer Authors",
"repository": "https://github.com/tjsavage/polymanage",
"dependencies": {
"cli-color": "^1.0.0",
"commander": "^2.8.1",
Expand Down
161 changes: 161 additions & 0 deletions src/githubManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,71 @@ GithubManager.prototype = {
}
}
});
},

assignAll: function assignAll(assignee, verbose) {
this._reposPromise.then(GithubManager._retrieveIssuesFromRepos).then(function(repos) {
var repoPromises = [];
var totalFailures = 0;
var totalSuccesses = 0;
for(var i = 0; i < repos.length; i++) {
var repoPromise = new Promise(function (resolve, reject){
var repo = repos[i];
var repoOutput = "";
repo.successes = 0;
repo.failures = 0;
var issues = repo.issues;
// String like: "= owner/repo (i/n) ="
var displayName = "= " + repo.owner.login + "/" + repo.name + " (" + (i + 1) + "/" + repos.length + ") =";
if(verbose) {
repoOutput += GithubManager._repeat("=", displayName.length) + "\n";
repoOutput += displayName + "\n";
repoOutput += GithubManager._repeat("=", displayName.length) + "\n";
repoOutput += "Found " + issues.length + " unassigned issues.\n";
}
var issuePromises = [];
for (var j = 0; j < issues.length; j++) {
var issuePromise = new Promise(function(res, rej) {
var issue = issues[j];
GithubManager._githubAPI.issues.edit({
user: repo.owner.login,
repo: repo.name,
number: issue.number,
assignee: assignee
}, function(err, data) {
if (err && err.hasOwnProperty("message") && /Validation Failed/.test(err.message)) {
if (verbose) {
repoOutput += "#" + issue.number + ": fail\n";
}
repo.failures += 1;
} else {
if (verbose) {
repoOutput += "#" + issue.number + ": assigned to " + assignee + "\n";
}
repo.successes += 1;
}
res(issue);
});
});
issuePromises.push(issue);
}
Promise.all(issuePromises).then(function(issues) {
if (verbose) {
console.log(repoOutput);
console.log("> " + repo.successes + " successes and " + repo.failures + " failures.");
}
totalSuccesses += repo.successes;
totalFailures += repo.failures;
resolve(repo);
});
});
repoPromises.push(repoPromise);
}
return Promise.all(repoPromises).then(function(repos) {
console.log("Done: " + totalSuccesses + " successes and " + totalFailures + " failures.");
return repos;
})
});
}
}

Expand Down Expand Up @@ -100,6 +165,102 @@ GithubManager._retrieveLabelsFromRepos = function(repos) {
return repos;
});
}

/*
* @param {Array<Repos>} repos A list of repo objects to retrieve tags for
* @return {Promise<Array<Repos>>} A promise that resolves to a list of repo
* objects, with the list of tags added as a top-level property
*/
GithubManager._retrieveIssuesFromRepos = function(repos) {
var issuePromises = [];

for (var i = 0; i < repos.length; i++) {
var issuePromise = new Promise(function(resolve, reject) {
var repo = repos[i];

GithubManager._githubAPI.issues.repoIssues({
user: repo.owner.login,
repo: repo.name,
assignee: 'none',
state: 'open',
per_page: 100
}, GithubManager._promoteError(reject, function(res) {
GithubManager._followPages(function() {}, reject, [], res);
repo["issues"] = res;
resolve(repo);
}));
});

issuePromises.push(issuePromise);
}

return Promise.all(issuePromises).then(function(repos) {
return repos;
});
}

/*
* @param {String} pattern A string to repeat
* @param {Number} count The number of times to repeat it
* @return {String} the pattern, repeated count times.
*/
GithubManager._repeat = function(pattern, count) {
if (count < 1) return '';
var result = '';
while (count > 1) {
if (count & 1) result += pattern;
count >>= 1, pattern += pattern;
}
return result + pattern;
}

/*
* @param {function} reject The Promise reject
* @param {function} resolve The Promise resolve
* @return a GithubManager callback function that prints errors.
*/
GithubManager._promoteError = function(reject, resolve) {
return function(err, res) {
if (err) {
if (err.hasOwnProperty("message") && /rate limit exceeded/.test(err.message)) {
rateLimitExceeded = true;
}

console.error("caught error: %s", err);
reject(err);
}
else {
resolve(res);
}
};
}

/*
* @param {function} reject The Promise reject
* @param {function} resolve The Promise resolve
* @param {Array<Object>} result an array of JSON responses
* @param {Object} res The response from the response
* @return a GithubManager callback function that prints errors.
*/
GithubManager._followPages = function(resolve, reject, result, res) {
var i;

for (i = 0; i < res.length; ++i) {
result.push(res[i]);
}

if (GithubManager._githubAPI.hasNextPage(res)) {
GithubManager._githubAPI.getNextPage(res, GithubManager._promoteError(reject, function(res) {
GithubManager._followPages(resolve, reject, result, res);
}));
}
else {
resolve(result);
}
}



/*
* @param {Array<String>} repoStrings A list of strings to resolve to repos
* @return {Promise} Returns a promise that resolves to a list of repo objects
Expand Down