comparison contrib/phabricator.py @ 33443:e48082e0a8d5

phabricator: verify local tags before trusting them Previously we trust local tags blindly and that could cause wrong Differential Revision to be updated, when people switch between Phabricator instances. This patch adds verification logic to detect such issue and remove problematic tags. For example, a tag "D19" was on node "X", the code will fetch all diffs attached to D19, and check if nodes server-side overlaps with nodes in precursors. If they do not overlap, create a new Differential Revision. Test Plan: Use a test Phabricator instance, send patches using `hg phabsend`, then change the local tag manually to a wrong Differential Revision number. Amend the patch and send again. Make sure the tag gets ignored and deleted. Differential Revision: https://phab.mercurial-scm.org/D36
author Jun Wu <quark@fb.com>
date Tue, 11 Jul 2017 08:17:29 -0700
parents 3ab0d5767b54
children b7a75b9a3386
comparison
equal deleted inserted replaced
33442:3ab0d5767b54 33443:e48082e0a8d5
33 from __future__ import absolute_import 33 from __future__ import absolute_import
34 34
35 import json 35 import json
36 import re 36 import re
37 37
38 from mercurial.node import bin, nullid
38 from mercurial.i18n import _ 39 from mercurial.i18n import _
39 from mercurial import ( 40 from mercurial import (
40 encoding, 41 encoding,
41 error, 42 error,
42 mdiff, 43 mdiff,
156 url, token = readurltoken(repo) 157 url, token = readurltoken(repo)
157 unfi = repo.unfiltered() 158 unfi = repo.unfiltered()
158 nodemap = unfi.changelog.nodemap 159 nodemap = unfi.changelog.nodemap
159 160
160 result = {} # {node: (oldnode or None, drev)} 161 result = {} # {node: (oldnode or None, drev)}
162 toconfirm = {} # {node: (oldnode, {precnode}, drev)}
161 for node in nodelist: 163 for node in nodelist:
162 ctx = unfi[node] 164 ctx = unfi[node]
163 # Check tags like "D123" 165 # For tags like "D123", put them into "toconfirm" to verify later
164 for n in obsolete.allprecursors(unfi.obsstore, [node]): 166 precnodes = list(obsolete.allprecursors(unfi.obsstore, [node]))
167 for n in precnodes:
165 if n in nodemap: 168 if n in nodemap:
166 for tag in unfi.nodetags(n): 169 for tag in unfi.nodetags(n):
167 m = _differentialrevisiontagre.match(tag) 170 m = _differentialrevisiontagre.match(tag)
168 if m: 171 if m:
169 result[node] = (n, int(m.group(1))) 172 toconfirm[node] = (n, set(precnodes), int(m.group(1)))
170 continue 173 continue
171 174
172 # Check commit message 175 # Check commit message
173 m = _differentialrevisiondescre.search(ctx.description()) 176 m = _differentialrevisiondescre.search(ctx.description())
174 if m: 177 if m:
175 result[node] = (None, int(m.group(1))) 178 result[node] = (None, int(m.group(1)))
179
180 # Double check if tags are genuine by collecting all old nodes from
181 # Phabricator, and expect precursors overlap with it.
182 if toconfirm:
183 confirmed = {} # {drev: {oldnode}}
184 drevs = [drev for n, precs, drev in toconfirm.values()]
185 diffs = callconduit(unfi, 'differential.querydiffs',
186 {'revisionIDs': drevs})
187 for diff in diffs.values():
188 drev = int(diff[r'revisionID'])
189 oldnode = bin(encoding.unitolocal(getdiffmeta(diff).get(r'node')))
190 if node:
191 confirmed.setdefault(drev, set()).add(oldnode)
192 for newnode, (oldnode, precset, drev) in toconfirm.items():
193 if bool(precset & confirmed.get(drev, set())):
194 result[newnode] = (oldnode, drev)
195 else:
196 tagname = 'D%d' % drev
197 tags.tag(repo, tagname, nullid, message=None, user=None,
198 date=None, local=True)
199 unfi.ui.warn(_('D%s: local tag removed - does not match '
200 'Differential history\n') % drev)
176 201
177 return result 202 return result
178 203
179 def getdiff(ctx, diffopts): 204 def getdiff(ctx, diffopts):
180 """plain-text diff without header (user, commit message, etc)""" 205 """plain-text diff without header (user, commit message, etc)"""