-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit b97c632
Showing
8 changed files
with
368 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
lib |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
src |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright (c) 2016 Jordan Neill | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
## gh-migrations | ||
|
||
Export all of an organization's GitHub data using the (in preview) [Migrations API](https://developer.github.com/v3/migration/migrations/) | ||
|
||
Create a migration and then download the archive, containing each repository's: | ||
|
||
* `.git` directory | ||
* Wiki (as git repo) | ||
* Issues | ||
* Pull Requests | ||
* Comments | ||
* Releases | ||
* Milestones | ||
* Events (e.g. issue closed then reopened) | ||
* Attachments (e.g. images in comments) | ||
|
||
**Note** This is a temporary hack until the Migrations API comes out of preview and GitHub presumably provide a proper UI for it. It's an an extremely rough-and-ready tool: no tests, no error handling. | ||
|
||
### Installation | ||
|
||
Requires [node.js](https://nodejs.org/) (>= 4.0) | ||
|
||
``` | ||
$ npm install -g gh-migrations | ||
``` | ||
|
||
### Configuration | ||
|
||
The tool can be configured using options on the command-line, or in `~/.gh.json` (as for [node-gh](https://github.com/node-gh/gh#config)) | ||
|
||
```json | ||
{ | ||
"github_token": "e72e16c7e42f292c6912e7710c838347ae178b4a", | ||
"default_org": "octokit" | ||
} | ||
``` | ||
|
||
##### OAuth Token | ||
|
||
This is the token used to access the GitHub API. You'll need to [create a new personal access token](https://github.com/settings/tokens/new?scopes=repo&description=gh-migrations) with `repo` scope. You can set the token using the `--token` option on the command-line, or as `"github_token"` in `~/.gh.json` | ||
|
||
##### Organization | ||
|
||
Migrations are created in the scope of a GitHub Organization. You can set the organization name using the `--org` option on the command-line, or as `"default_org"` in `~/.gh.json` | ||
|
||
### Usage | ||
|
||
Create a new migration, specifying the list of repositories to include. Repository names need to be the full name (i.e. of the form `{user}/{repo}`) | ||
|
||
``` | ||
$ gh-migrations create octokit/octokit.net octokit/go-octokit octokit/octokit.rb | ||
Migration 4797 | ||
State: pending | ||
Created: Mon Nov 8 12:25:35 2016 +0000 | ||
Updated: Mon Nov 8 12:25:36 2016 +0000 | ||
Repositories: | ||
octokit/octokit.net | ||
octokit/go-octokit | ||
octokit/octokit.rb | ||
``` | ||
|
||
View the list of existing migrations | ||
|
||
``` | ||
$ gh-migrations list | ||
┌──────────┬───────────┬─────────────────┬────────────────────────────────────┐ | ||
│ ID │ State │ Updated │ Repositories │ | ||
├──────────┼───────────┼─────────────────┼────────────────────────────────────┤ | ||
│ 4797 │ exporting │ 1 minute ago │ octokit/octokit.net │ | ||
│ │ │ │ octokit/go-octokit │ | ||
│ │ │ │ octokit/octokit.rb │ | ||
├──────────┼───────────┼─────────────────┼────────────────────────────────────┤ | ||
│ 4791 │ exported │ 9 hours ago │ octokit/octokit.py │ | ||
└──────────┴───────────┴─────────────────┴────────────────────────────────────┘ | ||
``` | ||
|
||
Once the migration state changes to `exported`, you can download the archive: | ||
|
||
``` | ||
$ gh-migrations download 4797 > gh4797.tar.gz | ||
``` | ||
|
||
### Library | ||
|
||
This tool can also be used as a node module to access the Migrations API. | ||
|
||
The library uses [ES6 generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*) and [`co`](https://github.com/tj/co) to emulate [ES7 async/await](https://github.com/lukehoban/ecmascript-asyncawait). | ||
|
||
```coffeescript | ||
Api = require("gh-migrations") | ||
|
||
api = new Api(token) | ||
migrations = api.migrations("octokit") | ||
|
||
co -> | ||
migration = yield migrations.create([ "octokit/octokit.objc" ]) | ||
console.log migration.state # pending | ||
|
||
# later | ||
list = yield migrations.findAll() | ||
migration = _.find(list, (m) -> m.id is migration.id) | ||
console.log migration.state # exporting | ||
|
||
# later still | ||
migration = yield migrations.get(migration.id) | ||
console.log migration.state # exported | ||
|
||
migrations.download(migration.id) | ||
.pipe(fs.createWriteStream("out.tar.gz")) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
#!/usr/bin/env node | ||
|
||
var path = require('path') | ||
var fs = require('fs') | ||
|
||
var dir = path.dirname(fs.realpathSync(__filename)) | ||
|
||
var cli = null | ||
try { | ||
// use .js files if they exist (published module) | ||
cli = require(path.join(dir, "../lib/cli")) | ||
} catch (err) { | ||
// otherwise use the .coffee versions (development) | ||
require("coffee-script/register") | ||
cli = require(path.join(dir, '../src/cli')) | ||
} | ||
|
||
cli.run() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
{ | ||
"name": "gh-migrations", | ||
"description": "GitHub Migrations CLI", | ||
"version": "1.0.0", | ||
"homepage": "https://github.com/jneill/gh-migrations", | ||
"keyworks": [ | ||
"github", | ||
"api", | ||
"export", | ||
"migration", | ||
"migrations" | ||
], | ||
"repository": "jneill/gh-migrations", | ||
"author": "Jordan Neill <[email protected]>", | ||
"license": "MIT", | ||
"main": "./lib/index.js", | ||
"bin": { | ||
"gh-migrations": "./bin/gh-migrations" | ||
}, | ||
"preferGlobal": true, | ||
"scripts": { | ||
"prepublish": "./node_modules/coffee-script/bin/coffee --compile --output lib/ src/" | ||
}, | ||
"engines": { | ||
"node": ">=4.0.0" | ||
}, | ||
"dependencies": { | ||
"cli-table": "^0.3.1", | ||
"co": "^4.6.0", | ||
"co-fs": "^1.2.0", | ||
"co-request": "^1.0.0", | ||
"commander": "^2.9.0", | ||
"lodash": "^4.5.1", | ||
"moment": "^2.11.2", | ||
"request": "^2.69.0" | ||
}, | ||
"devDependencies": { | ||
"coffee-script": "^1.10.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
|
||
fs = require("co-fs") | ||
os = require("os") | ||
path = require("path") | ||
|
||
_ = require("lodash") | ||
co = require("co") | ||
moment = require("moment") | ||
Table = require("cli-table") | ||
|
||
Api = require("./index") | ||
|
||
TIME_FORMAT = "ddd MMM D HH:mm:ss YYYY ZZ" | ||
|
||
getConfig = -> | ||
options = program.opts() | ||
try json = yield fs.readFile(path.join(os.homedir(), ".gh.json"), "UTF8") | ||
globalConfig = if json then JSON.parse(json) else {} | ||
return { | ||
token: options.token or globalConfig.github_token | ||
org: options.org or globalConfig.default_org | ||
} | ||
|
||
migrations = -> | ||
{ token, org } = yield getConfig() | ||
new Api(token).migrations(org) | ||
|
||
program = require("commander") | ||
|
||
program | ||
.version "1.0.0" | ||
.description "GitHub Migrations CLI" | ||
.option("-t, --token [token]", "a valid GitHub OAuth2 token") | ||
.option("-o, --org [org]", "name of the organisation to migrate") | ||
|
||
program | ||
.command "create [repos...]" | ||
.description "create new migration" | ||
.action (repos) -> execAsync -> | ||
migrations = yield migrations() | ||
item = yield migrations.create(repos) | ||
process.stdout.write formatItem(item) + "\n" | ||
|
||
program | ||
.command "list" | ||
.description "list existing migrations" | ||
.action -> execAsync -> | ||
migrations = yield migrations() | ||
list = yield migrations.find() | ||
process.stdout.write formatList(list) + "\n" | ||
|
||
program | ||
.command "view [id]" | ||
.description "view existing migration" | ||
.action (id) -> execAsync -> | ||
migrations = yield migrations() | ||
item = yield migrations.get(id) | ||
process.stdout.write formatItem(item) + "\n" | ||
|
||
program | ||
.command "download [id]" | ||
.description "download migration archive" | ||
.action (id) -> execAsync -> | ||
migrations = yield migrations() | ||
content = migrations.download(id) | ||
content.pipe(process.stdout) | ||
|
||
execAsync = (fn) -> | ||
co(fn).catch (err) -> | ||
process.stderr.write(err.stack) + "\n" | ||
process.exit(1) | ||
|
||
formatList = (migrations) -> | ||
table = new Table | ||
head: [ "ID", "State", "Updated", "Repositories" ] | ||
colWidths: [ 10, 11, 17, 36 ] | ||
style: { head: [ "bold", "cyan" ], border: [ "white" ] } | ||
for migration in migrations | ||
table.push [ | ||
migration.id | ||
migration.state | ||
moment(migration.updated_at).fromNow() | ||
_.map(migration.repositories, "full_name").join("\n") | ||
] | ||
return table.toString() | ||
|
||
formatItem = (migration) -> | ||
""" | ||
Migration #{migration.id} | ||
State: #{migration.state} | ||
Created: #{moment(migration.created_at).format(TIME_FORMAT)} | ||
Updated: #{moment(migration.updated_at).format(TIME_FORMAT)} | ||
Repositories: | ||
#{_.map(migration.repositories, "full_name").join("\n ")} | ||
""" | ||
|
||
module.exports = | ||
run: ({ argv } = process) -> | ||
command = argv[2] | ||
program.help() unless command in [ | ||
"list", "create", "view", "download" | ||
] | ||
program.parse(argv) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
|
||
_ = require("lodash") | ||
request = require("co-request") | ||
|
||
USER_AGENT = "gh-migrations (https://github.com/jneill/gh-migrations)" | ||
|
||
class Api | ||
|
||
constructor: (token) -> | ||
@_client = new Client(token) | ||
|
||
migrations: (organization) -> | ||
@migrations = new Migrations(@_client, organization) | ||
|
||
class Migrations | ||
|
||
constructor: (client, organization) -> | ||
@org = organization | ||
@_client = client | ||
|
||
find: -> | ||
yield @_client.get("/orgs/#{@org}/migrations") | ||
|
||
get: (id) -> | ||
yield @_client.get("/orgs/#{@org}/migrations/#{id}") | ||
|
||
create: (repositories) -> | ||
yield @_client.post("/orgs/#{@org}/migrations", { body: { repositories } }) | ||
|
||
download: (id) -> | ||
@_client.stream "GET", "/orgs/#{@org}/migrations/#{id}/archive", | ||
json: false | ||
encoding: null | ||
|
||
class Client | ||
|
||
mergeOptions = (method, uri = "", options = {}) -> | ||
if _.isObject(uri) | ||
options = uri | ||
options.uri or= "" | ||
else if _.isString(uri) | ||
options.uri = uri | ||
options.method = method | ||
options | ||
|
||
constructor: (token) -> | ||
@_requestDefaults = | ||
baseUrl: "https://api.github.com" | ||
json: true | ||
headers: | ||
"User-Agent": USER_AGENT | ||
"Authorization": "token #{token}" | ||
"Accept": "application/vnd.github.wyandotte-preview+json" | ||
@_request = request.defaults(@_requestDefaults) | ||
|
||
stream: (method, uri, options) -> | ||
options = mergeOptions(method, uri, options) | ||
_.defaults(options, { json: false, encoding: null }, @_requestDefaults) | ||
# don't use co-request because we want the stream | ||
require("request")(options) | ||
|
||
request: (method, uri, options) -> | ||
options = mergeOptions(method, uri, options) | ||
res = yield @_request(options) | ||
res.body | ||
|
||
get: (uri, options) -> | ||
yield @request("GET", uri, options) | ||
|
||
post: (uri, options) -> | ||
yield @request("POST", uri, options) | ||
|
||
module.exports = Api |