##// END OF EJS Templates
eol: don't refer to a random name-captured ui object...
Bryan O'Sullivan -
r17955:4ae21a75 default
parent child Browse files
Show More
@@ -1,349 +1,349 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 copy. 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 copy 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 behaviour; 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 The rules will first apply when files are touched in the working
53 53 copy, e.g. by updating to null and back to tip to touch all files.
54 54
55 55 The extension uses an optional ``[eol]`` section read from both the
56 56 normal Mercurial configuration files and the ``.hgeol`` file, with the
57 57 latter overriding the former. You can use that section to control the
58 58 overall behavior. There are three settings:
59 59
60 60 - ``eol.native`` (default ``os.linesep``) can be set to ``LF`` or
61 61 ``CRLF`` to override the default interpretation of ``native`` for
62 62 checkout. This can be used with :hg:`archive` on Unix, say, to
63 63 generate an archive where files have line endings for Windows.
64 64
65 65 - ``eol.only-consistent`` (default True) can be set to False to make
66 66 the extension convert files with inconsistent EOLs. Inconsistent
67 67 means that there is both ``CRLF`` and ``LF`` present in the file.
68 68 Such files are normally not touched under the assumption that they
69 69 have mixed EOLs on purpose.
70 70
71 71 - ``eol.fix-trailing-newline`` (default False) can be set to True to
72 72 ensure that converted files end with a EOL character (either ``\\n``
73 73 or ``\\r\\n`` as per the configured patterns).
74 74
75 75 The extension provides ``cleverencode:`` and ``cleverdecode:`` filters
76 76 like the deprecated win32text extension does. This means that you can
77 77 disable win32text and enable eol and your filters will still work. You
78 78 only need to these filters until you have prepared a ``.hgeol`` file.
79 79
80 80 The ``win32text.forbid*`` hooks provided by the win32text extension
81 81 have been unified into a single hook named ``eol.checkheadshook``. The
82 82 hook will lookup the expected line endings from the ``.hgeol`` file,
83 83 which means you must migrate to a ``.hgeol`` file first before using
84 84 the hook. ``eol.checkheadshook`` only checks heads, intermediate
85 85 invalid revisions will be pushed. To forbid them completely, use the
86 86 ``eol.checkallhook`` hook. These hooks are best used as
87 87 ``pretxnchangegroup`` hooks.
88 88
89 89 See :hg:`help patterns` for more information about the glob patterns
90 90 used.
91 91 """
92 92
93 93 from mercurial.i18n import _
94 94 from mercurial import util, config, extensions, match, error
95 95 import re, os
96 96
97 97 testedwith = 'internal'
98 98
99 99 # Matches a lone LF, i.e., one that is not part of CRLF.
100 100 singlelf = re.compile('(^|[^\r])\n')
101 101 # Matches a single EOL which can either be a CRLF where repeated CR
102 102 # are removed or a LF. We do not care about old Macintosh files, so a
103 103 # stray CR is an error.
104 104 eolre = re.compile('\r*\n')
105 105
106 106
107 107 def inconsistenteol(data):
108 108 return '\r\n' in data and singlelf.search(data)
109 109
110 110 def tolf(s, params, ui, **kwargs):
111 111 """Filter to convert to LF EOLs."""
112 112 if util.binary(s):
113 113 return s
114 114 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
115 115 return s
116 116 if (ui.configbool('eol', 'fix-trailing-newline', False)
117 117 and s and s[-1] != '\n'):
118 118 s = s + '\n'
119 119 return eolre.sub('\n', s)
120 120
121 121 def tocrlf(s, params, ui, **kwargs):
122 122 """Filter to convert to CRLF EOLs."""
123 123 if util.binary(s):
124 124 return s
125 125 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
126 126 return s
127 127 if (ui.configbool('eol', 'fix-trailing-newline', False)
128 128 and s and s[-1] != '\n'):
129 129 s = s + '\n'
130 130 return eolre.sub('\r\n', s)
131 131
132 132 def isbinary(s, params):
133 133 """Filter to do nothing with the file."""
134 134 return s
135 135
136 136 filters = {
137 137 'to-lf': tolf,
138 138 'to-crlf': tocrlf,
139 139 'is-binary': isbinary,
140 140 # The following provide backwards compatibility with win32text
141 141 'cleverencode:': tolf,
142 142 'cleverdecode:': tocrlf
143 143 }
144 144
145 145 class eolfile(object):
146 146 def __init__(self, ui, root, data):
147 147 self._decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
148 148 self._encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
149 149
150 150 self.cfg = config.config()
151 151 # Our files should not be touched. The pattern must be
152 152 # inserted first override a '** = native' pattern.
153 153 self.cfg.set('patterns', '.hg*', 'BIN')
154 154 # We can then parse the user's patterns.
155 155 self.cfg.parse('.hgeol', data)
156 156
157 157 isrepolf = self.cfg.get('repository', 'native') != 'CRLF'
158 158 self._encode['NATIVE'] = isrepolf and 'to-lf' or 'to-crlf'
159 159 iswdlf = ui.config('eol', 'native', os.linesep) in ('LF', '\n')
160 160 self._decode['NATIVE'] = iswdlf and 'to-lf' or 'to-crlf'
161 161
162 162 include = []
163 163 exclude = []
164 164 for pattern, style in self.cfg.items('patterns'):
165 165 key = style.upper()
166 166 if key == 'BIN':
167 167 exclude.append(pattern)
168 168 else:
169 169 include.append(pattern)
170 170 # This will match the files for which we need to care
171 171 # about inconsistent newlines.
172 172 self.match = match.match(root, '', [], include, exclude)
173 173
174 174 def copytoui(self, ui):
175 175 for pattern, style in self.cfg.items('patterns'):
176 176 key = style.upper()
177 177 try:
178 178 ui.setconfig('decode', pattern, self._decode[key])
179 179 ui.setconfig('encode', pattern, self._encode[key])
180 180 except KeyError:
181 181 ui.warn(_("ignoring unknown EOL style '%s' from %s\n")
182 182 % (style, self.cfg.source('patterns', pattern)))
183 183 # eol.only-consistent can be specified in ~/.hgrc or .hgeol
184 184 for k, v in self.cfg.items('eol'):
185 185 ui.setconfig('eol', k, v)
186 186
187 187 def checkrev(self, repo, ctx, files):
188 188 failed = []
189 189 for f in (files or ctx.files()):
190 190 if f not in ctx:
191 191 continue
192 192 for pattern, style in self.cfg.items('patterns'):
193 193 if not match.match(repo.root, '', [pattern])(f):
194 194 continue
195 195 target = self._encode[style.upper()]
196 196 data = ctx[f].data()
197 197 if (target == "to-lf" and "\r\n" in data
198 198 or target == "to-crlf" and singlelf.search(data)):
199 199 failed.append((str(ctx), target, f))
200 200 break
201 201 return failed
202 202
203 203 def parseeol(ui, repo, nodes):
204 204 try:
205 205 for node in nodes:
206 206 try:
207 207 if node is None:
208 208 # Cannot use workingctx.data() since it would load
209 209 # and cache the filters before we configure them.
210 210 data = repo.wfile('.hgeol').read()
211 211 else:
212 212 data = repo[node]['.hgeol'].data()
213 213 return eolfile(ui, repo.root, data)
214 214 except (IOError, LookupError):
215 215 pass
216 216 except error.ParseError, inst:
217 217 ui.warn(_("warning: ignoring .hgeol file due to parse error "
218 218 "at %s: %s\n") % (inst.args[1], inst.args[0]))
219 219 return None
220 220
221 221 def _checkhook(ui, repo, node, headsonly):
222 222 # Get revisions to check and touched files at the same time
223 223 files = set()
224 224 revs = set()
225 225 for rev in xrange(repo[node].rev(), len(repo)):
226 226 revs.add(rev)
227 227 if headsonly:
228 228 ctx = repo[rev]
229 229 files.update(ctx.files())
230 230 for pctx in ctx.parents():
231 231 revs.discard(pctx.rev())
232 232 failed = []
233 233 for rev in revs:
234 234 ctx = repo[rev]
235 235 eol = parseeol(ui, repo, [ctx.node()])
236 236 if eol:
237 237 failed.extend(eol.checkrev(repo, ctx, files))
238 238
239 239 if failed:
240 240 eols = {'to-lf': 'CRLF', 'to-crlf': 'LF'}
241 241 msgs = []
242 242 for node, target, f in failed:
243 243 msgs.append(_(" %s in %s should not have %s line endings") %
244 244 (f, node, eols[target]))
245 245 raise util.Abort(_("end-of-line check failed:\n") + "\n".join(msgs))
246 246
247 247 def checkallhook(ui, repo, node, hooktype, **kwargs):
248 248 """verify that files have expected EOLs"""
249 249 _checkhook(ui, repo, node, False)
250 250
251 251 def checkheadshook(ui, repo, node, hooktype, **kwargs):
252 252 """verify that files have expected EOLs"""
253 253 _checkhook(ui, repo, node, True)
254 254
255 255 # "checkheadshook" used to be called "hook"
256 256 hook = checkheadshook
257 257
258 258 def preupdate(ui, repo, hooktype, parent1, parent2):
259 259 repo.loadeol([parent1])
260 260 return False
261 261
262 262 def uisetup(ui):
263 263 ui.setconfig('hooks', 'preupdate.eol', preupdate)
264 264
265 265 def extsetup(ui):
266 266 try:
267 267 extensions.find('win32text')
268 268 ui.warn(_("the eol extension is incompatible with the "
269 269 "win32text extension\n"))
270 270 except KeyError:
271 271 pass
272 272
273 273
274 274 def reposetup(ui, repo):
275 275 uisetup(repo.ui)
276 276
277 277 if not repo.local():
278 278 return
279 279 for name, fn in filters.iteritems():
280 280 repo.adddatafilter(name, fn)
281 281
282 282 ui.setconfig('patch', 'eol', 'auto')
283 283
284 284 class eolrepo(repo.__class__):
285 285
286 286 def loadeol(self, nodes):
287 287 eol = parseeol(self.ui, self, nodes)
288 288 if eol is None:
289 289 return None
290 290 eol.copytoui(self.ui)
291 291 return eol.match
292 292
293 293 def _hgcleardirstate(self):
294 294 self._eolfile = self.loadeol([None, 'tip'])
295 295 if not self._eolfile:
296 296 self._eolfile = util.never
297 297 return
298 298
299 299 try:
300 300 cachemtime = os.path.getmtime(self.join("eol.cache"))
301 301 except OSError:
302 302 cachemtime = 0
303 303
304 304 try:
305 305 eolmtime = os.path.getmtime(self.wjoin(".hgeol"))
306 306 except OSError:
307 307 eolmtime = 0
308 308
309 309 if eolmtime > cachemtime:
310 ui.debug("eol: detected change in .hgeol\n")
310 self.ui.debug("eol: detected change in .hgeol\n")
311 311 wlock = None
312 312 try:
313 313 wlock = self.wlock()
314 314 for f in self.dirstate:
315 315 if self.dirstate[f] == 'n':
316 316 # all normal files need to be looked at
317 317 # again since the new .hgeol file might no
318 318 # longer match a file it matched before
319 319 self.dirstate.normallookup(f)
320 320 # Create or touch the cache to update mtime
321 321 self.opener("eol.cache", "w").close()
322 322 wlock.release()
323 323 except error.LockUnavailable:
324 324 # If we cannot lock the repository and clear the
325 325 # dirstate, then a commit might not see all files
326 326 # as modified. But if we cannot lock the
327 327 # repository, then we can also not make a commit,
328 328 # so ignore the error.
329 329 pass
330 330
331 331 def commitctx(self, ctx, error=False):
332 332 for f in sorted(ctx.added() + ctx.modified()):
333 333 if not self._eolfile(f):
334 334 continue
335 335 try:
336 336 data = ctx[f].data()
337 337 except IOError:
338 338 continue
339 339 if util.binary(data):
340 340 # We should not abort here, since the user should
341 341 # be able to say "** = native" to automatically
342 342 # have all non-binary files taken care of.
343 343 continue
344 344 if inconsistenteol(data):
345 345 raise util.Abort(_("inconsistent newline style "
346 346 "in %s\n" % f))
347 347 return super(eolrepo, self).commitctx(ctx, error)
348 348 repo.__class__ = eolrepo
349 349 repo._hgcleardirstate()
General Comments 0
You need to be logged in to leave comments. Login now