##// END OF EJS Templates
eol: cache needs update, also if it has same timestamp as the source...
Mads Kiilerich -
r43475:fd8c3f59 default
parent child Browse files
Show More
@@ -1,468 +1,468
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 for pattern, key, m in self.patterns:
225 225 try:
226 226 ui.setconfig(b'decode', pattern, self._decode[key], b'eol')
227 227 ui.setconfig(b'encode', pattern, self._encode[key], b'eol')
228 228 except KeyError:
229 229 ui.warn(
230 230 _(b"ignoring unknown EOL style '%s' from %s\n")
231 231 % (key, self.cfg.source(b'patterns', pattern))
232 232 )
233 233 # eol.only-consistent can be specified in ~/.hgrc or .hgeol
234 234 for k, v in self.cfg.items(b'eol'):
235 235 ui.setconfig(b'eol', k, v, b'eol')
236 236
237 237 def checkrev(self, repo, ctx, files):
238 238 failed = []
239 239 for f in files or ctx.files():
240 240 if f not in ctx:
241 241 continue
242 242 for pattern, key, m in self.patterns:
243 243 if not m(f):
244 244 continue
245 245 target = self._encode[key]
246 246 data = ctx[f].data()
247 247 if (
248 248 target == b"to-lf"
249 249 and b"\r\n" in data
250 250 or target == b"to-crlf"
251 251 and singlelf.search(data)
252 252 ):
253 253 failed.append((f, target, bytes(ctx)))
254 254 break
255 255 return failed
256 256
257 257
258 258 def parseeol(ui, repo, nodes):
259 259 try:
260 260 for node in nodes:
261 261 try:
262 262 if node is None:
263 263 # Cannot use workingctx.data() since it would load
264 264 # and cache the filters before we configure them.
265 265 data = repo.wvfs(b'.hgeol').read()
266 266 else:
267 267 data = repo[node][b'.hgeol'].data()
268 268 return eolfile(ui, repo.root, data)
269 269 except (IOError, LookupError):
270 270 pass
271 271 except errormod.ParseError as inst:
272 272 ui.warn(
273 273 _(
274 274 b"warning: ignoring .hgeol file due to parse error "
275 275 b"at %s: %s\n"
276 276 )
277 277 % (inst.args[1], inst.args[0])
278 278 )
279 279 return None
280 280
281 281
282 282 def ensureenabled(ui):
283 283 """make sure the extension is enabled when used as hook
284 284
285 285 When eol is used through hooks, the extension is never formally loaded and
286 286 enabled. This has some side effect, for example the config declaration is
287 287 never loaded. This function ensure the extension is enabled when running
288 288 hooks.
289 289 """
290 290 if b'eol' in ui._knownconfig:
291 291 return
292 292 ui.setconfig(b'extensions', b'eol', b'', source=b'internal')
293 293 extensions.loadall(ui, [b'eol'])
294 294
295 295
296 296 def _checkhook(ui, repo, node, headsonly):
297 297 # Get revisions to check and touched files at the same time
298 298 ensureenabled(ui)
299 299 files = set()
300 300 revs = set()
301 301 for rev in pycompat.xrange(repo[node].rev(), len(repo)):
302 302 revs.add(rev)
303 303 if headsonly:
304 304 ctx = repo[rev]
305 305 files.update(ctx.files())
306 306 for pctx in ctx.parents():
307 307 revs.discard(pctx.rev())
308 308 failed = []
309 309 for rev in revs:
310 310 ctx = repo[rev]
311 311 eol = parseeol(ui, repo, [ctx.node()])
312 312 if eol:
313 313 failed.extend(eol.checkrev(repo, ctx, files))
314 314
315 315 if failed:
316 316 eols = {b'to-lf': b'CRLF', b'to-crlf': b'LF'}
317 317 msgs = []
318 318 for f, target, node in sorted(failed):
319 319 msgs.append(
320 320 _(b" %s in %s should not have %s line endings")
321 321 % (f, node, eols[target])
322 322 )
323 323 raise errormod.Abort(
324 324 _(b"end-of-line check failed:\n") + b"\n".join(msgs)
325 325 )
326 326
327 327
328 328 def checkallhook(ui, repo, node, hooktype, **kwargs):
329 329 """verify that files have expected EOLs"""
330 330 _checkhook(ui, repo, node, False)
331 331
332 332
333 333 def checkheadshook(ui, repo, node, hooktype, **kwargs):
334 334 """verify that files have expected EOLs"""
335 335 _checkhook(ui, repo, node, True)
336 336
337 337
338 338 # "checkheadshook" used to be called "hook"
339 339 hook = checkheadshook
340 340
341 341
342 342 def preupdate(ui, repo, hooktype, parent1, parent2):
343 343 p1node = scmutil.resolvehexnodeidprefix(repo, parent1)
344 344 repo.loadeol([p1node])
345 345 return False
346 346
347 347
348 348 def uisetup(ui):
349 349 ui.setconfig(b'hooks', b'preupdate.eol', preupdate, b'eol')
350 350
351 351
352 352 def extsetup(ui):
353 353 try:
354 354 extensions.find(b'win32text')
355 355 ui.warn(
356 356 _(
357 357 b"the eol extension is incompatible with the "
358 358 b"win32text extension\n"
359 359 )
360 360 )
361 361 except KeyError:
362 362 pass
363 363
364 364
365 365 def reposetup(ui, repo):
366 366 uisetup(repo.ui)
367 367
368 368 if not repo.local():
369 369 return
370 370 for name, fn in pycompat.iteritems(filters):
371 371 repo.adddatafilter(name, fn)
372 372
373 373 ui.setconfig(b'patch', b'eol', b'auto', b'eol')
374 374
375 375 class eolrepo(repo.__class__):
376 376 def loadeol(self, nodes):
377 377 eol = parseeol(self.ui, self, nodes)
378 378 if eol is None:
379 379 return None
380 380 eol.copytoui(self.ui)
381 381 return eol.match
382 382
383 383 def _hgcleardirstate(self):
384 384 self._eolmatch = self.loadeol([None, b'tip'])
385 385 if not self._eolmatch:
386 386 self._eolmatch = util.never
387 387 return
388 388
389 389 oldeol = None
390 390 try:
391 391 cachemtime = os.path.getmtime(self.vfs.join(b"eol.cache"))
392 392 except OSError:
393 393 cachemtime = 0
394 394 else:
395 395 olddata = self.vfs.read(b"eol.cache")
396 396 if olddata:
397 397 oldeol = eolfile(self.ui, self.root, olddata)
398 398
399 399 try:
400 400 eolmtime = os.path.getmtime(self.wjoin(b".hgeol"))
401 401 except OSError:
402 402 eolmtime = 0
403 403
404 if eolmtime > cachemtime:
404 if eolmtime >= cachemtime and eolmtime > 0:
405 405 self.ui.debug(b"eol: detected change in .hgeol\n")
406 406
407 407 hgeoldata = self.wvfs.read(b'.hgeol')
408 408 neweol = eolfile(self.ui, self.root, hgeoldata)
409 409
410 410 wlock = None
411 411 try:
412 412 wlock = self.wlock()
413 413 for f in self.dirstate:
414 414 if self.dirstate[f] != b'n':
415 415 continue
416 416 if oldeol is not None:
417 417 if not oldeol.match(f) and not neweol.match(f):
418 418 continue
419 419 oldkey = None
420 420 for pattern, key, m in oldeol.patterns:
421 421 if m(f):
422 422 oldkey = key
423 423 break
424 424 newkey = None
425 425 for pattern, key, m in neweol.patterns:
426 426 if m(f):
427 427 newkey = key
428 428 break
429 429 if oldkey == newkey:
430 430 continue
431 431 # all normal files need to be looked at again since
432 432 # the new .hgeol file specify a different filter
433 433 self.dirstate.normallookup(f)
434 434 # Write the cache to update mtime and cache .hgeol
435 435 with self.vfs(b"eol.cache", b"w") as f:
436 436 f.write(hgeoldata)
437 437 except errormod.LockUnavailable:
438 438 # If we cannot lock the repository and clear the
439 439 # dirstate, then a commit might not see all files
440 440 # as modified. But if we cannot lock the
441 441 # repository, then we can also not make a commit,
442 442 # so ignore the error.
443 443 pass
444 444 finally:
445 445 if wlock is not None:
446 446 wlock.release()
447 447
448 448 def commitctx(self, ctx, error=False, origctx=None):
449 449 for f in sorted(ctx.added() + ctx.modified()):
450 450 if not self._eolmatch(f):
451 451 continue
452 452 fctx = ctx[f]
453 453 if fctx is None:
454 454 continue
455 455 data = fctx.data()
456 456 if stringutil.binary(data):
457 457 # We should not abort here, since the user should
458 458 # be able to say "** = native" to automatically
459 459 # have all non-binary files taken care of.
460 460 continue
461 461 if inconsistenteol(data):
462 462 raise errormod.Abort(
463 463 _(b"inconsistent newline style in %s\n") % f
464 464 )
465 465 return super(eolrepo, self).commitctx(ctx, error, origctx)
466 466
467 467 repo.__class__ = eolrepo
468 468 repo._hgcleardirstate()
General Comments 0
You need to be logged in to leave comments. Login now