in eden/scm/edenscm/mercurial/cmdutil.py [0:0]
def revert(ui, repo, ctx, parents, *pats, **opts):
parent, p2 = parents
node = ctx.node()
mf = ctx.manifest()
if node == p2:
parent = p2
# need all matching names in dirstate and manifest of target rev,
# so have to walk both. do not print errors if files exist in one
# but not other. in both cases, filesets should be evaluated against
# workingctx to get consistent result (issue4497). this means 'set:**'
# cannot be used to select missing files from target rev.
# `names` is a mapping for all elements in working copy and target revision
# The mapping is in the form:
# <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
names = {}
with repo.wlock():
## filling of the `names` mapping
# walk dirstate to fill `names`
interactive = opts.get("interactive", False)
wctx = repo[None]
m = scmutil.match(wctx, pats, opts)
# we'll need this later
badfiles = set()
def badfn(path, msg):
# We only report errors about paths that do not exist in the original
# node.
#
# For other errors we normally will successfully revert the file anyway.
# This includes situations where the file was replaced by an unsupported
# file type (e.g., a FIFO, socket, or device node.
if path not in ctx:
badfiles.add(path)
msg = _("no such file in rev %s") % short(node)
ui.warn("%s: %s\n" % (m.rel(path), msg))
changes = repo.status(node1=node, match=matchmod.badmatch(m, badfn))
for kind in changes:
for abs in kind:
names[abs] = m.rel(abs), m.exact(abs)
# Look for exact filename matches that were not returned in the results.
# These will not be returned if they are clean, but we want to include them
# to report them as not needing changes.
for abs in m.files():
if abs in names or abs in badfiles:
continue
# Check to see if this looks like a file or directory.
# We don't need to report directories in the clean list.
try:
st = repo.wvfs.lstat(abs)
if stat.S_ISDIR(st.st_mode):
continue
except OSError:
# This is can occur if the file was locally removed and is
# untracked, and did not exist in the node we are reverting from,
# but does exist in the current commit.
# Continue on and report this file as clean.
pass
names[abs] = m.rel(abs), m.exact(abs)
if abs in wctx:
changes.clean.append(abs)
else:
# We don't really know if this file is unknown or ignored, but
# fortunately this does not matter. Revert treats unknown and
# ignored exactly files the same.
changes.unknown.append(abs)
modified = set(changes.modified)
added = set(changes.added)
removed = set(changes.removed)
_deleted = set(changes.deleted)
unknown = set(changes.unknown)
unknown.update(changes.ignored)
clean = set(changes.clean)
modadded = set()
# We need to account for the state of the file in the dirstate,
# even when we revert against something else than parent. This will
# slightly alter the behavior of revert (doing back up or not, delete
# or just forget etc).
if parent == node:
dsmodified = modified
dsadded = added
dsremoved = removed
# store all local modifications, useful later for rename detection
localchanges = dsmodified | dsadded
modified, added, removed = set(), set(), set()
else:
exactfilesmatch = scmutil.matchfiles(repo, names)
changes = repo.status(node1=parent, match=exactfilesmatch)
dsmodified = set(changes.modified)
dsadded = set(changes.added)
dsremoved = set(changes.removed)
# store all local modifications, useful later for rename detection
localchanges = dsmodified | dsadded
# only take into account for removes between wc and target
clean |= dsremoved - removed
dsremoved &= removed
# distinct between dirstate remove and other
removed -= dsremoved
modadded = added & dsmodified
added -= modadded
# tell newly modified apart.
dsmodified &= modified
dsmodified |= modified & dsadded # dirstate added may need backup
modified -= dsmodified
# We need to wait for some post-processing to update this set
# before making the distinction. The dirstate will be used for
# that purpose.
dsadded = added
# in case of merge, files that are actually added can be reported as
# modified, we need to post process the result
if p2 != nullid:
mergeadd = set(dsmodified)
for path in dsmodified:
if path in mf:
mergeadd.remove(path)
dsadded |= mergeadd
dsmodified -= mergeadd
# if f is a rename, update `names` to also revert the source
cwd = repo.getcwd()
for f in localchanges:
src = repo.dirstate.copied(f)
# XXX should we check for rename down to target node?
if src and src not in names and repo.dirstate[src] == "r":
dsremoved.add(src)
names[src] = (repo.pathto(src, cwd), True)
# determine the exact nature of the deleted changesets
deladded = set(_deleted)
for path in _deleted:
if path in mf:
deladded.remove(path)
deleted = _deleted - deladded
# distinguish between file to forget and the other
added = set()
for abs in dsadded:
if repo.dirstate[abs] != "a":
added.add(abs)
dsadded -= added
for abs in deladded:
if repo.dirstate[abs] == "a":
dsadded.add(abs)
deladded -= dsadded
# For files marked as removed, we check if an unknown file is present at
# the same path. If a such file exists it may need to be backed up.
# Making the distinction at this stage helps have simpler backup
# logic.
removunk = set()
for abs in removed:
target = repo.wjoin(abs)
if os.path.lexists(target):
removunk.add(abs)
removed -= removunk
dsremovunk = set()
for abs in dsremoved:
target = repo.wjoin(abs)
if os.path.lexists(target):
dsremovunk.add(abs)
dsremoved -= dsremovunk
# action to be actually performed by revert
# (<list of file>, message>) tuple
actions = {
"revert": ([], _("reverting %s\n")),
"add": ([], _("adding %s\n")),
"remove": ([], _("removing %s\n")),
"drop": ([], _("removing %s\n")),
"forget": ([], _("forgetting %s\n")),
"undelete": ([], _("undeleting %s\n")),
"noop": (None, _("no changes needed to %s\n")),
"unknown": (None, _("file not managed: %s\n")),
}
# "constant" that convey the backup strategy.
# All set to `discard` if `no-backup` is set do avoid checking
# no_backup lower in the code.
# These values are ordered for comparison purposes
backupinteractive = 3 # do backup if interactively modified
backup = 2 # unconditionally do backup
check = 1 # check if the existing file differs from target
discard = 0 # never do backup
if opts.get("no_backup"):
backupinteractive = backup = check = discard
if interactive:
dsmodifiedbackup = backupinteractive
else:
dsmodifiedbackup = backup
tobackup = set()
backupanddel = actions["remove"]
if not opts.get("no_backup"):
backupanddel = actions["drop"]
disptable = (
# dispatch table:
# file state
# action
# make backup
## Sets that results that will change file on disk
# Modified compared to target, no local change
(modified, actions["revert"], discard),
# Modified compared to target, but local file is deleted
(deleted, actions["revert"], discard),
# Modified compared to target, local change
(dsmodified, actions["revert"], dsmodifiedbackup),
# Added since target
(added, actions["remove"], discard),
# Added in working directory
(dsadded, actions["forget"], discard),
# Added since target, have local modification
(modadded, backupanddel, backup),
# Added since target but file is missing in working directory
(deladded, actions["drop"], discard),
# Removed since target, before working copy parent
(removed, actions["add"], discard),
# Same as `removed` but an unknown file exists at the same path
(removunk, actions["add"], check),
# Removed since targe, marked as such in working copy parent
(dsremoved, actions["undelete"], discard),
# Same as `dsremoved` but an unknown file exists at the same path
(dsremovunk, actions["undelete"], check),
## the following sets does not result in any file changes
# File with no modification
(clean, actions["noop"], discard),
# Existing file, not tracked anywhere
(unknown, actions["unknown"], discard),
)
for abs, (rel, exact) in sorted(names.items()):
# target file to be touch on disk (relative to cwd)
target = repo.wjoin(abs)
# search the entry in the dispatch table.
# if the file is in any of these sets, it was touched in the working
# directory parent and we are sure it needs to be reverted.
for table, (xlist, msg), dobackup in disptable:
if abs not in table:
continue
if xlist is not None:
xlist.append(abs)
if dobackup:
# If in interactive mode, don't automatically create
# .orig files (issue4793)
if dobackup == backupinteractive:
tobackup.add(abs)
elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
bakname = scmutil.origpath(ui, repo, rel)
ui.note(
_("saving current version of %s as %s\n")
% (rel, bakname)
)
if not opts.get("dry_run"):
# Don't backup symlinks, since they can
# interfere with future backup paths that
# overlap with the symlink path (like
# accidentally trying to move something
# into the symlink).
if not os.path.islink(target):
if interactive:
util.copyfile(target, bakname)
else:
util.rename(target, bakname)
if ui.verbose or not exact:
if not isinstance(msg, str):
msg = msg(abs)
ui.status(msg % rel)
elif exact:
ui.warn(msg % rel)
break
if not opts.get("dry_run"):
needdata = ("revert", "add", "undelete")
_revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
_performrevert(
repo,
parents,
ctx,
actions,
interactive,
tobackup,
forcecopytracing=opts.get("forcecopytracing"),
)