[PATCH 2 of 3] phabricator: add phabsend command to send a stack
Phillip Cohen
phillip at phillip.io
Mon Jul 3 16:38:34 EDT 2017
> + """
> + revs = list(revs) + opts.get('rev', [])
> + revs = scmutil.revrange(repo, revs)
> +
I'd add something like this here:
if not revs:
raise error.Abort(_('must specify at least one changeset with -r'))
On Sun, Jul 2, 2017 at 8:10 PM, Jun Wu <quark at fb.com> wrote:
> # HG changeset patch
> # User Jun Wu <quark at fb.com>
> # Date 1499051289 25200
> # Sun Jul 02 20:08:09 2017 -0700
> # Node ID edb97a6c5f1065227180e41010076ad09697b408
> # Parent dde88a5ead698208226301a62a5f2aa2629d7839
> # Available At https://bitbucket.org/quark-zju/hg-draft
> # hg pull https://bitbucket.org/quark-zju/hg-draft -r edb97a6c5f10
> phabricator: add phabsend command to send a stack
>
> The `phabsend` command is intended to provide `hg email`-like experience -
> sending a stack, setup dependency information and do not amend existing
> changesets.
>
> It uses differential.createrawdiff and differential.revision.edit Conduit
> API to create or update a Differential Revision.
>
> Local tags like `D123` are written indicating certain changesets were sent
> to Phabricator. The `phabsend` command will use obsstore and tags
> information to decide whether to update or create Differential Revisions.
>
> diff --git a/contrib/phabricator.py b/contrib/phabricator.py
> --- a/contrib/phabricator.py
> +++ b/contrib/phabricator.py
> @@ -7,4 +7,11 @@
> """simple Phabricator integration
>
> +This extension provides a ``phabsend`` command which sends a stack of
> +changesets to Phabricator without amending commit messages.
> +
> +By default, Phabricator requires ``Test Plan`` which might prevent some
> +changeset from being sent. The requirement could be disabled by changing
> +``differential.require-test-plan-field`` config server side.
> +
> Config::
>
> @@ -15,4 +22,8 @@ Config::
> # API token. Get it from https://$HOST/conduit/login/
> token = cli-xxxxxxxxxxxxxxxxxxxxxxxxxxxx
> +
> + # Repo callsign. If a repo has a URL https://$HOST/diffusion/FOO, then its
> + # callsign is "FOO".
> + callsign = FOO
> """
>
> @@ -20,9 +31,16 @@ from __future__ import absolute_import
>
> import json
> +import re
>
> from mercurial.i18n import _
> from mercurial import (
> + encoding,
> error,
> + mdiff,
> + obsolete,
> + patch,
> registrar,
> + scmutil,
> + tags,
> url as urlmod,
> util,
> @@ -97,2 +115,161 @@ def debugcallconduit(ui, repo, name):
> s = json.dumps(result, sort_keys=True, indent=2, separators=(',', ': '))
> ui.write('%s\n' % s)
> +
> +def getrepophid(repo):
> + """given callsign, return repository PHID or None"""
> + # developer config: phabricator.repophid
> + repophid = repo.ui.config('phabricator', 'repophid')
> + if repophid:
> + return repophid
> + callsign = repo.ui.config('phabricator', 'callsign')
> + if not callsign:
> + return None
> + query = callconduit(repo, 'diffusion.repository.search',
> + {'constraints': {'callsigns': [callsign]}})
> + if len(query[r'data']) == 0:
> + return None
> + repophid = encoding.strtolocal(query[r'data'][0][r'phid'])
> + repo.ui.setconfig('phabricator', 'repophid', repophid)
> + return repophid
> +
> +_differentialrevisionre = re.compile('\AD([1-9][0-9]*)\Z')
> +
> +def getmapping(ctx):
> + """return (node, associated Differential Revision ID) or (None, None)
> +
> + Examines all precursors and their tags. Tags with format like "D1234" are
> + considered a match and the node with that tag, and the number after "D"
> + (ex. 1234) will be returned.
> + """
> + unfi = ctx.repo().unfiltered()
> + nodemap = unfi.changelog.nodemap
> + for n in obsolete.allprecursors(unfi.obsstore, [ctx.node()]):
> + if n in nodemap:
> + for tag in unfi.nodetags(n):
> + m = _differentialrevisionre.match(tag)
> + if m:
> + return n, int(m.group(1))
> + return None, None
> +
> +def getdiff(ctx, diffopts):
> + """plain-text diff without header (user, commit message, etc)"""
> + output = util.stringio()
> + for chunk, _label in patch.diffui(ctx.repo(), ctx.p1().node(), ctx.node(),
> + None, opts=diffopts):
> + output.write(chunk)
> + return output.getvalue()
> +
> +def creatediff(ctx):
> + """create a Differential Diff"""
> + repo = ctx.repo()
> + repophid = getrepophid(repo)
> + # Create a "Differential Diff" via "differential.createrawdiff" API
> + params = {'diff': getdiff(ctx, mdiff.diffopts(git=True, context=32767))}
> + if repophid:
> + params['repositoryPHID'] = repophid
> + diff = callconduit(repo, 'differential.createrawdiff', params)
> + if not diff:
> + raise error.Abort(_('cannot create diff for %s') % ctx)
> + return diff
> +
> +def writediffproperties(ctx, diff):
> + """write metadata to diff so patches could be applied losslessly"""
> + params = {
> + 'diff_id': diff[r'id'],
> + 'name': 'hg:meta',
> + 'data': json.dumps({
> + 'user': ctx.user(),
> + 'date': '%d %d' % ctx.date(),
> + }),
> + }
> + callconduit(ctx.repo(), 'differential.setdiffproperty', params)
> +
> +def createdifferentialrevision(ctx, revid=None, parentrevid=None):
> + """create or update a Differential Revision
> +
> + If revid is None, create a new Differential Revision, otherwise update
> + revid. If parentrevid is not None, set it as a dependency.
> + """
> + repo = ctx.repo()
> + diff = creatediff(ctx)
> + writediffproperties(ctx, diff)
> +
> + transactions = [{'type': 'update', 'value': diff[r'phid']}]
> +
> + # Use a temporary summary to set dependency. There might be better ways but
> + # I cannot find them for now. But do not do that if we are updating an
> + # existing revision (revid is not None) since that introduces visible
> + # churns (someone edited "Summary" twice) on the web page.
> + if parentrevid and revid is None:
> + summary = 'Depends on D%s' % parentrevid
> + transactions += [{'type': 'summary', 'value': summary},
> + {'type': 'summary', 'value': ' '}]
> +
> + # Parse commit message and update related fields.
> + desc = ctx.description()
> + info = callconduit(repo, 'differential.parsecommitmessage',
> + {'corpus': desc})
> + for k, v in info[r'fields'].items():
> + if k in ['title', 'summary', 'testPlan']:
> + transactions.append({'type': k, 'value': v})
> +
> + params = {'transactions': transactions}
> + if revid is not None:
> + # Update an existing Differential Revision
> + params['objectIdentifier'] = revid
> +
> + revision = callconduit(repo, 'differential.revision.edit', params)
> + if not revision:
> + raise error.Abort(_('cannot create revision for %s') % ctx)
> +
> + return revision
> +
> + at command('phabsend',
> + [('r', 'rev', [], _('revisions to send'), _('REV'))],
> + _('REV [OPTIONS]'))
> +def phabsend(ui, repo, *revs, **opts):
> + """upload changesets to Phabricator
> +
> + If there are multiple revisions specified, they will be send as a stack
> + with a linear dependencies relationship using the order specified by the
> + revset.
> +
> + For the first time uploading changesets, local tags will be created to
> + maintain the association. After the first time, phabsend will check
> + obsstore and tags information so it can figure out whether to update an
> + existing Differential Revision, or create a new one.
> + """
> + revs = list(revs) + opts.get('rev', [])
> + revs = scmutil.revrange(repo, revs)
> +
> + # Send patches one by one so we know their Differential Revision IDs and
> + # can provide dependency relationship
> + lastrevid = None
> + for rev in revs:
> + ui.debug('sending rev %d\n' % rev)
> + ctx = repo[rev]
> +
> + # Get Differential Revision ID
> + oldnode, revid = getmapping(ctx)
> + if oldnode != ctx.node():
> + # Create or update Differential Revision
> + revision = createdifferentialrevision(ctx, revid, lastrevid)
> + newrevid = int(revision[r'object'][r'id'])
> + if revid:
> + action = _('updated')
> + else:
> + action = _('created')
> +
> + # Create a local tag to note the association
> + tagname = 'D%d' % newrevid
> + tags.tag(repo, tagname, ctx.node(), message=None, user=None,
> + date=None, local=True)
> + else:
> + # Nothing changed. But still set "newrevid" so the next revision
> + # could depend on this one.
> + newrevid = revid
> + action = _('skipped')
> +
> + ui.write(_('D%s: %s - %s: %s\n') % (newrevid, action, ctx,
> + ctx.description().split('\n')[0]))
> + lastrevid = newrevid
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
More information about the Mercurial-devel
mailing list