--- a/hgext/fix.py Sat Oct 05 10:29:34 2019 -0400
+++ b/hgext/fix.py Sun Oct 06 09:45:02 2019 -0400
@@ -189,26 +189,43 @@
# problem.
configitem('fix', 'failure', default='continue')
+
def checktoolfailureaction(ui, message, hint=None):
"""Abort with 'message' if fix.failure=abort"""
action = ui.config('fix', 'failure')
if action not in ('continue', 'abort'):
- raise error.Abort(_('unknown fix.failure action: %s') % (action,),
- hint=_('use "continue" or "abort"'))
+ raise error.Abort(
+ _('unknown fix.failure action: %s') % (action,),
+ hint=_('use "continue" or "abort"'),
+ )
if action == 'abort':
raise error.Abort(message, hint=hint)
+
allopt = ('', 'all', False, _('fix all non-public non-obsolete revisions'))
-baseopt = ('', 'base', [], _('revisions to diff against (overrides automatic '
- 'selection, and applies to every revision being '
- 'fixed)'), _('REV'))
+baseopt = (
+ '',
+ 'base',
+ [],
+ _(
+ 'revisions to diff against (overrides automatic '
+ 'selection, and applies to every revision being '
+ 'fixed)'
+ ),
+ _('REV'),
+)
revopt = ('r', 'rev', [], _('revisions to fix'), _('REV'))
wdiropt = ('w', 'working-dir', False, _('fix the working directory'))
wholeopt = ('', 'whole', False, _('always fix every line of a file'))
usage = _('[OPTION]... [FILE]...')
-@command('fix', [allopt, baseopt, revopt, wdiropt, wholeopt], usage,
- helpcategory=command.CATEGORY_FILE_CONTENTS)
+
+@command(
+ 'fix',
+ [allopt, baseopt, revopt, wdiropt, wholeopt],
+ usage,
+ helpcategory=command.CATEGORY_FILE_CONTENTS,
+)
def fix(ui, repo, *pats, **opts):
"""rewrite file content in changesets or working directory
@@ -241,8 +258,9 @@
with repo.wlock(), repo.lock(), repo.transaction('fix'):
revstofix = getrevstofix(ui, repo, opts)
basectxs = getbasectxs(repo, opts, revstofix)
- workqueue, numitems = getworkqueue(ui, repo, pats, opts, revstofix,
- basectxs)
+ workqueue, numitems = getworkqueue(
+ ui, repo, pats, opts, revstofix, basectxs
+ )
fixers = getfixers(ui)
# There are no data dependencies between the workers fixing each file
@@ -251,14 +269,21 @@
for rev, path in items:
ctx = repo[rev]
olddata = ctx[path].data()
- metadata, newdata = fixfile(ui, repo, opts, fixers, ctx, path,
- basectxs[rev])
+ metadata, newdata = fixfile(
+ ui, repo, opts, fixers, ctx, path, basectxs[rev]
+ )
# Don't waste memory/time passing unchanged content back, but
# produce one result per item either way.
- yield (rev, path, metadata,
- newdata if newdata != olddata else None)
- results = worker.worker(ui, 1.0, getfixes, tuple(), workqueue,
- threadsafe=False)
+ yield (
+ rev,
+ path,
+ metadata,
+ newdata if newdata != olddata else None,
+ )
+
+ results = worker.worker(
+ ui, 1.0, getfixes, tuple(), workqueue, threadsafe=False
+ )
# We have to hold on to the data for each successor revision in memory
# until all its parents are committed. We ensure this by committing and
@@ -271,8 +296,9 @@
replacements = {}
wdirwritten = False
commitorder = sorted(revstofix, reverse=True)
- with ui.makeprogress(topic=_('fixing'), unit=_('files'),
- total=sum(numitems.values())) as progress:
+ with ui.makeprogress(
+ topic=_('fixing'), unit=_('files'), total=sum(numitems.values())
+ ) as progress:
for rev, path, filerevmetadata, newdata in results:
progress.increment(item=path)
for fixername, fixermetadata in filerevmetadata.items():
@@ -280,12 +306,15 @@
if newdata is not None:
filedata[rev][path] = newdata
hookargs = {
- 'rev': rev,
- 'path': path,
- 'metadata': filerevmetadata,
+ 'rev': rev,
+ 'path': path,
+ 'metadata': filerevmetadata,
}
- repo.hook('postfixfile', throw=False,
- **pycompat.strkwargs(hookargs))
+ repo.hook(
+ 'postfixfile',
+ throw=False,
+ **pycompat.strkwargs(hookargs)
+ )
numitems[rev] -= 1
# Apply the fixes for this and any other revisions that are
# ready and sitting at the front of the queue. Using a loop here
@@ -309,6 +338,7 @@
}
repo.hook('postfix', throw=True, **pycompat.strkwargs(hookargs))
+
def cleanup(repo, replacements, wdirwritten):
"""Calls scmutil.cleanupnodes() with the given replacements.
@@ -325,6 +355,7 @@
replacements = {prec: [succ] for prec, succ in replacements.iteritems()}
scmutil.cleanupnodes(repo, replacements, 'fix', fixphase=True)
+
def getworkqueue(ui, repo, pats, opts, revstofix, basectxs):
""""Constructs the list of files to be fixed at specific revisions
@@ -348,19 +379,23 @@
for rev in sorted(revstofix):
fixctx = repo[rev]
match = scmutil.match(fixctx, pats, opts)
- for path in sorted(pathstofix(
- ui, repo, pats, opts, match, basectxs[rev], fixctx)):
+ for path in sorted(
+ pathstofix(ui, repo, pats, opts, match, basectxs[rev], fixctx)
+ ):
fctx = fixctx[path]
if fctx.islink():
continue
if fctx.size() > maxfilesize:
- ui.warn(_('ignoring file larger than %s: %s\n') %
- (util.bytecount(maxfilesize), path))
+ ui.warn(
+ _('ignoring file larger than %s: %s\n')
+ % (util.bytecount(maxfilesize), path)
+ )
continue
workqueue.append((rev, path))
numitems[rev] += 1
return workqueue, numitems
+
def getrevstofix(ui, repo, opts):
"""Returns the set of revision numbers that should be fixed"""
revs = set(scmutil.revrange(repo, opts['rev']))
@@ -375,27 +410,35 @@
raise error.Abort('unresolved conflicts', hint="use 'hg resolve'")
if not revs:
raise error.Abort(
- 'no changesets specified', hint='use --rev or --working-dir')
+ 'no changesets specified', hint='use --rev or --working-dir'
+ )
return revs
+
def checknodescendants(repo, revs):
- if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
- repo.revs('(%ld::) - (%ld)', revs, revs)):
- raise error.Abort(_('can only fix a changeset together '
- 'with all its descendants'))
+ if not obsolete.isenabled(repo, obsolete.allowunstableopt) and repo.revs(
+ '(%ld::) - (%ld)', revs, revs
+ ):
+ raise error.Abort(
+ _('can only fix a changeset together ' 'with all its descendants')
+ )
+
def checkfixablectx(ui, repo, ctx):
"""Aborts if the revision shouldn't be replaced with a fixed one."""
if not ctx.mutable():
- raise error.Abort('can\'t fix immutable changeset %s' %
- (scmutil.formatchangeid(ctx),))
+ raise error.Abort(
+ 'can\'t fix immutable changeset %s' % (scmutil.formatchangeid(ctx),)
+ )
if ctx.obsolete():
# It would be better to actually check if the revision has a successor.
- allowdivergence = ui.configbool('experimental',
- 'evolution.allowdivergence')
+ allowdivergence = ui.configbool(
+ 'experimental', 'evolution.allowdivergence'
+ )
if not allowdivergence:
raise error.Abort('fixing obsolete revision could cause divergence')
+
def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx):
"""Returns the set of files that should be fixed in a context
@@ -405,13 +448,19 @@
"""
files = set()
for basectx in basectxs:
- stat = basectx.status(fixctx, match=match, listclean=bool(pats),
- listunknown=bool(pats))
+ stat = basectx.status(
+ fixctx, match=match, listclean=bool(pats), listunknown=bool(pats)
+ )
files.update(
- set(itertools.chain(stat.added, stat.modified, stat.clean,
- stat.unknown)))
+ set(
+ itertools.chain(
+ stat.added, stat.modified, stat.clean, stat.unknown
+ )
+ )
+ )
return files
+
def lineranges(opts, path, basectxs, fixctx, content2):
"""Returns the set of line ranges that should be fixed in a file
@@ -439,6 +488,7 @@
rangeslist.extend(difflineranges(content1, content2))
return unionranges(rangeslist)
+
def unionranges(rangeslist):
"""Return the union of some closed intervals
@@ -473,6 +523,7 @@
unioned[-1] = (c, max(b, d))
return unioned
+
def difflineranges(content1, content2):
"""Return list of line number ranges in content2 that differ from content1.
@@ -519,6 +570,7 @@
ranges.append((firstline + 1, lastline))
return ranges
+
def getbasectxs(repo, opts, revstofix):
"""Returns a map of the base contexts for each revision
@@ -548,6 +600,7 @@
basectxs[rev].add(pctx)
return basectxs
+
def fixfile(ui, repo, opts, fixers, fixctx, path, basectxs):
"""Run any configured fixers that should affect the file in this context
@@ -575,7 +628,8 @@
cwd=repo.root,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
+ stderr=subprocess.PIPE,
+ )
stdout, stderr = proc.communicate(newdata)
if stderr:
showstderr(ui, fixctx.rev(), fixername, stderr)
@@ -585,8 +639,10 @@
metadatajson, newerdata = stdout.split('\0', 1)
metadata[fixername] = json.loads(metadatajson)
except ValueError:
- ui.warn(_('ignored invalid output from fixer tool: %s\n') %
- (fixername,))
+ ui.warn(
+ _('ignored invalid output from fixer tool: %s\n')
+ % (fixername,)
+ )
continue
else:
metadata[fixername] = None
@@ -597,11 +653,16 @@
message = _('exited with status %d\n') % (proc.returncode,)
showstderr(ui, fixctx.rev(), fixername, message)
checktoolfailureaction(
- ui, _('no fixes will be applied'),
- hint=_('use --config fix.failure=continue to apply any '
- 'successful fixes anyway'))
+ ui,
+ _('no fixes will be applied'),
+ hint=_(
+ 'use --config fix.failure=continue to apply any '
+ 'successful fixes anyway'
+ ),
+ )
return metadata, newdata
+
def showstderr(ui, rev, fixername, stderr):
"""Writes the lines of the stderr string as warnings on the ui
@@ -612,12 +673,13 @@
"""
for line in re.split('[\r\n]+', stderr):
if line:
- ui.warn(('['))
+ ui.warn('[')
if rev is None:
ui.warn(_('wdir'), label='evolve.rev')
else:
ui.warn((str(rev)), label='evolve.rev')
- ui.warn(('] %s: %s\n') % (fixername, line))
+ ui.warn('] %s: %s\n' % (fixername, line))
+
def writeworkingdir(repo, ctx, filedata, replacements):
"""Write new content to the working copy and check out the new p1 if any
@@ -640,6 +702,7 @@
if newparentnodes != oldparentnodes:
repo.setparents(*newparentnodes)
+
def replacerev(ui, repo, ctx, filedata, replacements):
"""Commit a new revision like the given one, but with file content changes
@@ -671,9 +734,11 @@
# intervention to evolve. We can't rely on commit() to avoid creating the
# un-needed revision because the extra field added below produces a new hash
# regardless of file content changes.
- if (not filedata and
- p1ctx.node() not in replacements and
- p2ctx.node() not in replacements):
+ if (
+ not filedata
+ and p1ctx.node() not in replacements
+ and p2ctx.node() not in replacements
+ ):
return
def filectxfn(repo, memctx, path):
@@ -688,7 +753,8 @@
data=filedata.get(path, fctx.data()),
islink=fctx.islink(),
isexec=fctx.isexec(),
- copysource=copysource)
+ copysource=copysource,
+ )
extra = ctx.extra().copy()
extra['fix_source'] = ctx.hex()
@@ -703,7 +769,8 @@
date=ctx.date(),
extra=extra,
branch=ctx.branch(),
- editor=None)
+ editor=None,
+ )
sucnode = memctx.commit()
prenode = ctx.node()
if prenode == sucnode:
@@ -711,6 +778,7 @@
else:
replacements[ctx.node()] = sucnode
+
def getfixers(ui):
"""Returns a map of configured fixer tools indexed by their names
@@ -722,8 +790,11 @@
fixers[name] = Fixer()
attrs = ui.configsuboptions('fix', name)[1]
for key, default in FIXER_ATTRS.items():
- setattr(fixers[name], pycompat.sysstr('_' + key),
- attrs.get(key, default))
+ setattr(
+ fixers[name],
+ pycompat.sysstr('_' + key),
+ attrs.get(key, default),
+ )
fixers[name]._priority = int(fixers[name]._priority)
fixers[name]._metadata = stringutil.parsebool(fixers[name]._metadata)
fixers[name]._skipclean = stringutil.parsebool(fixers[name]._skipclean)
@@ -734,14 +805,16 @@
# default.
if fixers[name]._pattern is None:
ui.warn(
- _('fixer tool has no pattern configuration: %s\n') % (name,))
+ _('fixer tool has no pattern configuration: %s\n') % (name,)
+ )
del fixers[name]
elif not fixers[name]._enabled:
ui.debug('ignoring disabled fixer tool: %s\n' % (name,))
del fixers[name]
return collections.OrderedDict(
- sorted(fixers.items(), key=lambda item: item[1]._priority,
- reverse=True))
+ sorted(fixers.items(), key=lambda item: item[1]._priority, reverse=True)
+ )
+
def fixernames(ui):
"""Returns the names of [fix] config options that have suboptions"""
@@ -751,13 +824,15 @@
names.add(k.split(':', 1)[0])
return names
+
class Fixer(object):
"""Wraps the raw config values for a fixer with methods"""
def affects(self, opts, fixctx, path):
"""Should this fixer run on the file at the given path and context?"""
- return (self._pattern is not None and
- scmutil.match(fixctx, [self._pattern], opts)(path))
+ return self._pattern is not None and scmutil.match(
+ fixctx, [self._pattern], opts
+ )(path)
def shouldoutputmetadata(self):
"""Should the stdout of this fixer start with JSON and a null byte?"""
@@ -770,13 +845,19 @@
parameters.
"""
expand = cmdutil.rendercommandtemplate
- parts = [expand(ui, self._command,
- {'rootpath': path, 'basename': os.path.basename(path)})]
+ parts = [
+ expand(
+ ui,
+ self._command,
+ {'rootpath': path, 'basename': os.path.basename(path)},
+ )
+ ]
if self._linerange:
if self._skipclean and not ranges:
# No line ranges to fix, so don't run the fixer.
return None
for first, last in ranges:
- parts.append(expand(ui, self._linerange,
- {'first': first, 'last': last}))
+ parts.append(
+ expand(ui, self._linerange, {'first': first, 'last': last})
+ )
return ' '.join(parts)