├── README.md ├── package.json └── index.js /README.md: -------------------------------------------------------------------------------- 1 | # clone-pull-requests 2 | 3 | Given a source repository and a destination repository, both on GitHub, 4 | take all PRs on the source and recreate them on the destination. 5 | 6 | ## Install 7 | 8 | ```sh 9 | $ npm install -g clone-pull-requests 10 | ``` 11 | 12 | ## Options 13 | 14 | ``` 15 | Options: 16 | --from source repo [required] 17 | --to destination repo [required] 18 | --branch destination branch [required] 19 | ``` 20 | 21 | ## Example 22 | 23 | ```sh 24 | $ clone-pull-requests --from=leereilly/swot --to=mapbox/swot --branch=master 25 | ``` 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clone-pull-requests", 3 | "description": "clone all pull requests from one repository to another", 4 | "version": "1.0.0", 5 | "author": "Tom MacWright", 6 | "bin": { 7 | "clone-pull-requests": "./index.js" 8 | }, 9 | "dependencies": { 10 | "bole": "^2.0.0", 11 | "ghauth": "^2.0.0", 12 | "parse-link-header": "^0.2.0", 13 | "queue-async": "^1.0.7", 14 | "request": "^2.53.0", 15 | "yargs": "^3.5.4" 16 | }, 17 | "keywords": [ 18 | "git", 19 | "github", 20 | "prs", 21 | "pull-request" 22 | ], 23 | "license": "ISC", 24 | "main": "index.js", 25 | "repository": { 26 | "type": "git", 27 | "url": "git@github.com:tmcw/clone-pull-requests.git" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var ghauth = require('ghauth'), 4 | bole = require('bole'), 5 | queue = require('queue-async'), 6 | log = bole('clonepr'), 7 | request = require('request'), 8 | parseLinkHeader = require('parse-link-header'), 9 | argv = require('yargs') 10 | .describe('from', 'source repo') 11 | .describe('to', 'destination repo') 12 | .describe('branch', 'destination branch') 13 | .demand(['from', 'to', 'branch']) 14 | .argv; 15 | 16 | bole.output({ 17 | level: 'debug', 18 | stream: process.stdout 19 | }); 20 | 21 | var concurrency = 1; 22 | var throttle = 2000; 23 | 24 | ghauth({ 25 | configName: 'clone-pull-requests', 26 | scopes: ['repo'] 27 | }, function(err, authData) { 28 | if (err) throw err; 29 | 30 | var auth = { bearer: authData.token }; 31 | var headers = { 32 | 'User-Agent': 'clone repo pull requests' 33 | }; 34 | 35 | var prs = [], completed = 0; 36 | 37 | function getAll(page) { 38 | request({ 39 | url: 'https://api.github.com/repos/' + argv.from + '/pulls?per_page=100&page=' + page, 40 | auth: auth, 41 | headers: headers, 42 | json: true 43 | }, function(err, res, body) { 44 | if (err) throw err; 45 | prs = prs.concat(body); 46 | if (res.headers.link) { 47 | var links = parseLinkHeader(res.headers.link); 48 | if (links.next) { 49 | log.info('downloading page ', links.next.page); 50 | getAll(links.next.page); 51 | } else { 52 | prsDownloaded(); 53 | } 54 | } 55 | }); 56 | } 57 | 58 | function prsDownloaded() { 59 | log.info('downloaded %s pull requests', prs.length); 60 | log.info('creating pull requests with concurrency=%s', concurrency); 61 | 62 | var q = queue(concurrency); 63 | prs.filter(function(pr) { 64 | return pr && pr.head && pr.head.repo && pr.head.repo.name; 65 | }) 66 | .forEach(function(pr) { 67 | q.defer(createPullRequest, pr); 68 | }); 69 | 70 | q.awaitAll(function() { 71 | log.info('PR CLONE COMPLETE'); 72 | }); 73 | } 74 | 75 | function createPullRequest(pr, callback) { 76 | setTimeout(function() { 77 | var head = pr.head.user.login + ':' + pr.head.ref; 78 | log.info('POST: https://api.github.com/repos/%s/pulls', argv.to); 79 | log.info('head %s', head); 80 | request({ 81 | url: 'https://api.github.com/repos/' + argv.to + '/pulls', 82 | method: 'POST', 83 | auth: auth, 84 | body: { 85 | title: pr.title, 86 | body: pr.body, 87 | head: head, 88 | base: argv.branch 89 | }, 90 | headers: headers, 91 | json: true 92 | }, function(reqErr, res, body) { 93 | callback(reqErr, body); 94 | if (body && body.message) { 95 | log.info(body.message, body.errors); 96 | } 97 | if (!reqErr) { 98 | log.info('%s/%s complete', ++completed, prs.length); 99 | } else { 100 | log.error(reqErr); 101 | } 102 | }); 103 | }, throttle); 104 | } 105 | 106 | getAll(1); 107 | }); 108 | --------------------------------------------------------------------------------