##// END OF EJS Templates
dirstate-item: use maybe_clean instead of `state` in the eol extension...
marmoute -
r48902:5b6ffd6b default
parent child Browse files
Show More
@@ -1,480 +1,480 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',
122 122 b'fix-trailing-newline',
123 123 default=False,
124 124 )
125 125 configitem(
126 126 b'eol',
127 127 b'native',
128 128 default=pycompat.oslinesep,
129 129 )
130 130 configitem(
131 131 b'eol',
132 132 b'only-consistent',
133 133 default=True,
134 134 )
135 135
136 136 # Matches a lone LF, i.e., one that is not part of CRLF.
137 137 singlelf = re.compile(b'(^|[^\r])\n')
138 138
139 139
140 140 def inconsistenteol(data):
141 141 return b'\r\n' in data and singlelf.search(data)
142 142
143 143
144 144 def tolf(s, params, ui, **kwargs):
145 145 """Filter to convert to LF EOLs."""
146 146 if stringutil.binary(s):
147 147 return s
148 148 if ui.configbool(b'eol', b'only-consistent') and inconsistenteol(s):
149 149 return s
150 150 if (
151 151 ui.configbool(b'eol', b'fix-trailing-newline')
152 152 and s
153 153 and not s.endswith(b'\n')
154 154 ):
155 155 s = s + b'\n'
156 156 return util.tolf(s)
157 157
158 158
159 159 def tocrlf(s, params, ui, **kwargs):
160 160 """Filter to convert to CRLF EOLs."""
161 161 if stringutil.binary(s):
162 162 return s
163 163 if ui.configbool(b'eol', b'only-consistent') and inconsistenteol(s):
164 164 return s
165 165 if (
166 166 ui.configbool(b'eol', b'fix-trailing-newline')
167 167 and s
168 168 and not s.endswith(b'\n')
169 169 ):
170 170 s = s + b'\n'
171 171 return util.tocrlf(s)
172 172
173 173
174 174 def isbinary(s, params, ui, **kwargs):
175 175 """Filter to do nothing with the file."""
176 176 return s
177 177
178 178
179 179 filters = {
180 180 b'to-lf': tolf,
181 181 b'to-crlf': tocrlf,
182 182 b'is-binary': isbinary,
183 183 # The following provide backwards compatibility with win32text
184 184 b'cleverencode:': tolf,
185 185 b'cleverdecode:': tocrlf,
186 186 }
187 187
188 188
189 189 class eolfile(object):
190 190 def __init__(self, ui, root, data):
191 191 self._decode = {
192 192 b'LF': b'to-lf',
193 193 b'CRLF': b'to-crlf',
194 194 b'BIN': b'is-binary',
195 195 }
196 196 self._encode = {
197 197 b'LF': b'to-lf',
198 198 b'CRLF': b'to-crlf',
199 199 b'BIN': b'is-binary',
200 200 }
201 201
202 202 self.cfg = config.config()
203 203 # Our files should not be touched. The pattern must be
204 204 # inserted first override a '** = native' pattern.
205 205 self.cfg.set(b'patterns', b'.hg*', b'BIN', b'eol')
206 206 # We can then parse the user's patterns.
207 207 self.cfg.parse(b'.hgeol', data)
208 208
209 209 isrepolf = self.cfg.get(b'repository', b'native') != b'CRLF'
210 210 self._encode[b'NATIVE'] = isrepolf and b'to-lf' or b'to-crlf'
211 211 iswdlf = ui.config(b'eol', b'native') in (b'LF', b'\n')
212 212 self._decode[b'NATIVE'] = iswdlf and b'to-lf' or b'to-crlf'
213 213
214 214 include = []
215 215 exclude = []
216 216 self.patterns = []
217 217 for pattern, style in self.cfg.items(b'patterns'):
218 218 key = style.upper()
219 219 if key == b'BIN':
220 220 exclude.append(pattern)
221 221 else:
222 222 include.append(pattern)
223 223 m = match.match(root, b'', [pattern])
224 224 self.patterns.append((pattern, key, m))
225 225 # This will match the files for which we need to care
226 226 # about inconsistent newlines.
227 227 self.match = match.match(root, b'', [], include, exclude)
228 228
229 229 def copytoui(self, ui):
230 230 newpatterns = {pattern for pattern, key, m in self.patterns}
231 231 for section in (b'decode', b'encode'):
232 232 for oldpattern, _filter in ui.configitems(section):
233 233 if oldpattern not in newpatterns:
234 234 if ui.configsource(section, oldpattern) == b'eol':
235 235 ui.setconfig(section, oldpattern, b'!', b'eol')
236 236 for pattern, key, m in self.patterns:
237 237 try:
238 238 ui.setconfig(b'decode', pattern, self._decode[key], b'eol')
239 239 ui.setconfig(b'encode', pattern, self._encode[key], b'eol')
240 240 except KeyError:
241 241 ui.warn(
242 242 _(b"ignoring unknown EOL style '%s' from %s\n")
243 243 % (key, self.cfg.source(b'patterns', pattern))
244 244 )
245 245 # eol.only-consistent can be specified in ~/.hgrc or .hgeol
246 246 for k, v in self.cfg.items(b'eol'):
247 247 ui.setconfig(b'eol', k, v, b'eol')
248 248
249 249 def checkrev(self, repo, ctx, files):
250 250 failed = []
251 251 for f in files or ctx.files():
252 252 if f not in ctx:
253 253 continue
254 254 for pattern, key, m in self.patterns:
255 255 if not m(f):
256 256 continue
257 257 target = self._encode[key]
258 258 data = ctx[f].data()
259 259 if (
260 260 target == b"to-lf"
261 261 and b"\r\n" in data
262 262 or target == b"to-crlf"
263 263 and singlelf.search(data)
264 264 ):
265 265 failed.append((f, target, bytes(ctx)))
266 266 break
267 267 return failed
268 268
269 269
270 270 def parseeol(ui, repo, nodes):
271 271 try:
272 272 for node in nodes:
273 273 try:
274 274 if node is None:
275 275 # Cannot use workingctx.data() since it would load
276 276 # and cache the filters before we configure them.
277 277 data = repo.wvfs(b'.hgeol').read()
278 278 else:
279 279 data = repo[node][b'.hgeol'].data()
280 280 return eolfile(ui, repo.root, data)
281 281 except (IOError, LookupError):
282 282 pass
283 283 except errormod.ConfigError as inst:
284 284 ui.warn(
285 285 _(
286 286 b"warning: ignoring .hgeol file due to parse error "
287 287 b"at %s: %s\n"
288 288 )
289 289 % (inst.location, inst.message)
290 290 )
291 291 return None
292 292
293 293
294 294 def ensureenabled(ui):
295 295 """make sure the extension is enabled when used as hook
296 296
297 297 When eol is used through hooks, the extension is never formally loaded and
298 298 enabled. This has some side effect, for example the config declaration is
299 299 never loaded. This function ensure the extension is enabled when running
300 300 hooks.
301 301 """
302 302 if b'eol' in ui._knownconfig:
303 303 return
304 304 ui.setconfig(b'extensions', b'eol', b'', source=b'internal')
305 305 extensions.loadall(ui, [b'eol'])
306 306
307 307
308 308 def _checkhook(ui, repo, node, headsonly):
309 309 # Get revisions to check and touched files at the same time
310 310 ensureenabled(ui)
311 311 files = set()
312 312 revs = set()
313 313 for rev in pycompat.xrange(repo[node].rev(), len(repo)):
314 314 revs.add(rev)
315 315 if headsonly:
316 316 ctx = repo[rev]
317 317 files.update(ctx.files())
318 318 for pctx in ctx.parents():
319 319 revs.discard(pctx.rev())
320 320 failed = []
321 321 for rev in revs:
322 322 ctx = repo[rev]
323 323 eol = parseeol(ui, repo, [ctx.node()])
324 324 if eol:
325 325 failed.extend(eol.checkrev(repo, ctx, files))
326 326
327 327 if failed:
328 328 eols = {b'to-lf': b'CRLF', b'to-crlf': b'LF'}
329 329 msgs = []
330 330 for f, target, node in sorted(failed):
331 331 msgs.append(
332 332 _(b" %s in %s should not have %s line endings")
333 333 % (f, node, eols[target])
334 334 )
335 335 raise errormod.Abort(
336 336 _(b"end-of-line check failed:\n") + b"\n".join(msgs)
337 337 )
338 338
339 339
340 340 def checkallhook(ui, repo, node, hooktype, **kwargs):
341 341 """verify that files have expected EOLs"""
342 342 _checkhook(ui, repo, node, False)
343 343
344 344
345 345 def checkheadshook(ui, repo, node, hooktype, **kwargs):
346 346 """verify that files have expected EOLs"""
347 347 _checkhook(ui, repo, node, True)
348 348
349 349
350 350 # "checkheadshook" used to be called "hook"
351 351 hook = checkheadshook
352 352
353 353
354 354 def preupdate(ui, repo, hooktype, parent1, parent2):
355 355 p1node = scmutil.resolvehexnodeidprefix(repo, parent1)
356 356 repo.loadeol([p1node])
357 357 return False
358 358
359 359
360 360 def uisetup(ui):
361 361 ui.setconfig(b'hooks', b'preupdate.eol', preupdate, b'eol')
362 362
363 363
364 364 def extsetup(ui):
365 365 try:
366 366 extensions.find(b'win32text')
367 367 ui.warn(
368 368 _(
369 369 b"the eol extension is incompatible with the "
370 370 b"win32text extension\n"
371 371 )
372 372 )
373 373 except KeyError:
374 374 pass
375 375
376 376
377 377 def reposetup(ui, repo):
378 378 uisetup(repo.ui)
379 379
380 380 if not repo.local():
381 381 return
382 382 for name, fn in pycompat.iteritems(filters):
383 383 repo.adddatafilter(name, fn)
384 384
385 385 ui.setconfig(b'patch', b'eol', b'auto', b'eol')
386 386
387 387 class eolrepo(repo.__class__):
388 388 def loadeol(self, nodes):
389 389 eol = parseeol(self.ui, self, nodes)
390 390 if eol is None:
391 391 return None
392 392 eol.copytoui(self.ui)
393 393 return eol.match
394 394
395 395 def _hgcleardirstate(self):
396 396 self._eolmatch = self.loadeol([None])
397 397 if not self._eolmatch:
398 398 self._eolmatch = util.never
399 399 return
400 400
401 401 oldeol = None
402 402 try:
403 403 cachemtime = os.path.getmtime(self.vfs.join(b"eol.cache"))
404 404 except OSError:
405 405 cachemtime = 0
406 406 else:
407 407 olddata = self.vfs.read(b"eol.cache")
408 408 if olddata:
409 409 oldeol = eolfile(self.ui, self.root, olddata)
410 410
411 411 try:
412 412 eolmtime = os.path.getmtime(self.wjoin(b".hgeol"))
413 413 except OSError:
414 414 eolmtime = 0
415 415
416 416 if eolmtime >= cachemtime and eolmtime > 0:
417 417 self.ui.debug(b"eol: detected change in .hgeol\n")
418 418
419 419 hgeoldata = self.wvfs.read(b'.hgeol')
420 420 neweol = eolfile(self.ui, self.root, hgeoldata)
421 421
422 422 wlock = None
423 423 try:
424 424 wlock = self.wlock()
425 425 for f in self.dirstate:
426 if self.dirstate[f] != b'n':
426 if not self.dirstate.get_entry(f).maybe_clean:
427 427 continue
428 428 if oldeol is not None:
429 429 if not oldeol.match(f) and not neweol.match(f):
430 430 continue
431 431 oldkey = None
432 432 for pattern, key, m in oldeol.patterns:
433 433 if m(f):
434 434 oldkey = key
435 435 break
436 436 newkey = None
437 437 for pattern, key, m in neweol.patterns:
438 438 if m(f):
439 439 newkey = key
440 440 break
441 441 if oldkey == newkey:
442 442 continue
443 443 # all normal files need to be looked at again since
444 444 # the new .hgeol file specify a different filter
445 445 self.dirstate.set_possibly_dirty(f)
446 446 # Write the cache to update mtime and cache .hgeol
447 447 with self.vfs(b"eol.cache", b"w") as f:
448 448 f.write(hgeoldata)
449 449 except errormod.LockUnavailable:
450 450 # If we cannot lock the repository and clear the
451 451 # dirstate, then a commit might not see all files
452 452 # as modified. But if we cannot lock the
453 453 # repository, then we can also not make a commit,
454 454 # so ignore the error.
455 455 pass
456 456 finally:
457 457 if wlock is not None:
458 458 wlock.release()
459 459
460 460 def commitctx(self, ctx, error=False, origctx=None):
461 461 for f in sorted(ctx.added() + ctx.modified()):
462 462 if not self._eolmatch(f):
463 463 continue
464 464 fctx = ctx[f]
465 465 if fctx is None:
466 466 continue
467 467 data = fctx.data()
468 468 if stringutil.binary(data):
469 469 # We should not abort here, since the user should
470 470 # be able to say "** = native" to automatically
471 471 # have all non-binary files taken care of.
472 472 continue
473 473 if inconsistenteol(data):
474 474 raise errormod.Abort(
475 475 _(b"inconsistent newline style in %s\n") % f
476 476 )
477 477 return super(eolrepo, self).commitctx(ctx, error, origctx)
478 478
479 479 repo.__class__ = eolrepo
480 480 repo._hgcleardirstate()
General Comments 0
You need to be logged in to leave comments. Login now