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