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