mirror of
https://github.com/facebook/sapling.git
synced 2024-10-06 23:07:18 +03:00
mutation: add debugmutationfromobsolete to backfill mutation records
Summary: Add `hg debugmutationfromobsolete` which backfills mutation information from obsmarkers. Reviewed By: DurhamG Differential Revision: D14603199 fbshipit-source-id: 64458c52ba0e9f7bc9b38abeb94660cd1423fca7
This commit is contained in:
parent
59231d903d
commit
2f0922256b
@ -20,6 +20,7 @@ import time
|
||||
from . import (
|
||||
debug,
|
||||
debugcheckoutidentifier,
|
||||
debugmutation,
|
||||
debugstatus,
|
||||
debugstrip,
|
||||
eden,
|
||||
@ -81,6 +82,7 @@ readonly = registrar.command.readonly
|
||||
|
||||
table.update(uncommit.cmdtable)
|
||||
table.update(debugcheckoutidentifier.command._table)
|
||||
table.update(debugmutation.command._table)
|
||||
table.update(debugstatus.command._table)
|
||||
table.update(debugstrip.command._table)
|
||||
table.update(eden.command._table)
|
||||
|
@ -3289,45 +3289,3 @@ def debugtreestate(ui, repo, cmd="status", **opts):
|
||||
)
|
||||
else:
|
||||
raise error.Abort("unrecognised command: %s" % cmd)
|
||||
|
||||
|
||||
@command(b"debugmutation", [], _("[REV]"))
|
||||
def debugmutation(ui, repo, *revs, **opts):
|
||||
"""display the mutation history of a commit"""
|
||||
repo = repo.unfiltered()
|
||||
opts = pycompat.byteskwargs(opts)
|
||||
for rev in scmutil.revrange(repo, revs):
|
||||
ctx = repo[rev]
|
||||
nodestack = [[ctx.node()]]
|
||||
while nodestack:
|
||||
node = nodestack[-1].pop()
|
||||
ui.status(("%s%s") % (" " * len(nodestack), hex(node)))
|
||||
entry = mutation.lookup(repo, node)
|
||||
if entry is not None:
|
||||
preds = entry.preds()
|
||||
mutop = entry.op()
|
||||
mutuser = entry.user()
|
||||
mutdate = util.shortdatetime((entry.time(), entry.tz()))
|
||||
mutsplit = entry.split() or None
|
||||
origin = entry.origin()
|
||||
origin = {
|
||||
None: "",
|
||||
mutation.ORIGIN_LOCAL: "",
|
||||
mutation.ORIGIN_COMMIT: " (from remote commit)",
|
||||
mutation.ORIGIN_OBSMARKER: " (from obsmarker)",
|
||||
mutation.ORIGIN_SYNTHETIC: " (synthetic)",
|
||||
}.get(origin, " (unknown origin %s)" % origin)
|
||||
extra = ""
|
||||
if mutsplit is not None:
|
||||
extra += " (split into this and: %s)" % ", ".join(
|
||||
[hex(n) for n in mutsplit]
|
||||
)
|
||||
ui.status(
|
||||
(" %s by %s at %s%s%s from:")
|
||||
% (mutop, mutuser, mutdate, extra, origin)
|
||||
)
|
||||
nodestack.append(list(reversed(preds)))
|
||||
ui.status(("\n"))
|
||||
while nodestack and not nodestack[-1]:
|
||||
nodestack.pop()
|
||||
return 0
|
||||
|
185
edenscm/mercurial/commands/debugmutation.py
Normal file
185
edenscm/mercurial/commands/debugmutation.py
Normal file
@ -0,0 +1,185 @@
|
||||
# debugmutation.py - command processing for debugmutation* commands
|
||||
#
|
||||
# Copyright 2019 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.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from .. import mutation, node as nodemod, pycompat, registrar, scmutil, util
|
||||
from ..i18n import _
|
||||
|
||||
|
||||
command = registrar.command()
|
||||
|
||||
|
||||
@command(b"debugmutation", [], _("[REV]"))
|
||||
def debugmutation(ui, repo, *revs, **opts):
|
||||
"""display the mutation history of a commit"""
|
||||
repo = repo.unfiltered()
|
||||
opts = pycompat.byteskwargs(opts)
|
||||
for rev in scmutil.revrange(repo, revs):
|
||||
ctx = repo[rev]
|
||||
nodestack = [[ctx.node()]]
|
||||
while nodestack:
|
||||
node = nodestack[-1].pop()
|
||||
ui.status(("%s%s") % (" " * len(nodestack), nodemod.hex(node)))
|
||||
entry = mutation.lookup(repo, node)
|
||||
if entry is not None:
|
||||
preds = entry.preds()
|
||||
mutop = entry.op()
|
||||
mutuser = entry.user()
|
||||
mutdate = util.shortdatetime((entry.time(), entry.tz()))
|
||||
mutsplit = entry.split() or None
|
||||
origin = entry.origin()
|
||||
origin = {
|
||||
None: "",
|
||||
mutation.ORIGIN_LOCAL: "",
|
||||
mutation.ORIGIN_COMMIT: " (from remote commit)",
|
||||
mutation.ORIGIN_OBSMARKER: " (from obsmarker)",
|
||||
mutation.ORIGIN_SYNTHETIC: " (synthetic)",
|
||||
}.get(origin, " (unknown origin %s)" % origin)
|
||||
extra = ""
|
||||
if mutsplit is not None:
|
||||
extra += " (split into this and: %s)" % ", ".join(
|
||||
[nodemod.hex(n) for n in mutsplit]
|
||||
)
|
||||
ui.status(
|
||||
(" %s by %s at %s%s%s from:")
|
||||
% (mutop, mutuser, mutdate, extra, origin)
|
||||
)
|
||||
nodestack.append(list(reversed(preds)))
|
||||
ui.status(("\n"))
|
||||
while nodestack and not nodestack[-1]:
|
||||
nodestack.pop()
|
||||
return 0
|
||||
|
||||
|
||||
@command(b"debugmutationfromobsmarkers", [])
|
||||
def debugmutationfromobsmarkers(ui, repo, **opts):
|
||||
"""convert obsolescence markers to mutation records"""
|
||||
obsmarkers = repo.obsstore._all
|
||||
# Sort obsmarkers by date. Applying them in probable date order gives us
|
||||
# a better chance of resolving cycles in the right way.
|
||||
obsmarkers.sort(key=lambda x: x[4])
|
||||
newmut = {}
|
||||
|
||||
def checkloopfree(pred, succs):
|
||||
candidates = {pred}
|
||||
while candidates:
|
||||
candidate = candidates.pop()
|
||||
if candidate in newmut:
|
||||
mutpreds, mutsplit, _markers = newmut[candidate]
|
||||
for succ in succs:
|
||||
if succ in mutpreds:
|
||||
repo.ui.debug(
|
||||
"ignoring loop: %s -> %s\n history loops at %s -> %s\n"
|
||||
% (
|
||||
nodemod.hex(pred),
|
||||
", ".join([nodemod.hex(s) for s in succs]),
|
||||
nodemod.hex(succ),
|
||||
nodemod.hex(candidate),
|
||||
)
|
||||
)
|
||||
return False
|
||||
candidates.update(mutpreds)
|
||||
return True
|
||||
|
||||
dropprune = 0
|
||||
droprevive = 0
|
||||
dropundo = 0
|
||||
droploop = 0
|
||||
dropinvalid = 0
|
||||
|
||||
for obsmarker in obsmarkers:
|
||||
obspred, obssuccs, obsflag, obsmeta, obsdate, obsparents = obsmarker
|
||||
if not obssuccs:
|
||||
# Skip prune markers
|
||||
dropprune += 1
|
||||
continue
|
||||
if obssuccs == (obspred,):
|
||||
# Skip revive markers
|
||||
droprevive += 1
|
||||
continue
|
||||
obsmeta = dict(obsmeta)
|
||||
if obsmeta.get("operation") in ("undo", "uncommit", "unamend"):
|
||||
# Skip undo-style markers
|
||||
dropundo += 1
|
||||
continue
|
||||
if not checkloopfree(obspred, obssuccs):
|
||||
# Skip markers that introduce loops
|
||||
droploop += 1
|
||||
continue
|
||||
if len(obssuccs) > 1:
|
||||
# Split marker
|
||||
succ = obssuccs[-1]
|
||||
if succ in newmut:
|
||||
preds, split, markers = newmut[succ]
|
||||
if obsmarker in markers:
|
||||
# duplicate
|
||||
continue
|
||||
repo.ui.debug(
|
||||
"invalid obsmarker found: %s -> %s is both split and folded\n"
|
||||
% (nodemod.hex(obspred), nodemod.hex(succ))
|
||||
)
|
||||
dropinvalid += 1
|
||||
continue
|
||||
newmut[succ] = ([obspred], obssuccs[:-1], [obsmarker])
|
||||
elif obssuccs[0] in newmut:
|
||||
preds, split, markers = newmut[obssuccs[0]]
|
||||
if obsmarker in markers:
|
||||
# duplicate
|
||||
continue
|
||||
# Fold marker
|
||||
preds.append(obspred)
|
||||
markers.append(obsmarker)
|
||||
else:
|
||||
# Normal marker
|
||||
newmut[obssuccs[0]] = ([obspred], None, [obsmarker])
|
||||
|
||||
repo.ui.debug(
|
||||
"dropped markers: prune: %s, revive: %s, undo: %s, loop: %s, invalid: %s\n"
|
||||
% (dropprune, droprevive, dropundo, droploop, dropinvalid)
|
||||
)
|
||||
|
||||
entries = []
|
||||
|
||||
for succ, (preds, split, obsmarkers) in newmut.items():
|
||||
if mutation.lookup(repo, succ) is not None:
|
||||
# Have already converted this successor, or already know about it
|
||||
continue
|
||||
mutop = ""
|
||||
mutuser = ""
|
||||
mutdate = None
|
||||
for obsmarker in obsmarkers:
|
||||
obspred, obssuccs, obsflag, obsmeta, obsdate, obsparents = obsmarker
|
||||
obsmeta = dict(obsmeta)
|
||||
obsop = obsmeta.get("operation", "")
|
||||
if not mutop or obsop not in ("", "copy"):
|
||||
mutop = obsop
|
||||
obsuser = obsmeta.get("user", "")
|
||||
if not mutuser and obsuser:
|
||||
mutuser = obsuser
|
||||
if not mutdate:
|
||||
mutdate = obsdate
|
||||
|
||||
entries.append(
|
||||
mutation.createsyntheticentry(
|
||||
repo,
|
||||
mutation.ORIGIN_OBSMARKER,
|
||||
preds,
|
||||
succ,
|
||||
mutop,
|
||||
split,
|
||||
mutuser,
|
||||
mutdate,
|
||||
)
|
||||
)
|
||||
|
||||
repo.ui.write(
|
||||
_("generated %s entries for %s commits\n") % (len(entries), len(newmut))
|
||||
)
|
||||
with repo.lock():
|
||||
count = mutation.recordentries(repo, entries, skipexisting=False)
|
||||
repo.ui.write(_("wrote %s entries\n") % count)
|
@ -127,6 +127,7 @@ def createcommitentry(repo, node):
|
||||
|
||||
|
||||
def recordentries(repo, entries, skipexisting=True):
|
||||
count = 0
|
||||
with repo.transaction("record-mutation") as tr:
|
||||
unfi = repo.unfiltered()
|
||||
ms = repo._mutationstore
|
||||
@ -137,6 +138,8 @@ def recordentries(repo, entries, skipexisting=True):
|
||||
if succ in unfi or ms.has(succ):
|
||||
continue
|
||||
ms.add(entry)
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
def lookup(repo, node, extra=None):
|
||||
|
@ -112,6 +112,7 @@ Show debug commands if there are no other candidates
|
||||
debuglocks
|
||||
debugmergestate
|
||||
debugmutation
|
||||
debugmutationfromobsmarkers
|
||||
debugnamecomplete
|
||||
debugobsolete
|
||||
debugpathcomplete
|
||||
@ -306,6 +307,7 @@ Show all commands + options
|
||||
debuglocks: force-lock, force-wlock, force-undolog-lock, set-lock, set-wlock
|
||||
debugmergestate:
|
||||
debugmutation:
|
||||
debugmutationfromobsmarkers:
|
||||
debugnamecomplete:
|
||||
debugobsolete: flags, record-parents, rev, exclusive, index, delete, date, user, template
|
||||
debugpathcomplete: full, normal, added, removed
|
||||
|
@ -1004,6 +1004,8 @@ Test list of internal help commands
|
||||
print merge state
|
||||
debugmutation
|
||||
display the mutation history of a commit
|
||||
debugmutationfromobsmarkers
|
||||
convert obsolescence markers to mutation records
|
||||
debugnamecomplete
|
||||
complete "names" - tags, open branch names, bookmark names
|
||||
debugobsolete
|
||||
|
Loading…
Reference in New Issue
Block a user