hgext/eol.py
changeset 43076 2372284d9457
parent 42620 d98ec36be808
child 43077 687b865b95ad
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
   104     pycompat,
   104     pycompat,
   105     registrar,
   105     registrar,
   106     scmutil,
   106     scmutil,
   107     util,
   107     util,
   108 )
   108 )
   109 from mercurial.utils import (
   109 from mercurial.utils import stringutil
   110     stringutil,
       
   111 )
       
   112 
   110 
   113 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
   111 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
   114 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
   112 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
   115 # be specifying the version(s) of Mercurial they are tested with, or
   113 # be specifying the version(s) of Mercurial they are tested with, or
   116 # leave the attribute unspecified.
   114 # leave the attribute unspecified.
   117 testedwith = 'ships-with-hg-core'
   115 testedwith = 'ships-with-hg-core'
   118 
   116 
   119 configtable = {}
   117 configtable = {}
   120 configitem = registrar.configitem(configtable)
   118 configitem = registrar.configitem(configtable)
   121 
   119 
   122 configitem('eol', 'fix-trailing-newline',
   120 configitem(
   123     default=False,
   121     'eol', 'fix-trailing-newline', default=False,
   124 )
   122 )
   125 configitem('eol', 'native',
   123 configitem(
   126     default=pycompat.oslinesep,
   124     'eol', 'native', default=pycompat.oslinesep,
   127 )
   125 )
   128 configitem('eol', 'only-consistent',
   126 configitem(
   129     default=True,
   127     'eol', 'only-consistent', default=True,
   130 )
   128 )
   131 
   129 
   132 # Matches a lone LF, i.e., one that is not part of CRLF.
   130 # Matches a lone LF, i.e., one that is not part of CRLF.
   133 singlelf = re.compile('(^|[^\r])\n')
   131 singlelf = re.compile('(^|[^\r])\n')
   134 
   132 
       
   133 
   135 def inconsistenteol(data):
   134 def inconsistenteol(data):
   136     return '\r\n' in data and singlelf.search(data)
   135     return '\r\n' in data and singlelf.search(data)
       
   136 
   137 
   137 
   138 def tolf(s, params, ui, **kwargs):
   138 def tolf(s, params, ui, **kwargs):
   139     """Filter to convert to LF EOLs."""
   139     """Filter to convert to LF EOLs."""
   140     if stringutil.binary(s):
   140     if stringutil.binary(s):
   141         return s
   141         return s
   142     if ui.configbool('eol', 'only-consistent') and inconsistenteol(s):
   142     if ui.configbool('eol', 'only-consistent') and inconsistenteol(s):
   143         return s
   143         return s
   144     if (ui.configbool('eol', 'fix-trailing-newline')
   144     if (
   145         and s and not s.endswith('\n')):
   145         ui.configbool('eol', 'fix-trailing-newline')
       
   146         and s
       
   147         and not s.endswith('\n')
       
   148     ):
   146         s = s + '\n'
   149         s = s + '\n'
   147     return util.tolf(s)
   150     return util.tolf(s)
       
   151 
   148 
   152 
   149 def tocrlf(s, params, ui, **kwargs):
   153 def tocrlf(s, params, ui, **kwargs):
   150     """Filter to convert to CRLF EOLs."""
   154     """Filter to convert to CRLF EOLs."""
   151     if stringutil.binary(s):
   155     if stringutil.binary(s):
   152         return s
   156         return s
   153     if ui.configbool('eol', 'only-consistent') and inconsistenteol(s):
   157     if ui.configbool('eol', 'only-consistent') and inconsistenteol(s):
   154         return s
   158         return s
   155     if (ui.configbool('eol', 'fix-trailing-newline')
   159     if (
   156         and s and not s.endswith('\n')):
   160         ui.configbool('eol', 'fix-trailing-newline')
       
   161         and s
       
   162         and not s.endswith('\n')
       
   163     ):
   157         s = s + '\n'
   164         s = s + '\n'
   158     return util.tocrlf(s)
   165     return util.tocrlf(s)
       
   166 
   159 
   167 
   160 def isbinary(s, params):
   168 def isbinary(s, params):
   161     """Filter to do nothing with the file."""
   169     """Filter to do nothing with the file."""
   162     return s
   170     return s
       
   171 
   163 
   172 
   164 filters = {
   173 filters = {
   165     'to-lf': tolf,
   174     'to-lf': tolf,
   166     'to-crlf': tocrlf,
   175     'to-crlf': tocrlf,
   167     'is-binary': isbinary,
   176     'is-binary': isbinary,
   168     # The following provide backwards compatibility with win32text
   177     # The following provide backwards compatibility with win32text
   169     'cleverencode:': tolf,
   178     'cleverencode:': tolf,
   170     'cleverdecode:': tocrlf
   179     'cleverdecode:': tocrlf,
   171 }
   180 }
       
   181 
   172 
   182 
   173 class eolfile(object):
   183 class eolfile(object):
   174     def __init__(self, ui, root, data):
   184     def __init__(self, ui, root, data):
   175         self._decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
   185         self._decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
   176         self._encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
   186         self._encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
   206         for pattern, key, m in self.patterns:
   216         for pattern, key, m in self.patterns:
   207             try:
   217             try:
   208                 ui.setconfig('decode', pattern, self._decode[key], 'eol')
   218                 ui.setconfig('decode', pattern, self._decode[key], 'eol')
   209                 ui.setconfig('encode', pattern, self._encode[key], 'eol')
   219                 ui.setconfig('encode', pattern, self._encode[key], 'eol')
   210             except KeyError:
   220             except KeyError:
   211                 ui.warn(_("ignoring unknown EOL style '%s' from %s\n")
   221                 ui.warn(
   212                         % (key, self.cfg.source('patterns', pattern)))
   222                     _("ignoring unknown EOL style '%s' from %s\n")
       
   223                     % (key, self.cfg.source('patterns', pattern))
       
   224                 )
   213         # eol.only-consistent can be specified in ~/.hgrc or .hgeol
   225         # eol.only-consistent can be specified in ~/.hgrc or .hgeol
   214         for k, v in self.cfg.items('eol'):
   226         for k, v in self.cfg.items('eol'):
   215             ui.setconfig('eol', k, v, 'eol')
   227             ui.setconfig('eol', k, v, 'eol')
   216 
   228 
   217     def checkrev(self, repo, ctx, files):
   229     def checkrev(self, repo, ctx, files):
   218         failed = []
   230         failed = []
   219         for f in (files or ctx.files()):
   231         for f in files or ctx.files():
   220             if f not in ctx:
   232             if f not in ctx:
   221                 continue
   233                 continue
   222             for pattern, key, m in self.patterns:
   234             for pattern, key, m in self.patterns:
   223                 if not m(f):
   235                 if not m(f):
   224                     continue
   236                     continue
   225                 target = self._encode[key]
   237                 target = self._encode[key]
   226                 data = ctx[f].data()
   238                 data = ctx[f].data()
   227                 if (target == "to-lf" and "\r\n" in data
   239                 if (
   228                     or target == "to-crlf" and singlelf.search(data)):
   240                     target == "to-lf"
       
   241                     and "\r\n" in data
       
   242                     or target == "to-crlf"
       
   243                     and singlelf.search(data)
       
   244                 ):
   229                     failed.append((f, target, bytes(ctx)))
   245                     failed.append((f, target, bytes(ctx)))
   230                 break
   246                 break
   231         return failed
   247         return failed
       
   248 
   232 
   249 
   233 def parseeol(ui, repo, nodes):
   250 def parseeol(ui, repo, nodes):
   234     try:
   251     try:
   235         for node in nodes:
   252         for node in nodes:
   236             try:
   253             try:
   242                     data = repo[node]['.hgeol'].data()
   259                     data = repo[node]['.hgeol'].data()
   243                 return eolfile(ui, repo.root, data)
   260                 return eolfile(ui, repo.root, data)
   244             except (IOError, LookupError):
   261             except (IOError, LookupError):
   245                 pass
   262                 pass
   246     except errormod.ParseError as inst:
   263     except errormod.ParseError as inst:
   247         ui.warn(_("warning: ignoring .hgeol file due to parse error "
   264         ui.warn(
   248                   "at %s: %s\n") % (inst.args[1], inst.args[0]))
   265             _("warning: ignoring .hgeol file due to parse error " "at %s: %s\n")
       
   266             % (inst.args[1], inst.args[0])
       
   267         )
   249     return None
   268     return None
       
   269 
   250 
   270 
   251 def ensureenabled(ui):
   271 def ensureenabled(ui):
   252     """make sure the extension is enabled when used as hook
   272     """make sure the extension is enabled when used as hook
   253 
   273 
   254     When eol is used through hooks, the extension is never formally loaded and
   274     When eol is used through hooks, the extension is never formally loaded and
   258     """
   278     """
   259     if 'eol' in ui._knownconfig:
   279     if 'eol' in ui._knownconfig:
   260         return
   280         return
   261     ui.setconfig('extensions', 'eol', '', source='internal')
   281     ui.setconfig('extensions', 'eol', '', source='internal')
   262     extensions.loadall(ui, ['eol'])
   282     extensions.loadall(ui, ['eol'])
       
   283 
   263 
   284 
   264 def _checkhook(ui, repo, node, headsonly):
   285 def _checkhook(ui, repo, node, headsonly):
   265     # Get revisions to check and touched files at the same time
   286     # Get revisions to check and touched files at the same time
   266     ensureenabled(ui)
   287     ensureenabled(ui)
   267     files = set()
   288     files = set()
   282 
   303 
   283     if failed:
   304     if failed:
   284         eols = {'to-lf': 'CRLF', 'to-crlf': 'LF'}
   305         eols = {'to-lf': 'CRLF', 'to-crlf': 'LF'}
   285         msgs = []
   306         msgs = []
   286         for f, target, node in sorted(failed):
   307         for f, target, node in sorted(failed):
   287             msgs.append(_("  %s in %s should not have %s line endings") %
   308             msgs.append(
   288                         (f, node, eols[target]))
   309                 _("  %s in %s should not have %s line endings")
       
   310                 % (f, node, eols[target])
       
   311             )
   289         raise errormod.Abort(_("end-of-line check failed:\n") + "\n".join(msgs))
   312         raise errormod.Abort(_("end-of-line check failed:\n") + "\n".join(msgs))
       
   313 
   290 
   314 
   291 def checkallhook(ui, repo, node, hooktype, **kwargs):
   315 def checkallhook(ui, repo, node, hooktype, **kwargs):
   292     """verify that files have expected EOLs"""
   316     """verify that files have expected EOLs"""
   293     _checkhook(ui, repo, node, False)
   317     _checkhook(ui, repo, node, False)
   294 
   318 
       
   319 
   295 def checkheadshook(ui, repo, node, hooktype, **kwargs):
   320 def checkheadshook(ui, repo, node, hooktype, **kwargs):
   296     """verify that files have expected EOLs"""
   321     """verify that files have expected EOLs"""
   297     _checkhook(ui, repo, node, True)
   322     _checkhook(ui, repo, node, True)
   298 
   323 
       
   324 
   299 # "checkheadshook" used to be called "hook"
   325 # "checkheadshook" used to be called "hook"
   300 hook = checkheadshook
   326 hook = checkheadshook
       
   327 
   301 
   328 
   302 def preupdate(ui, repo, hooktype, parent1, parent2):
   329 def preupdate(ui, repo, hooktype, parent1, parent2):
   303     p1node = scmutil.resolvehexnodeidprefix(repo, parent1)
   330     p1node = scmutil.resolvehexnodeidprefix(repo, parent1)
   304     repo.loadeol([p1node])
   331     repo.loadeol([p1node])
   305     return False
   332     return False
   306 
   333 
       
   334 
   307 def uisetup(ui):
   335 def uisetup(ui):
   308     ui.setconfig('hooks', 'preupdate.eol', preupdate, 'eol')
   336     ui.setconfig('hooks', 'preupdate.eol', preupdate, 'eol')
       
   337 
   309 
   338 
   310 def extsetup(ui):
   339 def extsetup(ui):
   311     try:
   340     try:
   312         extensions.find('win32text')
   341         extensions.find('win32text')
   313         ui.warn(_("the eol extension is incompatible with the "
   342         ui.warn(
   314                   "win32text extension\n"))
   343             _(
       
   344                 "the eol extension is incompatible with the "
       
   345                 "win32text extension\n"
       
   346             )
       
   347         )
   315     except KeyError:
   348     except KeyError:
   316         pass
   349         pass
   317 
   350 
   318 
   351 
   319 def reposetup(ui, repo):
   352 def reposetup(ui, repo):
   325         repo.adddatafilter(name, fn)
   358         repo.adddatafilter(name, fn)
   326 
   359 
   327     ui.setconfig('patch', 'eol', 'auto', 'eol')
   360     ui.setconfig('patch', 'eol', 'auto', 'eol')
   328 
   361 
   329     class eolrepo(repo.__class__):
   362     class eolrepo(repo.__class__):
   330 
       
   331         def loadeol(self, nodes):
   363         def loadeol(self, nodes):
   332             eol = parseeol(self.ui, self, nodes)
   364             eol = parseeol(self.ui, self, nodes)
   333             if eol is None:
   365             if eol is None:
   334                 return None
   366                 return None
   335             eol.copytoui(self.ui)
   367             eol.copytoui(self.ui)
   412                     # We should not abort here, since the user should
   444                     # We should not abort here, since the user should
   413                     # be able to say "** = native" to automatically
   445                     # be able to say "** = native" to automatically
   414                     # have all non-binary files taken care of.
   446                     # have all non-binary files taken care of.
   415                     continue
   447                     continue
   416                 if inconsistenteol(data):
   448                 if inconsistenteol(data):
   417                     raise errormod.Abort(_("inconsistent newline style "
   449                     raise errormod.Abort(
   418                                            "in %s\n") % f)
   450                         _("inconsistent newline style " "in %s\n") % f
       
   451                     )
   419             return super(eolrepo, self).commitctx(ctx, error, origctx)
   452             return super(eolrepo, self).commitctx(ctx, error, origctx)
       
   453 
   420     repo.__class__ = eolrepo
   454     repo.__class__ = eolrepo
   421     repo._hgcleardirstate()
   455     repo._hgcleardirstate()