mirror of
https://github.com/facebook/sapling.git
synced 2024-12-27 06:52:23 +03:00
mutation: public commits are never obsolete
Summary: The computation of commit obsolescence is inconsistent. If we compute the full set of obsolete commits in `mutation.obsoletecache.obsoletenodes`, then we correctly ignore public commits as they cannot be obsolete. However, if we compute the obsolescence state for a single public commit with `mutation.obsoletecache.isobsolete`, and that commit somehow has a visible successor, then we will incorrectly consider the commit as obsolete. Similarly, `allpredecessors` and `allsuccessors` should stop when they hit a public commit. Reviewed By: quark-zju Differential Revision: D20892778 fbshipit-source-id: 223cb8b2bc9f2f08124df6ff51c2eb208bb8eb5f
This commit is contained in:
parent
beb27516b4
commit
c02f38dfb7
@ -182,6 +182,7 @@ def allpredecessors(repo, nodes, startdepth=None, stopdepth=None):
|
|||||||
thislevel = set(nodes)
|
thislevel = set(nodes)
|
||||||
nextlevel = set()
|
nextlevel = set()
|
||||||
seen = {nullid}
|
seen = {nullid}
|
||||||
|
ispublic = getispublicfunc(repo)
|
||||||
while thislevel and (stopdepth is None or depth < stopdepth):
|
while thislevel and (stopdepth is None or depth < stopdepth):
|
||||||
for current in thislevel:
|
for current in thislevel:
|
||||||
if current in seen:
|
if current in seen:
|
||||||
@ -195,7 +196,7 @@ def allpredecessors(repo, nodes, startdepth=None, stopdepth=None):
|
|||||||
pred = entry.preds()
|
pred = entry.preds()
|
||||||
if pred is not None:
|
if pred is not None:
|
||||||
for nextnode in pred:
|
for nextnode in pred:
|
||||||
if nextnode not in seen:
|
if nextnode not in seen and not ispublic(nextnode):
|
||||||
nextlevel.add(nextnode)
|
nextlevel.add(nextnode)
|
||||||
depth += 1
|
depth += 1
|
||||||
thislevel = nextlevel
|
thislevel = nextlevel
|
||||||
@ -210,6 +211,7 @@ def allsuccessors(repo, nodes, startdepth=None, stopdepth=None):
|
|||||||
thislevel = set(nodes)
|
thislevel = set(nodes)
|
||||||
nextlevel = set()
|
nextlevel = set()
|
||||||
seen = set()
|
seen = set()
|
||||||
|
ispublic = getispublicfunc(repo)
|
||||||
while thislevel and (stopdepth is None or depth < stopdepth):
|
while thislevel and (stopdepth is None or depth < stopdepth):
|
||||||
for current in thislevel:
|
for current in thislevel:
|
||||||
if current in seen:
|
if current in seen:
|
||||||
@ -217,6 +219,8 @@ def allsuccessors(repo, nodes, startdepth=None, stopdepth=None):
|
|||||||
seen.add(current)
|
seen.add(current)
|
||||||
if startdepth is None or depth >= startdepth:
|
if startdepth is None or depth >= startdepth:
|
||||||
yield current
|
yield current
|
||||||
|
if ispublic(current):
|
||||||
|
continue
|
||||||
succsets = lookupsuccessors(repo, current)
|
succsets = lookupsuccessors(repo, current)
|
||||||
if succsets:
|
if succsets:
|
||||||
nextlevel = nextlevel.union(*succsets)
|
nextlevel = nextlevel.union(*succsets)
|
||||||
@ -243,6 +247,9 @@ class obsoletecache(object):
|
|||||||
return False
|
return False
|
||||||
if node not in repo:
|
if node not in repo:
|
||||||
return False
|
return False
|
||||||
|
ispublic = getispublicfunc(repo)
|
||||||
|
if ispublic(node):
|
||||||
|
return False
|
||||||
obsolete = self.obsolete[repo.filtername]
|
obsolete = self.obsolete[repo.filtername]
|
||||||
if node in obsolete:
|
if node in obsolete:
|
||||||
return True
|
return True
|
||||||
@ -339,8 +346,7 @@ def fate(repo, node):
|
|||||||
This returns a list of ([nodes], operation) pairs, indicating mutations that
|
This returns a list of ([nodes], operation) pairs, indicating mutations that
|
||||||
happened to this node that resulted in one or more visible commits.
|
happened to this node that resulted in one or more visible commits.
|
||||||
"""
|
"""
|
||||||
clrev = repo.changelog.rev
|
ispublic = getispublicfunc(repo)
|
||||||
phase = repo._phasecache.phase
|
|
||||||
fate = []
|
fate = []
|
||||||
if isobsolete(repo, node):
|
if isobsolete(repo, node):
|
||||||
for succset in successorssets(repo, node, closest=True):
|
for succset in successorssets(repo, node, closest=True):
|
||||||
@ -351,7 +357,7 @@ def fate(repo, node):
|
|||||||
else:
|
else:
|
||||||
succ = succset[0]
|
succ = succset[0]
|
||||||
# Base the default operation name on the successor's phase
|
# Base the default operation name on the successor's phase
|
||||||
if succ in repo and phase(repo, clrev(succ)) == phases.public:
|
if ispublic(succ):
|
||||||
op = "land"
|
op = "land"
|
||||||
else:
|
else:
|
||||||
op = "rewrite"
|
op = "rewrite"
|
||||||
@ -377,6 +383,7 @@ def predecessorsset(repo, startnode, closest=False):
|
|||||||
the start node.
|
the start node.
|
||||||
"""
|
"""
|
||||||
seen = {startnode}
|
seen = {startnode}
|
||||||
|
ispublic = getispublicfunc(repo)
|
||||||
|
|
||||||
def get(node):
|
def get(node):
|
||||||
"""Get immediate predecessors
|
"""Get immediate predecessors
|
||||||
@ -391,7 +398,9 @@ def predecessorsset(repo, startnode, closest=False):
|
|||||||
if entry is not None:
|
if entry is not None:
|
||||||
preds = entry.preds()
|
preds = entry.preds()
|
||||||
if preds is not None:
|
if preds is not None:
|
||||||
return [pred for pred in preds if pred not in seen] or [node]
|
return [
|
||||||
|
pred for pred in preds if pred not in seen and not ispublic(pred)
|
||||||
|
] or [node]
|
||||||
return [node]
|
return [node]
|
||||||
|
|
||||||
preds = [startnode]
|
preds = [startnode]
|
||||||
@ -480,6 +489,7 @@ def successorssets(repo, startnode, closest=False, cache=None):
|
|||||||
signature-compatible with ``obsutil.successorssets``.
|
signature-compatible with ``obsutil.successorssets``.
|
||||||
"""
|
"""
|
||||||
seen = {startnode}
|
seen = {startnode}
|
||||||
|
ispublic = getispublicfunc(repo)
|
||||||
|
|
||||||
def getsets(node):
|
def getsets(node):
|
||||||
"""Get immediate successors sets
|
"""Get immediate successors sets
|
||||||
@ -490,6 +500,8 @@ def successorssets(repo, startnode, closest=False, cache=None):
|
|||||||
If the node has no successors, returns a list containing a single
|
If the node has no successors, returns a list containing a single
|
||||||
successors set which contains the node itself.
|
successors set which contains the node itself.
|
||||||
"""
|
"""
|
||||||
|
if ispublic(node):
|
||||||
|
return [[node]]
|
||||||
succsets = [
|
succsets = [
|
||||||
succset
|
succset
|
||||||
for succset in lookupsuccessors(repo, node)
|
for succset in lookupsuccessors(repo, node)
|
||||||
@ -826,3 +838,20 @@ def getisvisiblefunc(repo):
|
|||||||
else:
|
else:
|
||||||
# Use cl.hasnode to test
|
# Use cl.hasnode to test
|
||||||
return repo.changelog.hasnode
|
return repo.changelog.hasnode
|
||||||
|
|
||||||
|
|
||||||
|
def getispublicfunc(repo):
|
||||||
|
"""get a (node) -> bool function to test whether a commit is public"""
|
||||||
|
nodemap = repo.changelog.nodemap
|
||||||
|
getphase = repo._phasecache.phase
|
||||||
|
|
||||||
|
def ispublic(
|
||||||
|
node, nodemap=nodemap, getphase=getphase, repo=repo, public=phases.public
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
rev = nodemap[node]
|
||||||
|
except error.RevlogError:
|
||||||
|
return False
|
||||||
|
return getphase(repo, rev) == public
|
||||||
|
|
||||||
|
return ispublic
|
||||||
|
46
eden/scm/tests/test-mutation-phases.t
Normal file
46
eden/scm/tests/test-mutation-phases.t
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#require py2
|
||||||
|
#chg-compatible
|
||||||
|
|
||||||
|
$ enable amend
|
||||||
|
$ setconfig experimental.evolution=obsolete
|
||||||
|
$ setconfig experimental.narrow-heads=false
|
||||||
|
$ setconfig visibility.enabled=true
|
||||||
|
$ setconfig mutation.record=true mutation.enabled=true mutation.date="0 0"
|
||||||
|
|
||||||
|
$ newrepo
|
||||||
|
$ echo "base" > base
|
||||||
|
$ hg commit -Aqm base
|
||||||
|
$ echo 1 > file
|
||||||
|
$ hg commit -Aqm commit1
|
||||||
|
$ echo 2 > file
|
||||||
|
$ hg amend -Aqm commit1-amended
|
||||||
|
$ tglogm --hidden
|
||||||
|
@ 2: f9719601f84a 'commit1-amended'
|
||||||
|
|
|
||||||
|
| x 1: e6c779c67aa9 'commit1' (Rewritten using amend into f9719601f84a)
|
||||||
|
|/
|
||||||
|
o 0: d20a80d4def3 'base'
|
||||||
|
|
||||||
|
$ hg log -r 'successors(1)' -T '{node} {desc}\n' --hidden
|
||||||
|
e6c779c67aa947c951f334f4f312bd2b21d27e55 commit1
|
||||||
|
f9719601f84ab527273dc915bfb41704b111058c commit1-amended
|
||||||
|
$ hg log -r 'predecessors(2)' -T '{node} {desc}\n' --hidden
|
||||||
|
e6c779c67aa947c951f334f4f312bd2b21d27e55 commit1
|
||||||
|
f9719601f84ab527273dc915bfb41704b111058c commit1-amended
|
||||||
|
|
||||||
|
Set the phase of the obsolete commit to public, simulating the older version being landed.
|
||||||
|
$ hg phase -p 1 --hidden
|
||||||
|
|
||||||
|
The commit should no longer show up as amended.
|
||||||
|
$ tglogm --hidden
|
||||||
|
@ 2: f9719601f84a 'commit1-amended'
|
||||||
|
|
|
||||||
|
| o 1: e6c779c67aa9 'commit1'
|
||||||
|
|/
|
||||||
|
o 0: d20a80d4def3 'base'
|
||||||
|
|
||||||
|
The predecessor and successor relationship has been removed.
|
||||||
|
$ hg log -r 'successors(1)' -T '{node} {desc}\n' --hidden
|
||||||
|
e6c779c67aa947c951f334f4f312bd2b21d27e55 commit1
|
||||||
|
$ hg log -r 'predecessors(2)' -T '{node} {desc}\n' --hidden
|
||||||
|
f9719601f84ab527273dc915bfb41704b111058c commit1-amended
|
Loading…
Reference in New Issue
Block a user