sapling/pullcreatemarkers.py
Adam Simpkins db56b7802d [pullcreatemarkers] also obsolete parents of commits landed in this pull
Summary:
When a pull causes commits on the tip of a branch to be obsoleted due to
landing, make sure that parent commits that were landed in previous pulls also
get hidden.  The parent commits would already have obsolete markers, but they
would have been inhibited because there were non-obsolete children.  Once their
children are landed their obsolete markers can now be deinhibited.

Test Plan: Added a new test case.

Reviewers: #sourcecontrol, durham, lcharignon, ikostia, pyd, ttung

Subscribers: net-systems-diffs@, yogeshwer, mjpieters

Differential Revision: https://phabricator.fb.com/D3028597
2016-03-21 18:08:04 -07:00

124 lines
3.9 KiB
Python

# pullcreatemarkers.py - create obsolescence markers on pull for better rebases
#
# Copyright 2015 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
#
# The goal of this extensions is to create obsolescence markers locally for
# commits previously landed.
# It uses the phabricator revision number in the commit message to detect the
# relationship between a draft commit and its landed counterpart.
# Thanks to these markers, less information is displayed and rebases can have
# less irrelevant conflicts.
import re
from mercurial import commands
from mercurial import obsolete
from mercurial import phases
from mercurial import extensions
def getdiff(rev):
m = re.findall("Differential Revision: https://phabricator.fb.com/D(.*)",
rev.description(),
re.MULTILINE)
if m:
try:
diffnum = int(m[0])
return diffnum
except ValueError:
return None
else:
return None
def extsetup(ui):
extensions.wrapcommand(commands.table, 'pull', _pull)
def _pull(orig, ui, repo, *args, **opts):
if not obsolete.isenabled(repo, obsolete.createmarkersopt):
return orig(ui, repo, *args, **opts)
maxrevbeforepull = len(repo.changelog)
r = orig(ui, repo, *args, **opts)
maxrevafterpull = len(repo.changelog)
# Collect the diff number of the landed diffs
landeddiffs = {}
for rev in range(maxrevbeforepull, maxrevafterpull):
n = repo[rev]
if n.phase() == phases.public:
diff = getdiff(n)
if diff is not None:
landeddiffs[diff] = n
if not landeddiffs:
return r
# Try to find match with the drafts
tocreate = []
unfiltered = repo.unfiltered()
for rev in unfiltered.revs("draft() - hidden()"):
n = unfiltered[rev]
diff = getdiff(n)
if diff in landeddiffs:
tocreate.append((n, (landeddiffs[diff],)))
if not tocreate:
return r
inhibit, deinhibitnodes = _deinhibitancestors(unfiltered, tocreate)
with unfiltered.lock() as l:
with unfiltered.transaction('pullcreatemarkers') as t:
obsolete.createmarkers(unfiltered, tocreate)
if deinhibitnodes:
inhibit._deinhibitmarkers(unfiltered, deinhibitnodes)
return r
def _deinhibitancestors(repo, markers):
"""Compute the set of commits that already have obsolescence markers
which were possibly inhibited, and should be deinhibited because of this
new pull operation.
Returns a tuple of (inhibit module, node set).
Returns (None, None) if the inhibit extension is not enabled."""
try:
inhibit = extensions.find('inhibit')
except KeyError:
return None, None
if not inhibit._inhibitenabled(repo):
return None, None
# Commits for which we should deinhibit obsolescence markers
deinhibitset = set()
# Commits whose parents we should process
toprocess = set([ctx for ctx, successor in markers])
# Commits that are already in toprocess or have already been processed
seen = toprocess.copy()
# Commits that we deinhibit obsolescence markers for
while toprocess:
ctx = toprocess.pop()
for p in ctx.parents():
if p in seen:
continue
seen.add(p)
if _isobsolete(p):
deinhibitset.add(p.node())
toprocess.add(p)
return inhibit, deinhibitset
def _isobsolete(ctx):
# A commit is obsolete if it has at least one successor marker.
#
# successormarkers() returns a generator. generators unfortunately
# evaluate to True even if they are "empty", so pull one element off and
# see if anything exists or not.
i = obsolete.successormarkers(ctx)
try:
next(i)
except StopIteration:
return False
return True