contrib/phabricator.py
changeset 33691 1664406a44d9
parent 33690 40cfe3197bc1
child 33692 f100354cce52
equal deleted inserted replaced
33690:40cfe3197bc1 33691:1664406a44d9
   136     repo.ui.setconfig('phabricator', 'repophid', repophid)
   136     repo.ui.setconfig('phabricator', 'repophid', repophid)
   137     return repophid
   137     return repophid
   138 
   138 
   139 _differentialrevisiontagre = re.compile('\AD([1-9][0-9]*)\Z')
   139 _differentialrevisiontagre = re.compile('\AD([1-9][0-9]*)\Z')
   140 _differentialrevisiondescre = re.compile(
   140 _differentialrevisiondescre = re.compile(
   141     '^Differential Revision:\s*(.*)D([1-9][0-9]*)$', re.M)
   141     '^Differential Revision:\s*(?:.*)D([1-9][0-9]*)$', re.M)
   142 
   142 
   143 def getoldnodedrevmap(repo, nodelist):
   143 def getoldnodedrevmap(repo, nodelist):
   144     """find previous nodes that has been sent to Phabricator
   144     """find previous nodes that has been sent to Phabricator
   145 
   145 
   146     return {node: (oldnode or None, Differential Revision ID)}
   146     return {node: (oldnode, Differential diff, Differential Revision ID)}
   147     for node in nodelist with known previous sent versions, or associated
   147     for node in nodelist with known previous sent versions, or associated
   148     Differential Revision IDs.
   148     Differential Revision IDs. ``oldnode`` and ``Differential diff`` could
   149 
   149     be ``None``.
   150     Examines all precursors and their tags. Tags with format like "D1234" are
   150 
   151     considered a match and the node with that tag, and the number after "D"
   151     Examines commit messages like "Differential Revision:" to get the
   152     (ex. 1234) will be returned.
   152     association information.
   153 
   153 
   154     If tags are not found, examine commit message. The "Differential Revision:"
   154     If such commit message line is not found, examines all precursors and their
   155     line could associate this changeset to a Differential Revision.
   155     tags. Tags with format like "D1234" are considered a match and the node
       
   156     with that tag, and the number after "D" (ex. 1234) will be returned.
       
   157 
       
   158     The ``old node``, if not None, is guaranteed to be the last diff of
       
   159     corresponding Differential Revision, and exist in the repo.
   156     """
   160     """
   157     url, token = readurltoken(repo)
   161     url, token = readurltoken(repo)
   158     unfi = repo.unfiltered()
   162     unfi = repo.unfiltered()
   159     nodemap = unfi.changelog.nodemap
   163     nodemap = unfi.changelog.nodemap
   160 
   164 
   161     result = {} # {node: (oldnode or None, drev)}
   165     result = {} # {node: (oldnode?, lastdiff?, drev)}
   162     toconfirm = {} # {node: (oldnode, {precnode}, drev)}
   166     toconfirm = {} # {node: (force, {precnode}, drev)}
   163     for node in nodelist:
   167     for node in nodelist:
   164         ctx = unfi[node]
   168         ctx = unfi[node]
   165         # For tags like "D123", put them into "toconfirm" to verify later
   169         # For tags like "D123", put them into "toconfirm" to verify later
   166         precnodes = list(obsolete.allprecursors(unfi.obsstore, [node]))
   170         precnodes = list(obsolete.allprecursors(unfi.obsstore, [node]))
   167         for n in precnodes:
   171         for n in precnodes:
   168             if n in nodemap:
   172             if n in nodemap:
   169                 for tag in unfi.nodetags(n):
   173                 for tag in unfi.nodetags(n):
   170                     m = _differentialrevisiontagre.match(tag)
   174                     m = _differentialrevisiontagre.match(tag)
   171                     if m:
   175                     if m:
   172                         toconfirm[node] = (n, set(precnodes), int(m.group(1)))
   176                         toconfirm[node] = (0, set(precnodes), int(m.group(1)))
   173                         continue
   177                         continue
   174 
   178 
   175         # Check commit message (make sure URL matches)
   179         # Check commit message
   176         m = _differentialrevisiondescre.search(ctx.description())
   180         m = _differentialrevisiondescre.search(ctx.description())
   177         if m:
   181         if m:
   178             if m.group(1).rstrip('/') == url.rstrip('/'):
   182             toconfirm[node] = (1, set(precnodes), int(m.group(1)))
   179                 result[node] = (None, int(m.group(2)))
       
   180             else:
       
   181                 unfi.ui.warn(_('%s: Differential Revision URL ignored - host '
       
   182                                'does not match config\n') % ctx)
       
   183 
   183 
   184     # Double check if tags are genuine by collecting all old nodes from
   184     # Double check if tags are genuine by collecting all old nodes from
   185     # Phabricator, and expect precursors overlap with it.
   185     # Phabricator, and expect precursors overlap with it.
   186     if toconfirm:
   186     if toconfirm:
   187         confirmed = {} # {drev: {oldnode}}
   187         drevs = [drev for force, precs, drev in toconfirm.values()]
   188         drevs = [drev for n, precs, drev in toconfirm.values()]
   188         alldiffs = callconduit(unfi, 'differential.querydiffs',
   189         diffs = callconduit(unfi, 'differential.querydiffs',
   189                                {'revisionIDs': drevs})
   190                             {'revisionIDs': drevs})
   190         getnode = lambda d: bin(encoding.unitolocal(
   191         for diff in diffs.values():
   191             getdiffmeta(d).get(r'node', ''))) or None
   192             drev = int(diff[r'revisionID'])
   192         for newnode, (force, precset, drev) in toconfirm.items():
   193             oldnode = bin(encoding.unitolocal(getdiffmeta(diff).get(r'node')))
   193             diffs = [d for d in alldiffs.values()
   194             if node:
   194                      if int(d[r'revisionID']) == drev]
   195                 confirmed.setdefault(drev, set()).add(oldnode)
   195 
   196         for newnode, (oldnode, precset, drev) in toconfirm.items():
   196             # "precursors" as known by Phabricator
   197             if bool(precset & confirmed.get(drev, set())):
   197             phprecset = set(getnode(d) for d in diffs)
   198                 result[newnode] = (oldnode, drev)
   198 
   199             else:
   199             # Ignore if precursors (Phabricator and local repo) do not overlap,
       
   200             # and force is not set (when commit message says nothing)
       
   201             if not force and not bool(phprecset & precset):
   200                 tagname = 'D%d' % drev
   202                 tagname = 'D%d' % drev
   201                 tags.tag(repo, tagname, nullid, message=None, user=None,
   203                 tags.tag(repo, tagname, nullid, message=None, user=None,
   202                          date=None, local=True)
   204                          date=None, local=True)
   203                 unfi.ui.warn(_('D%s: local tag removed - does not match '
   205                 unfi.ui.warn(_('D%s: local tag removed - does not match '
   204                                'Differential history\n') % drev)
   206                                'Differential history\n') % drev)
       
   207                 continue
       
   208 
       
   209             # Find the last node using Phabricator metadata, and make sure it
       
   210             # exists in the repo
       
   211             oldnode = lastdiff = None
       
   212             if diffs:
       
   213                 lastdiff = max(diffs, key=lambda d: int(d[r'id']))
       
   214                 oldnode = getnode(lastdiff)
       
   215                 if oldnode and oldnode not in nodemap:
       
   216                     oldnode = None
       
   217 
       
   218             result[newnode] = (oldnode, lastdiff, drev)
   205 
   219 
   206     return result
   220     return result
   207 
   221 
   208 def getdiff(ctx, diffopts):
   222 def getdiff(ctx, diffopts):
   209     """plain-text diff without header (user, commit message, etc)"""
   223     """plain-text diff without header (user, commit message, etc)"""
   352     reviewers = opts.get('reviewer', [])
   366     reviewers = opts.get('reviewer', [])
   353     if reviewers:
   367     if reviewers:
   354         phids = userphids(repo, reviewers)
   368         phids = userphids(repo, reviewers)
   355         actions.append({'type': 'reviewers.add', 'value': phids})
   369         actions.append({'type': 'reviewers.add', 'value': phids})
   356 
   370 
   357     oldnodedrev = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
   371     # {newnode: (oldnode, olddiff, olddrev}
       
   372     oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
   358 
   373 
   359     # Send patches one by one so we know their Differential Revision IDs and
   374     # Send patches one by one so we know their Differential Revision IDs and
   360     # can provide dependency relationship
   375     # can provide dependency relationship
   361     lastrevid = None
   376     lastrevid = None
   362     for rev in revs:
   377     for rev in revs:
   363         ui.debug('sending rev %d\n' % rev)
   378         ui.debug('sending rev %d\n' % rev)
   364         ctx = repo[rev]
   379         ctx = repo[rev]
   365 
   380 
   366         # Get Differential Revision ID
   381         # Get Differential Revision ID
   367         oldnode, revid = oldnodedrev.get(ctx.node(), (None, None))
   382         oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None))
   368         if oldnode != ctx.node():
   383         if oldnode != ctx.node():
   369             # Create or update Differential Revision
   384             # Create or update Differential Revision
   370             revision = createdifferentialrevision(ctx, revid, lastrevid,
   385             revision = createdifferentialrevision(ctx, revid, lastrevid,
   371                                                   oldnode, actions)
   386                                                   oldnode, actions)
   372             newrevid = int(revision[r'object'][r'id'])
   387             newrevid = int(revision[r'object'][r'id'])