hgext/phabricator.py
changeset 42434 f163e2b2594c
parent 42433 500b64c5d991
child 42435 16312ea45a8b
equal deleted inserted replaced
42433:500b64c5d991 42434:f163e2b2594c
   208         raise error.Abort(_(b'Can\'t find conduit token associated to %s')
   208         raise error.Abort(_(b'Can\'t find conduit token associated to %s')
   209                             % (url,))
   209                             % (url,))
   210 
   210 
   211     return url, token
   211     return url, token
   212 
   212 
   213 def callconduit(repo, name, params):
   213 def callconduit(ui, name, params):
   214     """call Conduit API, params is a dict. return json.loads result, or None"""
   214     """call Conduit API, params is a dict. return json.loads result, or None"""
   215     host, token = readurltoken(repo.ui)
   215     host, token = readurltoken(ui)
   216     url, authinfo = util.url(b'/'.join([host, b'api', name])).authinfo()
   216     url, authinfo = util.url(b'/'.join([host, b'api', name])).authinfo()
   217     repo.ui.debug(b'Conduit Call: %s %s\n' % (url, pycompat.byterepr(params)))
   217     ui.debug(b'Conduit Call: %s %s\n' % (url, pycompat.byterepr(params)))
   218     params = params.copy()
   218     params = params.copy()
   219     params[b'api.token'] = token
   219     params[b'api.token'] = token
   220     data = urlencodenested(params)
   220     data = urlencodenested(params)
   221     curlcmd = repo.ui.config(b'phabricator', b'curlcmd')
   221     curlcmd = ui.config(b'phabricator', b'curlcmd')
   222     if curlcmd:
   222     if curlcmd:
   223         sin, sout = procutil.popen2(b'%s -d @- %s'
   223         sin, sout = procutil.popen2(b'%s -d @- %s'
   224                                     % (curlcmd, procutil.shellquote(url)))
   224                                     % (curlcmd, procutil.shellquote(url)))
   225         sin.write(data)
   225         sin.write(data)
   226         sin.close()
   226         sin.close()
   227         body = sout.read()
   227         body = sout.read()
   228     else:
   228     else:
   229         urlopener = urlmod.opener(repo.ui, authinfo)
   229         urlopener = urlmod.opener(ui, authinfo)
   230         request = util.urlreq.request(pycompat.strurl(url), data=data)
   230         request = util.urlreq.request(pycompat.strurl(url), data=data)
   231         with contextlib.closing(urlopener.open(request)) as rsp:
   231         with contextlib.closing(urlopener.open(request)) as rsp:
   232             body = rsp.read()
   232             body = rsp.read()
   233     repo.ui.debug(b'Conduit Response: %s\n' % body)
   233     ui.debug(b'Conduit Response: %s\n' % body)
   234     parsed = pycompat.rapply(
   234     parsed = pycompat.rapply(
   235         lambda x: encoding.unitolocal(x) if isinstance(x, pycompat.unicode)
   235         lambda x: encoding.unitolocal(x) if isinstance(x, pycompat.unicode)
   236         else x,
   236         else x,
   237         json.loads(body)
   237         json.loads(body)
   238     )
   238     )
   257         json.loads(rawparams)
   257         json.loads(rawparams)
   258     )
   258     )
   259     # json.dumps only accepts unicode strings
   259     # json.dumps only accepts unicode strings
   260     result = pycompat.rapply(lambda x:
   260     result = pycompat.rapply(lambda x:
   261         encoding.unifromlocal(x) if isinstance(x, bytes) else x,
   261         encoding.unifromlocal(x) if isinstance(x, bytes) else x,
   262         callconduit(repo, name, params)
   262         callconduit(ui, name, params)
   263     )
   263     )
   264     s = json.dumps(result, sort_keys=True, indent=2, separators=(u',', u': '))
   264     s = json.dumps(result, sort_keys=True, indent=2, separators=(u',', u': '))
   265     ui.write(b'%s\n' % encoding.unitolocal(s))
   265     ui.write(b'%s\n' % encoding.unitolocal(s))
   266 
   266 
   267 def getrepophid(repo):
   267 def getrepophid(repo):
   271     if repophid:
   271     if repophid:
   272         return repophid
   272         return repophid
   273     callsign = repo.ui.config(b'phabricator', b'callsign')
   273     callsign = repo.ui.config(b'phabricator', b'callsign')
   274     if not callsign:
   274     if not callsign:
   275         return None
   275         return None
   276     query = callconduit(repo, b'diffusion.repository.search',
   276     query = callconduit(repo.ui, b'diffusion.repository.search',
   277                         {b'constraints': {b'callsigns': [callsign]}})
   277                         {b'constraints': {b'callsigns': [callsign]}})
   278     if len(query[b'data']) == 0:
   278     if len(query[b'data']) == 0:
   279         return None
   279         return None
   280     repophid = query[b'data'][0][b'phid']
   280     repophid = query[b'data'][0][b'phid']
   281     repo.ui.setconfig(b'phabricator', b'repophid', repophid)
   281     repo.ui.setconfig(b'phabricator', b'repophid', repophid)
   327 
   327 
   328     # Double check if tags are genuine by collecting all old nodes from
   328     # Double check if tags are genuine by collecting all old nodes from
   329     # Phabricator, and expect precursors overlap with it.
   329     # Phabricator, and expect precursors overlap with it.
   330     if toconfirm:
   330     if toconfirm:
   331         drevs = [drev for force, precs, drev in toconfirm.values()]
   331         drevs = [drev for force, precs, drev in toconfirm.values()]
   332         alldiffs = callconduit(unfi, b'differential.querydiffs',
   332         alldiffs = callconduit(unfi.ui, b'differential.querydiffs',
   333                                {b'revisionIDs': drevs})
   333                                {b'revisionIDs': drevs})
   334         getnode = lambda d: bin(
   334         getnode = lambda d: bin(
   335             getdiffmeta(d).get(b'node', b'')) or None
   335             getdiffmeta(d).get(b'node', b'')) or None
   336         for newnode, (force, precset, drev) in toconfirm.items():
   336         for newnode, (force, precset, drev) in toconfirm.items():
   337             diffs = [d for d in alldiffs.values()
   337             diffs = [d for d in alldiffs.values()
   377     repophid = getrepophid(repo)
   377     repophid = getrepophid(repo)
   378     # Create a "Differential Diff" via "differential.createrawdiff" API
   378     # Create a "Differential Diff" via "differential.createrawdiff" API
   379     params = {b'diff': getdiff(ctx, mdiff.diffopts(git=True, context=32767))}
   379     params = {b'diff': getdiff(ctx, mdiff.diffopts(git=True, context=32767))}
   380     if repophid:
   380     if repophid:
   381         params[b'repositoryPHID'] = repophid
   381         params[b'repositoryPHID'] = repophid
   382     diff = callconduit(repo, b'differential.createrawdiff', params)
   382     diff = callconduit(repo.ui, b'differential.createrawdiff', params)
   383     if not diff:
   383     if not diff:
   384         raise error.Abort(_(b'cannot create diff for %s') % ctx)
   384         raise error.Abort(_(b'cannot create diff for %s') % ctx)
   385     return diff
   385     return diff
   386 
   386 
   387 def writediffproperties(ctx, diff):
   387 def writediffproperties(ctx, diff):
   395             b'branch': ctx.branch(),
   395             b'branch': ctx.branch(),
   396             b'node': ctx.hex(),
   396             b'node': ctx.hex(),
   397             b'parent': ctx.p1().hex(),
   397             b'parent': ctx.p1().hex(),
   398         }),
   398         }),
   399     }
   399     }
   400     callconduit(ctx.repo(), b'differential.setdiffproperty', params)
   400     callconduit(ctx.repo().ui, b'differential.setdiffproperty', params)
   401 
   401 
   402     params = {
   402     params = {
   403         b'diff_id': diff[b'id'],
   403         b'diff_id': diff[b'id'],
   404         b'name': b'local:commits',
   404         b'name': b'local:commits',
   405         b'data': templatefilters.json({
   405         b'data': templatefilters.json({
   411                 b'parents': [ctx.p1().hex()],
   411                 b'parents': [ctx.p1().hex()],
   412                 b'branch': ctx.branch(),
   412                 b'branch': ctx.branch(),
   413             },
   413             },
   414         }),
   414         }),
   415     }
   415     }
   416     callconduit(ctx.repo(), b'differential.setdiffproperty', params)
   416     callconduit(ctx.repo().ui, b'differential.setdiffproperty', params)
   417 
   417 
   418 def createdifferentialrevision(ctx, revid=None, parentrevid=None, oldnode=None,
   418 def createdifferentialrevision(ctx, revid=None, parentrevid=None, oldnode=None,
   419                                olddiff=None, actions=None, comment=None):
   419                                olddiff=None, actions=None, comment=None):
   420     """create or update a Differential Revision
   420     """create or update a Differential Revision
   421 
   421 
   461     if actions:
   461     if actions:
   462         transactions += actions
   462         transactions += actions
   463 
   463 
   464     # Parse commit message and update related fields.
   464     # Parse commit message and update related fields.
   465     desc = ctx.description()
   465     desc = ctx.description()
   466     info = callconduit(repo, b'differential.parsecommitmessage',
   466     info = callconduit(repo.ui, b'differential.parsecommitmessage',
   467                        {b'corpus': desc})
   467                        {b'corpus': desc})
   468     for k, v in info[b'fields'].items():
   468     for k, v in info[b'fields'].items():
   469         if k in [b'title', b'summary', b'testPlan']:
   469         if k in [b'title', b'summary', b'testPlan']:
   470             transactions.append({b'type': k, b'value': v})
   470             transactions.append({b'type': k, b'value': v})
   471 
   471 
   472     params = {b'transactions': transactions}
   472     params = {b'transactions': transactions}
   473     if revid is not None:
   473     if revid is not None:
   474         # Update an existing Differential Revision
   474         # Update an existing Differential Revision
   475         params[b'objectIdentifier'] = revid
   475         params[b'objectIdentifier'] = revid
   476 
   476 
   477     revision = callconduit(repo, b'differential.revision.edit', params)
   477     revision = callconduit(repo.ui, b'differential.revision.edit', params)
   478     if not revision:
   478     if not revision:
   479         raise error.Abort(_(b'cannot create revision for %s') % ctx)
   479         raise error.Abort(_(b'cannot create revision for %s') % ctx)
   480 
   480 
   481     return revision, diff
   481     return revision, diff
   482 
   482 
   483 def userphids(repo, names):
   483 def userphids(repo, names):
   484     """convert user names to PHIDs"""
   484     """convert user names to PHIDs"""
   485     names = [name.lower() for name in names]
   485     names = [name.lower() for name in names]
   486     query = {b'constraints': {b'usernames': names}}
   486     query = {b'constraints': {b'usernames': names}}
   487     result = callconduit(repo, b'user.search', query)
   487     result = callconduit(repo.ui, b'user.search', query)
   488     # username not found is not an error of the API. So check if we have missed
   488     # username not found is not an error of the API. So check if we have missed
   489     # some names here.
   489     # some names here.
   490     data = result[b'data']
   490     data = result[b'data']
   491     resolved = set(entry[b'fields'][b'username'].lower() for entry in data)
   491     resolved = set(entry[b'fields'][b'username'].lower() for entry in data)
   492     unresolved = set(names) - resolved
   492     unresolved = set(names) - resolved
   607         lastrevid = newrevid
   607         lastrevid = newrevid
   608 
   608 
   609     # Update commit messages and remove tags
   609     # Update commit messages and remove tags
   610     if opts.get(b'amend'):
   610     if opts.get(b'amend'):
   611         unfi = repo.unfiltered()
   611         unfi = repo.unfiltered()
   612         drevs = callconduit(repo, b'differential.query', {b'ids': drevids})
   612         drevs = callconduit(ui, b'differential.query', {b'ids': drevids})
   613         with repo.wlock(), repo.lock(), repo.transaction(b'phabsend'):
   613         with repo.wlock(), repo.lock(), repo.transaction(b'phabsend'):
   614             wnode = unfi[b'.'].node()
   614             wnode = unfi[b'.'].node()
   615             mapping = {} # {oldnode: [newnode]}
   615             mapping = {} # {oldnode: [newnode]}
   616             for i, rev in enumerate(revs):
   616             for i, rev in enumerate(revs):
   617                 old = unfi[rev]
   617                 old = unfi[rev]
   793     def fetch(params):
   793     def fetch(params):
   794         """params -> single drev or None"""
   794         """params -> single drev or None"""
   795         key = (params.get(b'ids') or params.get(b'phids') or [None])[0]
   795         key = (params.get(b'ids') or params.get(b'phids') or [None])[0]
   796         if key in prefetched:
   796         if key in prefetched:
   797             return prefetched[key]
   797             return prefetched[key]
   798         drevs = callconduit(repo, b'differential.query', params)
   798         drevs = callconduit(repo.ui, b'differential.query', params)
   799         # Fill prefetched with the result
   799         # Fill prefetched with the result
   800         for drev in drevs:
   800         for drev in drevs:
   801             prefetched[drev[b'phid']] = drev
   801             prefetched[drev[b'phid']] = drev
   802             prefetched[int(drev[b'id'])] = drev
   802             prefetched[int(drev[b'id'])] = drev
   803         if key not in prefetched:
   803         if key not in prefetched:
   951     write is usually ui.write. drevs is what "querydrev" returns, results of
   951     write is usually ui.write. drevs is what "querydrev" returns, results of
   952     "differential.query".
   952     "differential.query".
   953     """
   953     """
   954     # Prefetch hg:meta property for all diffs
   954     # Prefetch hg:meta property for all diffs
   955     diffids = sorted(set(max(int(v) for v in drev[b'diffs']) for drev in drevs))
   955     diffids = sorted(set(max(int(v) for v in drev[b'diffs']) for drev in drevs))
   956     diffs = callconduit(repo, b'differential.querydiffs', {b'ids': diffids})
   956     diffs = callconduit(repo.ui, b'differential.querydiffs', {b'ids': diffids})
   957 
   957 
   958     # Generate patch for each drev
   958     # Generate patch for each drev
   959     for drev in drevs:
   959     for drev in drevs:
   960         repo.ui.note(_(b'reading D%s\n') % drev[b'id'])
   960         repo.ui.note(_(b'reading D%s\n') % drev[b'id'])
   961 
   961 
   962         diffid = max(int(v) for v in drev[b'diffs'])
   962         diffid = max(int(v) for v in drev[b'diffs'])
   963         body = callconduit(repo, b'differential.getrawdiff',
   963         body = callconduit(repo.ui, b'differential.getrawdiff',
   964                            {b'diffID': diffid})
   964                            {b'diffID': diffid})
   965         desc = getdescfromdrev(drev)
   965         desc = getdescfromdrev(drev)
   966         header = b'# HG changeset patch\n'
   966         header = b'# HG changeset patch\n'
   967 
   967 
   968         # Try to preserve metadata from hg:meta property. Write hg patch
   968         # Try to preserve metadata from hg:meta property. Write hg patch
  1032         if i + 1 == len(drevs) and opts.get(b'comment'):
  1032         if i + 1 == len(drevs) and opts.get(b'comment'):
  1033             actions.append({b'type': b'comment', b'value': opts[b'comment']})
  1033             actions.append({b'type': b'comment', b'value': opts[b'comment']})
  1034         if actions:
  1034         if actions:
  1035             params = {b'objectIdentifier': drev[b'phid'],
  1035             params = {b'objectIdentifier': drev[b'phid'],
  1036                       b'transactions': actions}
  1036                       b'transactions': actions}
  1037             callconduit(repo, b'differential.revision.edit', params)
  1037             callconduit(ui, b'differential.revision.edit', params)
  1038 
  1038 
  1039 templatekeyword = registrar.templatekeyword()
  1039 templatekeyword = registrar.templatekeyword()
  1040 
  1040 
  1041 @templatekeyword(b'phabreview', requires={b'ctx'})
  1041 @templatekeyword(b'phabreview', requires={b'ctx'})
  1042 def template_review(context, mapping):
  1042 def template_review(context, mapping):