##// END OF EJS Templates
eol: import 'error' as 'errormod'...
marmoute -
r32995:74930cf4 stable
parent child Browse files
Show More
@@ -1,388 +1,388 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 error,
101 error as errormod,
102 102 extensions,
103 103 match,
104 104 pycompat,
105 105 util,
106 106 )
107 107
108 108 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
109 109 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
110 110 # be specifying the version(s) of Mercurial they are tested with, or
111 111 # leave the attribute unspecified.
112 112 testedwith = 'ships-with-hg-core'
113 113
114 114 # Matches a lone LF, i.e., one that is not part of CRLF.
115 115 singlelf = re.compile('(^|[^\r])\n')
116 116
117 117 def inconsistenteol(data):
118 118 return '\r\n' in data and singlelf.search(data)
119 119
120 120 def tolf(s, params, ui, **kwargs):
121 121 """Filter to convert to LF EOLs."""
122 122 if util.binary(s):
123 123 return s
124 124 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
125 125 return s
126 126 if (ui.configbool('eol', 'fix-trailing-newline', False)
127 127 and s and s[-1] != '\n'):
128 128 s = s + '\n'
129 129 return util.tolf(s)
130 130
131 131 def tocrlf(s, params, ui, **kwargs):
132 132 """Filter to convert to CRLF EOLs."""
133 133 if util.binary(s):
134 134 return s
135 135 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
136 136 return s
137 137 if (ui.configbool('eol', 'fix-trailing-newline', False)
138 138 and s and s[-1] != '\n'):
139 139 s = s + '\n'
140 140 return util.tocrlf(s)
141 141
142 142 def isbinary(s, params):
143 143 """Filter to do nothing with the file."""
144 144 return s
145 145
146 146 filters = {
147 147 'to-lf': tolf,
148 148 'to-crlf': tocrlf,
149 149 'is-binary': isbinary,
150 150 # The following provide backwards compatibility with win32text
151 151 'cleverencode:': tolf,
152 152 'cleverdecode:': tocrlf
153 153 }
154 154
155 155 class eolfile(object):
156 156 def __init__(self, ui, root, data):
157 157 self._decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
158 158 self._encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
159 159
160 160 self.cfg = config.config()
161 161 # Our files should not be touched. The pattern must be
162 162 # inserted first override a '** = native' pattern.
163 163 self.cfg.set('patterns', '.hg*', 'BIN', 'eol')
164 164 # We can then parse the user's patterns.
165 165 self.cfg.parse('.hgeol', data)
166 166
167 167 isrepolf = self.cfg.get('repository', 'native') != 'CRLF'
168 168 self._encode['NATIVE'] = isrepolf and 'to-lf' or 'to-crlf'
169 169 iswdlf = ui.config('eol', 'native', pycompat.oslinesep) in ('LF', '\n')
170 170 self._decode['NATIVE'] = iswdlf and 'to-lf' or 'to-crlf'
171 171
172 172 include = []
173 173 exclude = []
174 174 self.patterns = []
175 175 for pattern, style in self.cfg.items('patterns'):
176 176 key = style.upper()
177 177 if key == 'BIN':
178 178 exclude.append(pattern)
179 179 else:
180 180 include.append(pattern)
181 181 m = match.match(root, '', [pattern])
182 182 self.patterns.append((pattern, key, m))
183 183 # This will match the files for which we need to care
184 184 # about inconsistent newlines.
185 185 self.match = match.match(root, '', [], include, exclude)
186 186
187 187 def copytoui(self, ui):
188 188 for pattern, key, m in self.patterns:
189 189 try:
190 190 ui.setconfig('decode', pattern, self._decode[key], 'eol')
191 191 ui.setconfig('encode', pattern, self._encode[key], 'eol')
192 192 except KeyError:
193 193 ui.warn(_("ignoring unknown EOL style '%s' from %s\n")
194 194 % (key, self.cfg.source('patterns', pattern)))
195 195 # eol.only-consistent can be specified in ~/.hgrc or .hgeol
196 196 for k, v in self.cfg.items('eol'):
197 197 ui.setconfig('eol', k, v, 'eol')
198 198
199 199 def checkrev(self, repo, ctx, files):
200 200 failed = []
201 201 for f in (files or ctx.files()):
202 202 if f not in ctx:
203 203 continue
204 204 for pattern, key, m in self.patterns:
205 205 if not m(f):
206 206 continue
207 207 target = self._encode[key]
208 208 data = ctx[f].data()
209 209 if (target == "to-lf" and "\r\n" in data
210 210 or target == "to-crlf" and singlelf.search(data)):
211 211 failed.append((f, target, str(ctx)))
212 212 break
213 213 return failed
214 214
215 215 def parseeol(ui, repo, nodes):
216 216 try:
217 217 for node in nodes:
218 218 try:
219 219 if node is None:
220 220 # Cannot use workingctx.data() since it would load
221 221 # and cache the filters before we configure them.
222 222 data = repo.wvfs('.hgeol').read()
223 223 else:
224 224 data = repo[node]['.hgeol'].data()
225 225 return eolfile(ui, repo.root, data)
226 226 except (IOError, LookupError):
227 227 pass
228 except error.ParseError as inst:
228 except errormod.ParseError as inst:
229 229 ui.warn(_("warning: ignoring .hgeol file due to parse error "
230 230 "at %s: %s\n") % (inst.args[1], inst.args[0]))
231 231 return None
232 232
233 233 def _checkhook(ui, repo, node, headsonly):
234 234 # Get revisions to check and touched files at the same time
235 235 files = set()
236 236 revs = set()
237 237 for rev in xrange(repo[node].rev(), len(repo)):
238 238 revs.add(rev)
239 239 if headsonly:
240 240 ctx = repo[rev]
241 241 files.update(ctx.files())
242 242 for pctx in ctx.parents():
243 243 revs.discard(pctx.rev())
244 244 failed = []
245 245 for rev in revs:
246 246 ctx = repo[rev]
247 247 eol = parseeol(ui, repo, [ctx.node()])
248 248 if eol:
249 249 failed.extend(eol.checkrev(repo, ctx, files))
250 250
251 251 if failed:
252 252 eols = {'to-lf': 'CRLF', 'to-crlf': 'LF'}
253 253 msgs = []
254 254 for f, target, node in sorted(failed):
255 255 msgs.append(_(" %s in %s should not have %s line endings") %
256 256 (f, node, eols[target]))
257 raise error.Abort(_("end-of-line check failed:\n") + "\n".join(msgs))
257 raise errormod.Abort(_("end-of-line check failed:\n") + "\n".join(msgs))
258 258
259 259 def checkallhook(ui, repo, node, hooktype, **kwargs):
260 260 """verify that files have expected EOLs"""
261 261 _checkhook(ui, repo, node, False)
262 262
263 263 def checkheadshook(ui, repo, node, hooktype, **kwargs):
264 264 """verify that files have expected EOLs"""
265 265 _checkhook(ui, repo, node, True)
266 266
267 267 # "checkheadshook" used to be called "hook"
268 268 hook = checkheadshook
269 269
270 270 def preupdate(ui, repo, hooktype, parent1, parent2):
271 271 repo.loadeol([parent1])
272 272 return False
273 273
274 274 def uisetup(ui):
275 275 ui.setconfig('hooks', 'preupdate.eol', preupdate, 'eol')
276 276
277 277 def extsetup(ui):
278 278 try:
279 279 extensions.find('win32text')
280 280 ui.warn(_("the eol extension is incompatible with the "
281 281 "win32text extension\n"))
282 282 except KeyError:
283 283 pass
284 284
285 285
286 286 def reposetup(ui, repo):
287 287 uisetup(repo.ui)
288 288
289 289 if not repo.local():
290 290 return
291 291 for name, fn in filters.iteritems():
292 292 repo.adddatafilter(name, fn)
293 293
294 294 ui.setconfig('patch', 'eol', 'auto', 'eol')
295 295
296 296 class eolrepo(repo.__class__):
297 297
298 298 def loadeol(self, nodes):
299 299 eol = parseeol(self.ui, self, nodes)
300 300 if eol is None:
301 301 return None
302 302 eol.copytoui(self.ui)
303 303 return eol.match
304 304
305 305 def _hgcleardirstate(self):
306 306 self._eolmatch = self.loadeol([None, 'tip'])
307 307 if not self._eolmatch:
308 308 self._eolmatch = util.never
309 309 return
310 310
311 311 oldeol = None
312 312 try:
313 313 cachemtime = os.path.getmtime(self.vfs.join("eol.cache"))
314 314 except OSError:
315 315 cachemtime = 0
316 316 else:
317 317 olddata = self.vfs.read("eol.cache")
318 318 if olddata:
319 319 oldeol = eolfile(self.ui, self.root, olddata)
320 320
321 321 try:
322 322 eolmtime = os.path.getmtime(self.wjoin(".hgeol"))
323 323 except OSError:
324 324 eolmtime = 0
325 325
326 326 if eolmtime > cachemtime:
327 327 self.ui.debug("eol: detected change in .hgeol\n")
328 328
329 329 hgeoldata = self.wvfs.read('.hgeol')
330 330 neweol = eolfile(self.ui, self.root, hgeoldata)
331 331
332 332 wlock = None
333 333 try:
334 334 wlock = self.wlock()
335 335 for f in self.dirstate:
336 336 if self.dirstate[f] != 'n':
337 337 continue
338 338 if oldeol is not None:
339 339 if not oldeol.match(f) and not neweol.match(f):
340 340 continue
341 341 oldkey = None
342 342 for pattern, key, m in oldeol.patterns:
343 343 if m(f):
344 344 oldkey = key
345 345 break
346 346 newkey = None
347 347 for pattern, key, m in neweol.patterns:
348 348 if m(f):
349 349 newkey = key
350 350 break
351 351 if oldkey == newkey:
352 352 continue
353 353 # all normal files need to be looked at again since
354 354 # the new .hgeol file specify a different filter
355 355 self.dirstate.normallookup(f)
356 356 # Write the cache to update mtime and cache .hgeol
357 357 with self.vfs("eol.cache", "w") as f:
358 358 f.write(hgeoldata)
359 except error.LockUnavailable:
359 except errormod.LockUnavailable:
360 360 # If we cannot lock the repository and clear the
361 361 # dirstate, then a commit might not see all files
362 362 # as modified. But if we cannot lock the
363 363 # repository, then we can also not make a commit,
364 364 # so ignore the error.
365 365 pass
366 366 finally:
367 367 if wlock is not None:
368 368 wlock.release()
369 369
370 370 def commitctx(self, ctx, haserror=False):
371 371 for f in sorted(ctx.added() + ctx.modified()):
372 372 if not self._eolmatch(f):
373 373 continue
374 374 fctx = ctx[f]
375 375 if fctx is None:
376 376 continue
377 377 data = fctx.data()
378 378 if util.binary(data):
379 379 # We should not abort here, since the user should
380 380 # be able to say "** = native" to automatically
381 381 # have all non-binary files taken care of.
382 382 continue
383 383 if inconsistenteol(data):
384 raise error.Abort(_("inconsistent newline style "
385 "in %s\n") % f)
384 raise errormod.Abort(_("inconsistent newline style "
385 "in %s\n") % f)
386 386 return super(eolrepo, self).commitctx(ctx, haserror)
387 387 repo.__class__ = eolrepo
388 388 repo._hgcleardirstate()
General Comments 0
You need to be logged in to leave comments. Login now