##// END OF EJS Templates
eol: fix update - don't use and apply removed .hgeol patterns...
Mads Kiilerich -
r43476:dfaa477e default
parent child Browse files
Show More
@@ -1,468 +1,474
1 """automatically manage newlines in repository files
1 """automatically manage newlines in repository files
2
2
3 This extension allows you to manage the type of line endings (CRLF or
3 This extension allows you to manage the type of line endings (CRLF or
4 LF) that are used in the repository and in the local working
4 LF) that are used in the repository and in the local working
5 directory. That way you can get CRLF line endings on Windows and LF on
5 directory. That way you can get CRLF line endings on Windows and LF on
6 Unix/Mac, thereby letting everybody use their OS native line endings.
6 Unix/Mac, thereby letting everybody use their OS native line endings.
7
7
8 The extension reads its configuration from a versioned ``.hgeol``
8 The extension reads its configuration from a versioned ``.hgeol``
9 configuration file found in the root of the working directory. The
9 configuration file found in the root of the working directory. The
10 ``.hgeol`` file use the same syntax as all other Mercurial
10 ``.hgeol`` file use the same syntax as all other Mercurial
11 configuration files. It uses two sections, ``[patterns]`` and
11 configuration files. It uses two sections, ``[patterns]`` and
12 ``[repository]``.
12 ``[repository]``.
13
13
14 The ``[patterns]`` section specifies how line endings should be
14 The ``[patterns]`` section specifies how line endings should be
15 converted between the working directory and the repository. The format is
15 converted between the working directory and the repository. The format is
16 specified by a file pattern. The first match is used, so put more
16 specified by a file pattern. The first match is used, so put more
17 specific patterns first. The available line endings are ``LF``,
17 specific patterns first. The available line endings are ``LF``,
18 ``CRLF``, and ``BIN``.
18 ``CRLF``, and ``BIN``.
19
19
20 Files with the declared format of ``CRLF`` or ``LF`` are always
20 Files with the declared format of ``CRLF`` or ``LF`` are always
21 checked out and stored in the repository in that format and files
21 checked out and stored in the repository in that format and files
22 declared to be binary (``BIN``) are left unchanged. Additionally,
22 declared to be binary (``BIN``) are left unchanged. Additionally,
23 ``native`` is an alias for checking out in the platform's default line
23 ``native`` is an alias for checking out in the platform's default line
24 ending: ``LF`` on Unix (including Mac OS X) and ``CRLF`` on
24 ending: ``LF`` on Unix (including Mac OS X) and ``CRLF`` on
25 Windows. Note that ``BIN`` (do nothing to line endings) is Mercurial's
25 Windows. Note that ``BIN`` (do nothing to line endings) is Mercurial's
26 default behavior; it is only needed if you need to override a later,
26 default behavior; it is only needed if you need to override a later,
27 more general pattern.
27 more general pattern.
28
28
29 The optional ``[repository]`` section specifies the line endings to
29 The optional ``[repository]`` section specifies the line endings to
30 use for files stored in the repository. It has a single setting,
30 use for files stored in the repository. It has a single setting,
31 ``native``, which determines the storage line endings for files
31 ``native``, which determines the storage line endings for files
32 declared as ``native`` in the ``[patterns]`` section. It can be set to
32 declared as ``native`` in the ``[patterns]`` section. It can be set to
33 ``LF`` or ``CRLF``. The default is ``LF``. For example, this means
33 ``LF`` or ``CRLF``. The default is ``LF``. For example, this means
34 that on Windows, files configured as ``native`` (``CRLF`` by default)
34 that on Windows, files configured as ``native`` (``CRLF`` by default)
35 will be converted to ``LF`` when stored in the repository. Files
35 will be converted to ``LF`` when stored in the repository. Files
36 declared as ``LF``, ``CRLF``, or ``BIN`` in the ``[patterns]`` section
36 declared as ``LF``, ``CRLF``, or ``BIN`` in the ``[patterns]`` section
37 are always stored as-is in the repository.
37 are always stored as-is in the repository.
38
38
39 Example versioned ``.hgeol`` file::
39 Example versioned ``.hgeol`` file::
40
40
41 [patterns]
41 [patterns]
42 **.py = native
42 **.py = native
43 **.vcproj = CRLF
43 **.vcproj = CRLF
44 **.txt = native
44 **.txt = native
45 Makefile = LF
45 Makefile = LF
46 **.jpg = BIN
46 **.jpg = BIN
47
47
48 [repository]
48 [repository]
49 native = LF
49 native = LF
50
50
51 .. note::
51 .. note::
52
52
53 The rules will first apply when files are touched in the working
53 The rules will first apply when files are touched in the working
54 directory, e.g. by updating to null and back to tip to touch all files.
54 directory, e.g. by updating to null and back to tip to touch all files.
55
55
56 The extension uses an optional ``[eol]`` section read from both the
56 The extension uses an optional ``[eol]`` section read from both the
57 normal Mercurial configuration files and the ``.hgeol`` file, with the
57 normal Mercurial configuration files and the ``.hgeol`` file, with the
58 latter overriding the former. You can use that section to control the
58 latter overriding the former. You can use that section to control the
59 overall behavior. There are three settings:
59 overall behavior. There are three settings:
60
60
61 - ``eol.native`` (default ``os.linesep``) can be set to ``LF`` or
61 - ``eol.native`` (default ``os.linesep``) can be set to ``LF`` or
62 ``CRLF`` to override the default interpretation of ``native`` for
62 ``CRLF`` to override the default interpretation of ``native`` for
63 checkout. This can be used with :hg:`archive` on Unix, say, to
63 checkout. This can be used with :hg:`archive` on Unix, say, to
64 generate an archive where files have line endings for Windows.
64 generate an archive where files have line endings for Windows.
65
65
66 - ``eol.only-consistent`` (default True) can be set to False to make
66 - ``eol.only-consistent`` (default True) can be set to False to make
67 the extension convert files with inconsistent EOLs. Inconsistent
67 the extension convert files with inconsistent EOLs. Inconsistent
68 means that there is both ``CRLF`` and ``LF`` present in the file.
68 means that there is both ``CRLF`` and ``LF`` present in the file.
69 Such files are normally not touched under the assumption that they
69 Such files are normally not touched under the assumption that they
70 have mixed EOLs on purpose.
70 have mixed EOLs on purpose.
71
71
72 - ``eol.fix-trailing-newline`` (default False) can be set to True to
72 - ``eol.fix-trailing-newline`` (default False) can be set to True to
73 ensure that converted files end with a EOL character (either ``\\n``
73 ensure that converted files end with a EOL character (either ``\\n``
74 or ``\\r\\n`` as per the configured patterns).
74 or ``\\r\\n`` as per the configured patterns).
75
75
76 The extension provides ``cleverencode:`` and ``cleverdecode:`` filters
76 The extension provides ``cleverencode:`` and ``cleverdecode:`` filters
77 like the deprecated win32text extension does. This means that you can
77 like the deprecated win32text extension does. This means that you can
78 disable win32text and enable eol and your filters will still work. You
78 disable win32text and enable eol and your filters will still work. You
79 only need to these filters until you have prepared a ``.hgeol`` file.
79 only need to these filters until you have prepared a ``.hgeol`` file.
80
80
81 The ``win32text.forbid*`` hooks provided by the win32text extension
81 The ``win32text.forbid*`` hooks provided by the win32text extension
82 have been unified into a single hook named ``eol.checkheadshook``. The
82 have been unified into a single hook named ``eol.checkheadshook``. The
83 hook will lookup the expected line endings from the ``.hgeol`` file,
83 hook will lookup the expected line endings from the ``.hgeol`` file,
84 which means you must migrate to a ``.hgeol`` file first before using
84 which means you must migrate to a ``.hgeol`` file first before using
85 the hook. ``eol.checkheadshook`` only checks heads, intermediate
85 the hook. ``eol.checkheadshook`` only checks heads, intermediate
86 invalid revisions will be pushed. To forbid them completely, use the
86 invalid revisions will be pushed. To forbid them completely, use the
87 ``eol.checkallhook`` hook. These hooks are best used as
87 ``eol.checkallhook`` hook. These hooks are best used as
88 ``pretxnchangegroup`` hooks.
88 ``pretxnchangegroup`` hooks.
89
89
90 See :hg:`help patterns` for more information about the glob patterns
90 See :hg:`help patterns` for more information about the glob patterns
91 used.
91 used.
92 """
92 """
93
93
94 from __future__ import absolute_import
94 from __future__ import absolute_import
95
95
96 import os
96 import os
97 import re
97 import re
98 from mercurial.i18n import _
98 from mercurial.i18n import _
99 from mercurial import (
99 from mercurial import (
100 config,
100 config,
101 error as errormod,
101 error as errormod,
102 extensions,
102 extensions,
103 match,
103 match,
104 pycompat,
104 pycompat,
105 registrar,
105 registrar,
106 scmutil,
106 scmutil,
107 util,
107 util,
108 )
108 )
109 from mercurial.utils import stringutil
109 from mercurial.utils import stringutil
110
110
111 # 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
112 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
112 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
113 # 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
114 # leave the attribute unspecified.
114 # leave the attribute unspecified.
115 testedwith = b'ships-with-hg-core'
115 testedwith = b'ships-with-hg-core'
116
116
117 configtable = {}
117 configtable = {}
118 configitem = registrar.configitem(configtable)
118 configitem = registrar.configitem(configtable)
119
119
120 configitem(
120 configitem(
121 b'eol', b'fix-trailing-newline', default=False,
121 b'eol', b'fix-trailing-newline', default=False,
122 )
122 )
123 configitem(
123 configitem(
124 b'eol', b'native', default=pycompat.oslinesep,
124 b'eol', b'native', default=pycompat.oslinesep,
125 )
125 )
126 configitem(
126 configitem(
127 b'eol', b'only-consistent', default=True,
127 b'eol', b'only-consistent', default=True,
128 )
128 )
129
129
130 # 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.
131 singlelf = re.compile(b'(^|[^\r])\n')
131 singlelf = re.compile(b'(^|[^\r])\n')
132
132
133
133
134 def inconsistenteol(data):
134 def inconsistenteol(data):
135 return b'\r\n' in data and singlelf.search(data)
135 return b'\r\n' in data and singlelf.search(data)
136
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(b'eol', b'only-consistent') and inconsistenteol(s):
142 if ui.configbool(b'eol', b'only-consistent') and inconsistenteol(s):
143 return s
143 return s
144 if (
144 if (
145 ui.configbool(b'eol', b'fix-trailing-newline')
145 ui.configbool(b'eol', b'fix-trailing-newline')
146 and s
146 and s
147 and not s.endswith(b'\n')
147 and not s.endswith(b'\n')
148 ):
148 ):
149 s = s + b'\n'
149 s = s + b'\n'
150 return util.tolf(s)
150 return util.tolf(s)
151
151
152
152
153 def tocrlf(s, params, ui, **kwargs):
153 def tocrlf(s, params, ui, **kwargs):
154 """Filter to convert to CRLF EOLs."""
154 """Filter to convert to CRLF EOLs."""
155 if stringutil.binary(s):
155 if stringutil.binary(s):
156 return s
156 return s
157 if ui.configbool(b'eol', b'only-consistent') and inconsistenteol(s):
157 if ui.configbool(b'eol', b'only-consistent') and inconsistenteol(s):
158 return s
158 return s
159 if (
159 if (
160 ui.configbool(b'eol', b'fix-trailing-newline')
160 ui.configbool(b'eol', b'fix-trailing-newline')
161 and s
161 and s
162 and not s.endswith(b'\n')
162 and not s.endswith(b'\n')
163 ):
163 ):
164 s = s + b'\n'
164 s = s + b'\n'
165 return util.tocrlf(s)
165 return util.tocrlf(s)
166
166
167
167
168 def isbinary(s, params, ui, **kwargs):
168 def isbinary(s, params, ui, **kwargs):
169 """Filter to do nothing with the file."""
169 """Filter to do nothing with the file."""
170 return s
170 return s
171
171
172
172
173 filters = {
173 filters = {
174 b'to-lf': tolf,
174 b'to-lf': tolf,
175 b'to-crlf': tocrlf,
175 b'to-crlf': tocrlf,
176 b'is-binary': isbinary,
176 b'is-binary': isbinary,
177 # The following provide backwards compatibility with win32text
177 # The following provide backwards compatibility with win32text
178 b'cleverencode:': tolf,
178 b'cleverencode:': tolf,
179 b'cleverdecode:': tocrlf,
179 b'cleverdecode:': tocrlf,
180 }
180 }
181
181
182
182
183 class eolfile(object):
183 class eolfile(object):
184 def __init__(self, ui, root, data):
184 def __init__(self, ui, root, data):
185 self._decode = {
185 self._decode = {
186 b'LF': b'to-lf',
186 b'LF': b'to-lf',
187 b'CRLF': b'to-crlf',
187 b'CRLF': b'to-crlf',
188 b'BIN': b'is-binary',
188 b'BIN': b'is-binary',
189 }
189 }
190 self._encode = {
190 self._encode = {
191 b'LF': b'to-lf',
191 b'LF': b'to-lf',
192 b'CRLF': b'to-crlf',
192 b'CRLF': b'to-crlf',
193 b'BIN': b'is-binary',
193 b'BIN': b'is-binary',
194 }
194 }
195
195
196 self.cfg = config.config()
196 self.cfg = config.config()
197 # Our files should not be touched. The pattern must be
197 # Our files should not be touched. The pattern must be
198 # inserted first override a '** = native' pattern.
198 # inserted first override a '** = native' pattern.
199 self.cfg.set(b'patterns', b'.hg*', b'BIN', b'eol')
199 self.cfg.set(b'patterns', b'.hg*', b'BIN', b'eol')
200 # We can then parse the user's patterns.
200 # We can then parse the user's patterns.
201 self.cfg.parse(b'.hgeol', data)
201 self.cfg.parse(b'.hgeol', data)
202
202
203 isrepolf = self.cfg.get(b'repository', b'native') != b'CRLF'
203 isrepolf = self.cfg.get(b'repository', b'native') != b'CRLF'
204 self._encode[b'NATIVE'] = isrepolf and b'to-lf' or b'to-crlf'
204 self._encode[b'NATIVE'] = isrepolf and b'to-lf' or b'to-crlf'
205 iswdlf = ui.config(b'eol', b'native') in (b'LF', b'\n')
205 iswdlf = ui.config(b'eol', b'native') in (b'LF', b'\n')
206 self._decode[b'NATIVE'] = iswdlf and b'to-lf' or b'to-crlf'
206 self._decode[b'NATIVE'] = iswdlf and b'to-lf' or b'to-crlf'
207
207
208 include = []
208 include = []
209 exclude = []
209 exclude = []
210 self.patterns = []
210 self.patterns = []
211 for pattern, style in self.cfg.items(b'patterns'):
211 for pattern, style in self.cfg.items(b'patterns'):
212 key = style.upper()
212 key = style.upper()
213 if key == b'BIN':
213 if key == b'BIN':
214 exclude.append(pattern)
214 exclude.append(pattern)
215 else:
215 else:
216 include.append(pattern)
216 include.append(pattern)
217 m = match.match(root, b'', [pattern])
217 m = match.match(root, b'', [pattern])
218 self.patterns.append((pattern, key, m))
218 self.patterns.append((pattern, key, m))
219 # This will match the files for which we need to care
219 # This will match the files for which we need to care
220 # about inconsistent newlines.
220 # about inconsistent newlines.
221 self.match = match.match(root, b'', [], include, exclude)
221 self.match = match.match(root, b'', [], include, exclude)
222
222
223 def copytoui(self, ui):
223 def copytoui(self, ui):
224 newpatterns = set(pattern for pattern, key, m in self.patterns)
225 for section in (b'decode', b'encode'):
226 for oldpattern, _filter in ui.configitems(section):
227 if oldpattern not in newpatterns:
228 if ui.configsource(section, oldpattern) == b'eol':
229 ui.setconfig(section, oldpattern, b'!', b'eol')
224 for pattern, key, m in self.patterns:
230 for pattern, key, m in self.patterns:
225 try:
231 try:
226 ui.setconfig(b'decode', pattern, self._decode[key], b'eol')
232 ui.setconfig(b'decode', pattern, self._decode[key], b'eol')
227 ui.setconfig(b'encode', pattern, self._encode[key], b'eol')
233 ui.setconfig(b'encode', pattern, self._encode[key], b'eol')
228 except KeyError:
234 except KeyError:
229 ui.warn(
235 ui.warn(
230 _(b"ignoring unknown EOL style '%s' from %s\n")
236 _(b"ignoring unknown EOL style '%s' from %s\n")
231 % (key, self.cfg.source(b'patterns', pattern))
237 % (key, self.cfg.source(b'patterns', pattern))
232 )
238 )
233 # eol.only-consistent can be specified in ~/.hgrc or .hgeol
239 # eol.only-consistent can be specified in ~/.hgrc or .hgeol
234 for k, v in self.cfg.items(b'eol'):
240 for k, v in self.cfg.items(b'eol'):
235 ui.setconfig(b'eol', k, v, b'eol')
241 ui.setconfig(b'eol', k, v, b'eol')
236
242
237 def checkrev(self, repo, ctx, files):
243 def checkrev(self, repo, ctx, files):
238 failed = []
244 failed = []
239 for f in files or ctx.files():
245 for f in files or ctx.files():
240 if f not in ctx:
246 if f not in ctx:
241 continue
247 continue
242 for pattern, key, m in self.patterns:
248 for pattern, key, m in self.patterns:
243 if not m(f):
249 if not m(f):
244 continue
250 continue
245 target = self._encode[key]
251 target = self._encode[key]
246 data = ctx[f].data()
252 data = ctx[f].data()
247 if (
253 if (
248 target == b"to-lf"
254 target == b"to-lf"
249 and b"\r\n" in data
255 and b"\r\n" in data
250 or target == b"to-crlf"
256 or target == b"to-crlf"
251 and singlelf.search(data)
257 and singlelf.search(data)
252 ):
258 ):
253 failed.append((f, target, bytes(ctx)))
259 failed.append((f, target, bytes(ctx)))
254 break
260 break
255 return failed
261 return failed
256
262
257
263
258 def parseeol(ui, repo, nodes):
264 def parseeol(ui, repo, nodes):
259 try:
265 try:
260 for node in nodes:
266 for node in nodes:
261 try:
267 try:
262 if node is None:
268 if node is None:
263 # Cannot use workingctx.data() since it would load
269 # Cannot use workingctx.data() since it would load
264 # and cache the filters before we configure them.
270 # and cache the filters before we configure them.
265 data = repo.wvfs(b'.hgeol').read()
271 data = repo.wvfs(b'.hgeol').read()
266 else:
272 else:
267 data = repo[node][b'.hgeol'].data()
273 data = repo[node][b'.hgeol'].data()
268 return eolfile(ui, repo.root, data)
274 return eolfile(ui, repo.root, data)
269 except (IOError, LookupError):
275 except (IOError, LookupError):
270 pass
276 pass
271 except errormod.ParseError as inst:
277 except errormod.ParseError as inst:
272 ui.warn(
278 ui.warn(
273 _(
279 _(
274 b"warning: ignoring .hgeol file due to parse error "
280 b"warning: ignoring .hgeol file due to parse error "
275 b"at %s: %s\n"
281 b"at %s: %s\n"
276 )
282 )
277 % (inst.args[1], inst.args[0])
283 % (inst.args[1], inst.args[0])
278 )
284 )
279 return None
285 return None
280
286
281
287
282 def ensureenabled(ui):
288 def ensureenabled(ui):
283 """make sure the extension is enabled when used as hook
289 """make sure the extension is enabled when used as hook
284
290
285 When eol is used through hooks, the extension is never formally loaded and
291 When eol is used through hooks, the extension is never formally loaded and
286 enabled. This has some side effect, for example the config declaration is
292 enabled. This has some side effect, for example the config declaration is
287 never loaded. This function ensure the extension is enabled when running
293 never loaded. This function ensure the extension is enabled when running
288 hooks.
294 hooks.
289 """
295 """
290 if b'eol' in ui._knownconfig:
296 if b'eol' in ui._knownconfig:
291 return
297 return
292 ui.setconfig(b'extensions', b'eol', b'', source=b'internal')
298 ui.setconfig(b'extensions', b'eol', b'', source=b'internal')
293 extensions.loadall(ui, [b'eol'])
299 extensions.loadall(ui, [b'eol'])
294
300
295
301
296 def _checkhook(ui, repo, node, headsonly):
302 def _checkhook(ui, repo, node, headsonly):
297 # Get revisions to check and touched files at the same time
303 # Get revisions to check and touched files at the same time
298 ensureenabled(ui)
304 ensureenabled(ui)
299 files = set()
305 files = set()
300 revs = set()
306 revs = set()
301 for rev in pycompat.xrange(repo[node].rev(), len(repo)):
307 for rev in pycompat.xrange(repo[node].rev(), len(repo)):
302 revs.add(rev)
308 revs.add(rev)
303 if headsonly:
309 if headsonly:
304 ctx = repo[rev]
310 ctx = repo[rev]
305 files.update(ctx.files())
311 files.update(ctx.files())
306 for pctx in ctx.parents():
312 for pctx in ctx.parents():
307 revs.discard(pctx.rev())
313 revs.discard(pctx.rev())
308 failed = []
314 failed = []
309 for rev in revs:
315 for rev in revs:
310 ctx = repo[rev]
316 ctx = repo[rev]
311 eol = parseeol(ui, repo, [ctx.node()])
317 eol = parseeol(ui, repo, [ctx.node()])
312 if eol:
318 if eol:
313 failed.extend(eol.checkrev(repo, ctx, files))
319 failed.extend(eol.checkrev(repo, ctx, files))
314
320
315 if failed:
321 if failed:
316 eols = {b'to-lf': b'CRLF', b'to-crlf': b'LF'}
322 eols = {b'to-lf': b'CRLF', b'to-crlf': b'LF'}
317 msgs = []
323 msgs = []
318 for f, target, node in sorted(failed):
324 for f, target, node in sorted(failed):
319 msgs.append(
325 msgs.append(
320 _(b" %s in %s should not have %s line endings")
326 _(b" %s in %s should not have %s line endings")
321 % (f, node, eols[target])
327 % (f, node, eols[target])
322 )
328 )
323 raise errormod.Abort(
329 raise errormod.Abort(
324 _(b"end-of-line check failed:\n") + b"\n".join(msgs)
330 _(b"end-of-line check failed:\n") + b"\n".join(msgs)
325 )
331 )
326
332
327
333
328 def checkallhook(ui, repo, node, hooktype, **kwargs):
334 def checkallhook(ui, repo, node, hooktype, **kwargs):
329 """verify that files have expected EOLs"""
335 """verify that files have expected EOLs"""
330 _checkhook(ui, repo, node, False)
336 _checkhook(ui, repo, node, False)
331
337
332
338
333 def checkheadshook(ui, repo, node, hooktype, **kwargs):
339 def checkheadshook(ui, repo, node, hooktype, **kwargs):
334 """verify that files have expected EOLs"""
340 """verify that files have expected EOLs"""
335 _checkhook(ui, repo, node, True)
341 _checkhook(ui, repo, node, True)
336
342
337
343
338 # "checkheadshook" used to be called "hook"
344 # "checkheadshook" used to be called "hook"
339 hook = checkheadshook
345 hook = checkheadshook
340
346
341
347
342 def preupdate(ui, repo, hooktype, parent1, parent2):
348 def preupdate(ui, repo, hooktype, parent1, parent2):
343 p1node = scmutil.resolvehexnodeidprefix(repo, parent1)
349 p1node = scmutil.resolvehexnodeidprefix(repo, parent1)
344 repo.loadeol([p1node])
350 repo.loadeol([p1node])
345 return False
351 return False
346
352
347
353
348 def uisetup(ui):
354 def uisetup(ui):
349 ui.setconfig(b'hooks', b'preupdate.eol', preupdate, b'eol')
355 ui.setconfig(b'hooks', b'preupdate.eol', preupdate, b'eol')
350
356
351
357
352 def extsetup(ui):
358 def extsetup(ui):
353 try:
359 try:
354 extensions.find(b'win32text')
360 extensions.find(b'win32text')
355 ui.warn(
361 ui.warn(
356 _(
362 _(
357 b"the eol extension is incompatible with the "
363 b"the eol extension is incompatible with the "
358 b"win32text extension\n"
364 b"win32text extension\n"
359 )
365 )
360 )
366 )
361 except KeyError:
367 except KeyError:
362 pass
368 pass
363
369
364
370
365 def reposetup(ui, repo):
371 def reposetup(ui, repo):
366 uisetup(repo.ui)
372 uisetup(repo.ui)
367
373
368 if not repo.local():
374 if not repo.local():
369 return
375 return
370 for name, fn in pycompat.iteritems(filters):
376 for name, fn in pycompat.iteritems(filters):
371 repo.adddatafilter(name, fn)
377 repo.adddatafilter(name, fn)
372
378
373 ui.setconfig(b'patch', b'eol', b'auto', b'eol')
379 ui.setconfig(b'patch', b'eol', b'auto', b'eol')
374
380
375 class eolrepo(repo.__class__):
381 class eolrepo(repo.__class__):
376 def loadeol(self, nodes):
382 def loadeol(self, nodes):
377 eol = parseeol(self.ui, self, nodes)
383 eol = parseeol(self.ui, self, nodes)
378 if eol is None:
384 if eol is None:
379 return None
385 return None
380 eol.copytoui(self.ui)
386 eol.copytoui(self.ui)
381 return eol.match
387 return eol.match
382
388
383 def _hgcleardirstate(self):
389 def _hgcleardirstate(self):
384 self._eolmatch = self.loadeol([None, b'tip'])
390 self._eolmatch = self.loadeol([None, b'tip'])
385 if not self._eolmatch:
391 if not self._eolmatch:
386 self._eolmatch = util.never
392 self._eolmatch = util.never
387 return
393 return
388
394
389 oldeol = None
395 oldeol = None
390 try:
396 try:
391 cachemtime = os.path.getmtime(self.vfs.join(b"eol.cache"))
397 cachemtime = os.path.getmtime(self.vfs.join(b"eol.cache"))
392 except OSError:
398 except OSError:
393 cachemtime = 0
399 cachemtime = 0
394 else:
400 else:
395 olddata = self.vfs.read(b"eol.cache")
401 olddata = self.vfs.read(b"eol.cache")
396 if olddata:
402 if olddata:
397 oldeol = eolfile(self.ui, self.root, olddata)
403 oldeol = eolfile(self.ui, self.root, olddata)
398
404
399 try:
405 try:
400 eolmtime = os.path.getmtime(self.wjoin(b".hgeol"))
406 eolmtime = os.path.getmtime(self.wjoin(b".hgeol"))
401 except OSError:
407 except OSError:
402 eolmtime = 0
408 eolmtime = 0
403
409
404 if eolmtime >= cachemtime and eolmtime > 0:
410 if eolmtime >= cachemtime and eolmtime > 0:
405 self.ui.debug(b"eol: detected change in .hgeol\n")
411 self.ui.debug(b"eol: detected change in .hgeol\n")
406
412
407 hgeoldata = self.wvfs.read(b'.hgeol')
413 hgeoldata = self.wvfs.read(b'.hgeol')
408 neweol = eolfile(self.ui, self.root, hgeoldata)
414 neweol = eolfile(self.ui, self.root, hgeoldata)
409
415
410 wlock = None
416 wlock = None
411 try:
417 try:
412 wlock = self.wlock()
418 wlock = self.wlock()
413 for f in self.dirstate:
419 for f in self.dirstate:
414 if self.dirstate[f] != b'n':
420 if self.dirstate[f] != b'n':
415 continue
421 continue
416 if oldeol is not None:
422 if oldeol is not None:
417 if not oldeol.match(f) and not neweol.match(f):
423 if not oldeol.match(f) and not neweol.match(f):
418 continue
424 continue
419 oldkey = None
425 oldkey = None
420 for pattern, key, m in oldeol.patterns:
426 for pattern, key, m in oldeol.patterns:
421 if m(f):
427 if m(f):
422 oldkey = key
428 oldkey = key
423 break
429 break
424 newkey = None
430 newkey = None
425 for pattern, key, m in neweol.patterns:
431 for pattern, key, m in neweol.patterns:
426 if m(f):
432 if m(f):
427 newkey = key
433 newkey = key
428 break
434 break
429 if oldkey == newkey:
435 if oldkey == newkey:
430 continue
436 continue
431 # all normal files need to be looked at again since
437 # all normal files need to be looked at again since
432 # the new .hgeol file specify a different filter
438 # the new .hgeol file specify a different filter
433 self.dirstate.normallookup(f)
439 self.dirstate.normallookup(f)
434 # Write the cache to update mtime and cache .hgeol
440 # Write the cache to update mtime and cache .hgeol
435 with self.vfs(b"eol.cache", b"w") as f:
441 with self.vfs(b"eol.cache", b"w") as f:
436 f.write(hgeoldata)
442 f.write(hgeoldata)
437 except errormod.LockUnavailable:
443 except errormod.LockUnavailable:
438 # If we cannot lock the repository and clear the
444 # If we cannot lock the repository and clear the
439 # dirstate, then a commit might not see all files
445 # dirstate, then a commit might not see all files
440 # as modified. But if we cannot lock the
446 # as modified. But if we cannot lock the
441 # repository, then we can also not make a commit,
447 # repository, then we can also not make a commit,
442 # so ignore the error.
448 # so ignore the error.
443 pass
449 pass
444 finally:
450 finally:
445 if wlock is not None:
451 if wlock is not None:
446 wlock.release()
452 wlock.release()
447
453
448 def commitctx(self, ctx, error=False, origctx=None):
454 def commitctx(self, ctx, error=False, origctx=None):
449 for f in sorted(ctx.added() + ctx.modified()):
455 for f in sorted(ctx.added() + ctx.modified()):
450 if not self._eolmatch(f):
456 if not self._eolmatch(f):
451 continue
457 continue
452 fctx = ctx[f]
458 fctx = ctx[f]
453 if fctx is None:
459 if fctx is None:
454 continue
460 continue
455 data = fctx.data()
461 data = fctx.data()
456 if stringutil.binary(data):
462 if stringutil.binary(data):
457 # We should not abort here, since the user should
463 # We should not abort here, since the user should
458 # be able to say "** = native" to automatically
464 # be able to say "** = native" to automatically
459 # have all non-binary files taken care of.
465 # have all non-binary files taken care of.
460 continue
466 continue
461 if inconsistenteol(data):
467 if inconsistenteol(data):
462 raise errormod.Abort(
468 raise errormod.Abort(
463 _(b"inconsistent newline style in %s\n") % f
469 _(b"inconsistent newline style in %s\n") % f
464 )
470 )
465 return super(eolrepo, self).commitctx(ctx, error, origctx)
471 return super(eolrepo, self).commitctx(ctx, error, origctx)
466
472
467 repo.__class__ = eolrepo
473 repo.__class__ = eolrepo
468 repo._hgcleardirstate()
474 repo._hgcleardirstate()
@@ -1,312 +1,276
1 Test EOL update
1 Test EOL update
2
2
3 $ cat >> $HGRCPATH <<EOF
3 $ cat >> $HGRCPATH <<EOF
4 > [diff]
4 > [diff]
5 > git = 1
5 > git = 1
6 > EOF
6 > EOF
7
7
8 $ seteol () {
8 $ seteol () {
9 > if [ $1 = "LF" ]; then
9 > if [ $1 = "LF" ]; then
10 > EOL='\n'
10 > EOL='\n'
11 > else
11 > else
12 > EOL='\r\n'
12 > EOL='\r\n'
13 > fi
13 > fi
14 > }
14 > }
15
15
16 $ makerepo () {
16 $ makerepo () {
17 > echo
17 > echo
18 > echo "# ==== setup repository ===="
18 > echo "# ==== setup repository ===="
19 > echo '% hg init'
19 > echo '% hg init'
20 > hg init repo
20 > hg init repo
21 > cd repo
21 > cd repo
22 >
22 >
23 > cat > .hgeol <<EOF
23 > cat > .hgeol <<EOF
24 > [patterns]
24 > [patterns]
25 > **.txt = LF
25 > **.txt = LF
26 > EOF
26 > EOF
27 >
27 >
28 > printf "first\nsecond\nthird\n" > a.txt
28 > printf "first\nsecond\nthird\n" > a.txt
29 > printf "f\r\n" > f
29 > printf "f\r\n" > f
30 > hg commit --addremove -m 'LF commit'
30 > hg commit --addremove -m 'LF commit'
31 >
31 >
32 > cat > .hgeol <<EOF
32 > cat > .hgeol <<EOF
33 > [patterns]
33 > [patterns]
34 > **.txt = CRLF
34 > **.txt = CRLF
35 > f = LF
35 > f = LF
36 > EOF
36 > EOF
37 >
37 >
38 > printf "first\r\nsecond\r\nthird\r\n" > a.txt
38 > printf "first\r\nsecond\r\nthird\r\n" > a.txt
39 > printf "f\n" > f
39 > printf "f\n" > f
40 > hg commit -m 'CRLF commit'
40 > hg commit -m 'CRLF commit'
41 >
41 >
42 > cd ..
42 > cd ..
43 > }
43 > }
44
44
45 $ dotest () {
45 $ dotest () {
46 > seteol $1
46 > seteol $1
47 >
47 >
48 > echo
48 > echo
49 > echo "% hg clone repo repo-$1"
49 > echo "% hg clone repo repo-$1"
50 > hg clone --noupdate repo repo-$1
50 > hg clone --noupdate repo repo-$1
51 > cd repo-$1
51 > cd repo-$1
52 >
52 >
53 > cat > .hg/hgrc <<EOF
53 > cat > .hg/hgrc <<EOF
54 > [extensions]
54 > [extensions]
55 > eol =
55 > eol =
56 > EOF
56 > EOF
57 >
57 >
58 > hg update
58 > hg update
59 >
59 >
60 > echo '% a.txt (before)'
60 > echo '% a.txt (before)'
61 > cat a.txt
61 > cat a.txt
62 >
62 >
63 > printf "first${EOL}third${EOL}" > a.txt
63 > printf "first${EOL}third${EOL}" > a.txt
64 >
64 >
65 > echo '% a.txt (after)'
65 > echo '% a.txt (after)'
66 > cat a.txt
66 > cat a.txt
67 > echo '% hg diff'
67 > echo '% hg diff'
68 > hg diff
68 > hg diff
69 >
69 >
70 > echo '% hg update 0'
70 > echo '% hg update 0'
71 > hg update 0
71 > hg update 0
72 >
72 >
73 > echo '% a.txt'
73 > echo '% a.txt'
74 > cat a.txt
74 > cat a.txt
75 > echo '% hg diff'
75 > echo '% hg diff'
76 > hg diff
76 > hg diff
77 >
77 >
78 >
78 >
79 > cd ..
79 > cd ..
80 > rm -r repo-$1
80 > rm -r repo-$1
81 > }
81 > }
82
82
83 $ makerepo
83 $ makerepo
84
84
85 # ==== setup repository ====
85 # ==== setup repository ====
86 % hg init
86 % hg init
87 adding .hgeol
87 adding .hgeol
88 adding a.txt
88 adding a.txt
89 adding f
89 adding f
90 $ dotest LF
90 $ dotest LF
91
91
92 % hg clone repo repo-LF
92 % hg clone repo repo-LF
93 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
94 % a.txt (before)
94 % a.txt (before)
95 first\r (esc)
95 first\r (esc)
96 second\r (esc)
96 second\r (esc)
97 third\r (esc)
97 third\r (esc)
98 % a.txt (after)
98 % a.txt (after)
99 first
99 first
100 third
100 third
101 % hg diff
101 % hg diff
102 diff --git a/a.txt b/a.txt
102 diff --git a/a.txt b/a.txt
103 --- a/a.txt
103 --- a/a.txt
104 +++ b/a.txt
104 +++ b/a.txt
105 @@ -1,3 +1,2 @@
105 @@ -1,3 +1,2 @@
106 first\r (esc)
106 first\r (esc)
107 -second\r (esc)
107 -second\r (esc)
108 third\r (esc)
108 third\r (esc)
109 % hg update 0
109 % hg update 0
110 merging a.txt
110 merging a.txt
111 2 files updated, 1 files merged, 0 files removed, 0 files unresolved
111 2 files updated, 1 files merged, 0 files removed, 0 files unresolved
112 % a.txt
112 % a.txt
113 first
113 first
114 third
114 third
115 % hg diff
115 % hg diff
116 diff --git a/a.txt b/a.txt
116 diff --git a/a.txt b/a.txt
117 --- a/a.txt
117 --- a/a.txt
118 +++ b/a.txt
118 +++ b/a.txt
119 @@ -1,3 +1,2 @@
119 @@ -1,3 +1,2 @@
120 first
120 first
121 -second
121 -second
122 third
122 third
123 diff --git a/f b/f
124 --- a/f
125 +++ b/f
126 @@ -1,1 +1,1 @@
127 -f\r (esc)
128 +f
129 $ dotest CRLF
123 $ dotest CRLF
130
124
131 % hg clone repo repo-CRLF
125 % hg clone repo repo-CRLF
132 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
126 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
133 % a.txt (before)
127 % a.txt (before)
134 first\r (esc)
128 first\r (esc)
135 second\r (esc)
129 second\r (esc)
136 third\r (esc)
130 third\r (esc)
137 % a.txt (after)
131 % a.txt (after)
138 first\r (esc)
132 first\r (esc)
139 third\r (esc)
133 third\r (esc)
140 % hg diff
134 % hg diff
141 diff --git a/a.txt b/a.txt
135 diff --git a/a.txt b/a.txt
142 --- a/a.txt
136 --- a/a.txt
143 +++ b/a.txt
137 +++ b/a.txt
144 @@ -1,3 +1,2 @@
138 @@ -1,3 +1,2 @@
145 first\r (esc)
139 first\r (esc)
146 -second\r (esc)
140 -second\r (esc)
147 third\r (esc)
141 third\r (esc)
148 % hg update 0
142 % hg update 0
149 merging a.txt
143 merging a.txt
150 2 files updated, 1 files merged, 0 files removed, 0 files unresolved
144 2 files updated, 1 files merged, 0 files removed, 0 files unresolved
151 % a.txt
145 % a.txt
152 first
146 first
153 third
147 third
154 % hg diff
148 % hg diff
155 diff --git a/a.txt b/a.txt
149 diff --git a/a.txt b/a.txt
156 --- a/a.txt
150 --- a/a.txt
157 +++ b/a.txt
151 +++ b/a.txt
158 @@ -1,3 +1,2 @@
152 @@ -1,3 +1,2 @@
159 first
153 first
160 -second
154 -second
161 third
155 third
162 diff --git a/f b/f
163 --- a/f
164 +++ b/f
165 @@ -1,1 +1,1 @@
166 -f\r (esc)
167 +f
168
156
169 Test in repo using eol extension, while keeping an eye on how filters are
157 Test in repo using eol extension, while keeping an eye on how filters are
170 applied:
158 applied:
171
159
172 $ cd repo
160 $ cd repo
173
161
174 $ hg up -q -c -r null
162 $ hg up -q -c -r null
175 $ cat > .hg/hgrc <<EOF
163 $ cat > .hg/hgrc <<EOF
176 > [extensions]
164 > [extensions]
177 > eol =
165 > eol =
178 > EOF
166 > EOF
179
167
180 Update to revision 0 which has no .hgeol . Unfortunately, it uses the filter
168 Update to revision 0 which has no .hgeol, shouldn't use any filters, and
181 from tip ... which evidently is wrong:
169 obviously should leave things as tidy as they were before the clean update.
182
170
183 $ hg up -c -r 0 -v --debug
171 $ hg up -c -r 0 -v --debug
184 resolving manifests
172 resolving manifests
185 branchmerge: False, force: False, partial: False
173 branchmerge: False, force: False, partial: False
186 ancestor: 000000000000, local: 000000000000+, remote: 15cbdf8ca3db
174 ancestor: 000000000000, local: 000000000000+, remote: 15cbdf8ca3db
187 calling hook preupdate.eol: hgext.eol.preupdate
175 calling hook preupdate.eol: hgext.eol.preupdate
188 .hgeol: remote created -> g
176 .hgeol: remote created -> g
189 getting .hgeol
177 getting .hgeol
190 filtering .hgeol through isbinary
178 filtering .hgeol through isbinary
191 a.txt: remote created -> g
179 a.txt: remote created -> g
192 getting a.txt
180 getting a.txt
193 filtering a.txt through tolf
181 filtering a.txt through tolf
194 f: remote created -> g
182 f: remote created -> g
195 getting f
183 getting f
196 filtering f through tolf
197 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
184 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
198 $ hg st
185 $ hg st
199 M f
200 $ touch .hgeol * # ensure consistent dirtyness checks ignoring dirstate
201 $ hg up -C -r 0 -v --debug
202 eol: detected change in .hgeol
203 filtering .hgeol through isbinary
204 filtering a.txt through tolf
205 resolving manifests
206 branchmerge: False, force: True, partial: False
207 ancestor: 15cbdf8ca3db+, local: 15cbdf8ca3db+, remote: 15cbdf8ca3db
208 calling hook preupdate.eol: hgext.eol.preupdate
209 f: remote is newer -> g
210 getting f
211 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
212
186
213 $ hg branch b
187 $ hg branch b
214 marked working directory as branch b
188 marked working directory as branch b
215 (branches are permanent and global, did you want a bookmark?)
189 (branches are permanent and global, did you want a bookmark?)
216 $ hg ci -m b
190 $ hg ci -m b
217
191
218 Merge changes that apply a filter to f:
192 Merge changes that apply a filter to f:
219
193
220 $ hg merge 1
194 $ hg merge 1
221 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
195 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
222 (branch merge, don't forget to commit)
196 (branch merge, don't forget to commit)
223 $ hg st
197 $ hg st
224 M .hgeol
198 M .hgeol
225 M a.txt
199 M a.txt
226 M f
200 M f
227 $ hg diff
201 $ hg diff
228 diff --git a/.hgeol b/.hgeol
202 diff --git a/.hgeol b/.hgeol
229 --- a/.hgeol
203 --- a/.hgeol
230 +++ b/.hgeol
204 +++ b/.hgeol
231 @@ -1,2 +1,3 @@
205 @@ -1,2 +1,3 @@
232 [patterns]
206 [patterns]
233 -**.txt = LF
207 -**.txt = LF
234 +**.txt = CRLF
208 +**.txt = CRLF
235 +f = LF
209 +f = LF
236 diff --git a/a.txt b/a.txt
210 diff --git a/a.txt b/a.txt
237 --- a/a.txt
211 --- a/a.txt
238 +++ b/a.txt
212 +++ b/a.txt
239 @@ -1,3 +1,3 @@
213 @@ -1,3 +1,3 @@
240 -first
214 -first
241 -second
215 -second
242 -third
216 -third
243 +first\r (esc)
217 +first\r (esc)
244 +second\r (esc)
218 +second\r (esc)
245 +third\r (esc)
219 +third\r (esc)
246 diff --git a/f b/f
220 diff --git a/f b/f
247 --- a/f
221 --- a/f
248 +++ b/f
222 +++ b/f
249 @@ -1,1 +1,1 @@
223 @@ -1,1 +1,1 @@
250 -f\r (esc)
224 -f\r (esc)
251 +f
225 +f
252
226
253 Abort the merge with up -C to revision 0 ... but notice how .hgeol changes are
227 Abort the merge with up -C to revision 0.
254 not detected correctly: f is filtered with tolf even though there is no filter
228 Note that files are filtered correctly for revision 0: f is not filtered, a.txt
255 for f in revision 0, and it thus ends up with working directory changes.
229 is filtered with tolf, and everything is left tidy.
256
230
257 $ touch .hgeol * # ensure consistent dirtyness checks ignoring dirstate
231 $ touch .hgeol * # ensure consistent dirtyness checks ignoring dirstate
258 $ hg up -C -r 0 -v --debug
232 $ hg up -C -r 0 -v --debug
259 eol: detected change in .hgeol
233 eol: detected change in .hgeol
260 resolving manifests
234 resolving manifests
261 branchmerge: False, force: True, partial: False
235 branchmerge: False, force: True, partial: False
262 ancestor: 1db78bdd3bd6+, local: 1db78bdd3bd6+, remote: 15cbdf8ca3db
236 ancestor: 1db78bdd3bd6+, local: 1db78bdd3bd6+, remote: 15cbdf8ca3db
263 calling hook preupdate.eol: hgext.eol.preupdate
237 calling hook preupdate.eol: hgext.eol.preupdate
264 .hgeol: remote is newer -> g
238 .hgeol: remote is newer -> g
265 getting .hgeol
239 getting .hgeol
266 filtering .hgeol through isbinary
240 filtering .hgeol through isbinary
267 a.txt: remote is newer -> g
241 a.txt: remote is newer -> g
268 getting a.txt
242 getting a.txt
269 filtering a.txt through tolf
243 filtering a.txt through tolf
270 f: remote is newer -> g
244 f: remote is newer -> g
271 getting f
245 getting f
272 filtering f through tolf
273 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
246 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
274
247
275 $ touch .hgeol *
248 $ touch .hgeol *
276 $ hg st --debug
249 $ hg st --debug
277 eol: detected change in .hgeol
250 eol: detected change in .hgeol
278 filtering .hgeol through isbinary
251 filtering .hgeol through isbinary
279 filtering a.txt through tolf
252 filtering a.txt through tolf
280 M f
281 $ hg diff
253 $ hg diff
282 diff --git a/f b/f
283 --- a/f
284 +++ b/f
285 @@ -1,1 +1,1 @@
286 -f\r (esc)
287 +f
288
254
289 Workaround: Update again - this will read the right .hgeol:
255 Things were clean, and updating again will not change anything:
290
256
291 $ touch .hgeol *
257 $ touch .hgeol *
292 $ hg up -C -r 0 -v --debug
258 $ hg up -C -r 0 -v --debug
293 eol: detected change in .hgeol
259 eol: detected change in .hgeol
294 filtering .hgeol through isbinary
260 filtering .hgeol through isbinary
295 filtering a.txt through tolf
261 filtering a.txt through tolf
296 resolving manifests
262 resolving manifests
297 branchmerge: False, force: True, partial: False
263 branchmerge: False, force: True, partial: False
298 ancestor: 15cbdf8ca3db+, local: 15cbdf8ca3db+, remote: 15cbdf8ca3db
264 ancestor: 15cbdf8ca3db+, local: 15cbdf8ca3db+, remote: 15cbdf8ca3db
299 calling hook preupdate.eol: hgext.eol.preupdate
265 calling hook preupdate.eol: hgext.eol.preupdate
300 f: remote is newer -> g
266 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
301 getting f
302 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
303
267
304 $ touch .hgeol *
268 $ touch .hgeol *
305 $ hg st --debug
269 $ hg st --debug
306 eol: detected change in .hgeol
270 eol: detected change in .hgeol
307 filtering .hgeol through isbinary
271 filtering .hgeol through isbinary
308 filtering a.txt through tolf
272 filtering a.txt through tolf
309
273
310 $ cd ..
274 $ cd ..
311
275
312 $ rm -r repo
276 $ rm -r repo
General Comments 0
You need to be logged in to leave comments. Login now