1 | var fs = require('fs')
|
2 | var u = require('./util')
|
3 | var multicb = require('multicb')
|
4 | var Mentions = require('ssb-mentions')
|
5 |
|
6 | exports.help = `
|
7 | Usage: git ssb pull-request [-b <base>] [-h <head>],
|
8 | [-m <message> | -F <file>]
|
9 |
|
10 | Create a pull request. This requests that changes from <head>
|
11 | be merged into <base>.
|
12 |
|
13 | Arguments:
|
14 | head the head repo/branch, in format "[<repo>:]<branch>"
|
15 | Defaults to 'origin' or 'ssb', and the current branch.
|
16 | base the base repo/branch, in format "[<repo>:]<branch>"
|
17 | where <repo> may be a repo id or git remote name.
|
18 | Defaults to the upstream of <head>, or <head>,
|
19 | and its default branch (usually 'master')
|
20 | message the text for the pull-request message
|
21 | file name of file from which to read pull-request text
|
22 | `
|
23 |
|
24 | function splitEnd(str) {
|
25 | return str ? /(?:(.*?):)?(.*)/.exec(str).slice(1) : []
|
26 | }
|
27 |
|
28 | function mdLink(text, href) {
|
29 | return !text || text == href ? href : '[' + text + '](' + href + ')'
|
30 | }
|
31 |
|
32 | function getRev(repo, branch, cb) {
|
33 | var Repo = require('pull-git-repo')
|
34 | Repo(repo).resolveRef(branch, function (err, rev) {
|
35 | if (err && err.name === 'NotFoundError') err = null
|
36 | cb(null, rev)
|
37 | })
|
38 | }
|
39 |
|
40 | function formatGitLog(a, b) {
|
41 | var range = a + '..' + b
|
42 | try {
|
43 |
|
44 | return u.gitSync('log', '--no-color', '--cherry',
|
45 | '--format=%h (%aN, %ar)%n%w(78,3,3)%s%n%+b', range)
|
46 | } catch(e) {
|
47 | return '`git log ' + range + '`'
|
48 | }
|
49 | }
|
50 |
|
51 | exports.fn = function pullRequest(argv) {
|
52 | if (argv._.length > 0) return u.help('pull-request')
|
53 |
|
54 | var head = splitEnd(argv.head || argv.h)
|
55 | var headRepoId = u.getRemote(head[0])
|
56 | var headBranch = head[1] || u.getCurrentBranch()
|
57 | if (!headRepoId || !headBranch) throw 'unable to find head'
|
58 |
|
59 | var text = argv.message || argv.m
|
60 | var filename = argv.file || argv.F
|
61 | if (text && filename)
|
62 | throw 'only one of message and file option may be specified'
|
63 | if (filename && !fs.existsSync(filename))
|
64 | throw 'file ' + JSON.stringify(filename) + ' does not exist'
|
65 |
|
66 | var base = splitEnd(argv.base || argv.b)
|
67 | var baseRepoId = base[0]
|
68 | var baseBranch = base[1]
|
69 | if (baseRepoId) {
|
70 | baseRepoId = u.getRemote(baseRepoId)
|
71 | if (!baseRepoId) throw 'invalid base repo ' + JSON.stringify(base[0])
|
72 | }
|
73 |
|
74 | var ssbGit = require('ssb-git-repo')
|
75 | var sbot
|
76 | var done = multicb({pluck: 1, spread: true})
|
77 | var gotFeed = done()
|
78 | var gotHeadRepo = done()
|
79 | var gotBaseRepo = done()
|
80 | u.getSbot(argv, function (err, _sbot) {
|
81 | if (err) throw err
|
82 | sbot = _sbot
|
83 | sbot.whoami(gotFeed)
|
84 | ssbGit.getRepo(sbot, headRepoId, gotHeadRepo)
|
85 | if (baseRepoId) ssbGit.getRepo(sbot, baseRepoId, gotBaseRepo)
|
86 | else gotBaseRepo()
|
87 | })
|
88 |
|
89 | done(function (err, feed, headRepo, baseRepo) {
|
90 | if (err) throw err
|
91 | sbot.id = feed.id
|
92 |
|
93 |
|
94 |
|
95 | if (!baseRepo) {
|
96 | if (headRepo.upstream) {
|
97 | baseRepo = headRepo.upstream
|
98 | } else {
|
99 | baseRepo = headRepo
|
100 | }
|
101 | baseRepoId = baseRepo.id
|
102 | }
|
103 |
|
104 | if (baseBranch) next()
|
105 | else baseRepo.getHead(function (err, ref) {
|
106 | if (err) throw err
|
107 | baseBranch = ref && ref.replace(/refs\/heads\//, '') || 'master'
|
108 | next()
|
109 | })
|
110 |
|
111 | function next() {
|
112 | if (text) gotText(text, doneEditing)
|
113 | else if (filename) fs.readFile(filename, 'utf8', gotText)
|
114 | else {
|
115 | var done = multicb({pluck: 1, spread: true})
|
116 | u.getName(sbot, [sbot.id, null], baseRepoId, done())
|
117 | u.getName(sbot, [sbot.id, null], headRepoId, done())
|
118 | getRev(baseRepo, baseBranch, done())
|
119 | getRev(headRepo, headBranch, done())
|
120 | done(editText)
|
121 | }
|
122 | }
|
123 | })
|
124 |
|
125 | function editText(err, baseRepoName, headRepoName, baseRev, headRev) {
|
126 | if (err) throw err
|
127 | var defaultText = 'Requesting a pull ' +
|
128 | 'to ' + mdLink(baseRepoName, baseRepoId) + ':' + baseBranch + '\n' +
|
129 | 'from ' + mdLink(headRepoName, headRepoId) + ':' + headBranch + '\n\n' +
|
130 | 'Write message text for this pull request.' +
|
131 | (baseRev && headRev
|
132 | ? '\n\nChanges:\n\n' + formatGitLog(baseRev, headRev)
|
133 | : '')
|
134 | u.editor('PULLREQ', defaultText, gotText, doneEditing)
|
135 | }
|
136 |
|
137 | function gotText(text, cb) {
|
138 | if (!text) return cb('empty message: aborting')
|
139 | var prSchemas = require('ssb-pull-requests/lib/schemas')
|
140 | var value = prSchemas.new(baseRepoId, baseBranch,
|
141 | headRepoId, headBranch, text)
|
142 | var mentions = Mentions(text)
|
143 | if (mentions.length) value.mentions = mentions
|
144 | sbot.publish(value, function (err, msg) {
|
145 | if (err) return cb(err)
|
146 | console.log(JSON.stringify(msg, 0, 2))
|
147 | cb(null)
|
148 | })
|
149 | }
|
150 |
|
151 | function doneEditing(err) {
|
152 | if (err) console.error(err)
|
153 | sbot.close()
|
154 | }
|
155 | }
|