--- a/mercurial/merge.py Sat Oct 05 10:29:34 2019 -0400
+++ b/mercurial/merge.py Sun Oct 06 09:45:02 2019 -0400
@@ -23,9 +23,7 @@
nullid,
nullrev,
)
-from .thirdparty import (
- attr,
-)
+from .thirdparty import attr
from . import (
copies,
encoding,
@@ -43,12 +41,14 @@
_pack = struct.pack
_unpack = struct.unpack
+
def _droponode(data):
# used for compatibility for v1
bits = data.split('\0')
bits = bits[:-2] + bits[-1:]
return '\0'.join(bits)
+
# Merge state record types. See ``mergestate`` docs for more.
RECORD_LOCAL = b'L'
RECORD_OTHER = b'O'
@@ -90,6 +90,7 @@
ACTION_EXEC = b'e'
ACTION_CREATED_MERGE = b'cm'
+
class mergestate(object):
'''track 3-way merge state of individual files
@@ -136,6 +137,7 @@
The resolve command transitions between 'u' and 'r' for conflicts and
'pu' and 'pr' for path conflicts.
'''
+
statepathv1 = 'merge/state'
statepathv2 = 'merge/state2'
@@ -209,15 +211,21 @@
bits = record.split('\0', 1)
mdstate = bits[1]
if len(mdstate) != 1 or mdstate not in (
- MERGE_DRIVER_STATE_UNMARKED, MERGE_DRIVER_STATE_MARKED,
- MERGE_DRIVER_STATE_SUCCESS):
+ MERGE_DRIVER_STATE_UNMARKED,
+ MERGE_DRIVER_STATE_MARKED,
+ MERGE_DRIVER_STATE_SUCCESS,
+ ):
# the merge driver should be idempotent, so just rerun it
mdstate = MERGE_DRIVER_STATE_UNMARKED
self._readmergedriver = bits[0]
self._mdstate = mdstate
- elif rtype in (RECORD_MERGED, RECORD_CHANGEDELETE_CONFLICT,
- RECORD_PATH_CONFLICT, RECORD_MERGE_DRIVER_MERGE):
+ elif rtype in (
+ RECORD_MERGED,
+ RECORD_CHANGEDELETE_CONFLICT,
+ RECORD_PATH_CONFLICT,
+ RECORD_MERGE_DRIVER_MERGE,
+ ):
bits = record.split('\0')
self._state[bits[0]] = bits[1:]
elif rtype == RECORD_FILE_VALUES:
@@ -276,7 +284,7 @@
return v1records
def _v1v2match(self, v1records, v2records):
- oldv2 = set() # old format version of v2 record
+ oldv2 = set() # old format version of v2 record
for rec in v2records:
if rec[0] == RECORD_LOCAL:
oldv2.add(rec)
@@ -336,11 +344,11 @@
off = 0
end = len(data)
while off < end:
- rtype = data[off:off + 1]
+ rtype = data[off : off + 1]
off += 1
- length = _unpack('>I', data[off:(off + 4)])[0]
+ length = _unpack('>I', data[off : (off + 4)])[0]
off += 4
- record = data[off:(off + length)]
+ record = data[off : (off + length)]
off += length
if rtype == RECORD_OVERRIDE:
rtype, record = record[0:1], record[1:]
@@ -362,11 +370,14 @@
# - B then continues the merge and the malicious merge driver
# gets invoked
configmergedriver = self._repo.ui.config('experimental', 'mergedriver')
- if (self._readmergedriver is not None
- and self._readmergedriver != configmergedriver):
+ if (
+ self._readmergedriver is not None
+ and self._readmergedriver != configmergedriver
+ ):
raise error.ConfigError(
_("merge driver changed since merge started"),
- hint=_("revert merge driver change or abort merge"))
+ hint=_("revert merge driver change or abort merge"),
+ )
return configmergedriver
@@ -392,9 +403,12 @@
"""
# Check local variables before looking at filesystem for performance
# reasons.
- return (bool(self._local) or bool(self._state) or
- self._repo.vfs.exists(self.statepathv1) or
- self._repo.vfs.exists(self.statepathv2))
+ return (
+ bool(self._local)
+ or bool(self._state)
+ or self._repo.vfs.exists(self.statepathv1)
+ or self._repo.vfs.exists(self.statepathv2)
+ )
def commit(self):
"""Write current state on disk (if necessary)"""
@@ -408,8 +422,12 @@
records.append((RECORD_LOCAL, hex(self._local)))
records.append((RECORD_OTHER, hex(self._other)))
if self.mergedriver:
- records.append((RECORD_MERGE_DRIVER_STATE, '\0'.join([
- self.mergedriver, self._mdstate])))
+ records.append(
+ (
+ RECORD_MERGE_DRIVER_STATE,
+ '\0'.join([self.mergedriver, self._mdstate]),
+ )
+ )
# Write out state items. In all cases, the value of the state map entry
# is written as the contents of the record. The record type depends on
# the type of state that is stored, and capital-letter records are used
@@ -418,30 +436,36 @@
for filename, v in self._state.iteritems():
if v[0] == MERGE_RECORD_DRIVER_RESOLVED:
# Driver-resolved merge. These are stored in 'D' records.
- records.append((RECORD_MERGE_DRIVER_MERGE,
- '\0'.join([filename] + v)))
- elif v[0] in (MERGE_RECORD_UNRESOLVED_PATH,
- MERGE_RECORD_RESOLVED_PATH):
+ records.append(
+ (RECORD_MERGE_DRIVER_MERGE, '\0'.join([filename] + v))
+ )
+ elif v[0] in (
+ MERGE_RECORD_UNRESOLVED_PATH,
+ MERGE_RECORD_RESOLVED_PATH,
+ ):
# Path conflicts. These are stored in 'P' records. The current
# resolution state ('pu' or 'pr') is stored within the record.
- records.append((RECORD_PATH_CONFLICT,
- '\0'.join([filename] + v)))
+ records.append(
+ (RECORD_PATH_CONFLICT, '\0'.join([filename] + v))
+ )
elif v[1] == nullhex or v[6] == nullhex:
# Change/Delete or Delete/Change conflicts. These are stored in
# 'C' records. v[1] is the local file, and is nullhex when the
# file is deleted locally ('dc'). v[6] is the remote file, and
# is nullhex when the file is deleted remotely ('cd').
- records.append((RECORD_CHANGEDELETE_CONFLICT,
- '\0'.join([filename] + v)))
+ records.append(
+ (RECORD_CHANGEDELETE_CONFLICT, '\0'.join([filename] + v))
+ )
else:
# Normal files. These are stored in 'F' records.
- records.append((RECORD_MERGED,
- '\0'.join([filename] + v)))
+ records.append((RECORD_MERGED, '\0'.join([filename] + v)))
for filename, extras in sorted(self._stateextras.iteritems()):
- rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
- extras.iteritems())
- records.append((RECORD_FILE_VALUES,
- '%s\0%s' % (filename, rawextras)))
+ rawextras = '\0'.join(
+ '%s\0%s' % (k, v) for k, v in extras.iteritems()
+ )
+ records.append(
+ (RECORD_FILE_VALUES, '%s\0%s' % (filename, rawextras))
+ )
if self._labels is not None:
labels = '\0'.join(self._labels)
records.append((RECORD_LABELS, labels))
@@ -500,10 +524,16 @@
else:
localkey = mergestate.getlocalkey(fcl.path())
self._repo.vfs.write('merge/' + localkey, fcl.data())
- self._state[fd] = [MERGE_RECORD_UNRESOLVED, localkey, fcl.path(),
- fca.path(), hex(fca.filenode()),
- fco.path(), hex(fco.filenode()),
- fcl.flags()]
+ self._state[fd] = [
+ MERGE_RECORD_UNRESOLVED,
+ localkey,
+ fcl.path(),
+ fca.path(),
+ hex(fca.filenode()),
+ fco.path(),
+ hex(fco.filenode()),
+ fcl.flags(),
+ ]
self._stateextras[fd] = {'ancestorlinknode': hex(fca.node())}
self._dirty = True
@@ -539,8 +569,10 @@
"""Obtain the paths of unresolved files."""
for f, entry in self._state.iteritems():
- if entry[0] in (MERGE_RECORD_UNRESOLVED,
- MERGE_RECORD_UNRESOLVED_PATH):
+ if entry[0] in (
+ MERGE_RECORD_UNRESOLVED,
+ MERGE_RECORD_UNRESOLVED_PATH,
+ ):
yield f
def driverresolved(self):
@@ -555,8 +587,7 @@
def _resolve(self, preresolve, dfile, wctx):
"""rerun merge process for file path `dfile`"""
- if self[dfile] in (MERGE_RECORD_RESOLVED,
- MERGE_RECORD_DRIVER_RESOLVED):
+ if self[dfile] in (MERGE_RECORD_RESOLVED, MERGE_RECORD_DRIVER_RESOLVED):
return True, 0
stateentry = self._state[dfile]
state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
@@ -578,9 +609,12 @@
if fca.node() == nullid and flags != flo:
if preresolve:
self._repo.ui.warn(
- _('warning: cannot merge flags for %s '
- 'without common ancestor - keeping local flags\n')
- % afile)
+ _(
+ 'warning: cannot merge flags for %s '
+ 'without common ancestor - keeping local flags\n'
+ )
+ % afile
+ )
elif flags == fla:
flags = flo
if preresolve:
@@ -591,15 +625,27 @@
f.close()
else:
wctx[dfile].remove(ignoremissing=True)
- complete, r, deleted = filemerge.premerge(self._repo, wctx,
- self._local, lfile, fcd,
- fco, fca,
- labels=self._labels)
+ complete, r, deleted = filemerge.premerge(
+ self._repo,
+ wctx,
+ self._local,
+ lfile,
+ fcd,
+ fco,
+ fca,
+ labels=self._labels,
+ )
else:
- complete, r, deleted = filemerge.filemerge(self._repo, wctx,
- self._local, lfile, fcd,
- fco, fca,
- labels=self._labels)
+ complete, r, deleted = filemerge.filemerge(
+ self._repo,
+ wctx,
+ self._local,
+ lfile,
+ fcd,
+ fco,
+ fca,
+ labels=self._labels,
+ )
if r is None:
# no real conflict
del self._state[dfile]
@@ -619,9 +665,9 @@
# cd: remote picked (or otherwise deleted)
action = ACTION_REMOVE
else:
- if fcd.isabsent(): # dc: remote picked
+ if fcd.isabsent(): # dc: remote picked
action = ACTION_GET
- elif fco.isabsent(): # cd: local picked
+ elif fco.isabsent(): # cd: local picked
if dfile in self.localctx:
action = ACTION_ADD_MODIFIED
else:
@@ -704,16 +750,19 @@
Meant for use by custom merge drivers."""
self._results[f] = 0, ACTION_GET
+
def _getcheckunknownconfig(repo, section, name):
config = repo.ui.config(section, name)
valid = ['abort', 'ignore', 'warn']
if config not in valid:
validstr = ', '.join(["'" + v + "'" for v in valid])
- raise error.ConfigError(_("%s.%s not valid "
- "('%s' is none of %s)")
- % (section, name, config, validstr))
+ raise error.ConfigError(
+ _("%s.%s not valid " "('%s' is none of %s)")
+ % (section, name, config, validstr)
+ )
return config
+
def _checkunknownfile(repo, wctx, mctx, f, f2=None):
if wctx.isinmemory():
# Nothing to do in IMM because nothing in the "working copy" can be an
@@ -725,10 +774,13 @@
if f2 is None:
f2 = f
- return (repo.wvfs.audit.check(f)
+ return (
+ repo.wvfs.audit.check(f)
and repo.wvfs.isfileorlink(f)
and repo.dirstate.normalize(f) not in repo.dirstate
- and mctx[f2].cmp(wctx[f]))
+ and mctx[f2].cmp(wctx[f])
+ )
+
class _unknowndirschecker(object):
"""
@@ -740,6 +792,7 @@
Returns the shortest path at which a conflict occurs, or None if there is
no conflict.
"""
+
def __init__(self):
# A set of paths known to be good. This prevents repeated checking of
# dirs. It will be updated with any new dirs that are checked and found
@@ -763,8 +816,10 @@
if p in self._unknowndircache:
continue
if repo.wvfs.audit.check(p):
- if (repo.wvfs.isfileorlink(p)
- and repo.dirstate.normalize(p) not in repo.dirstate):
+ if (
+ repo.wvfs.isfileorlink(p)
+ and repo.dirstate.normalize(p) not in repo.dirstate
+ ):
return p
if not repo.wvfs.lexists(p):
self._missingdircache.add(p)
@@ -782,6 +837,7 @@
return f
return None
+
def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
"""
Considers any actions that care about the presence of conflicting unknown
@@ -796,6 +852,7 @@
ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
pathconfig = repo.ui.configbool('experimental', 'merge.checkpathconflicts')
if not force:
+
def collectconflicts(conflicts, config):
if config == 'abort':
abortconflicts.update(conflicts)
@@ -816,8 +873,7 @@
fileconflicts.add(f)
allconflicts = fileconflicts | pathconflicts
- ignoredconflicts = {c for c in allconflicts
- if repo.dirstate._ignore(c)}
+ ignoredconflicts = {c for c in allconflicts if repo.dirstate._ignore(c)}
unknownconflicts = allconflicts - ignoredconflicts
collectconflicts(ignoredconflicts, ignoredconfig)
collectconflicts(unknownconflicts, unknownconfig)
@@ -846,8 +902,11 @@
if not different:
actions[f] = (ACTION_GET, (fl2, False), 'remote created')
elif mergeforce or config == 'abort':
- actions[f] = (ACTION_MERGE, (f, f, None, False, anc),
- 'remote differs from untracked local')
+ actions[f] = (
+ ACTION_MERGE,
+ (f, f, None, False, anc),
+ 'remote differs from untracked local',
+ )
elif config == 'abort':
abortconflicts.add(f)
else:
@@ -865,8 +924,12 @@
else:
warn(_("%s: untracked file differs\n") % f)
if abortconflicts:
- raise error.Abort(_("untracked files in working directory "
- "differ from files in requested revision"))
+ raise error.Abort(
+ _(
+ "untracked files in working directory "
+ "differ from files in requested revision"
+ )
+ )
for f in sorted(warnconflicts):
if repo.wvfs.isfileorlink(f):
@@ -876,11 +939,15 @@
for f, (m, args, msg) in actions.iteritems():
if m == ACTION_CREATED:
- backup = (f in fileconflicts or f in pathconflicts or
- any(p in pathconflicts for p in util.finddirs(f)))
- flags, = args
+ backup = (
+ f in fileconflicts
+ or f in pathconflicts
+ or any(p in pathconflicts for p in util.finddirs(f))
+ )
+ (flags,) = args
actions[f] = (ACTION_GET, (flags, backup), msg)
+
def _forgetremoved(wctx, mctx, branchmerge):
"""
Forget removed files
@@ -911,6 +978,7 @@
return actions
+
def _checkcollision(repo, wmf, actions):
"""
Check for case-folding collisions.
@@ -934,8 +1002,14 @@
if actions:
# KEEP and EXEC are no-op
- for m in (ACTION_ADD, ACTION_ADD_MODIFIED, ACTION_FORGET, ACTION_GET,
- ACTION_CHANGED_DELETED, ACTION_DELETED_CHANGED):
+ for m in (
+ ACTION_ADD,
+ ACTION_ADD_MODIFIED,
+ ACTION_FORGET,
+ ACTION_GET,
+ ACTION_CHANGED_DELETED,
+ ACTION_DELETED_CHANGED,
+ ):
for f, args, msg in actions[m]:
pmmf.add(f)
for f, args, msg in actions[ACTION_REMOVE]:
@@ -957,8 +1031,10 @@
for f in pmmf:
fold = util.normcase(f)
if fold in foldmap:
- raise error.Abort(_("case-folding collision between %s and %s")
- % (f, foldmap[fold]))
+ raise error.Abort(
+ _("case-folding collision between %s and %s")
+ % (f, foldmap[fold])
+ )
foldmap[fold] = f
# check case-folding of directories
@@ -966,24 +1042,29 @@
for fold, f in sorted(foldmap.items()):
if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
# the folded prefix matches but actual casing is different
- raise error.Abort(_("case-folding collision between "
- "%s and directory of %s") % (lastfull, f))
+ raise error.Abort(
+ _("case-folding collision between " "%s and directory of %s")
+ % (lastfull, f)
+ )
foldprefix = fold + '/'
unfoldprefix = f + '/'
lastfull = f
+
def driverpreprocess(repo, ms, wctx, labels=None):
"""run the preprocess step of the merge driver, if any
This is currently not implemented -- it's an extension point."""
return True
+
def driverconclude(repo, ms, wctx, labels=None):
"""run the conclude step of the merge driver, if any
This is currently not implemented -- it's an extension point."""
return True
+
def _filesindirs(repo, manifest, dirs):
"""
Generator that yields pairs of all the files in the manifest that are found
@@ -996,6 +1077,7 @@
yield f, p
break
+
def checkpathconflicts(repo, wctx, mctx, actions):
"""
Check if any actions introduce path conflicts in the repository, updating
@@ -1022,8 +1104,12 @@
deletedfiles = set()
for f, (m, args, msg) in actions.items():
- if m in (ACTION_CREATED, ACTION_DELETED_CHANGED, ACTION_MERGE,
- ACTION_CREATED_MERGE):
+ if m in (
+ ACTION_CREATED,
+ ACTION_DELETED_CHANGED,
+ ACTION_MERGE,
+ ACTION_CREATED_MERGE,
+ ):
# This action may create a new local file.
createdfiledirs.update(util.finddirs(f))
if mf.hasdir(f):
@@ -1054,10 +1140,12 @@
# A file is in a directory which aliases a local file.
# We will need to rename the local file.
localconflicts.add(p)
- if p in actions and actions[p][0] in (ACTION_CREATED,
- ACTION_DELETED_CHANGED,
- ACTION_MERGE,
- ACTION_CREATED_MERGE):
+ if p in actions and actions[p][0] in (
+ ACTION_CREATED,
+ ACTION_DELETED_CHANGED,
+ ACTION_MERGE,
+ ACTION_CREATED_MERGE,
+ ):
# The file is in a directory which aliases a remote file.
# This is an internal inconsistency within the remote
# manifest.
@@ -1068,10 +1156,12 @@
if p not in deletedfiles:
ctxname = bytes(wctx).rstrip('+')
pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
- actions[pnew] = (ACTION_PATH_CONFLICT_RESOLVE, (p,),
- 'local path conflict')
- actions[p] = (ACTION_PATH_CONFLICT, (pnew, 'l'),
- 'path conflict')
+ actions[pnew] = (
+ ACTION_PATH_CONFLICT_RESOLVE,
+ (p,),
+ 'local path conflict',
+ )
+ actions[p] = (ACTION_PATH_CONFLICT, (pnew, 'l'), 'path conflict')
if remoteconflicts:
# Check if all files in the conflicting directories have been removed.
@@ -1086,10 +1176,16 @@
else:
# Action was create, change to renamed get action.
fl = args[0]
- actions[pnew] = (ACTION_LOCAL_DIR_RENAME_GET, (p, fl),
- 'remote path conflict')
- actions[p] = (ACTION_PATH_CONFLICT, (pnew, ACTION_REMOVE),
- 'path conflict')
+ actions[pnew] = (
+ ACTION_LOCAL_DIR_RENAME_GET,
+ (p, fl),
+ 'remote path conflict',
+ )
+ actions[p] = (
+ ACTION_PATH_CONFLICT,
+ (pnew, ACTION_REMOVE),
+ 'path conflict',
+ )
remoteconflicts.remove(p)
break
@@ -1098,6 +1194,7 @@
repo.ui.warn(_("%s: is both a file and a directory\n") % p)
raise error.Abort(_("destination manifest contains path conflicts"))
+
def _filternarrowactions(narrowmatch, branchmerge, actions):
"""
Filters out actions that can ignored because the repo is narrowed.
@@ -1105,7 +1202,7 @@
Raise an exception if the merge cannot be completed because the repo is
narrowed.
"""
- nooptypes = {'k'} # TODO: handle with nonconflicttypes
+ nooptypes = {'k'} # TODO: handle with nonconflicttypes
nonconflicttypes = set('a am c cm f g r e'.split())
# We mutate the items in the dict during iteration, so iterate
# over a copy.
@@ -1113,20 +1210,36 @@
if narrowmatch(f):
pass
elif not branchmerge:
- del actions[f] # just updating, ignore changes outside clone
+ del actions[f] # just updating, ignore changes outside clone
elif action[0] in nooptypes:
- del actions[f] # merge does not affect file
+ del actions[f] # merge does not affect file
elif action[0] in nonconflicttypes:
- raise error.Abort(_('merge affects file \'%s\' outside narrow, '
- 'which is not yet supported') % f,
- hint=_('merging in the other direction '
- 'may work'))
+ raise error.Abort(
+ _(
+ 'merge affects file \'%s\' outside narrow, '
+ 'which is not yet supported'
+ )
+ % f,
+ hint=_('merging in the other direction ' 'may work'),
+ )
else:
- raise error.Abort(_('conflict in file \'%s\' is outside '
- 'narrow clone') % f)
+ raise error.Abort(
+ _('conflict in file \'%s\' is outside ' 'narrow clone') % f
+ )
+
-def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
- acceptremote, followcopies, forcefulldiff=False):
+def manifestmerge(
+ repo,
+ wctx,
+ p2,
+ pa,
+ branchmerge,
+ force,
+ matcher,
+ acceptremote,
+ followcopies,
+ forcefulldiff=False,
+):
"""
Merge wctx and p2 with ancestor pa and generate merge action list
@@ -1140,8 +1253,10 @@
copy, movewithdir, diverge, renamedelete, dirmove = {}, {}, {}, {}, {}
# manifests fetched in order are going to be faster, so prime the caches
- [x.manifest() for x in
- sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)]
+ [
+ x.manifest()
+ for x in sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)
+ ]
if followcopies:
ret = copies.mergecopies(repo, wctx, p2, pa)
@@ -1151,8 +1266,9 @@
boolf = pycompat.bytestr(bool(force))
boolm = pycompat.bytestr(bool(matcher))
repo.ui.note(_("resolving manifests\n"))
- repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
- % (boolbm, boolf, boolm))
+ repo.ui.debug(
+ " branchmerge: %s, force: %s, partial: %s\n" % (boolbm, boolf, boolm)
+ )
repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
@@ -1170,7 +1286,7 @@
# - ma is the same as m1 or m2, which we're just going to diff again later
# - The caller specifically asks for a full diff, which is useful during bid
# merge.
- if (pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff):
+ if pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff:
# Identify which files are relevant to the merge, so we can limit the
# total m1-vs-m2 diff to just those files. This has significant
# performance benefits in large repositories.
@@ -1189,57 +1305,80 @@
actions = {}
for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
- if n1 and n2: # file exists on both local and remote side
+ if n1 and n2: # file exists on both local and remote side
if f not in ma:
fa = copy.get(f, None)
if fa is not None:
- actions[f] = (ACTION_MERGE, (f, f, fa, False, pa.node()),
- 'both renamed from %s' % fa)
+ actions[f] = (
+ ACTION_MERGE,
+ (f, f, fa, False, pa.node()),
+ 'both renamed from %s' % fa,
+ )
else:
- actions[f] = (ACTION_MERGE, (f, f, None, False, pa.node()),
- 'both created')
+ actions[f] = (
+ ACTION_MERGE,
+ (f, f, None, False, pa.node()),
+ 'both created',
+ )
else:
a = ma[f]
fla = ma.flags(f)
nol = 'l' not in fl1 + fl2 + fla
if n2 == a and fl2 == fla:
actions[f] = (ACTION_KEEP, (), 'remote unchanged')
- elif n1 == a and fl1 == fla: # local unchanged - use remote
- if n1 == n2: # optimization: keep local content
+ elif n1 == a and fl1 == fla: # local unchanged - use remote
+ if n1 == n2: # optimization: keep local content
actions[f] = (ACTION_EXEC, (fl2,), 'update permissions')
else:
- actions[f] = (ACTION_GET, (fl2, False),
- 'remote is newer')
- elif nol and n2 == a: # remote only changed 'x'
+ actions[f] = (
+ ACTION_GET,
+ (fl2, False),
+ 'remote is newer',
+ )
+ elif nol and n2 == a: # remote only changed 'x'
actions[f] = (ACTION_EXEC, (fl2,), 'update permissions')
- elif nol and n1 == a: # local only changed 'x'
+ elif nol and n1 == a: # local only changed 'x'
actions[f] = (ACTION_GET, (fl1, False), 'remote is newer')
- else: # both changed something
- actions[f] = (ACTION_MERGE, (f, f, f, False, pa.node()),
- 'versions differ')
- elif n1: # file exists only on local side
+ else: # both changed something
+ actions[f] = (
+ ACTION_MERGE,
+ (f, f, f, False, pa.node()),
+ 'versions differ',
+ )
+ elif n1: # file exists only on local side
if f in copied:
- pass # we'll deal with it on m2 side
- elif f in movewithdir: # directory rename, move local
+ pass # we'll deal with it on m2 side
+ elif f in movewithdir: # directory rename, move local
f2 = movewithdir[f]
if f2 in m2:
- actions[f2] = (ACTION_MERGE, (f, f2, None, True, pa.node()),
- 'remote directory rename, both created')
+ actions[f2] = (
+ ACTION_MERGE,
+ (f, f2, None, True, pa.node()),
+ 'remote directory rename, both created',
+ )
else:
- actions[f2] = (ACTION_DIR_RENAME_MOVE_LOCAL, (f, fl1),
- 'remote directory rename - move from %s' % f)
+ actions[f2] = (
+ ACTION_DIR_RENAME_MOVE_LOCAL,
+ (f, fl1),
+ 'remote directory rename - move from %s' % f,
+ )
elif f in copy:
f2 = copy[f]
- actions[f] = (ACTION_MERGE, (f, f2, f2, False, pa.node()),
- 'local copied/moved from %s' % f2)
- elif f in ma: # clean, a different, no remote
+ actions[f] = (
+ ACTION_MERGE,
+ (f, f2, f2, False, pa.node()),
+ 'local copied/moved from %s' % f2,
+ )
+ elif f in ma: # clean, a different, no remote
if n1 != ma[f]:
if acceptremote:
actions[f] = (ACTION_REMOVE, None, 'remote delete')
else:
- actions[f] = (ACTION_CHANGED_DELETED,
- (f, None, f, False, pa.node()),
- 'prompt changed/deleted')
+ actions[f] = (
+ ACTION_CHANGED_DELETED,
+ (f, None, f, False, pa.node()),
+ 'prompt changed/deleted',
+ )
elif n1 == addednodeid:
# This extra 'a' is added by working copy manifest to mark
# the file as locally added. We should forget it instead of
@@ -1247,26 +1386,37 @@
actions[f] = (ACTION_FORGET, None, 'remote deleted')
else:
actions[f] = (ACTION_REMOVE, None, 'other deleted')
- elif n2: # file exists only on remote side
+ elif n2: # file exists only on remote side
if f in copied:
- pass # we'll deal with it on m1 side
+ pass # we'll deal with it on m1 side
elif f in movewithdir:
f2 = movewithdir[f]
if f2 in m1:
- actions[f2] = (ACTION_MERGE,
- (f2, f, None, False, pa.node()),
- 'local directory rename, both created')
+ actions[f2] = (
+ ACTION_MERGE,
+ (f2, f, None, False, pa.node()),
+ 'local directory rename, both created',
+ )
else:
- actions[f2] = (ACTION_LOCAL_DIR_RENAME_GET, (f, fl2),
- 'local directory rename - get from %s' % f)
+ actions[f2] = (
+ ACTION_LOCAL_DIR_RENAME_GET,
+ (f, fl2),
+ 'local directory rename - get from %s' % f,
+ )
elif f in copy:
f2 = copy[f]
if f2 in m2:
- actions[f] = (ACTION_MERGE, (f2, f, f2, False, pa.node()),
- 'remote copied from %s' % f2)
+ actions[f] = (
+ ACTION_MERGE,
+ (f2, f, f2, False, pa.node()),
+ 'remote copied from %s' % f2,
+ )
else:
- actions[f] = (ACTION_MERGE, (f2, f, f2, True, pa.node()),
- 'remote moved from %s' % f2)
+ actions[f] = (
+ ACTION_MERGE,
+ (f2, f, f2, True, pa.node()),
+ 'remote moved from %s' % f2,
+ )
elif f not in ma:
# local unknown, remote created: the logic is described by the
# following table:
@@ -1284,25 +1434,32 @@
elif not branchmerge:
actions[f] = (ACTION_CREATED, (fl2,), 'remote created')
else:
- actions[f] = (ACTION_CREATED_MERGE, (fl2, pa.node()),
- 'remote created, get or merge')
+ actions[f] = (
+ ACTION_CREATED_MERGE,
+ (fl2, pa.node()),
+ 'remote created, get or merge',
+ )
elif n2 != ma[f]:
df = None
for d in dirmove:
if f.startswith(d):
# new file added in a directory that was moved
- df = dirmove[d] + f[len(d):]
+ df = dirmove[d] + f[len(d) :]
break
if df is not None and df in m1:
- actions[df] = (ACTION_MERGE, (df, f, f, False, pa.node()),
- 'local directory rename - respect move '
- 'from %s' % f)
+ actions[df] = (
+ ACTION_MERGE,
+ (df, f, f, False, pa.node()),
+ 'local directory rename - respect move ' 'from %s' % f,
+ )
elif acceptremote:
actions[f] = (ACTION_CREATED, (fl2,), 'remote recreating')
else:
- actions[f] = (ACTION_DELETED_CHANGED,
- (None, f, f, False, pa.node()),
- 'prompt deleted/changed')
+ actions[f] = (
+ ACTION_DELETED_CHANGED,
+ (None, f, f, False, pa.node()),
+ 'prompt deleted/changed',
+ )
if repo.ui.configbool('experimental', 'merge.checkpathconflicts'):
# If we are merging, look for path conflicts.
@@ -1315,48 +1472,88 @@
return actions, diverge, renamedelete
+
def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
"""Resolves false conflicts where the nodeid changed but the content
remained the same."""
# We force a copy of actions.items() because we're going to mutate
# actions as we resolve trivial conflicts.
for f, (m, args, msg) in list(actions.items()):
- if (m == ACTION_CHANGED_DELETED and f in ancestor
- and not wctx[f].cmp(ancestor[f])):
+ if (
+ m == ACTION_CHANGED_DELETED
+ and f in ancestor
+ and not wctx[f].cmp(ancestor[f])
+ ):
# local did change but ended up with same content
actions[f] = ACTION_REMOVE, None, 'prompt same'
- elif (m == ACTION_DELETED_CHANGED and f in ancestor
- and not mctx[f].cmp(ancestor[f])):
+ elif (
+ m == ACTION_DELETED_CHANGED
+ and f in ancestor
+ and not mctx[f].cmp(ancestor[f])
+ ):
# remote did change but ended up with same content
- del actions[f] # don't get = keep local deleted
+ del actions[f] # don't get = keep local deleted
+
-def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
- acceptremote, followcopies, matcher=None,
- mergeforce=False):
+def calculateupdates(
+ repo,
+ wctx,
+ mctx,
+ ancestors,
+ branchmerge,
+ force,
+ acceptremote,
+ followcopies,
+ matcher=None,
+ mergeforce=False,
+):
"""Calculate the actions needed to merge mctx into wctx using ancestors"""
# Avoid cycle.
from . import sparse
- if len(ancestors) == 1: # default
+ if len(ancestors) == 1: # default
actions, diverge, renamedelete = manifestmerge(
- repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
- acceptremote, followcopies)
+ repo,
+ wctx,
+ mctx,
+ ancestors[0],
+ branchmerge,
+ force,
+ matcher,
+ acceptremote,
+ followcopies,
+ )
_checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
- else: # only when merge.preferancestor=* - the default
+ else: # only when merge.preferancestor=* - the default
repo.ui.note(
- _("note: merging %s and %s using bids from ancestors %s\n") %
- (wctx, mctx, _(' and ').join(pycompat.bytestr(anc)
- for anc in ancestors)))
+ _("note: merging %s and %s using bids from ancestors %s\n")
+ % (
+ wctx,
+ mctx,
+ _(' and ').join(pycompat.bytestr(anc) for anc in ancestors),
+ )
+ )
# Call for bids
- fbids = {} # mapping filename to bids (action method to list af actions)
+ fbids = (
+ {}
+ ) # mapping filename to bids (action method to list af actions)
diverge, renamedelete = None, None
for ancestor in ancestors:
repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
actions, diverge1, renamedelete1 = manifestmerge(
- repo, wctx, mctx, ancestor, branchmerge, force, matcher,
- acceptremote, followcopies, forcefulldiff=True)
+ repo,
+ wctx,
+ mctx,
+ ancestor,
+ branchmerge,
+ force,
+ matcher,
+ acceptremote,
+ followcopies,
+ forcefulldiff=True,
+ )
_checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
# Track the shortest set of warning on the theory that bid
@@ -1384,9 +1581,9 @@
for f, bids in sorted(fbids.items()):
# bids is a mapping from action method to list af actions
# Consensus?
- if len(bids) == 1: # all bids are the same kind of method
+ if len(bids) == 1: # all bids are the same kind of method
m, l = list(bids.items())[0]
- if all(a == l[0] for a in l[1:]): # len(bids) is > 1
+ if all(a == l[0] for a in l[1:]): # len(bids) is > 1
repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
actions[f] = l[0]
continue
@@ -1410,8 +1607,9 @@
repo.ui.note(' %s -> %s\n' % (msg, m))
# Pick random action. TODO: Instead, prompt user when resolving
m, l = list(bids.items())[0]
- repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
- (f, m))
+ repo.ui.warn(
+ _(' %s: ambiguous merge - picked %s action\n') % (f, m)
+ )
actions[f] = l[0]
continue
repo.ui.note(_('end of auction\n\n'))
@@ -1420,12 +1618,14 @@
fractions = _forgetremoved(wctx, mctx, branchmerge)
actions.update(fractions)
- prunedactions = sparse.filterupdatesactions(repo, wctx, mctx, branchmerge,
- actions)
+ prunedactions = sparse.filterupdatesactions(
+ repo, wctx, mctx, branchmerge, actions
+ )
_resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
return prunedactions, diverge, renamedelete
+
def _getcwd():
try:
return encoding.getcwd()
@@ -1434,6 +1634,7 @@
return None
raise
+
def batchremove(repo, wctx, actions):
"""apply removes to the working directory
@@ -1450,8 +1651,9 @@
try:
wctx[f].remove(ignoremissing=True)
except OSError as inst:
- repo.ui.warn(_("update failed to remove %s: %s!\n") %
- (f, inst.strerror))
+ repo.ui.warn(
+ _("update failed to remove %s: %s!\n") % (f, inst.strerror)
+ )
if i == 100:
yield i, f
i = 0
@@ -1462,8 +1664,14 @@
if cwd and not _getcwd():
# cwd was removed in the course of removing files; print a helpful
# warning.
- repo.ui.warn(_("current directory was removed\n"
- "(consider changing to repo root: %s)\n") % repo.root)
+ repo.ui.warn(
+ _(
+ "current directory was removed\n"
+ "(consider changing to repo root: %s)\n"
+ )
+ % repo.root
+ )
+
def batchget(repo, mctx, wctx, wantfiledata, actions):
"""apply gets to the working directory
@@ -1502,14 +1710,17 @@
wfctx = wctx[f]
wfctx.clearunknown()
atomictemp = ui.configbool("experimental", "update.atomic-file")
- size = wfctx.write(fctx(f).data(), flags,
- backgroundclose=True,
- atomictemp=atomictemp)
+ size = wfctx.write(
+ fctx(f).data(),
+ flags,
+ backgroundclose=True,
+ atomictemp=atomictemp,
+ )
if wantfiledata:
s = wfctx.lstat()
mode = s.st_mode
mtime = s[stat.ST_MTIME]
- filedata[f] = ((mode, size, mtime)) # for dirstate.normal
+ filedata[f] = (mode, size, mtime) # for dirstate.normal
if i == 100:
yield False, (i, f)
i = 0
@@ -1518,6 +1729,7 @@
yield False, (i, f)
yield True, filedata
+
def _prefetchfiles(repo, ctx, actions):
"""Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
of merge actions. ``ctx`` is the context being merged in."""
@@ -1525,13 +1737,23 @@
# Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
# don't touch the context to be merged in. 'cd' is skipped, because
# changed/deleted never resolves to something from the remote side.
- oplist = [actions[a] for a in (ACTION_GET, ACTION_DELETED_CHANGED,
- ACTION_LOCAL_DIR_RENAME_GET, ACTION_MERGE)]
+ oplist = [
+ actions[a]
+ for a in (
+ ACTION_GET,
+ ACTION_DELETED_CHANGED,
+ ACTION_LOCAL_DIR_RENAME_GET,
+ ACTION_MERGE,
+ )
+ ]
prefetch = scmutil.prefetchfiles
matchfiles = scmutil.matchfiles
- prefetch(repo, [ctx.rev()],
- matchfiles(repo,
- [f for sublist in oplist for f, args, msg in sublist]))
+ prefetch(
+ repo,
+ [ctx.rev()],
+ matchfiles(repo, [f for sublist in oplist for f, args, msg in sublist]),
+ )
+
@attr.s(frozen=True)
class updateresult(object):
@@ -1541,30 +1763,40 @@
unresolvedcount = attr.ib()
def isempty(self):
- return not (self.updatedcount or self.mergedcount
- or self.removedcount or self.unresolvedcount)
+ return not (
+ self.updatedcount
+ or self.mergedcount
+ or self.removedcount
+ or self.unresolvedcount
+ )
+
def emptyactions():
"""create an actions dict, to be populated and passed to applyupdates()"""
- return dict((m, [])
- for m in (
- ACTION_ADD,
- ACTION_ADD_MODIFIED,
- ACTION_FORGET,
- ACTION_GET,
- ACTION_CHANGED_DELETED,
- ACTION_DELETED_CHANGED,
- ACTION_REMOVE,
- ACTION_DIR_RENAME_MOVE_LOCAL,
- ACTION_LOCAL_DIR_RENAME_GET,
- ACTION_MERGE,
- ACTION_EXEC,
- ACTION_KEEP,
- ACTION_PATH_CONFLICT,
- ACTION_PATH_CONFLICT_RESOLVE))
+ return dict(
+ (m, [])
+ for m in (
+ ACTION_ADD,
+ ACTION_ADD_MODIFIED,
+ ACTION_FORGET,
+ ACTION_GET,
+ ACTION_CHANGED_DELETED,
+ ACTION_DELETED_CHANGED,
+ ACTION_REMOVE,
+ ACTION_DIR_RENAME_MOVE_LOCAL,
+ ACTION_LOCAL_DIR_RENAME_GET,
+ ACTION_MERGE,
+ ACTION_EXEC,
+ ACTION_KEEP,
+ ACTION_PATH_CONFLICT,
+ ACTION_PATH_CONFLICT_RESOLVE,
+ )
+ )
-def applyupdates(repo, actions, wctx, mctx, overwrite, wantfiledata,
- labels=None):
+
+def applyupdates(
+ repo, actions, wctx, mctx, overwrite, wantfiledata, labels=None
+):
"""apply the merge action list to the working directory
wctx is the working copy context
@@ -1590,7 +1822,7 @@
mergeactions.extend(actions[ACTION_MERGE])
for f, args, msg in mergeactions:
f1, f2, fa, move, anc = args
- if f == '.hgsubstate': # merged internally
+ if f == '.hgsubstate': # merged internally
continue
if f1 is None:
fcl = filemerge.absentfilectx(wctx, fa)
@@ -1618,10 +1850,10 @@
wctx[f].audit()
wctx[f].remove()
- numupdates = sum(len(l) for m, l in actions.items()
- if m != ACTION_KEEP)
- progress = repo.ui.makeprogress(_('updating'), unit=_('files'),
- total=numupdates)
+ numupdates = sum(len(l) for m, l in actions.items() if m != ACTION_KEEP)
+ progress = repo.ui.makeprogress(
+ _('updating'), unit=_('files'), total=numupdates
+ )
if [a for a in actions[ACTION_REMOVE] if a[0] == '.hgsubstate']:
subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
@@ -1630,8 +1862,13 @@
for f, args, msg in actions[ACTION_PATH_CONFLICT]:
f1, fo = args
s = repo.ui.status
- s(_("%s: path conflict - a file or link has the same name as a "
- "directory\n") % f)
+ s(
+ _(
+ "%s: path conflict - a file or link has the same name as a "
+ "directory\n"
+ )
+ % f
+ )
if fo == 'l':
s(_("the local file has been renamed to %s\n") % f1)
else:
@@ -1645,8 +1882,9 @@
cost = 0 if wctx.isinmemory() else 0.001
# remove in parallel (must come before resolving path conflicts and getting)
- prog = worker.worker(repo.ui, cost, batchremove, (repo, wctx),
- actions[ACTION_REMOVE])
+ prog = worker.worker(
+ repo.ui, cost, batchremove, (repo, wctx), actions[ACTION_REMOVE]
+ )
for i, item in prog:
progress.increment(step=i, item=item)
removed = len(actions[ACTION_REMOVE])
@@ -1654,7 +1892,7 @@
# resolve path conflicts (must come before getting)
for f, args, msg in actions[ACTION_PATH_CONFLICT_RESOLVE]:
repo.ui.debug(" %s: %s -> pr\n" % (f, msg))
- f0, = args
+ (f0,) = args
if wctx[f0].lexists():
repo.ui.note(_("moving %s to %s\n") % (f0, f))
wctx[f].audit()
@@ -1663,13 +1901,18 @@
progress.increment(item=f)
# get in parallel.
- threadsafe = repo.ui.configbool('experimental',
- 'worker.wdir-get-thread-safe')
- prog = worker.worker(repo.ui, cost, batchget,
- (repo, mctx, wctx, wantfiledata),
- actions[ACTION_GET],
- threadsafe=threadsafe,
- hasretval=True)
+ threadsafe = repo.ui.configbool(
+ 'experimental', 'worker.wdir-get-thread-safe'
+ )
+ prog = worker.worker(
+ repo.ui,
+ cost,
+ batchget,
+ (repo, mctx, wctx, wantfiledata),
+ actions[ACTION_GET],
+ threadsafe=threadsafe,
+ hasretval=True,
+ )
getfiledata = {}
for final, res in prog:
if final:
@@ -1726,7 +1969,7 @@
for f, args, msg in actions[ACTION_EXEC]:
repo.ui.debug(" %s: %s -> e\n" % (f, msg))
progress.increment(item=f)
- flags, = args
+ (flags,) = args
wctx[f].audit()
wctx[f].setflags('l' in flags, 'x' in flags)
updated += 1
@@ -1738,8 +1981,9 @@
if usemergedriver:
if wctx.isinmemory():
- raise error.InMemoryMergeConflictsError("in-memory merge does not "
- "support mergedriver")
+ raise error.InMemoryMergeConflictsError(
+ "in-memory merge does not " "support mergedriver"
+ )
ms.commit()
proceed = driverpreprocess(repo, ms, wctx, labels=labels)
# the driver might leave some files unresolved
@@ -1747,8 +1991,9 @@
if not proceed:
# XXX setting unresolved to at least 1 is a hack to make sure we
# error out
- return updateresult(updated, merged, removed,
- max(len(unresolvedf), 1))
+ return updateresult(
+ updated, merged, removed, max(len(unresolvedf), 1)
+ )
newactions = []
for f, args, msg in mergeactions:
if f in unresolvedf:
@@ -1761,9 +2006,10 @@
for f, args, msg in mergeactions:
repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
progress.increment(item=f)
- if f == '.hgsubstate': # subrepo states need updating
- subrepoutil.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
- overwrite, labels)
+ if f == '.hgsubstate': # subrepo states need updating
+ subrepoutil.submerge(
+ repo, wctx, mctx, wctx.ancestor(mctx), overwrite, labels
+ )
continue
wctx[f].audit()
complete, r = ms.preresolve(f, wctx)
@@ -1782,8 +2028,11 @@
unresolved = ms.unresolvedcount()
- if (usemergedriver and not unresolved
- and ms.mdstate() != MERGE_DRIVER_STATE_SUCCESS):
+ if (
+ usemergedriver
+ and not unresolved
+ and ms.mdstate() != MERGE_DRIVER_STATE_SUCCESS
+ ):
if not driverconclude(repo, ms, wctx, labels=labels):
# XXX setting unresolved to at least 1 is a hack to make sure we
# error out
@@ -1823,13 +2072,15 @@
# those lists aren't consulted again.
mfiles.difference_update(a[0] for a in acts)
- actions[ACTION_MERGE] = [a for a in actions[ACTION_MERGE]
- if a[0] in mfiles]
+ actions[ACTION_MERGE] = [
+ a for a in actions[ACTION_MERGE] if a[0] in mfiles
+ ]
progress.complete()
assert len(getfiledata) == (len(actions[ACTION_GET]) if wantfiledata else 0)
return updateresult(updated, merged, removed, unresolved), getfiledata
+
def recordupdates(repo, actions, branchmerge, getfiledata):
"record merge actions to the dirstate"
# remove (must come first)
@@ -1845,7 +2096,7 @@
# resolve path conflicts
for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
- f0, = args
+ (f0,) = args
origf0 = repo.dirstate.copied(f0) or f0
repo.dirstate.add(f)
repo.dirstate.copy(origf0, f)
@@ -1888,7 +2139,7 @@
# We've done a branch merge, mark this file as merged
# so that we properly record the merger later
repo.dirstate.merge(f)
- if f1 != f2: # copy/rename
+ if f1 != f2: # copy/rename
if move:
repo.dirstate.remove(f1)
if f1 != f:
@@ -1901,7 +2152,7 @@
# of that file some time in the past. Thus our
# merge will appear as a normal local file
# modification.
- if f2 == f: # file not locally copied/moved
+ if f2 == f: # file not locally copied/moved
repo.dirstate.normallookup(f)
if move:
repo.dirstate.drop(f1)
@@ -1926,14 +2177,26 @@
else:
repo.dirstate.normal(f)
+
UPDATECHECK_ABORT = 'abort' # handled at higher layers
UPDATECHECK_NONE = 'none'
UPDATECHECK_LINEAR = 'linear'
UPDATECHECK_NO_CONFLICT = 'noconflict'
-def update(repo, node, branchmerge, force, ancestor=None,
- mergeancestor=False, labels=None, matcher=None, mergeforce=False,
- updatecheck=None, wc=None):
+
+def update(
+ repo,
+ node,
+ branchmerge,
+ force,
+ ancestor=None,
+ mergeancestor=False,
+ labels=None,
+ matcher=None,
+ mergeforce=False,
+ updatecheck=None,
+ wc=None,
+):
"""
Perform a merge between the working directory and the given node
@@ -1998,15 +2261,22 @@
# updatecheck='abort' to better suppport some of these callers.
if updatecheck is None:
updatecheck = UPDATECHECK_LINEAR
- if updatecheck not in (UPDATECHECK_NONE,
- UPDATECHECK_LINEAR,
- UPDATECHECK_NO_CONFLICT,
+ if updatecheck not in (
+ UPDATECHECK_NONE,
+ UPDATECHECK_LINEAR,
+ UPDATECHECK_NO_CONFLICT,
):
- raise ValueError(r'Invalid updatecheck %r (can accept %r)' % (
- updatecheck, (UPDATECHECK_NONE,
- UPDATECHECK_LINEAR,
- UPDATECHECK_NO_CONFLICT,
- )))
+ raise ValueError(
+ r'Invalid updatecheck %r (can accept %r)'
+ % (
+ updatecheck,
+ (
+ UPDATECHECK_NONE,
+ UPDATECHECK_LINEAR,
+ UPDATECHECK_NO_CONFLICT,
+ ),
+ )
+ )
# If we're doing a partial update, we need to skip updating
# the dirstate, so make a note of any partial-ness to the
# update here.
@@ -2038,33 +2308,44 @@
raise error.Abort(_("outstanding uncommitted merge"))
ms = mergestate.read(repo)
if list(ms.unresolved()):
- raise error.Abort(_("outstanding merge conflicts"),
- hint=_("use 'hg resolve' to resolve"))
+ raise error.Abort(
+ _("outstanding merge conflicts"),
+ hint=_("use 'hg resolve' to resolve"),
+ )
if branchmerge:
if pas == [p2]:
- raise error.Abort(_("merging with a working directory ancestor"
- " has no effect"))
+ raise error.Abort(
+ _(
+ "merging with a working directory ancestor"
+ " has no effect"
+ )
+ )
elif pas == [p1]:
if not mergeancestor and wc.branch() == p2.branch():
- raise error.Abort(_("nothing to merge"),
- hint=_("use 'hg update' "
- "or check 'hg heads'"))
+ raise error.Abort(
+ _("nothing to merge"),
+ hint=_("use 'hg update' " "or check 'hg heads'"),
+ )
if not force and (wc.files() or wc.deleted()):
- raise error.Abort(_("uncommitted changes"),
- hint=_("use 'hg status' to list changes"))
+ raise error.Abort(
+ _("uncommitted changes"),
+ hint=_("use 'hg status' to list changes"),
+ )
if not wc.isinmemory():
for s in sorted(wc.substate):
wc.sub(s).bailifchanged()
elif not overwrite:
- if p1 == p2: # no-op update
+ if p1 == p2: # no-op update
# call the hooks and exit early
repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
repo.hook('update', parent1=xp2, parent2='', error=0)
return updateresult(0, 0, 0, 0)
- if (updatecheck == UPDATECHECK_LINEAR and
- pas not in ([p1], [p2])): # nonlinear
+ if updatecheck == UPDATECHECK_LINEAR and pas not in (
+ [p1],
+ [p2],
+ ): # nonlinear
dirty = wc.dirty(missing=True)
if dirty:
# Branching is a bit strange to ensure we do the minimal
@@ -2072,7 +2353,7 @@
foreground = obsutil.foreground(repo, [p1.node()])
# note: the <node> variable contains a random identifier
if repo[node].node() in foreground:
- pass # allow updating to successors
+ pass # allow updating to successors
else:
msg = _("uncommitted changes")
hint = _("commit or update --clean to discard changes")
@@ -2097,13 +2378,27 @@
### calculate phase
actionbyfile, diverge, renamedelete = calculateupdates(
- repo, wc, p2, pas, branchmerge, force, mergeancestor,
- followcopies, matcher=matcher, mergeforce=mergeforce)
+ repo,
+ wc,
+ p2,
+ pas,
+ branchmerge,
+ force,
+ mergeancestor,
+ followcopies,
+ matcher=matcher,
+ mergeforce=mergeforce,
+ )
if updatecheck == UPDATECHECK_NO_CONFLICT:
for f, (m, args, msg) in actionbyfile.iteritems():
- if m not in (ACTION_GET, ACTION_KEEP, ACTION_EXEC,
- ACTION_REMOVE, ACTION_PATH_CONFLICT_RESOLVE):
+ if m not in (
+ ACTION_GET,
+ ACTION_KEEP,
+ ACTION_EXEC,
+ ACTION_REMOVE,
+ ACTION_PATH_CONFLICT_RESOLVE,
+ ):
msg = _("conflicting changes")
hint = _("commit or update --clean to discard changes")
raise error.Abort(msg, hint=hint)
@@ -2118,9 +2413,14 @@
prompts['f'] = f
if m == ACTION_CHANGED_DELETED:
if repo.ui.promptchoice(
- _("local%(l)s changed %(f)s which other%(o)s deleted\n"
- "use (c)hanged version or (d)elete?"
- "$$ &Changed $$ &Delete") % prompts, 0):
+ _(
+ "local%(l)s changed %(f)s which other%(o)s deleted\n"
+ "use (c)hanged version or (d)elete?"
+ "$$ &Changed $$ &Delete"
+ )
+ % prompts,
+ 0,
+ ):
actionbyfile[f] = (ACTION_REMOVE, None, 'prompt delete')
elif f in p1:
actionbyfile[f] = (ACTION_ADD_MODIFIED, None, 'prompt keep')
@@ -2129,12 +2429,23 @@
elif m == ACTION_DELETED_CHANGED:
f1, f2, fa, move, anc = args
flags = p2[f2].flags()
- if repo.ui.promptchoice(
- _("other%(o)s changed %(f)s which local%(l)s deleted\n"
- "use (c)hanged version or leave (d)eleted?"
- "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
- actionbyfile[f] = (ACTION_GET, (flags, False),
- 'prompt recreating')
+ if (
+ repo.ui.promptchoice(
+ _(
+ "other%(o)s changed %(f)s which local%(l)s deleted\n"
+ "use (c)hanged version or leave (d)eleted?"
+ "$$ &Changed $$ &Deleted"
+ )
+ % prompts,
+ 0,
+ )
+ == 0
+ ):
+ actionbyfile[f] = (
+ ACTION_GET,
+ (flags, False),
+ 'prompt recreating',
+ )
else:
del actionbyfile[f]
@@ -2147,28 +2458,39 @@
if not util.fscasesensitive(repo.path):
# check collision between files only in p2 for clean update
- if (not branchmerge and
- (force or not wc.dirty(missing=True, branch=False))):
+ if not branchmerge and (
+ force or not wc.dirty(missing=True, branch=False)
+ ):
_checkcollision(repo, p2.manifest(), None)
else:
_checkcollision(repo, wc.manifest(), actions)
# divergent renames
for f, fl in sorted(diverge.iteritems()):
- repo.ui.warn(_("note: possible conflict - %s was renamed "
- "multiple times to:\n") % f)
+ repo.ui.warn(
+ _(
+ "note: possible conflict - %s was renamed "
+ "multiple times to:\n"
+ )
+ % f
+ )
for nf in sorted(fl):
repo.ui.warn(" %s\n" % nf)
# rename and delete
for f, fl in sorted(renamedelete.iteritems()):
- repo.ui.warn(_("note: possible conflict - %s was deleted "
- "and renamed to:\n") % f)
+ repo.ui.warn(
+ _(
+ "note: possible conflict - %s was deleted "
+ "and renamed to:\n"
+ )
+ % f
+ )
for nf in sorted(fl):
repo.ui.warn(" %s\n" % nf)
### apply phase
- if not branchmerge: # just jump to the new rev
+ if not branchmerge: # just jump to the new rev
fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
if not partial and not wc.isinmemory():
repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
@@ -2186,11 +2508,13 @@
# We only allow on Linux and MacOS because that's where fsmonitor is
# considered stable.
fsmonitorwarning = repo.ui.configbool('fsmonitor', 'warn_when_unused')
- fsmonitorthreshold = repo.ui.configint('fsmonitor',
- 'warn_update_file_count')
+ fsmonitorthreshold = repo.ui.configint(
+ 'fsmonitor', 'warn_update_file_count'
+ )
try:
# avoid cycle: extensions -> cmdutil -> merge
from . import extensions
+
extensions.find('fsmonitor')
fsmonitorenabled = repo.ui.config('fsmonitor', 'mode') != 'off'
# We intentionally don't look at whether fsmonitor has disabled
@@ -2199,20 +2523,26 @@
except KeyError:
fsmonitorenabled = False
- if (fsmonitorwarning
- and not fsmonitorenabled
- and p1.node() == nullid
- and len(actions[ACTION_GET]) >= fsmonitorthreshold
- and pycompat.sysplatform.startswith(('linux', 'darwin'))):
+ if (
+ fsmonitorwarning
+ and not fsmonitorenabled
+ and p1.node() == nullid
+ and len(actions[ACTION_GET]) >= fsmonitorthreshold
+ and pycompat.sysplatform.startswith(('linux', 'darwin'))
+ ):
repo.ui.warn(
- _('(warning: large working directory being used without '
- 'fsmonitor enabled; enable fsmonitor to improve performance; '
- 'see "hg help -e fsmonitor")\n'))
+ _(
+ '(warning: large working directory being used without '
+ 'fsmonitor enabled; enable fsmonitor to improve performance; '
+ 'see "hg help -e fsmonitor")\n'
+ )
+ )
updatedirstate = not partial and not wc.isinmemory()
wantfiledata = updatedirstate and not branchmerge
- stats, getfiledata = applyupdates(repo, actions, wc, p2, overwrite,
- wantfiledata, labels=labels)
+ stats, getfiledata = applyupdates(
+ repo, actions, wc, p2, overwrite, wantfiledata, labels=labels
+ )
if updatedirstate:
with repo.dirstate.parentchange():
@@ -2230,12 +2560,15 @@
sparse.prunetemporaryincludes(repo)
if not partial:
- repo.hook('update', parent1=xp1, parent2=xp2,
- error=stats.unresolvedcount)
+ repo.hook(
+ 'update', parent1=xp1, parent2=xp2, error=stats.unresolvedcount
+ )
return stats
-def graft(repo, ctx, pctx, labels=None, keepparent=False,
- keepconflictparent=False):
+
+def graft(
+ repo, ctx, pctx, labels=None, keepparent=False, keepconflictparent=False
+):
"""Do a graft-like merge.
This is a merge where the merge ancestor is chosen such that one
@@ -2259,9 +2592,15 @@
# which local deleted".
mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
- stats = update(repo, ctx.node(), True, True, pctx.node(),
- mergeancestor=mergeancestor, labels=labels)
-
+ stats = update(
+ repo,
+ ctx.node(),
+ True,
+ True,
+ pctx.node(),
+ mergeancestor=mergeancestor,
+ labels=labels,
+ )
if keepconflictparent and stats.unresolvedcount:
pother = ctx.node()
@@ -2279,8 +2618,16 @@
copies.duplicatecopies(repo, repo[None], ctx.rev(), pctx.rev())
return stats
-def purge(repo, matcher, ignored=False, removeemptydirs=True,
- removefiles=True, abortonerror=False, noop=False):
+
+def purge(
+ repo,
+ matcher,
+ ignored=False,
+ removeemptydirs=True,
+ removefiles=True,
+ abortonerror=False,
+ noop=False,
+):
"""Purge the working directory of untracked files.
``matcher`` is a matcher configured to scan the working directory -