comparison hgext/phabricator.py @ 44646:5f9c917e3b50

phabricator: teach `getoldnodedrevmap()` to handle folded reviews The tricky part here is reasoning through all of the possible predecessor scenarios. In the typical case of submitting a folded range and then resubmitting it (also folded), filtering the list of commits for the diff stored on Phabricator through the local predecessor list for each single node will result in the typical 1:1 mapping to the old node. There are edge cases like using `hg fold` within the range prior to resubmitting, that will result in mapping to multiple old nodes. In that case, the first direct predecessor is needed for the base of the diff, and the last direct predecessor is needed for the head of the diff in order to make sure that the entire range is included in the diff content. And none of this matters for commits in the middle of the range, as they are never used. Fortunately the only crucial thing here is the `drev` number for each node. For these complicated cases where there are multiple old nodes, simply ignore them all. This will cause `createdifferentialrevision()` to generate a new diff (within the same Differential), and avoids complicating the code. Differential Revision: https://phab.mercurial-scm.org/D8311
author Matt Harbison <matt_harbison@yahoo.com>
date Mon, 16 Mar 2020 13:36:12 -0400
parents 419fec8237b7
children 99fa161a883c
comparison
equal deleted inserted replaced
44645:419fec8237b7 44646:5f9c917e3b50
481 if toconfirm: 481 if toconfirm:
482 drevs = [drev for force, precs, drev in toconfirm.values()] 482 drevs = [drev for force, precs, drev in toconfirm.values()]
483 alldiffs = callconduit( 483 alldiffs = callconduit(
484 unfi.ui, b'differential.querydiffs', {b'revisionIDs': drevs} 484 unfi.ui, b'differential.querydiffs', {b'revisionIDs': drevs}
485 ) 485 )
486 getnode = lambda d: bin(getdiffmeta(d).get(b'node', b'')) or None 486
487 def getnodes(d, precset):
488 # Ignore other nodes that were combined into the Differential
489 # that aren't predecessors of the current local node.
490 return [n for n in getlocalcommits(d) if n in precset]
491
487 for newnode, (force, precset, drev) in toconfirm.items(): 492 for newnode, (force, precset, drev) in toconfirm.items():
488 diffs = [ 493 diffs = [
489 d for d in alldiffs.values() if int(d[b'revisionID']) == drev 494 d for d in alldiffs.values() if int(d[b'revisionID']) == drev
490 ] 495 ]
491 496
492 # "precursors" as known by Phabricator 497 # local predecessors known by Phabricator
493 phprecset = {getnode(d) for d in diffs} 498 phprecset = {n for d in diffs for n in getnodes(d, precset)}
494 499
495 # Ignore if precursors (Phabricator and local repo) do not overlap, 500 # Ignore if precursors (Phabricator and local repo) do not overlap,
496 # and force is not set (when commit message says nothing) 501 # and force is not set (when commit message says nothing)
497 if not force and not bool(phprecset & precset): 502 if not force and not phprecset:
498 tagname = b'D%d' % drev 503 tagname = b'D%d' % drev
499 tags.tag( 504 tags.tag(
500 repo, 505 repo,
501 tagname, 506 tagname,
502 nullid, 507 nullid,
517 # Find the last node using Phabricator metadata, and make sure it 522 # Find the last node using Phabricator metadata, and make sure it
518 # exists in the repo 523 # exists in the repo
519 oldnode = lastdiff = None 524 oldnode = lastdiff = None
520 if diffs: 525 if diffs:
521 lastdiff = max(diffs, key=lambda d: int(d[b'id'])) 526 lastdiff = max(diffs, key=lambda d: int(d[b'id']))
522 oldnode = getnode(lastdiff) 527 oldnodes = getnodes(lastdiff, precset)
528
529 # If this commit was the result of `hg fold` after submission,
530 # and now resubmitted with --fold, the easiest thing to do is
531 # to leave the node clear. This only results in creating a new
532 # diff for the _same_ Differential Revision if this commit is
533 # the first or last in the selected range.
534 # If this commit is the result of `hg split` in the same
535 # scenario, there is a single oldnode here (and multiple
536 # newnodes mapped to it). That makes it the same as the normal
537 # case, as the edges of the newnode range cleanly maps to one
538 # oldnode each.
539 if len(oldnodes) == 1:
540 oldnode = oldnodes[0]
523 if oldnode and not has_node(oldnode): 541 if oldnode and not has_node(oldnode):
524 oldnode = None 542 oldnode = None
525 543
526 result[newnode] = (oldnode, lastdiff, drev) 544 result[newnode] = (oldnode, lastdiff, drev)
527 545
1665 return b'\n\n'.join([ctx.description(), uri]) 1683 return b'\n\n'.join([ctx.description(), uri])
1666 1684
1667 return _differentialrevisiondescre.sub(uri, ctx.description()) 1685 return _differentialrevisiondescre.sub(uri, ctx.description())
1668 1686
1669 1687
1688 def getlocalcommits(diff):
1689 """get the set of local commits from a diff object
1690
1691 See ``getdiffmeta()`` for an example diff object.
1692 """
1693 props = diff.get(b'properties') or {}
1694 commits = props.get(b'local:commits') or {}
1695 if len(commits) > 1:
1696 return {bin(c) for c in commits.keys()}
1697
1698 # Storing the diff metadata predates storing `local:commits`, so continue
1699 # to use that in the --no-fold case.
1700 return {bin(getdiffmeta(diff).get(b'node', b'')) or None}
1701
1702
1670 def getdiffmeta(diff): 1703 def getdiffmeta(diff):
1671 """get commit metadata (date, node, user, p1) from a diff object 1704 """get commit metadata (date, node, user, p1) from a diff object
1672 1705
1673 The metadata could be "hg:meta", sent by phabsend, like: 1706 The metadata could be "hg:meta", sent by phabsend, like:
1674 1707