hgext/fix.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43087 66f2cc210a29
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
   155 
   155 
   156 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
   156 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
   157 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
   157 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
   158 # be specifying the version(s) of Mercurial they are tested with, or
   158 # be specifying the version(s) of Mercurial they are tested with, or
   159 # leave the attribute unspecified.
   159 # leave the attribute unspecified.
   160 testedwith = 'ships-with-hg-core'
   160 testedwith = b'ships-with-hg-core'
   161 
   161 
   162 cmdtable = {}
   162 cmdtable = {}
   163 command = registrar.command(cmdtable)
   163 command = registrar.command(cmdtable)
   164 
   164 
   165 configtable = {}
   165 configtable = {}
   166 configitem = registrar.configitem(configtable)
   166 configitem = registrar.configitem(configtable)
   167 
   167 
   168 # Register the suboptions allowed for each configured fixer, and default values.
   168 # Register the suboptions allowed for each configured fixer, and default values.
   169 FIXER_ATTRS = {
   169 FIXER_ATTRS = {
   170     'command': None,
   170     b'command': None,
   171     'linerange': None,
   171     b'linerange': None,
   172     'pattern': None,
   172     b'pattern': None,
   173     'priority': 0,
   173     b'priority': 0,
   174     'metadata': 'false',
   174     b'metadata': b'false',
   175     'skipclean': 'true',
   175     b'skipclean': b'true',
   176     'enabled': 'true',
   176     b'enabled': b'true',
   177 }
   177 }
   178 
   178 
   179 for key, default in FIXER_ATTRS.items():
   179 for key, default in FIXER_ATTRS.items():
   180     configitem('fix', '.*(:%s)?' % key, default=default, generic=True)
   180     configitem(b'fix', b'.*(:%s)?' % key, default=default, generic=True)
   181 
   181 
   182 # A good default size allows most source code files to be fixed, but avoids
   182 # A good default size allows most source code files to be fixed, but avoids
   183 # letting fixer tools choke on huge inputs, which could be surprising to the
   183 # letting fixer tools choke on huge inputs, which could be surprising to the
   184 # user.
   184 # user.
   185 configitem('fix', 'maxfilesize', default='2MB')
   185 configitem(b'fix', b'maxfilesize', default=b'2MB')
   186 
   186 
   187 # Allow fix commands to exit non-zero if an executed fixer tool exits non-zero.
   187 # Allow fix commands to exit non-zero if an executed fixer tool exits non-zero.
   188 # This helps users do shell scripts that stop when a fixer tool signals a
   188 # This helps users do shell scripts that stop when a fixer tool signals a
   189 # problem.
   189 # problem.
   190 configitem('fix', 'failure', default='continue')
   190 configitem(b'fix', b'failure', default=b'continue')
   191 
   191 
   192 
   192 
   193 def checktoolfailureaction(ui, message, hint=None):
   193 def checktoolfailureaction(ui, message, hint=None):
   194     """Abort with 'message' if fix.failure=abort"""
   194     """Abort with 'message' if fix.failure=abort"""
   195     action = ui.config('fix', 'failure')
   195     action = ui.config(b'fix', b'failure')
   196     if action not in ('continue', 'abort'):
   196     if action not in (b'continue', b'abort'):
   197         raise error.Abort(
   197         raise error.Abort(
   198             _('unknown fix.failure action: %s') % (action,),
   198             _(b'unknown fix.failure action: %s') % (action,),
   199             hint=_('use "continue" or "abort"'),
   199             hint=_(b'use "continue" or "abort"'),
   200         )
   200         )
   201     if action == 'abort':
   201     if action == b'abort':
   202         raise error.Abort(message, hint=hint)
   202         raise error.Abort(message, hint=hint)
   203 
   203 
   204 
   204 
   205 allopt = ('', 'all', False, _('fix all non-public non-obsolete revisions'))
   205 allopt = (b'', b'all', False, _(b'fix all non-public non-obsolete revisions'))
   206 baseopt = (
   206 baseopt = (
   207     '',
   207     b'',
   208     'base',
   208     b'base',
   209     [],
   209     [],
   210     _(
   210     _(
   211         'revisions to diff against (overrides automatic '
   211         b'revisions to diff against (overrides automatic '
   212         'selection, and applies to every revision being '
   212         b'selection, and applies to every revision being '
   213         'fixed)'
   213         b'fixed)'
   214     ),
   214     ),
   215     _('REV'),
   215     _(b'REV'),
   216 )
   216 )
   217 revopt = ('r', 'rev', [], _('revisions to fix'), _('REV'))
   217 revopt = (b'r', b'rev', [], _(b'revisions to fix'), _(b'REV'))
   218 wdiropt = ('w', 'working-dir', False, _('fix the working directory'))
   218 wdiropt = (b'w', b'working-dir', False, _(b'fix the working directory'))
   219 wholeopt = ('', 'whole', False, _('always fix every line of a file'))
   219 wholeopt = (b'', b'whole', False, _(b'always fix every line of a file'))
   220 usage = _('[OPTION]... [FILE]...')
   220 usage = _(b'[OPTION]... [FILE]...')
   221 
   221 
   222 
   222 
   223 @command(
   223 @command(
   224     'fix',
   224     b'fix',
   225     [allopt, baseopt, revopt, wdiropt, wholeopt],
   225     [allopt, baseopt, revopt, wdiropt, wholeopt],
   226     usage,
   226     usage,
   227     helpcategory=command.CATEGORY_FILE_CONTENTS,
   227     helpcategory=command.CATEGORY_FILE_CONTENTS,
   228 )
   228 )
   229 def fix(ui, repo, *pats, **opts):
   229 def fix(ui, repo, *pats, **opts):
   248     set of revisions being fixed is considered, so that fixes to earlier
   248     set of revisions being fixed is considered, so that fixes to earlier
   249     revisions are not forgotten in later ones. The --base flag can be used to
   249     revisions are not forgotten in later ones. The --base flag can be used to
   250     override this default behavior, though it is not usually desirable to do so.
   250     override this default behavior, though it is not usually desirable to do so.
   251     """
   251     """
   252     opts = pycompat.byteskwargs(opts)
   252     opts = pycompat.byteskwargs(opts)
   253     if opts['all']:
   253     if opts[b'all']:
   254         if opts['rev']:
   254         if opts[b'rev']:
   255             raise error.Abort(_('cannot specify both "--rev" and "--all"'))
   255             raise error.Abort(_(b'cannot specify both "--rev" and "--all"'))
   256         opts['rev'] = ['not public() and not obsolete()']
   256         opts[b'rev'] = [b'not public() and not obsolete()']
   257         opts['working_dir'] = True
   257         opts[b'working_dir'] = True
   258     with repo.wlock(), repo.lock(), repo.transaction('fix'):
   258     with repo.wlock(), repo.lock(), repo.transaction(b'fix'):
   259         revstofix = getrevstofix(ui, repo, opts)
   259         revstofix = getrevstofix(ui, repo, opts)
   260         basectxs = getbasectxs(repo, opts, revstofix)
   260         basectxs = getbasectxs(repo, opts, revstofix)
   261         workqueue, numitems = getworkqueue(
   261         workqueue, numitems = getworkqueue(
   262             ui, repo, pats, opts, revstofix, basectxs
   262             ui, repo, pats, opts, revstofix, basectxs
   263         )
   263         )
   295         aggregatemetadata = collections.defaultdict(list)
   295         aggregatemetadata = collections.defaultdict(list)
   296         replacements = {}
   296         replacements = {}
   297         wdirwritten = False
   297         wdirwritten = False
   298         commitorder = sorted(revstofix, reverse=True)
   298         commitorder = sorted(revstofix, reverse=True)
   299         with ui.makeprogress(
   299         with ui.makeprogress(
   300             topic=_('fixing'), unit=_('files'), total=sum(numitems.values())
   300             topic=_(b'fixing'), unit=_(b'files'), total=sum(numitems.values())
   301         ) as progress:
   301         ) as progress:
   302             for rev, path, filerevmetadata, newdata in results:
   302             for rev, path, filerevmetadata, newdata in results:
   303                 progress.increment(item=path)
   303                 progress.increment(item=path)
   304                 for fixername, fixermetadata in filerevmetadata.items():
   304                 for fixername, fixermetadata in filerevmetadata.items():
   305                     aggregatemetadata[fixername].append(fixermetadata)
   305                     aggregatemetadata[fixername].append(fixermetadata)
   306                 if newdata is not None:
   306                 if newdata is not None:
   307                     filedata[rev][path] = newdata
   307                     filedata[rev][path] = newdata
   308                     hookargs = {
   308                     hookargs = {
   309                         'rev': rev,
   309                         b'rev': rev,
   310                         'path': path,
   310                         b'path': path,
   311                         'metadata': filerevmetadata,
   311                         b'metadata': filerevmetadata,
   312                     }
   312                     }
   313                     repo.hook(
   313                     repo.hook(
   314                         'postfixfile',
   314                         b'postfixfile',
   315                         throw=False,
   315                         throw=False,
   316                         **pycompat.strkwargs(hookargs)
   316                         **pycompat.strkwargs(hookargs)
   317                     )
   317                     )
   318                 numitems[rev] -= 1
   318                 numitems[rev] -= 1
   319                 # Apply the fixes for this and any other revisions that are
   319                 # Apply the fixes for this and any other revisions that are
   330                         replacerev(ui, repo, ctx, filedata[rev], replacements)
   330                         replacerev(ui, repo, ctx, filedata[rev], replacements)
   331                     del filedata[rev]
   331                     del filedata[rev]
   332 
   332 
   333         cleanup(repo, replacements, wdirwritten)
   333         cleanup(repo, replacements, wdirwritten)
   334         hookargs = {
   334         hookargs = {
   335             'replacements': replacements,
   335             b'replacements': replacements,
   336             'wdirwritten': wdirwritten,
   336             b'wdirwritten': wdirwritten,
   337             'metadata': aggregatemetadata,
   337             b'metadata': aggregatemetadata,
   338         }
   338         }
   339         repo.hook('postfix', throw=True, **pycompat.strkwargs(hookargs))
   339         repo.hook(b'postfix', throw=True, **pycompat.strkwargs(hookargs))
   340 
   340 
   341 
   341 
   342 def cleanup(repo, replacements, wdirwritten):
   342 def cleanup(repo, replacements, wdirwritten):
   343     """Calls scmutil.cleanupnodes() with the given replacements.
   343     """Calls scmutil.cleanupnodes() with the given replacements.
   344 
   344 
   351 
   351 
   352     Useful as a hook point for extending "hg fix" with output summarizing the
   352     Useful as a hook point for extending "hg fix" with output summarizing the
   353     effects of the command, though we choose not to output anything here.
   353     effects of the command, though we choose not to output anything here.
   354     """
   354     """
   355     replacements = {prec: [succ] for prec, succ in replacements.iteritems()}
   355     replacements = {prec: [succ] for prec, succ in replacements.iteritems()}
   356     scmutil.cleanupnodes(repo, replacements, 'fix', fixphase=True)
   356     scmutil.cleanupnodes(repo, replacements, b'fix', fixphase=True)
   357 
   357 
   358 
   358 
   359 def getworkqueue(ui, repo, pats, opts, revstofix, basectxs):
   359 def getworkqueue(ui, repo, pats, opts, revstofix, basectxs):
   360     """"Constructs the list of files to be fixed at specific revisions
   360     """"Constructs the list of files to be fixed at specific revisions
   361 
   361 
   373     items by ascending revision number to match the order in which we commit
   373     items by ascending revision number to match the order in which we commit
   374     the fixes later.
   374     the fixes later.
   375     """
   375     """
   376     workqueue = []
   376     workqueue = []
   377     numitems = collections.defaultdict(int)
   377     numitems = collections.defaultdict(int)
   378     maxfilesize = ui.configbytes('fix', 'maxfilesize')
   378     maxfilesize = ui.configbytes(b'fix', b'maxfilesize')
   379     for rev in sorted(revstofix):
   379     for rev in sorted(revstofix):
   380         fixctx = repo[rev]
   380         fixctx = repo[rev]
   381         match = scmutil.match(fixctx, pats, opts)
   381         match = scmutil.match(fixctx, pats, opts)
   382         for path in sorted(
   382         for path in sorted(
   383             pathstofix(ui, repo, pats, opts, match, basectxs[rev], fixctx)
   383             pathstofix(ui, repo, pats, opts, match, basectxs[rev], fixctx)
   385             fctx = fixctx[path]
   385             fctx = fixctx[path]
   386             if fctx.islink():
   386             if fctx.islink():
   387                 continue
   387                 continue
   388             if fctx.size() > maxfilesize:
   388             if fctx.size() > maxfilesize:
   389                 ui.warn(
   389                 ui.warn(
   390                     _('ignoring file larger than %s: %s\n')
   390                     _(b'ignoring file larger than %s: %s\n')
   391                     % (util.bytecount(maxfilesize), path)
   391                     % (util.bytecount(maxfilesize), path)
   392                 )
   392                 )
   393                 continue
   393                 continue
   394             workqueue.append((rev, path))
   394             workqueue.append((rev, path))
   395             numitems[rev] += 1
   395             numitems[rev] += 1
   396     return workqueue, numitems
   396     return workqueue, numitems
   397 
   397 
   398 
   398 
   399 def getrevstofix(ui, repo, opts):
   399 def getrevstofix(ui, repo, opts):
   400     """Returns the set of revision numbers that should be fixed"""
   400     """Returns the set of revision numbers that should be fixed"""
   401     revs = set(scmutil.revrange(repo, opts['rev']))
   401     revs = set(scmutil.revrange(repo, opts[b'rev']))
   402     for rev in revs:
   402     for rev in revs:
   403         checkfixablectx(ui, repo, repo[rev])
   403         checkfixablectx(ui, repo, repo[rev])
   404     if revs:
   404     if revs:
   405         cmdutil.checkunfinished(repo)
   405         cmdutil.checkunfinished(repo)
   406         checknodescendants(repo, revs)
   406         checknodescendants(repo, revs)
   407     if opts.get('working_dir'):
   407     if opts.get(b'working_dir'):
   408         revs.add(wdirrev)
   408         revs.add(wdirrev)
   409         if list(merge.mergestate.read(repo).unresolved()):
   409         if list(merge.mergestate.read(repo).unresolved()):
   410             raise error.Abort('unresolved conflicts', hint="use 'hg resolve'")
   410             raise error.Abort(b'unresolved conflicts', hint=b"use 'hg resolve'")
   411     if not revs:
   411     if not revs:
   412         raise error.Abort(
   412         raise error.Abort(
   413             'no changesets specified', hint='use --rev or --working-dir'
   413             b'no changesets specified', hint=b'use --rev or --working-dir'
   414         )
   414         )
   415     return revs
   415     return revs
   416 
   416 
   417 
   417 
   418 def checknodescendants(repo, revs):
   418 def checknodescendants(repo, revs):
   419     if not obsolete.isenabled(repo, obsolete.allowunstableopt) and repo.revs(
   419     if not obsolete.isenabled(repo, obsolete.allowunstableopt) and repo.revs(
   420         '(%ld::) - (%ld)', revs, revs
   420         b'(%ld::) - (%ld)', revs, revs
   421     ):
   421     ):
   422         raise error.Abort(
   422         raise error.Abort(
   423             _('can only fix a changeset together ' 'with all its descendants')
   423             _(b'can only fix a changeset together ' b'with all its descendants')
   424         )
   424         )
   425 
   425 
   426 
   426 
   427 def checkfixablectx(ui, repo, ctx):
   427 def checkfixablectx(ui, repo, ctx):
   428     """Aborts if the revision shouldn't be replaced with a fixed one."""
   428     """Aborts if the revision shouldn't be replaced with a fixed one."""
   429     if not ctx.mutable():
   429     if not ctx.mutable():
   430         raise error.Abort(
   430         raise error.Abort(
   431             'can\'t fix immutable changeset %s' % (scmutil.formatchangeid(ctx),)
   431             b'can\'t fix immutable changeset %s'
       
   432             % (scmutil.formatchangeid(ctx),)
   432         )
   433         )
   433     if ctx.obsolete():
   434     if ctx.obsolete():
   434         # It would be better to actually check if the revision has a successor.
   435         # It would be better to actually check if the revision has a successor.
   435         allowdivergence = ui.configbool(
   436         allowdivergence = ui.configbool(
   436             'experimental', 'evolution.allowdivergence'
   437             b'experimental', b'evolution.allowdivergence'
   437         )
   438         )
   438         if not allowdivergence:
   439         if not allowdivergence:
   439             raise error.Abort('fixing obsolete revision could cause divergence')
   440             raise error.Abort(
       
   441                 b'fixing obsolete revision could cause divergence'
       
   442             )
   440 
   443 
   441 
   444 
   442 def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx):
   445 def pathstofix(ui, repo, pats, opts, match, basectxs, fixctx):
   443     """Returns the set of files that should be fixed in a context
   446     """Returns the set of files that should be fixed in a context
   444 
   447 
   471     renamed versus any of them.
   474     renamed versus any of them.
   472 
   475 
   473     Another way to understand this is that we exclude line ranges that are
   476     Another way to understand this is that we exclude line ranges that are
   474     common to the file in all base contexts.
   477     common to the file in all base contexts.
   475     """
   478     """
   476     if opts.get('whole'):
   479     if opts.get(b'whole'):
   477         # Return a range containing all lines. Rely on the diff implementation's
   480         # Return a range containing all lines. Rely on the diff implementation's
   478         # idea of how many lines are in the file, instead of reimplementing it.
   481         # idea of how many lines are in the file, instead of reimplementing it.
   479         return difflineranges('', content2)
   482         return difflineranges(b'', content2)
   480 
   483 
   481     rangeslist = []
   484     rangeslist = []
   482     for basectx in basectxs:
   485     for basectx in basectxs:
   483         basepath = copies.pathcopies(basectx, fixctx).get(path, path)
   486         basepath = copies.pathcopies(basectx, fixctx).get(path, path)
   484         if basepath in basectx:
   487         if basepath in basectx:
   485             content1 = basectx[basepath].data()
   488             content1 = basectx[basepath].data()
   486         else:
   489         else:
   487             content1 = ''
   490             content1 = b''
   488         rangeslist.extend(difflineranges(content1, content2))
   491         rangeslist.extend(difflineranges(content1, content2))
   489     return unionranges(rangeslist)
   492     return unionranges(rangeslist)
   490 
   493 
   491 
   494 
   492 def unionranges(rangeslist):
   495 def unionranges(rangeslist):
   564     [(2, 4)]
   567     [(2, 4)]
   565     """
   568     """
   566     ranges = []
   569     ranges = []
   567     for lines, kind in mdiff.allblocks(content1, content2):
   570     for lines, kind in mdiff.allblocks(content1, content2):
   568         firstline, lastline = lines[2:4]
   571         firstline, lastline = lines[2:4]
   569         if kind == '!' and firstline != lastline:
   572         if kind == b'!' and firstline != lastline:
   570             ranges.append((firstline + 1, lastline))
   573             ranges.append((firstline + 1, lastline))
   571     return ranges
   574     return ranges
   572 
   575 
   573 
   576 
   574 def getbasectxs(repo, opts, revstofix):
   577 def getbasectxs(repo, opts, revstofix):
   579     files we attempt to fix, so it is important to compute this even when
   582     files we attempt to fix, so it is important to compute this even when
   580     --whole is used.
   583     --whole is used.
   581     """
   584     """
   582     # The --base flag overrides the usual logic, and we give every revision
   585     # The --base flag overrides the usual logic, and we give every revision
   583     # exactly the set of baserevs that the user specified.
   586     # exactly the set of baserevs that the user specified.
   584     if opts.get('base'):
   587     if opts.get(b'base'):
   585         baserevs = set(scmutil.revrange(repo, opts.get('base')))
   588         baserevs = set(scmutil.revrange(repo, opts.get(b'base')))
   586         if not baserevs:
   589         if not baserevs:
   587             baserevs = {nullrev}
   590             baserevs = {nullrev}
   588         basectxs = {repo[rev] for rev in baserevs}
   591         basectxs = {repo[rev] for rev in baserevs}
   589         return {rev: basectxs for rev in revstofix}
   592         return {rev: basectxs for rev in revstofix}
   590 
   593 
   619         if fixer.affects(opts, fixctx, path):
   622         if fixer.affects(opts, fixctx, path):
   620             ranges = lineranges(opts, path, basectxs, fixctx, newdata)
   623             ranges = lineranges(opts, path, basectxs, fixctx, newdata)
   621             command = fixer.command(ui, path, ranges)
   624             command = fixer.command(ui, path, ranges)
   622             if command is None:
   625             if command is None:
   623                 continue
   626                 continue
   624             ui.debug('subprocess: %s\n' % (command,))
   627             ui.debug(b'subprocess: %s\n' % (command,))
   625             proc = subprocess.Popen(
   628             proc = subprocess.Popen(
   626                 procutil.tonativestr(command),
   629                 procutil.tonativestr(command),
   627                 shell=True,
   630                 shell=True,
   628                 cwd=repo.root,
   631                 cwd=repo.root,
   629                 stdin=subprocess.PIPE,
   632                 stdin=subprocess.PIPE,
   634             if stderr:
   637             if stderr:
   635                 showstderr(ui, fixctx.rev(), fixername, stderr)
   638                 showstderr(ui, fixctx.rev(), fixername, stderr)
   636             newerdata = stdout
   639             newerdata = stdout
   637             if fixer.shouldoutputmetadata():
   640             if fixer.shouldoutputmetadata():
   638                 try:
   641                 try:
   639                     metadatajson, newerdata = stdout.split('\0', 1)
   642                     metadatajson, newerdata = stdout.split(b'\0', 1)
   640                     metadata[fixername] = json.loads(metadatajson)
   643                     metadata[fixername] = json.loads(metadatajson)
   641                 except ValueError:
   644                 except ValueError:
   642                     ui.warn(
   645                     ui.warn(
   643                         _('ignored invalid output from fixer tool: %s\n')
   646                         _(b'ignored invalid output from fixer tool: %s\n')
   644                         % (fixername,)
   647                         % (fixername,)
   645                     )
   648                     )
   646                     continue
   649                     continue
   647             else:
   650             else:
   648                 metadata[fixername] = None
   651                 metadata[fixername] = None
   649             if proc.returncode == 0:
   652             if proc.returncode == 0:
   650                 newdata = newerdata
   653                 newdata = newerdata
   651             else:
   654             else:
   652                 if not stderr:
   655                 if not stderr:
   653                     message = _('exited with status %d\n') % (proc.returncode,)
   656                     message = _(b'exited with status %d\n') % (proc.returncode,)
   654                     showstderr(ui, fixctx.rev(), fixername, message)
   657                     showstderr(ui, fixctx.rev(), fixername, message)
   655                 checktoolfailureaction(
   658                 checktoolfailureaction(
   656                     ui,
   659                     ui,
   657                     _('no fixes will be applied'),
   660                     _(b'no fixes will be applied'),
   658                     hint=_(
   661                     hint=_(
   659                         'use --config fix.failure=continue to apply any '
   662                         b'use --config fix.failure=continue to apply any '
   660                         'successful fixes anyway'
   663                         b'successful fixes anyway'
   661                     ),
   664                     ),
   662                 )
   665                 )
   663     return metadata, newdata
   666     return metadata, newdata
   664 
   667 
   665 
   668 
   669     Uses the revision number and fixername to give more context to each line of
   672     Uses the revision number and fixername to give more context to each line of
   670     the error message. Doesn't include file names, since those take up a lot of
   673     the error message. Doesn't include file names, since those take up a lot of
   671     space and would tend to be included in the error message if they were
   674     space and would tend to be included in the error message if they were
   672     relevant.
   675     relevant.
   673     """
   676     """
   674     for line in re.split('[\r\n]+', stderr):
   677     for line in re.split(b'[\r\n]+', stderr):
   675         if line:
   678         if line:
   676             ui.warn('[')
   679             ui.warn(b'[')
   677             if rev is None:
   680             if rev is None:
   678                 ui.warn(_('wdir'), label='evolve.rev')
   681                 ui.warn(_(b'wdir'), label=b'evolve.rev')
   679             else:
   682             else:
   680                 ui.warn((str(rev)), label='evolve.rev')
   683                 ui.warn((str(rev)), label=b'evolve.rev')
   681             ui.warn('] %s: %s\n' % (fixername, line))
   684             ui.warn(b'] %s: %s\n' % (fixername, line))
   682 
   685 
   683 
   686 
   684 def writeworkingdir(repo, ctx, filedata, replacements):
   687 def writeworkingdir(repo, ctx, filedata, replacements):
   685     """Write new content to the working copy and check out the new p1 if any
   688     """Write new content to the working copy and check out the new p1 if any
   686 
   689 
   692     Directly updates the dirstate for the affected files.
   695     Directly updates the dirstate for the affected files.
   693     """
   696     """
   694     for path, data in filedata.iteritems():
   697     for path, data in filedata.iteritems():
   695         fctx = ctx[path]
   698         fctx = ctx[path]
   696         fctx.write(data, fctx.flags())
   699         fctx.write(data, fctx.flags())
   697         if repo.dirstate[path] == 'n':
   700         if repo.dirstate[path] == b'n':
   698             repo.dirstate.normallookup(path)
   701             repo.dirstate.normallookup(path)
   699 
   702 
   700     oldparentnodes = repo.dirstate.parents()
   703     oldparentnodes = repo.dirstate.parents()
   701     newparentnodes = [replacements.get(n, n) for n in oldparentnodes]
   704     newparentnodes = [replacements.get(n, n) for n in oldparentnodes]
   702     if newparentnodes != oldparentnodes:
   705     if newparentnodes != oldparentnodes:
   755             isexec=fctx.isexec(),
   758             isexec=fctx.isexec(),
   756             copysource=copysource,
   759             copysource=copysource,
   757         )
   760         )
   758 
   761 
   759     extra = ctx.extra().copy()
   762     extra = ctx.extra().copy()
   760     extra['fix_source'] = ctx.hex()
   763     extra[b'fix_source'] = ctx.hex()
   761 
   764 
   762     memctx = context.memctx(
   765     memctx = context.memctx(
   763         repo,
   766         repo,
   764         parents=(newp1node, newp2node),
   767         parents=(newp1node, newp2node),
   765         text=ctx.description(),
   768         text=ctx.description(),
   772         editor=None,
   775         editor=None,
   773     )
   776     )
   774     sucnode = memctx.commit()
   777     sucnode = memctx.commit()
   775     prenode = ctx.node()
   778     prenode = ctx.node()
   776     if prenode == sucnode:
   779     if prenode == sucnode:
   777         ui.debug('node %s already existed\n' % (ctx.hex()))
   780         ui.debug(b'node %s already existed\n' % (ctx.hex()))
   778     else:
   781     else:
   779         replacements[ctx.node()] = sucnode
   782         replacements[ctx.node()] = sucnode
   780 
   783 
   781 
   784 
   782 def getfixers(ui):
   785 def getfixers(ui):
   786     fixer's config suboptions. Does not validate the config values.
   789     fixer's config suboptions. Does not validate the config values.
   787     """
   790     """
   788     fixers = {}
   791     fixers = {}
   789     for name in fixernames(ui):
   792     for name in fixernames(ui):
   790         fixers[name] = Fixer()
   793         fixers[name] = Fixer()
   791         attrs = ui.configsuboptions('fix', name)[1]
   794         attrs = ui.configsuboptions(b'fix', name)[1]
   792         for key, default in FIXER_ATTRS.items():
   795         for key, default in FIXER_ATTRS.items():
   793             setattr(
   796             setattr(
   794                 fixers[name],
   797                 fixers[name],
   795                 pycompat.sysstr('_' + key),
   798                 pycompat.sysstr(b'_' + key),
   796                 attrs.get(key, default),
   799                 attrs.get(key, default),
   797             )
   800             )
   798         fixers[name]._priority = int(fixers[name]._priority)
   801         fixers[name]._priority = int(fixers[name]._priority)
   799         fixers[name]._metadata = stringutil.parsebool(fixers[name]._metadata)
   802         fixers[name]._metadata = stringutil.parsebool(fixers[name]._metadata)
   800         fixers[name]._skipclean = stringutil.parsebool(fixers[name]._skipclean)
   803         fixers[name]._skipclean = stringutil.parsebool(fixers[name]._skipclean)
   803         # dangerous to let it affect all files. It would be pointless to let it
   806         # dangerous to let it affect all files. It would be pointless to let it
   804         # affect no files. There is no reasonable subset of files to use as the
   807         # affect no files. There is no reasonable subset of files to use as the
   805         # default.
   808         # default.
   806         if fixers[name]._pattern is None:
   809         if fixers[name]._pattern is None:
   807             ui.warn(
   810             ui.warn(
   808                 _('fixer tool has no pattern configuration: %s\n') % (name,)
   811                 _(b'fixer tool has no pattern configuration: %s\n') % (name,)
   809             )
   812             )
   810             del fixers[name]
   813             del fixers[name]
   811         elif not fixers[name]._enabled:
   814         elif not fixers[name]._enabled:
   812             ui.debug('ignoring disabled fixer tool: %s\n' % (name,))
   815             ui.debug(b'ignoring disabled fixer tool: %s\n' % (name,))
   813             del fixers[name]
   816             del fixers[name]
   814     return collections.OrderedDict(
   817     return collections.OrderedDict(
   815         sorted(fixers.items(), key=lambda item: item[1]._priority, reverse=True)
   818         sorted(fixers.items(), key=lambda item: item[1]._priority, reverse=True)
   816     )
   819     )
   817 
   820 
   818 
   821 
   819 def fixernames(ui):
   822 def fixernames(ui):
   820     """Returns the names of [fix] config options that have suboptions"""
   823     """Returns the names of [fix] config options that have suboptions"""
   821     names = set()
   824     names = set()
   822     for k, v in ui.configitems('fix'):
   825     for k, v in ui.configitems(b'fix'):
   823         if ':' in k:
   826         if b':' in k:
   824             names.add(k.split(':', 1)[0])
   827             names.add(k.split(b':', 1)[0])
   825     return names
   828     return names
   826 
   829 
   827 
   830 
   828 class Fixer(object):
   831 class Fixer(object):
   829     """Wraps the raw config values for a fixer with methods"""
   832     """Wraps the raw config values for a fixer with methods"""
   847         expand = cmdutil.rendercommandtemplate
   850         expand = cmdutil.rendercommandtemplate
   848         parts = [
   851         parts = [
   849             expand(
   852             expand(
   850                 ui,
   853                 ui,
   851                 self._command,
   854                 self._command,
   852                 {'rootpath': path, 'basename': os.path.basename(path)},
   855                 {b'rootpath': path, b'basename': os.path.basename(path)},
   853             )
   856             )
   854         ]
   857         ]
   855         if self._linerange:
   858         if self._linerange:
   856             if self._skipclean and not ranges:
   859             if self._skipclean and not ranges:
   857                 # No line ranges to fix, so don't run the fixer.
   860                 # No line ranges to fix, so don't run the fixer.
   858                 return None
   861                 return None
   859             for first, last in ranges:
   862             for first, last in ranges:
   860                 parts.append(
   863                 parts.append(
   861                     expand(ui, self._linerange, {'first': first, 'last': last})
   864                     expand(
       
   865                         ui, self._linerange, {b'first': first, b'last': last}
       
   866                     )
   862                 )
   867                 )
   863         return ' '.join(parts)
   868         return b' '.join(parts)