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 |
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): |
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 |
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""" |