Mercurial > hg
comparison contrib/phabricator.py @ 38392:81a4be7099fa
py3: byte-stringify literals in contrib/phabricator.py as example
Transformed by contrib/byteify-strings.py and adjusted exceeded lines
manually. Some of b''s would be wrong as the phabriactor extension has to
work with JSON data.
# skip-blame just many b prefixes
author | Yuya Nishihara <yuya@tcha.org> |
---|---|
date | Sun, 03 Jun 2018 18:18:36 +0900 |
parents | bb7e3c6ef592 |
children | 32fba6fe893d |
comparison
equal
deleted
inserted
replaced
38391:f77bbd34a1df | 38392:81a4be7099fa |
---|---|
75 | 75 |
76 configtable = {} | 76 configtable = {} |
77 configitem = registrar.configitem(configtable) | 77 configitem = registrar.configitem(configtable) |
78 | 78 |
79 # developer config: phabricator.batchsize | 79 # developer config: phabricator.batchsize |
80 configitem('phabricator', 'batchsize', | 80 configitem(b'phabricator', b'batchsize', |
81 default=12, | 81 default=12, |
82 ) | 82 ) |
83 configitem('phabricator', 'callsign', | 83 configitem(b'phabricator', b'callsign', |
84 default=None, | 84 default=None, |
85 ) | 85 ) |
86 configitem('phabricator', 'curlcmd', | 86 configitem(b'phabricator', b'curlcmd', |
87 default=None, | 87 default=None, |
88 ) | 88 ) |
89 # developer config: phabricator.repophid | 89 # developer config: phabricator.repophid |
90 configitem('phabricator', 'repophid', | 90 configitem(b'phabricator', b'repophid', |
91 default=None, | 91 default=None, |
92 ) | 92 ) |
93 configitem('phabricator', 'url', | 93 configitem(b'phabricator', b'url', |
94 default=None, | 94 default=None, |
95 ) | 95 ) |
96 configitem('phabsend', 'confirm', | 96 configitem(b'phabsend', b'confirm', |
97 default=False, | 97 default=False, |
98 ) | 98 ) |
99 | 99 |
100 colortable = { | 100 colortable = { |
101 'phabricator.action.created': 'green', | 101 b'phabricator.action.created': b'green', |
102 'phabricator.action.skipped': 'magenta', | 102 b'phabricator.action.skipped': b'magenta', |
103 'phabricator.action.updated': 'magenta', | 103 b'phabricator.action.updated': b'magenta', |
104 'phabricator.desc': '', | 104 b'phabricator.desc': b'', |
105 'phabricator.drev': 'bold', | 105 b'phabricator.drev': b'bold', |
106 'phabricator.node': '', | 106 b'phabricator.node': b'', |
107 } | 107 } |
108 | 108 |
109 def urlencodenested(params): | 109 def urlencodenested(params): |
110 """like urlencode, but works with nested parameters. | 110 """like urlencode, but works with nested parameters. |
111 | 111 |
119 if items is None: | 119 if items is None: |
120 flatparams[prefix] = obj | 120 flatparams[prefix] = obj |
121 else: | 121 else: |
122 for k, v in items(obj): | 122 for k, v in items(obj): |
123 if prefix: | 123 if prefix: |
124 process('%s[%s]' % (prefix, k), v) | 124 process(b'%s[%s]' % (prefix, k), v) |
125 else: | 125 else: |
126 process(k, v) | 126 process(k, v) |
127 process('', params) | 127 process(b'', params) |
128 return util.urlreq.urlencode(flatparams) | 128 return util.urlreq.urlencode(flatparams) |
129 | 129 |
130 printed_token_warning = False | 130 printed_token_warning = False |
131 | 131 |
132 def readlegacytoken(repo, url): | 132 def readlegacytoken(repo, url): |
133 """Transitional support for old phabricator tokens. | 133 """Transitional support for old phabricator tokens. |
134 | 134 |
135 Remove before the 4.7 release. | 135 Remove before the 4.7 release. |
136 """ | 136 """ |
137 groups = {} | 137 groups = {} |
138 for key, val in repo.ui.configitems('phabricator.auth'): | 138 for key, val in repo.ui.configitems(b'phabricator.auth'): |
139 if '.' not in key: | 139 if b'.' not in key: |
140 repo.ui.warn(_("ignoring invalid [phabricator.auth] key '%s'\n") | 140 repo.ui.warn(_(b"ignoring invalid [phabricator.auth] key '%s'\n") |
141 % key) | 141 % key) |
142 continue | 142 continue |
143 group, setting = key.rsplit('.', 1) | 143 group, setting = key.rsplit(b'.', 1) |
144 groups.setdefault(group, {})[setting] = val | 144 groups.setdefault(group, {})[setting] = val |
145 | 145 |
146 token = None | 146 token = None |
147 for group, auth in groups.iteritems(): | 147 for group, auth in groups.iteritems(): |
148 if url != auth.get('url'): | 148 if url != auth.get(b'url'): |
149 continue | 149 continue |
150 token = auth.get('token') | 150 token = auth.get(b'token') |
151 if token: | 151 if token: |
152 break | 152 break |
153 | 153 |
154 global printed_token_warning | 154 global printed_token_warning |
155 | 155 |
156 if token and not printed_token_warning: | 156 if token and not printed_token_warning: |
157 printed_token_warning = True | 157 printed_token_warning = True |
158 repo.ui.warn(_('phabricator.auth.token is deprecated - please ' | 158 repo.ui.warn(_(b'phabricator.auth.token is deprecated - please ' |
159 'migrate to auth.phabtoken.\n')) | 159 b'migrate to auth.phabtoken.\n')) |
160 return token | 160 return token |
161 | 161 |
162 def readurltoken(repo): | 162 def readurltoken(repo): |
163 """return conduit url, token and make sure they exist | 163 """return conduit url, token and make sure they exist |
164 | 164 |
165 Currently read from [auth] config section. In the future, it might | 165 Currently read from [auth] config section. In the future, it might |
166 make sense to read from .arcconfig and .arcrc as well. | 166 make sense to read from .arcconfig and .arcrc as well. |
167 """ | 167 """ |
168 url = repo.ui.config('phabricator', 'url') | 168 url = repo.ui.config(b'phabricator', b'url') |
169 if not url: | 169 if not url: |
170 raise error.Abort(_('config %s.%s is required') | 170 raise error.Abort(_(b'config %s.%s is required') |
171 % ('phabricator', 'url')) | 171 % (b'phabricator', b'url')) |
172 | 172 |
173 res = httpconnectionmod.readauthforuri(repo.ui, url, util.url(url).user) | 173 res = httpconnectionmod.readauthforuri(repo.ui, url, util.url(url).user) |
174 token = None | 174 token = None |
175 | 175 |
176 if res: | 176 if res: |
177 group, auth = res | 177 group, auth = res |
178 | 178 |
179 repo.ui.debug("using auth.%s.* for authentication\n" % group) | 179 repo.ui.debug(b"using auth.%s.* for authentication\n" % group) |
180 | 180 |
181 token = auth.get('phabtoken') | 181 token = auth.get(b'phabtoken') |
182 | 182 |
183 if not token: | 183 if not token: |
184 token = readlegacytoken(repo, url) | 184 token = readlegacytoken(repo, url) |
185 if not token: | 185 if not token: |
186 raise error.Abort(_('Can\'t find conduit token associated to %s') | 186 raise error.Abort(_(b'Can\'t find conduit token associated to %s') |
187 % (url,)) | 187 % (url,)) |
188 | 188 |
189 return url, token | 189 return url, token |
190 | 190 |
191 def callconduit(repo, name, params): | 191 def callconduit(repo, name, params): |
192 """call Conduit API, params is a dict. return json.loads result, or None""" | 192 """call Conduit API, params is a dict. return json.loads result, or None""" |
193 host, token = readurltoken(repo) | 193 host, token = readurltoken(repo) |
194 url, authinfo = util.url('/'.join([host, 'api', name])).authinfo() | 194 url, authinfo = util.url(b'/'.join([host, b'api', name])).authinfo() |
195 repo.ui.debug('Conduit Call: %s %s\n' % (url, params)) | 195 repo.ui.debug(b'Conduit Call: %s %s\n' % (url, params)) |
196 params = params.copy() | 196 params = params.copy() |
197 params['api.token'] = token | 197 params[b'api.token'] = token |
198 data = urlencodenested(params) | 198 data = urlencodenested(params) |
199 curlcmd = repo.ui.config('phabricator', 'curlcmd') | 199 curlcmd = repo.ui.config(b'phabricator', b'curlcmd') |
200 if curlcmd: | 200 if curlcmd: |
201 sin, sout = procutil.popen2('%s -d @- %s' | 201 sin, sout = procutil.popen2(b'%s -d @- %s' |
202 % (curlcmd, procutil.shellquote(url))) | 202 % (curlcmd, procutil.shellquote(url))) |
203 sin.write(data) | 203 sin.write(data) |
204 sin.close() | 204 sin.close() |
205 body = sout.read() | 205 body = sout.read() |
206 else: | 206 else: |
207 urlopener = urlmod.opener(repo.ui, authinfo) | 207 urlopener = urlmod.opener(repo.ui, authinfo) |
208 request = util.urlreq.request(url, data=data) | 208 request = util.urlreq.request(url, data=data) |
209 body = urlopener.open(request).read() | 209 body = urlopener.open(request).read() |
210 repo.ui.debug('Conduit Response: %s\n' % body) | 210 repo.ui.debug(b'Conduit Response: %s\n' % body) |
211 parsed = json.loads(body) | 211 parsed = json.loads(body) |
212 if parsed.get(r'error_code'): | 212 if parsed.get(r'error_code'): |
213 msg = (_('Conduit Error (%s): %s') | 213 msg = (_(b'Conduit Error (%s): %s') |
214 % (parsed[r'error_code'], parsed[r'error_info'])) | 214 % (parsed[r'error_code'], parsed[r'error_info'])) |
215 raise error.Abort(msg) | 215 raise error.Abort(msg) |
216 return parsed[r'result'] | 216 return parsed[r'result'] |
217 | 217 |
218 @command('debugcallconduit', [], _('METHOD')) | 218 @command(b'debugcallconduit', [], _(b'METHOD')) |
219 def debugcallconduit(ui, repo, name): | 219 def debugcallconduit(ui, repo, name): |
220 """call Conduit API | 220 """call Conduit API |
221 | 221 |
222 Call parameters are read from stdin as a JSON blob. Result will be written | 222 Call parameters are read from stdin as a JSON blob. Result will be written |
223 to stdout as a JSON blob. | 223 to stdout as a JSON blob. |
224 """ | 224 """ |
225 params = json.loads(ui.fin.read()) | 225 params = json.loads(ui.fin.read()) |
226 result = callconduit(repo, name, params) | 226 result = callconduit(repo, name, params) |
227 s = json.dumps(result, sort_keys=True, indent=2, separators=(',', ': ')) | 227 s = json.dumps(result, sort_keys=True, indent=2, separators=(b',', b': ')) |
228 ui.write('%s\n' % s) | 228 ui.write(b'%s\n' % s) |
229 | 229 |
230 def getrepophid(repo): | 230 def getrepophid(repo): |
231 """given callsign, return repository PHID or None""" | 231 """given callsign, return repository PHID or None""" |
232 # developer config: phabricator.repophid | 232 # developer config: phabricator.repophid |
233 repophid = repo.ui.config('phabricator', 'repophid') | 233 repophid = repo.ui.config(b'phabricator', b'repophid') |
234 if repophid: | 234 if repophid: |
235 return repophid | 235 return repophid |
236 callsign = repo.ui.config('phabricator', 'callsign') | 236 callsign = repo.ui.config(b'phabricator', b'callsign') |
237 if not callsign: | 237 if not callsign: |
238 return None | 238 return None |
239 query = callconduit(repo, 'diffusion.repository.search', | 239 query = callconduit(repo, b'diffusion.repository.search', |
240 {'constraints': {'callsigns': [callsign]}}) | 240 {b'constraints': {b'callsigns': [callsign]}}) |
241 if len(query[r'data']) == 0: | 241 if len(query[r'data']) == 0: |
242 return None | 242 return None |
243 repophid = encoding.strtolocal(query[r'data'][0][r'phid']) | 243 repophid = encoding.strtolocal(query[r'data'][0][r'phid']) |
244 repo.ui.setconfig('phabricator', 'repophid', repophid) | 244 repo.ui.setconfig(b'phabricator', b'repophid', repophid) |
245 return repophid | 245 return repophid |
246 | 246 |
247 _differentialrevisiontagre = re.compile('\AD([1-9][0-9]*)\Z') | 247 _differentialrevisiontagre = re.compile(b'\AD([1-9][0-9]*)\Z') |
248 _differentialrevisiondescre = re.compile( | 248 _differentialrevisiondescre = re.compile( |
249 '^Differential Revision:\s*(?P<url>(?:.*)D(?P<id>[1-9][0-9]*))$', re.M) | 249 b'^Differential Revision:\s*(?P<url>(?:.*)D(?P<id>[1-9][0-9]*))$', re.M) |
250 | 250 |
251 def getoldnodedrevmap(repo, nodelist): | 251 def getoldnodedrevmap(repo, nodelist): |
252 """find previous nodes that has been sent to Phabricator | 252 """find previous nodes that has been sent to Phabricator |
253 | 253 |
254 return {node: (oldnode, Differential diff, Differential Revision ID)} | 254 return {node: (oldnode, Differential diff, Differential Revision ID)} |
285 continue | 285 continue |
286 | 286 |
287 # Check commit message | 287 # Check commit message |
288 m = _differentialrevisiondescre.search(ctx.description()) | 288 m = _differentialrevisiondescre.search(ctx.description()) |
289 if m: | 289 if m: |
290 toconfirm[node] = (1, set(precnodes), int(m.group('id'))) | 290 toconfirm[node] = (1, set(precnodes), int(m.group(b'id'))) |
291 | 291 |
292 # Double check if tags are genuine by collecting all old nodes from | 292 # Double check if tags are genuine by collecting all old nodes from |
293 # Phabricator, and expect precursors overlap with it. | 293 # Phabricator, and expect precursors overlap with it. |
294 if toconfirm: | 294 if toconfirm: |
295 drevs = [drev for force, precs, drev in toconfirm.values()] | 295 drevs = [drev for force, precs, drev in toconfirm.values()] |
296 alldiffs = callconduit(unfi, 'differential.querydiffs', | 296 alldiffs = callconduit(unfi, b'differential.querydiffs', |
297 {'revisionIDs': drevs}) | 297 {b'revisionIDs': drevs}) |
298 getnode = lambda d: bin(encoding.unitolocal( | 298 getnode = lambda d: bin(encoding.unitolocal( |
299 getdiffmeta(d).get(r'node', ''))) or None | 299 getdiffmeta(d).get(r'node', b''))) or None |
300 for newnode, (force, precset, drev) in toconfirm.items(): | 300 for newnode, (force, precset, drev) in toconfirm.items(): |
301 diffs = [d for d in alldiffs.values() | 301 diffs = [d for d in alldiffs.values() |
302 if int(d[r'revisionID']) == drev] | 302 if int(d[r'revisionID']) == drev] |
303 | 303 |
304 # "precursors" as known by Phabricator | 304 # "precursors" as known by Phabricator |
305 phprecset = set(getnode(d) for d in diffs) | 305 phprecset = set(getnode(d) for d in diffs) |
306 | 306 |
307 # Ignore if precursors (Phabricator and local repo) do not overlap, | 307 # Ignore if precursors (Phabricator and local repo) do not overlap, |
308 # and force is not set (when commit message says nothing) | 308 # and force is not set (when commit message says nothing) |
309 if not force and not bool(phprecset & precset): | 309 if not force and not bool(phprecset & precset): |
310 tagname = 'D%d' % drev | 310 tagname = b'D%d' % drev |
311 tags.tag(repo, tagname, nullid, message=None, user=None, | 311 tags.tag(repo, tagname, nullid, message=None, user=None, |
312 date=None, local=True) | 312 date=None, local=True) |
313 unfi.ui.warn(_('D%s: local tag removed - does not match ' | 313 unfi.ui.warn(_(b'D%s: local tag removed - does not match ' |
314 'Differential history\n') % drev) | 314 b'Differential history\n') % drev) |
315 continue | 315 continue |
316 | 316 |
317 # Find the last node using Phabricator metadata, and make sure it | 317 # Find the last node using Phabricator metadata, and make sure it |
318 # exists in the repo | 318 # exists in the repo |
319 oldnode = lastdiff = None | 319 oldnode = lastdiff = None |
338 def creatediff(ctx): | 338 def creatediff(ctx): |
339 """create a Differential Diff""" | 339 """create a Differential Diff""" |
340 repo = ctx.repo() | 340 repo = ctx.repo() |
341 repophid = getrepophid(repo) | 341 repophid = getrepophid(repo) |
342 # Create a "Differential Diff" via "differential.createrawdiff" API | 342 # Create a "Differential Diff" via "differential.createrawdiff" API |
343 params = {'diff': getdiff(ctx, mdiff.diffopts(git=True, context=32767))} | 343 params = {b'diff': getdiff(ctx, mdiff.diffopts(git=True, context=32767))} |
344 if repophid: | 344 if repophid: |
345 params['repositoryPHID'] = repophid | 345 params[b'repositoryPHID'] = repophid |
346 diff = callconduit(repo, 'differential.createrawdiff', params) | 346 diff = callconduit(repo, b'differential.createrawdiff', params) |
347 if not diff: | 347 if not diff: |
348 raise error.Abort(_('cannot create diff for %s') % ctx) | 348 raise error.Abort(_(b'cannot create diff for %s') % ctx) |
349 return diff | 349 return diff |
350 | 350 |
351 def writediffproperties(ctx, diff): | 351 def writediffproperties(ctx, diff): |
352 """write metadata to diff so patches could be applied losslessly""" | 352 """write metadata to diff so patches could be applied losslessly""" |
353 params = { | 353 params = { |
354 'diff_id': diff[r'id'], | 354 b'diff_id': diff[r'id'], |
355 'name': 'hg:meta', | 355 b'name': b'hg:meta', |
356 'data': json.dumps({ | 356 b'data': json.dumps({ |
357 'user': ctx.user(), | 357 b'user': ctx.user(), |
358 'date': '%d %d' % ctx.date(), | 358 b'date': b'%d %d' % ctx.date(), |
359 'node': ctx.hex(), | 359 b'node': ctx.hex(), |
360 'parent': ctx.p1().hex(), | 360 b'parent': ctx.p1().hex(), |
361 }), | 361 }), |
362 } | 362 } |
363 callconduit(ctx.repo(), 'differential.setdiffproperty', params) | 363 callconduit(ctx.repo(), b'differential.setdiffproperty', params) |
364 | 364 |
365 params = { | 365 params = { |
366 'diff_id': diff[r'id'], | 366 b'diff_id': diff[r'id'], |
367 'name': 'local:commits', | 367 b'name': b'local:commits', |
368 'data': json.dumps({ | 368 b'data': json.dumps({ |
369 ctx.hex(): { | 369 ctx.hex(): { |
370 'author': stringutil.person(ctx.user()), | 370 b'author': stringutil.person(ctx.user()), |
371 'authorEmail': stringutil.email(ctx.user()), | 371 b'authorEmail': stringutil.email(ctx.user()), |
372 'time': ctx.date()[0], | 372 b'time': ctx.date()[0], |
373 }, | 373 }, |
374 }), | 374 }), |
375 } | 375 } |
376 callconduit(ctx.repo(), 'differential.setdiffproperty', params) | 376 callconduit(ctx.repo(), b'differential.setdiffproperty', params) |
377 | 377 |
378 def createdifferentialrevision(ctx, revid=None, parentrevid=None, oldnode=None, | 378 def createdifferentialrevision(ctx, revid=None, parentrevid=None, oldnode=None, |
379 olddiff=None, actions=None): | 379 olddiff=None, actions=None): |
380 """create or update a Differential Revision | 380 """create or update a Differential Revision |
381 | 381 |
396 neednewdiff = True | 396 neednewdiff = True |
397 | 397 |
398 transactions = [] | 398 transactions = [] |
399 if neednewdiff: | 399 if neednewdiff: |
400 diff = creatediff(ctx) | 400 diff = creatediff(ctx) |
401 transactions.append({'type': 'update', 'value': diff[r'phid']}) | 401 transactions.append({b'type': b'update', b'value': diff[r'phid']}) |
402 else: | 402 else: |
403 # Even if we don't need to upload a new diff because the patch content | 403 # Even if we don't need to upload a new diff because the patch content |
404 # does not change. We might still need to update its metadata so | 404 # does not change. We might still need to update its metadata so |
405 # pushers could know the correct node metadata. | 405 # pushers could know the correct node metadata. |
406 assert olddiff | 406 assert olddiff |
410 # Use a temporary summary to set dependency. There might be better ways but | 410 # Use a temporary summary to set dependency. There might be better ways but |
411 # I cannot find them for now. But do not do that if we are updating an | 411 # I cannot find them for now. But do not do that if we are updating an |
412 # existing revision (revid is not None) since that introduces visible | 412 # existing revision (revid is not None) since that introduces visible |
413 # churns (someone edited "Summary" twice) on the web page. | 413 # churns (someone edited "Summary" twice) on the web page. |
414 if parentrevid and revid is None: | 414 if parentrevid and revid is None: |
415 summary = 'Depends on D%s' % parentrevid | 415 summary = b'Depends on D%s' % parentrevid |
416 transactions += [{'type': 'summary', 'value': summary}, | 416 transactions += [{b'type': b'summary', b'value': summary}, |
417 {'type': 'summary', 'value': ' '}] | 417 {b'type': b'summary', b'value': b' '}] |
418 | 418 |
419 if actions: | 419 if actions: |
420 transactions += actions | 420 transactions += actions |
421 | 421 |
422 # Parse commit message and update related fields. | 422 # Parse commit message and update related fields. |
423 desc = ctx.description() | 423 desc = ctx.description() |
424 info = callconduit(repo, 'differential.parsecommitmessage', | 424 info = callconduit(repo, b'differential.parsecommitmessage', |
425 {'corpus': desc}) | 425 {b'corpus': desc}) |
426 for k, v in info[r'fields'].items(): | 426 for k, v in info[r'fields'].items(): |
427 if k in ['title', 'summary', 'testPlan']: | 427 if k in [b'title', b'summary', b'testPlan']: |
428 transactions.append({'type': k, 'value': v}) | 428 transactions.append({b'type': k, b'value': v}) |
429 | 429 |
430 params = {'transactions': transactions} | 430 params = {b'transactions': transactions} |
431 if revid is not None: | 431 if revid is not None: |
432 # Update an existing Differential Revision | 432 # Update an existing Differential Revision |
433 params['objectIdentifier'] = revid | 433 params[b'objectIdentifier'] = revid |
434 | 434 |
435 revision = callconduit(repo, 'differential.revision.edit', params) | 435 revision = callconduit(repo, b'differential.revision.edit', params) |
436 if not revision: | 436 if not revision: |
437 raise error.Abort(_('cannot create revision for %s') % ctx) | 437 raise error.Abort(_(b'cannot create revision for %s') % ctx) |
438 | 438 |
439 return revision, diff | 439 return revision, diff |
440 | 440 |
441 def userphids(repo, names): | 441 def userphids(repo, names): |
442 """convert user names to PHIDs""" | 442 """convert user names to PHIDs""" |
443 query = {'constraints': {'usernames': names}} | 443 query = {b'constraints': {b'usernames': names}} |
444 result = callconduit(repo, 'user.search', query) | 444 result = callconduit(repo, b'user.search', query) |
445 # username not found is not an error of the API. So check if we have missed | 445 # username not found is not an error of the API. So check if we have missed |
446 # some names here. | 446 # some names here. |
447 data = result[r'data'] | 447 data = result[r'data'] |
448 resolved = set(entry[r'fields'][r'username'] for entry in data) | 448 resolved = set(entry[r'fields'][r'username'] for entry in data) |
449 unresolved = set(names) - resolved | 449 unresolved = set(names) - resolved |
450 if unresolved: | 450 if unresolved: |
451 raise error.Abort(_('unknown username: %s') | 451 raise error.Abort(_(b'unknown username: %s') |
452 % ' '.join(sorted(unresolved))) | 452 % b' '.join(sorted(unresolved))) |
453 return [entry[r'phid'] for entry in data] | 453 return [entry[r'phid'] for entry in data] |
454 | 454 |
455 @command('phabsend', | 455 @command(b'phabsend', |
456 [('r', 'rev', [], _('revisions to send'), _('REV')), | 456 [(b'r', b'rev', [], _(b'revisions to send'), _(b'REV')), |
457 ('', 'amend', True, _('update commit messages')), | 457 (b'', b'amend', True, _(b'update commit messages')), |
458 ('', 'reviewer', [], _('specify reviewers')), | 458 (b'', b'reviewer', [], _(b'specify reviewers')), |
459 ('', 'confirm', None, _('ask for confirmation before sending'))], | 459 (b'', b'confirm', None, _(b'ask for confirmation before sending'))], |
460 _('REV [OPTIONS]')) | 460 _(b'REV [OPTIONS]')) |
461 def phabsend(ui, repo, *revs, **opts): | 461 def phabsend(ui, repo, *revs, **opts): |
462 """upload changesets to Phabricator | 462 """upload changesets to Phabricator |
463 | 463 |
464 If there are multiple revisions specified, they will be send as a stack | 464 If there are multiple revisions specified, they will be send as a stack |
465 with a linear dependencies relationship using the order specified by the | 465 with a linear dependencies relationship using the order specified by the |
483 confirm = true | 483 confirm = true |
484 | 484 |
485 phabsend will check obsstore and the above association to decide whether to | 485 phabsend will check obsstore and the above association to decide whether to |
486 update an existing Differential Revision, or create a new one. | 486 update an existing Differential Revision, or create a new one. |
487 """ | 487 """ |
488 revs = list(revs) + opts.get('rev', []) | 488 revs = list(revs) + opts.get(b'rev', []) |
489 revs = scmutil.revrange(repo, revs) | 489 revs = scmutil.revrange(repo, revs) |
490 | 490 |
491 if not revs: | 491 if not revs: |
492 raise error.Abort(_('phabsend requires at least one changeset')) | 492 raise error.Abort(_(b'phabsend requires at least one changeset')) |
493 if opts.get('amend'): | 493 if opts.get(b'amend'): |
494 cmdutil.checkunfinished(repo) | 494 cmdutil.checkunfinished(repo) |
495 | 495 |
496 # {newnode: (oldnode, olddiff, olddrev} | 496 # {newnode: (oldnode, olddiff, olddrev} |
497 oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs]) | 497 oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs]) |
498 | 498 |
499 confirm = ui.configbool('phabsend', 'confirm') | 499 confirm = ui.configbool(b'phabsend', b'confirm') |
500 confirm |= bool(opts.get('confirm')) | 500 confirm |= bool(opts.get(b'confirm')) |
501 if confirm: | 501 if confirm: |
502 confirmed = _confirmbeforesend(repo, revs, oldmap) | 502 confirmed = _confirmbeforesend(repo, revs, oldmap) |
503 if not confirmed: | 503 if not confirmed: |
504 raise error.Abort(_('phabsend cancelled')) | 504 raise error.Abort(_(b'phabsend cancelled')) |
505 | 505 |
506 actions = [] | 506 actions = [] |
507 reviewers = opts.get('reviewer', []) | 507 reviewers = opts.get(b'reviewer', []) |
508 if reviewers: | 508 if reviewers: |
509 phids = userphids(repo, reviewers) | 509 phids = userphids(repo, reviewers) |
510 actions.append({'type': 'reviewers.add', 'value': phids}) | 510 actions.append({b'type': b'reviewers.add', b'value': phids}) |
511 | 511 |
512 drevids = [] # [int] | 512 drevids = [] # [int] |
513 diffmap = {} # {newnode: diff} | 513 diffmap = {} # {newnode: diff} |
514 | 514 |
515 # Send patches one by one so we know their Differential Revision IDs and | 515 # Send patches one by one so we know their Differential Revision IDs and |
516 # can provide dependency relationship | 516 # can provide dependency relationship |
517 lastrevid = None | 517 lastrevid = None |
518 for rev in revs: | 518 for rev in revs: |
519 ui.debug('sending rev %d\n' % rev) | 519 ui.debug(b'sending rev %d\n' % rev) |
520 ctx = repo[rev] | 520 ctx = repo[rev] |
521 | 521 |
522 # Get Differential Revision ID | 522 # Get Differential Revision ID |
523 oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None)) | 523 oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None)) |
524 if oldnode != ctx.node() or opts.get('amend'): | 524 if oldnode != ctx.node() or opts.get(b'amend'): |
525 # Create or update Differential Revision | 525 # Create or update Differential Revision |
526 revision, diff = createdifferentialrevision( | 526 revision, diff = createdifferentialrevision( |
527 ctx, revid, lastrevid, oldnode, olddiff, actions) | 527 ctx, revid, lastrevid, oldnode, olddiff, actions) |
528 diffmap[ctx.node()] = diff | 528 diffmap[ctx.node()] = diff |
529 newrevid = int(revision[r'object'][r'id']) | 529 newrevid = int(revision[r'object'][r'id']) |
530 if revid: | 530 if revid: |
531 action = 'updated' | 531 action = b'updated' |
532 else: | 532 else: |
533 action = 'created' | 533 action = b'created' |
534 | 534 |
535 # Create a local tag to note the association, if commit message | 535 # Create a local tag to note the association, if commit message |
536 # does not have it already | 536 # does not have it already |
537 m = _differentialrevisiondescre.search(ctx.description()) | 537 m = _differentialrevisiondescre.search(ctx.description()) |
538 if not m or int(m.group('id')) != newrevid: | 538 if not m or int(m.group(b'id')) != newrevid: |
539 tagname = 'D%d' % newrevid | 539 tagname = b'D%d' % newrevid |
540 tags.tag(repo, tagname, ctx.node(), message=None, user=None, | 540 tags.tag(repo, tagname, ctx.node(), message=None, user=None, |
541 date=None, local=True) | 541 date=None, local=True) |
542 else: | 542 else: |
543 # Nothing changed. But still set "newrevid" so the next revision | 543 # Nothing changed. But still set "newrevid" so the next revision |
544 # could depend on this one. | 544 # could depend on this one. |
545 newrevid = revid | 545 newrevid = revid |
546 action = 'skipped' | 546 action = b'skipped' |
547 | 547 |
548 actiondesc = ui.label( | 548 actiondesc = ui.label( |
549 {'created': _('created'), | 549 {b'created': _(b'created'), |
550 'skipped': _('skipped'), | 550 b'skipped': _(b'skipped'), |
551 'updated': _('updated')}[action], | 551 b'updated': _(b'updated')}[action], |
552 'phabricator.action.%s' % action) | 552 b'phabricator.action.%s' % action) |
553 drevdesc = ui.label('D%s' % newrevid, 'phabricator.drev') | 553 drevdesc = ui.label(b'D%s' % newrevid, b'phabricator.drev') |
554 nodedesc = ui.label(bytes(ctx), 'phabricator.node') | 554 nodedesc = ui.label(bytes(ctx), b'phabricator.node') |
555 desc = ui.label(ctx.description().split('\n')[0], 'phabricator.desc') | 555 desc = ui.label(ctx.description().split(b'\n')[0], b'phabricator.desc') |
556 ui.write(_('%s - %s - %s: %s\n') % (drevdesc, actiondesc, nodedesc, | 556 ui.write(_(b'%s - %s - %s: %s\n') % (drevdesc, actiondesc, nodedesc, |
557 desc)) | 557 desc)) |
558 drevids.append(newrevid) | 558 drevids.append(newrevid) |
559 lastrevid = newrevid | 559 lastrevid = newrevid |
560 | 560 |
561 # Update commit messages and remove tags | 561 # Update commit messages and remove tags |
562 if opts.get('amend'): | 562 if opts.get(b'amend'): |
563 unfi = repo.unfiltered() | 563 unfi = repo.unfiltered() |
564 drevs = callconduit(repo, 'differential.query', {'ids': drevids}) | 564 drevs = callconduit(repo, b'differential.query', {b'ids': drevids}) |
565 with repo.wlock(), repo.lock(), repo.transaction('phabsend'): | 565 with repo.wlock(), repo.lock(), repo.transaction(b'phabsend'): |
566 wnode = unfi['.'].node() | 566 wnode = unfi[b'.'].node() |
567 mapping = {} # {oldnode: [newnode]} | 567 mapping = {} # {oldnode: [newnode]} |
568 for i, rev in enumerate(revs): | 568 for i, rev in enumerate(revs): |
569 old = unfi[rev] | 569 old = unfi[rev] |
570 drevid = drevids[i] | 570 drevid = drevids[i] |
571 drev = [d for d in drevs if int(d[r'id']) == drevid][0] | 571 drev = [d for d in drevs if int(d[r'id']) == drevid][0] |
578 ] | 578 ] |
579 new = context.metadataonlyctx( | 579 new = context.metadataonlyctx( |
580 repo, old, parents=parents, text=newdesc, | 580 repo, old, parents=parents, text=newdesc, |
581 user=old.user(), date=old.date(), extra=old.extra()) | 581 user=old.user(), date=old.date(), extra=old.extra()) |
582 | 582 |
583 overrides = {('phases', 'new-commit'): old.phase()} | 583 overrides = {(b'phases', b'new-commit'): old.phase()} |
584 with ui.configoverride(overrides, 'phabsend'): | 584 with ui.configoverride(overrides, b'phabsend'): |
585 newnode = new.commit() | 585 newnode = new.commit() |
586 | 586 |
587 mapping[old.node()] = [newnode] | 587 mapping[old.node()] = [newnode] |
588 # Update diff property | 588 # Update diff property |
589 writediffproperties(unfi[newnode], diffmap[old.node()]) | 589 writediffproperties(unfi[newnode], diffmap[old.node()]) |
590 # Remove local tags since it's no longer necessary | 590 # Remove local tags since it's no longer necessary |
591 tagname = 'D%d' % drevid | 591 tagname = b'D%d' % drevid |
592 if tagname in repo.tags(): | 592 if tagname in repo.tags(): |
593 tags.tag(repo, tagname, nullid, message=None, user=None, | 593 tags.tag(repo, tagname, nullid, message=None, user=None, |
594 date=None, local=True) | 594 date=None, local=True) |
595 scmutil.cleanupnodes(repo, mapping, 'phabsend') | 595 scmutil.cleanupnodes(repo, mapping, b'phabsend') |
596 if wnode in mapping: | 596 if wnode in mapping: |
597 unfi.setparents(mapping[wnode][0]) | 597 unfi.setparents(mapping[wnode][0]) |
598 | 598 |
599 # Map from "hg:meta" keys to header understood by "hg import". The order is | 599 # Map from "hg:meta" keys to header understood by "hg import". The order is |
600 # consistent with "hg export" output. | 600 # consistent with "hg export" output. |
601 _metanamemap = util.sortdict([(r'user', 'User'), (r'date', 'Date'), | 601 _metanamemap = util.sortdict([(r'user', b'User'), (r'date', b'Date'), |
602 (r'node', 'Node ID'), (r'parent', 'Parent ')]) | 602 (r'node', b'Node ID'), (r'parent', b'Parent ')]) |
603 | 603 |
604 def _confirmbeforesend(repo, revs, oldmap): | 604 def _confirmbeforesend(repo, revs, oldmap): |
605 url, token = readurltoken(repo) | 605 url, token = readurltoken(repo) |
606 ui = repo.ui | 606 ui = repo.ui |
607 for rev in revs: | 607 for rev in revs: |
608 ctx = repo[rev] | 608 ctx = repo[rev] |
609 desc = ctx.description().splitlines()[0] | 609 desc = ctx.description().splitlines()[0] |
610 oldnode, olddiff, drevid = oldmap.get(ctx.node(), (None, None, None)) | 610 oldnode, olddiff, drevid = oldmap.get(ctx.node(), (None, None, None)) |
611 if drevid: | 611 if drevid: |
612 drevdesc = ui.label('D%s' % drevid, 'phabricator.drev') | 612 drevdesc = ui.label(b'D%s' % drevid, b'phabricator.drev') |
613 else: | 613 else: |
614 drevdesc = ui.label(_('NEW'), 'phabricator.drev') | 614 drevdesc = ui.label(_(b'NEW'), b'phabricator.drev') |
615 | 615 |
616 ui.write(_('%s - %s: %s\n') % (drevdesc, | 616 ui.write(_(b'%s - %s: %s\n') |
617 ui.label(bytes(ctx), 'phabricator.node'), | 617 % (drevdesc, |
618 ui.label(desc, 'phabricator.desc'))) | 618 ui.label(bytes(ctx), b'phabricator.node'), |
619 | 619 ui.label(desc, b'phabricator.desc'))) |
620 if ui.promptchoice(_('Send the above changes to %s (yn)?' | 620 |
621 '$$ &Yes $$ &No') % url): | 621 if ui.promptchoice(_(b'Send the above changes to %s (yn)?' |
622 b'$$ &Yes $$ &No') % url): | |
622 return False | 623 return False |
623 | 624 |
624 return True | 625 return True |
625 | 626 |
626 _knownstatusnames = {'accepted', 'needsreview', 'needsrevision', 'closed', | 627 _knownstatusnames = {b'accepted', b'needsreview', b'needsrevision', b'closed', |
627 'abandoned'} | 628 b'abandoned'} |
628 | 629 |
629 def _getstatusname(drev): | 630 def _getstatusname(drev): |
630 """get normalized status name from a Differential Revision""" | 631 """get normalized status name from a Differential Revision""" |
631 return drev[r'statusName'].replace(' ', '').lower() | 632 return drev[r'statusName'].replace(b' ', b'').lower() |
632 | 633 |
633 # Small language to specify differential revisions. Support symbols: (), :X, | 634 # Small language to specify differential revisions. Support symbols: (), :X, |
634 # +, and -. | 635 # +, and -. |
635 | 636 |
636 _elements = { | 637 _elements = { |
637 # token-type: binding-strength, primary, prefix, infix, suffix | 638 # token-type: binding-strength, primary, prefix, infix, suffix |
638 '(': (12, None, ('group', 1, ')'), None, None), | 639 b'(': (12, None, (b'group', 1, b')'), None, None), |
639 ':': (8, None, ('ancestors', 8), None, None), | 640 b':': (8, None, (b'ancestors', 8), None, None), |
640 '&': (5, None, None, ('and_', 5), None), | 641 b'&': (5, None, None, (b'and_', 5), None), |
641 '+': (4, None, None, ('add', 4), None), | 642 b'+': (4, None, None, (b'add', 4), None), |
642 '-': (4, None, None, ('sub', 4), None), | 643 b'-': (4, None, None, (b'sub', 4), None), |
643 ')': (0, None, None, None, None), | 644 b')': (0, None, None, None, None), |
644 'symbol': (0, 'symbol', None, None, None), | 645 b'symbol': (0, b'symbol', None, None, None), |
645 'end': (0, None, None, None, None), | 646 b'end': (0, None, None, None, None), |
646 } | 647 } |
647 | 648 |
648 def _tokenize(text): | 649 def _tokenize(text): |
649 view = memoryview(text) # zero-copy slice | 650 view = memoryview(text) # zero-copy slice |
650 special = '():+-& ' | 651 special = b'():+-& ' |
651 pos = 0 | 652 pos = 0 |
652 length = len(text) | 653 length = len(text) |
653 while pos < length: | 654 while pos < length: |
654 symbol = ''.join(itertools.takewhile(lambda ch: ch not in special, | 655 symbol = b''.join(itertools.takewhile(lambda ch: ch not in special, |
655 view[pos:])) | 656 view[pos:])) |
656 if symbol: | 657 if symbol: |
657 yield ('symbol', symbol, pos) | 658 yield (b'symbol', symbol, pos) |
658 pos += len(symbol) | 659 pos += len(symbol) |
659 else: # special char, ignore space | 660 else: # special char, ignore space |
660 if text[pos] != ' ': | 661 if text[pos] != b' ': |
661 yield (text[pos], None, pos) | 662 yield (text[pos], None, pos) |
662 pos += 1 | 663 pos += 1 |
663 yield ('end', None, pos) | 664 yield (b'end', None, pos) |
664 | 665 |
665 def _parse(text): | 666 def _parse(text): |
666 tree, pos = parser.parser(_elements).parse(_tokenize(text)) | 667 tree, pos = parser.parser(_elements).parse(_tokenize(text)) |
667 if pos != len(text): | 668 if pos != len(text): |
668 raise error.ParseError('invalid token', pos) | 669 raise error.ParseError(b'invalid token', pos) |
669 return tree | 670 return tree |
670 | 671 |
671 def _parsedrev(symbol): | 672 def _parsedrev(symbol): |
672 """str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None""" | 673 """str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None""" |
673 if symbol.startswith('D') and symbol[1:].isdigit(): | 674 if symbol.startswith(b'D') and symbol[1:].isdigit(): |
674 return int(symbol[1:]) | 675 return int(symbol[1:]) |
675 if symbol.isdigit(): | 676 if symbol.isdigit(): |
676 return int(symbol) | 677 return int(symbol) |
677 | 678 |
678 def _prefetchdrevs(tree): | 679 def _prefetchdrevs(tree): |
679 """return ({single-drev-id}, {ancestor-drev-id}) to prefetch""" | 680 """return ({single-drev-id}, {ancestor-drev-id}) to prefetch""" |
680 drevs = set() | 681 drevs = set() |
681 ancestordrevs = set() | 682 ancestordrevs = set() |
682 op = tree[0] | 683 op = tree[0] |
683 if op == 'symbol': | 684 if op == b'symbol': |
684 r = _parsedrev(tree[1]) | 685 r = _parsedrev(tree[1]) |
685 if r: | 686 if r: |
686 drevs.add(r) | 687 drevs.add(r) |
687 elif op == 'ancestors': | 688 elif op == b'ancestors': |
688 r, a = _prefetchdrevs(tree[1]) | 689 r, a = _prefetchdrevs(tree[1]) |
689 drevs.update(r) | 690 drevs.update(r) |
690 ancestordrevs.update(r) | 691 ancestordrevs.update(r) |
691 ancestordrevs.update(a) | 692 ancestordrevs.update(a) |
692 else: | 693 else: |
741 def fetch(params): | 742 def fetch(params): |
742 """params -> single drev or None""" | 743 """params -> single drev or None""" |
743 key = (params.get(r'ids') or params.get(r'phids') or [None])[0] | 744 key = (params.get(r'ids') or params.get(r'phids') or [None])[0] |
744 if key in prefetched: | 745 if key in prefetched: |
745 return prefetched[key] | 746 return prefetched[key] |
746 drevs = callconduit(repo, 'differential.query', params) | 747 drevs = callconduit(repo, b'differential.query', params) |
747 # Fill prefetched with the result | 748 # Fill prefetched with the result |
748 for drev in drevs: | 749 for drev in drevs: |
749 prefetched[drev[r'phid']] = drev | 750 prefetched[drev[r'phid']] = drev |
750 prefetched[int(drev[r'id'])] = drev | 751 prefetched[int(drev[r'id'])] = drev |
751 if key not in prefetched: | 752 if key not in prefetched: |
752 raise error.Abort(_('cannot get Differential Revision %r') % params) | 753 raise error.Abort(_(b'cannot get Differential Revision %r') |
754 % params) | |
753 return prefetched[key] | 755 return prefetched[key] |
754 | 756 |
755 def getstack(topdrevids): | 757 def getstack(topdrevids): |
756 """given a top, get a stack from the bottom, [id] -> [id]""" | 758 """given a top, get a stack from the bottom, [id] -> [id]""" |
757 visited = set() | 759 visited = set() |
765 visited.add(drev[r'id']) | 767 visited.add(drev[r'id']) |
766 result.append(int(drev[r'id'])) | 768 result.append(int(drev[r'id'])) |
767 auxiliary = drev.get(r'auxiliary', {}) | 769 auxiliary = drev.get(r'auxiliary', {}) |
768 depends = auxiliary.get(r'phabricator:depends-on', []) | 770 depends = auxiliary.get(r'phabricator:depends-on', []) |
769 for phid in depends: | 771 for phid in depends: |
770 queue.append({'phids': [phid]}) | 772 queue.append({b'phids': [phid]}) |
771 result.reverse() | 773 result.reverse() |
772 return smartset.baseset(result) | 774 return smartset.baseset(result) |
773 | 775 |
774 # Initialize prefetch cache | 776 # Initialize prefetch cache |
775 prefetched = {} # {id or phid: drev} | 777 prefetched = {} # {id or phid: drev} |
776 | 778 |
777 tree = _parse(spec) | 779 tree = _parse(spec) |
778 drevs, ancestordrevs = _prefetchdrevs(tree) | 780 drevs, ancestordrevs = _prefetchdrevs(tree) |
779 | 781 |
780 # developer config: phabricator.batchsize | 782 # developer config: phabricator.batchsize |
781 batchsize = repo.ui.configint('phabricator', 'batchsize') | 783 batchsize = repo.ui.configint(b'phabricator', b'batchsize') |
782 | 784 |
783 # Prefetch Differential Revisions in batch | 785 # Prefetch Differential Revisions in batch |
784 tofetch = set(drevs) | 786 tofetch = set(drevs) |
785 for r in ancestordrevs: | 787 for r in ancestordrevs: |
786 tofetch.update(range(max(1, r - batchsize), r + 1)) | 788 tofetch.update(range(max(1, r - batchsize), r + 1)) |
789 validids = sorted(set(getstack(list(ancestordrevs))) | set(drevs)) | 791 validids = sorted(set(getstack(list(ancestordrevs))) | set(drevs)) |
790 | 792 |
791 # Walk through the tree, return smartsets | 793 # Walk through the tree, return smartsets |
792 def walk(tree): | 794 def walk(tree): |
793 op = tree[0] | 795 op = tree[0] |
794 if op == 'symbol': | 796 if op == b'symbol': |
795 drev = _parsedrev(tree[1]) | 797 drev = _parsedrev(tree[1]) |
796 if drev: | 798 if drev: |
797 return smartset.baseset([drev]) | 799 return smartset.baseset([drev]) |
798 elif tree[1] in _knownstatusnames: | 800 elif tree[1] in _knownstatusnames: |
799 drevs = [r for r in validids | 801 drevs = [r for r in validids |
800 if _getstatusname(prefetched[r]) == tree[1]] | 802 if _getstatusname(prefetched[r]) == tree[1]] |
801 return smartset.baseset(drevs) | 803 return smartset.baseset(drevs) |
802 else: | 804 else: |
803 raise error.Abort(_('unknown symbol: %s') % tree[1]) | 805 raise error.Abort(_(b'unknown symbol: %s') % tree[1]) |
804 elif op in {'and_', 'add', 'sub'}: | 806 elif op in {b'and_', b'add', b'sub'}: |
805 assert len(tree) == 3 | 807 assert len(tree) == 3 |
806 return getattr(operator, op)(walk(tree[1]), walk(tree[2])) | 808 return getattr(operator, op)(walk(tree[1]), walk(tree[2])) |
807 elif op == 'group': | 809 elif op == b'group': |
808 return walk(tree[1]) | 810 return walk(tree[1]) |
809 elif op == 'ancestors': | 811 elif op == b'ancestors': |
810 return getstack(walk(tree[1])) | 812 return getstack(walk(tree[1])) |
811 else: | 813 else: |
812 raise error.ProgrammingError('illegal tree: %r' % tree) | 814 raise error.ProgrammingError(b'illegal tree: %r' % tree) |
813 | 815 |
814 return [prefetched[r] for r in walk(tree)] | 816 return [prefetched[r] for r in walk(tree)] |
815 | 817 |
816 def getdescfromdrev(drev): | 818 def getdescfromdrev(drev): |
817 """get description (commit message) from "Differential Revision" | 819 """get description (commit message) from "Differential Revision" |
821 """ | 823 """ |
822 title = drev[r'title'] | 824 title = drev[r'title'] |
823 summary = drev[r'summary'].rstrip() | 825 summary = drev[r'summary'].rstrip() |
824 testplan = drev[r'testPlan'].rstrip() | 826 testplan = drev[r'testPlan'].rstrip() |
825 if testplan: | 827 if testplan: |
826 testplan = 'Test Plan:\n%s' % testplan | 828 testplan = b'Test Plan:\n%s' % testplan |
827 uri = 'Differential Revision: %s' % drev[r'uri'] | 829 uri = b'Differential Revision: %s' % drev[r'uri'] |
828 return '\n\n'.join(filter(None, [title, summary, testplan, uri])) | 830 return b'\n\n'.join(filter(None, [title, summary, testplan, uri])) |
829 | 831 |
830 def getdiffmeta(diff): | 832 def getdiffmeta(diff): |
831 """get commit metadata (date, node, user, p1) from a diff object | 833 """get commit metadata (date, node, user, p1) from a diff object |
832 | 834 |
833 The metadata could be "hg:meta", sent by phabsend, like: | 835 The metadata could be "hg:meta", sent by phabsend, like: |
883 write is usually ui.write. drevs is what "querydrev" returns, results of | 885 write is usually ui.write. drevs is what "querydrev" returns, results of |
884 "differential.query". | 886 "differential.query". |
885 """ | 887 """ |
886 # Prefetch hg:meta property for all diffs | 888 # Prefetch hg:meta property for all diffs |
887 diffids = sorted(set(max(int(v) for v in drev[r'diffs']) for drev in drevs)) | 889 diffids = sorted(set(max(int(v) for v in drev[r'diffs']) for drev in drevs)) |
888 diffs = callconduit(repo, 'differential.querydiffs', {'ids': diffids}) | 890 diffs = callconduit(repo, b'differential.querydiffs', {b'ids': diffids}) |
889 | 891 |
890 # Generate patch for each drev | 892 # Generate patch for each drev |
891 for drev in drevs: | 893 for drev in drevs: |
892 repo.ui.note(_('reading D%s\n') % drev[r'id']) | 894 repo.ui.note(_(b'reading D%s\n') % drev[r'id']) |
893 | 895 |
894 diffid = max(int(v) for v in drev[r'diffs']) | 896 diffid = max(int(v) for v in drev[r'diffs']) |
895 body = callconduit(repo, 'differential.getrawdiff', {'diffID': diffid}) | 897 body = callconduit(repo, b'differential.getrawdiff', |
898 {b'diffID': diffid}) | |
896 desc = getdescfromdrev(drev) | 899 desc = getdescfromdrev(drev) |
897 header = '# HG changeset patch\n' | 900 header = b'# HG changeset patch\n' |
898 | 901 |
899 # Try to preserve metadata from hg:meta property. Write hg patch | 902 # Try to preserve metadata from hg:meta property. Write hg patch |
900 # headers that can be read by the "import" command. See patchheadermap | 903 # headers that can be read by the "import" command. See patchheadermap |
901 # and extract in mercurial/patch.py for supported headers. | 904 # and extract in mercurial/patch.py for supported headers. |
902 meta = getdiffmeta(diffs[str(diffid)]) | 905 meta = getdiffmeta(diffs[str(diffid)]) |
903 for k in _metanamemap.keys(): | 906 for k in _metanamemap.keys(): |
904 if k in meta: | 907 if k in meta: |
905 header += '# %s %s\n' % (_metanamemap[k], meta[k]) | 908 header += b'# %s %s\n' % (_metanamemap[k], meta[k]) |
906 | 909 |
907 content = '%s%s\n%s' % (header, desc, body) | 910 content = b'%s%s\n%s' % (header, desc, body) |
908 write(encoding.unitolocal(content)) | 911 write(encoding.unitolocal(content)) |
909 | 912 |
910 @command('phabread', | 913 @command(b'phabread', |
911 [('', 'stack', False, _('read dependencies'))], | 914 [(b'', b'stack', False, _(b'read dependencies'))], |
912 _('DREVSPEC [OPTIONS]')) | 915 _(b'DREVSPEC [OPTIONS]')) |
913 def phabread(ui, repo, spec, **opts): | 916 def phabread(ui, repo, spec, **opts): |
914 """print patches from Phabricator suitable for importing | 917 """print patches from Phabricator suitable for importing |
915 | 918 |
916 DREVSPEC could be a Differential Revision identity, like ``D123``, or just | 919 DREVSPEC could be a Differential Revision identity, like ``D123``, or just |
917 the number ``123``. It could also have common operators like ``+``, ``-``, | 920 the number ``123``. It could also have common operators like ``+``, ``-``, |
927 stack up to D9. | 930 stack up to D9. |
928 | 931 |
929 If --stack is given, follow dependencies information and read all patches. | 932 If --stack is given, follow dependencies information and read all patches. |
930 It is equivalent to the ``:`` operator. | 933 It is equivalent to the ``:`` operator. |
931 """ | 934 """ |
932 if opts.get('stack'): | 935 if opts.get(b'stack'): |
933 spec = ':(%s)' % spec | 936 spec = b':(%s)' % spec |
934 drevs = querydrev(repo, spec) | 937 drevs = querydrev(repo, spec) |
935 readpatch(repo, drevs, ui.write) | 938 readpatch(repo, drevs, ui.write) |
936 | 939 |
937 @command('phabupdate', | 940 @command(b'phabupdate', |
938 [('', 'accept', False, _('accept revisions')), | 941 [(b'', b'accept', False, _(b'accept revisions')), |
939 ('', 'reject', False, _('reject revisions')), | 942 (b'', b'reject', False, _(b'reject revisions')), |
940 ('', 'abandon', False, _('abandon revisions')), | 943 (b'', b'abandon', False, _(b'abandon revisions')), |
941 ('', 'reclaim', False, _('reclaim revisions')), | 944 (b'', b'reclaim', False, _(b'reclaim revisions')), |
942 ('m', 'comment', '', _('comment on the last revision')), | 945 (b'm', b'comment', b'', _(b'comment on the last revision')), |
943 ], _('DREVSPEC [OPTIONS]')) | 946 ], _(b'DREVSPEC [OPTIONS]')) |
944 def phabupdate(ui, repo, spec, **opts): | 947 def phabupdate(ui, repo, spec, **opts): |
945 """update Differential Revision in batch | 948 """update Differential Revision in batch |
946 | 949 |
947 DREVSPEC selects revisions. See :hg:`help phabread` for its usage. | 950 DREVSPEC selects revisions. See :hg:`help phabread` for its usage. |
948 """ | 951 """ |
949 flags = [n for n in 'accept reject abandon reclaim'.split() if opts.get(n)] | 952 flags = [n for n in b'accept reject abandon reclaim'.split() if opts.get(n)] |
950 if len(flags) > 1: | 953 if len(flags) > 1: |
951 raise error.Abort(_('%s cannot be used together') % ', '.join(flags)) | 954 raise error.Abort(_(b'%s cannot be used together') % b', '.join(flags)) |
952 | 955 |
953 actions = [] | 956 actions = [] |
954 for f in flags: | 957 for f in flags: |
955 actions.append({'type': f, 'value': 'true'}) | 958 actions.append({b'type': f, b'value': b'true'}) |
956 | 959 |
957 drevs = querydrev(repo, spec) | 960 drevs = querydrev(repo, spec) |
958 for i, drev in enumerate(drevs): | 961 for i, drev in enumerate(drevs): |
959 if i + 1 == len(drevs) and opts.get('comment'): | 962 if i + 1 == len(drevs) and opts.get(b'comment'): |
960 actions.append({'type': 'comment', 'value': opts['comment']}) | 963 actions.append({b'type': b'comment', b'value': opts[b'comment']}) |
961 if actions: | 964 if actions: |
962 params = {'objectIdentifier': drev[r'phid'], | 965 params = {b'objectIdentifier': drev[r'phid'], |
963 'transactions': actions} | 966 b'transactions': actions} |
964 callconduit(repo, 'differential.revision.edit', params) | 967 callconduit(repo, b'differential.revision.edit', params) |
965 | 968 |
966 templatekeyword = registrar.templatekeyword() | 969 templatekeyword = registrar.templatekeyword() |
967 | 970 |
968 @templatekeyword('phabreview', requires={'ctx'}) | 971 @templatekeyword(b'phabreview', requires={b'ctx'}) |
969 def template_review(context, mapping): | 972 def template_review(context, mapping): |
970 """:phabreview: Object describing the review for this changeset. | 973 """:phabreview: Object describing the review for this changeset. |
971 Has attributes `url` and `id`. | 974 Has attributes `url` and `id`. |
972 """ | 975 """ |
973 ctx = context.resource(mapping, 'ctx') | 976 ctx = context.resource(mapping, b'ctx') |
974 m = _differentialrevisiondescre.search(ctx.description()) | 977 m = _differentialrevisiondescre.search(ctx.description()) |
975 if m: | 978 if m: |
976 return { | 979 return { |
977 'url': m.group('url'), | 980 b'url': m.group(b'url'), |
978 'id': "D{}".format(m.group('id')), | 981 b'id': b"D{}".format(m.group(b'id')), |
979 } | 982 } |