D121: phabricator: use Phabricator's last node information

2017-08-04 Thread quark (Jun Wu)
This revision was automatically updated to reflect the committed changes.
Closed by commit rHG1664406a44d9: phabricator: use Phabricator's last node 
information (authored by quark).

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D121?vs=550=565

REVISION DETAIL
  https://phab.mercurial-scm.org/D121

AFFECTED FILES
  contrib/phabricator.py

CHANGE DETAILS

diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -138,28 +138,32 @@
 
 _differentialrevisiontagre = re.compile('\AD([1-9][0-9]*)\Z')
 _differentialrevisiondescre = re.compile(
-'^Differential Revision:\s*(.*)D([1-9][0-9]*)$', re.M)
+'^Differential Revision:\s*(?:.*)D([1-9][0-9]*)$', re.M)
 
 def getoldnodedrevmap(repo, nodelist):
 """find previous nodes that has been sent to Phabricator
 
-return {node: (oldnode or None, Differential Revision ID)}
+return {node: (oldnode, Differential diff, Differential Revision ID)}
 for node in nodelist with known previous sent versions, or associated
-Differential Revision IDs.
+Differential Revision IDs. ``oldnode`` and ``Differential diff`` could
+be ``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.
+Examines commit messages like "Differential Revision:" to get the
+association information.
 
-If tags are not found, examine commit message. The "Differential Revision:"
-line could associate this changeset to a Differential Revision.
+If such commit message line is not found, 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.
+
+The ``old node``, if not None, is guaranteed to be the last diff of
+corresponding Differential Revision, and exist in the repo.
 """
 url, token = readurltoken(repo)
 unfi = repo.unfiltered()
 nodemap = unfi.changelog.nodemap
 
-result = {} # {node: (oldnode or None, drev)}
-toconfirm = {} # {node: (oldnode, {precnode}, drev)}
+result = {} # {node: (oldnode?, lastdiff?, drev)}
+toconfirm = {} # {node: (force, {precnode}, drev)}
 for node in nodelist:
 ctx = unfi[node]
 # For tags like "D123", put them into "toconfirm" to verify later
@@ -169,39 +173,49 @@
 for tag in unfi.nodetags(n):
 m = _differentialrevisiontagre.match(tag)
 if m:
-toconfirm[node] = (n, set(precnodes), int(m.group(1)))
+toconfirm[node] = (0, set(precnodes), int(m.group(1)))
 continue
 
-# Check commit message (make sure URL matches)
+# Check commit message
 m = _differentialrevisiondescre.search(ctx.description())
 if m:
-if m.group(1).rstrip('/') == url.rstrip('/'):
-result[node] = (None, int(m.group(2)))
-else:
-unfi.ui.warn(_('%s: Differential Revision URL ignored - host '
-   'does not match config\n') % ctx)
+toconfirm[node] = (1, set(precnodes), int(m.group(1)))
 
 # Double check if tags are genuine by collecting all old nodes from
 # Phabricator, and expect precursors overlap with it.
 if toconfirm:
-confirmed = {} # {drev: {oldnode}}
-drevs = [drev for n, precs, drev in toconfirm.values()]
-diffs = callconduit(unfi, 'differential.querydiffs',
-{'revisionIDs': drevs})
-for diff in diffs.values():
-drev = int(diff[r'revisionID'])
-oldnode = bin(encoding.unitolocal(getdiffmeta(diff).get(r'node')))
-if node:
-confirmed.setdefault(drev, set()).add(oldnode)
-for newnode, (oldnode, precset, drev) in toconfirm.items():
-if bool(precset & confirmed.get(drev, set())):
-result[newnode] = (oldnode, drev)
-else:
+drevs = [drev for force, precs, drev in toconfirm.values()]
+alldiffs = callconduit(unfi, 'differential.querydiffs',
+   {'revisionIDs': drevs})
+getnode = lambda d: bin(encoding.unitolocal(
+getdiffmeta(d).get(r'node', ''))) or None
+for newnode, (force, precset, drev) in toconfirm.items():
+diffs = [d for d in alldiffs.values()
+ if int(d[r'revisionID']) == drev]
+
+# "precursors" as known by Phabricator
+phprecset = set(getnode(d) for d in diffs)
+
+# Ignore if precursors (Phabricator and local repo) do not overlap,
+# and force is not set (when commit message says nothing)
+if not force and not bool(phprecset & precset):
   

D121: phabricator: use Phabricator's last node information

2017-08-01 Thread quark (Jun Wu)
quark updated this revision to Diff 484.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D121?vs=232=484

REVISION DETAIL
  https://phab.mercurial-scm.org/D121

AFFECTED FILES
  contrib/phabricator.py

CHANGE DETAILS

diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -147,19 +147,22 @@
 for node in nodelist with known previous sent versions, or associated
 Differential Revision IDs.
 
-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.
+Examines commit messages like "Differential Revision:" to get the
+association information.
 
-If tags are not found, examine commit message. The "Differential Revision:"
-line could associate this changeset to a Differential Revision.
+If such commit message line is not found, 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.
+
+The ``old node``, if not None, is guaranteed to be the last diff of
+corresponding Differential Revision, and exist in the repo.
 """
 url, token = readurltoken(repo)
 unfi = repo.unfiltered()
 nodemap = unfi.changelog.nodemap
 
 result = {} # {node: (oldnode or None, drev)}
-toconfirm = {} # {node: (oldnode, {precnode}, drev)}
+toconfirm = {} # {node: (force, {precnode}, drev)}
 for node in nodelist:
 ctx = unfi[node]
 # For tags like "D123", put them into "toconfirm" to verify later
@@ -169,39 +172,47 @@
 for tag in unfi.nodetags(n):
 m = _differentialrevisiontagre.match(tag)
 if m:
-toconfirm[node] = (n, set(precnodes), int(m.group(1)))
+toconfirm[node] = (0, set(precnodes), int(m.group(1)))
 continue
 
-# Check commit message (make sure URL matches)
+# Check commit message
 m = _differentialrevisiondescre.search(ctx.description())
 if m:
-if m.group(1).rstrip('/') == url.rstrip('/'):
-result[node] = (None, int(m.group(2)))
-else:
-unfi.ui.warn(_('%s: Differential Revision URL ignored - host '
-   'does not match config\n') % ctx)
+toconfirm[node] = (1, set(precnodes), int(m.group(1)))
 
 # Double check if tags are genuine by collecting all old nodes from
 # Phabricator, and expect precursors overlap with it.
 if toconfirm:
-confirmed = {} # {drev: {oldnode}}
-drevs = [drev for n, precs, drev in toconfirm.values()]
-diffs = callconduit(unfi, 'differential.querydiffs',
-{'revisionIDs': drevs})
-for diff in diffs.values():
-drev = int(diff[r'revisionID'])
-oldnode = bin(encoding.unitolocal(getdiffmeta(diff).get(r'node')))
-if node:
-confirmed.setdefault(drev, set()).add(oldnode)
-for newnode, (oldnode, precset, drev) in toconfirm.items():
-if bool(precset & confirmed.get(drev, set())):
-result[newnode] = (oldnode, drev)
-else:
+drevs = [drev for force, precs, drev in toconfirm.values()]
+alldiffs = callconduit(unfi, 'differential.querydiffs',
+   {'revisionIDs': drevs})
+getnode = lambda d: bin(encoding.unitolocal(
+getdiffmeta(d).get(r'node', ''))) or None
+for newnode, (force, precset, drev) in toconfirm.items():
+diffs = [d for d in alldiffs.values()
+ if int(d[r'revisionID']) == drev]
+
+# "precursors" as known by Phabricator
+phprecset = set(getnode(d) for d in diffs)
+
+# Ignore if precursors (Phabricator and local repo) do not overlap,
+# and force is not set (when commit message says nothing)
+if not force and not bool(phprecset & precset):
 tagname = 'D%d' % drev
 tags.tag(repo, tagname, nullid, message=None, user=None,
  date=None, local=True)
 unfi.ui.warn(_('D%s: local tag removed - does not match '
'Differential history\n') % drev)
+continue
+
+# Find the last node using Phabricator metadata, and make sure it
+# exists in the repo
+lastdiff = max(diffs, key=lambda d: int(d[r'id']))
+oldnode = getnode(lastdiff)
+if oldnode and oldnode not in nodemap:
+oldnode = None
+
+result[newnode] = (oldnode, drev)
 
 return result
 



To: quark, #hg-reviewers, mitrandir
Cc: mercurial-devel

Re: D121: phabricator: use Phabricator's last node information

2017-07-17 Thread quark (Jun Wu)
quark created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  This makes it more strict when checking whether or not we should update a
  Differential Revision. For example,
  
a) Alice updates D1 to content 1.
b) Bob updates D1 to content 2.
c) Alice tries to update D1 to content 1.
  
  Previously, `c)` will do nothing because `phabsend` detects the patch is not
  changed. A more correct behavior is to override Bob's update here, hence the
  patch.
  
  This also makes it possible to return a reaonsable "last node" when there is
  no tags but only `Differential Revision` commit messages.

TEST PLAN
for i in A B C; do echo $i > $i; hg ci -m $i -A $i; done

hg phabsend 0::
# D40: created
# D41: created
# D42: created

echo 3 >> C; hg amend; hg phabsend .
# D42: updated

hg tag --local --hidden -r 2 -f D42
# move tag to the previous version

hg phabsend .
# D42: skipped (previously it would be "updated")

rm -rf .hg; hg init
hg phabread --stack D42 | hg import -
hg phabsend .
# D42: updated
hg tag --local --remove D42
hg commit --amend
hg phabsend .
# D42: updated (no new diff uploaded, previously it will update new diff)

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D121

AFFECTED FILES
  contrib/phabricator.py

CHANGE DETAILS

diff --git a/contrib/phabricator.py b/contrib/phabricator.py
--- a/contrib/phabricator.py
+++ b/contrib/phabricator.py
@@ -147,19 +147,22 @@
 for node in nodelist with known previous sent versions, or associated
 Differential Revision IDs.
 
-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.
+Examines commit messages like "Differential Revision:" to get the
+association information.
 
-If tags are not found, examine commit message. The "Differential Revision:"
-line could associate this changeset to a Differential Revision.
+If such commit message line is not found, 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.
+
+The ``old node``, if not None, is guaranteed to be the last diff of
+corresponding Differential Revision, and exist in the repo.
 """
 url, token = readurltoken(repo)
 unfi = repo.unfiltered()
 nodemap = unfi.changelog.nodemap
 
 result = {} # {node: (oldnode or None, drev)}
-toconfirm = {} # {node: (oldnode, {precnode}, drev)}
+toconfirm = {} # {node: (force, {precnode}, drev)}
 for node in nodelist:
 ctx = unfi[node]
 # For tags like "D123", put them into "toconfirm" to verify later
@@ -169,35 +172,48 @@
 for tag in unfi.nodetags(n):
 m = _differentialrevisiontagre.match(tag)
 if m:
-toconfirm[node] = (n, set(precnodes), int(m.group(1)))
+toconfirm[node] = (0, set(precnodes), int(m.group(1)))
 continue
 
-# Check commit message
+# Check commit message. If "Differential Revision:" exists and matches
+# the URL. Trust it blindly even if precursors do not match.
 m = _differentialrevisiondescre.search(ctx.description())
 if m:
-result[node] = (None, int(m.group(1)))
+toconfirm[node] = (1, set(precnodes), int(m.group(1)))
 
 # Double check if tags are genuine by collecting all old nodes from
 # Phabricator, and expect precursors overlap with it.
 if toconfirm:
-confirmed = {} # {drev: {oldnode}}
-drevs = [drev for n, precs, drev in toconfirm.values()]
-diffs = callconduit(unfi, 'differential.querydiffs',
-{'revisionIDs': drevs})
-for diff in diffs.values():
-drev = int(diff[r'revisionID'])
-oldnode = bin(encoding.unitolocal(getdiffmeta(diff).get(r'node')))
-if node:
-confirmed.setdefault(drev, set()).add(oldnode)
-for newnode, (oldnode, precset, drev) in toconfirm.items():
-if bool(precset & confirmed.get(drev, set())):
-result[newnode] = (oldnode, drev)
-else:
+drevs = [drev for force, precs, drev in toconfirm.values()]
+alldiffs = callconduit(unfi, 'differential.querydiffs',
+   {'revisionIDs': drevs})
+getnode = lambda d: bin(encoding.unitolocal(
+getdiffmeta(d).get(r'node', ''))) or None
+for newnode, (force, precset, drev) in toconfirm.items():
+diffs = [d for d in alldiffs.values()
+ if int(d[r'revisionID']) == drev]
+
+# "precursors"