##// END OF EJS Templates
eol: make output stable...
Bryan O'Sullivan -
r27524:f5b6b4e5 default
parent child Browse files
Show More
@@ -1,354 +1,354
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 mercurial.i18n import _
95 95 from mercurial import util, config, extensions, match, error
96 96 import re, os
97 97
98 98 # Note for extension authors: ONLY specify testedwith = 'internal' for
99 99 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
100 100 # be specifying the version(s) of Mercurial they are tested with, or
101 101 # leave the attribute unspecified.
102 102 testedwith = 'internal'
103 103
104 104 # Matches a lone LF, i.e., one that is not part of CRLF.
105 105 singlelf = re.compile('(^|[^\r])\n')
106 106 # Matches a single EOL which can either be a CRLF where repeated CR
107 107 # are removed or a LF. We do not care about old Macintosh files, so a
108 108 # stray CR is an error.
109 109 eolre = re.compile('\r*\n')
110 110
111 111
112 112 def inconsistenteol(data):
113 113 return '\r\n' in data and singlelf.search(data)
114 114
115 115 def tolf(s, params, ui, **kwargs):
116 116 """Filter to convert to LF EOLs."""
117 117 if util.binary(s):
118 118 return s
119 119 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
120 120 return s
121 121 if (ui.configbool('eol', 'fix-trailing-newline', False)
122 122 and s and s[-1] != '\n'):
123 123 s = s + '\n'
124 124 return eolre.sub('\n', s)
125 125
126 126 def tocrlf(s, params, ui, **kwargs):
127 127 """Filter to convert to CRLF EOLs."""
128 128 if util.binary(s):
129 129 return s
130 130 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
131 131 return s
132 132 if (ui.configbool('eol', 'fix-trailing-newline', False)
133 133 and s and s[-1] != '\n'):
134 134 s = s + '\n'
135 135 return eolre.sub('\r\n', s)
136 136
137 137 def isbinary(s, params):
138 138 """Filter to do nothing with the file."""
139 139 return s
140 140
141 141 filters = {
142 142 'to-lf': tolf,
143 143 'to-crlf': tocrlf,
144 144 'is-binary': isbinary,
145 145 # The following provide backwards compatibility with win32text
146 146 'cleverencode:': tolf,
147 147 'cleverdecode:': tocrlf
148 148 }
149 149
150 150 class eolfile(object):
151 151 def __init__(self, ui, root, data):
152 152 self._decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
153 153 self._encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
154 154
155 155 self.cfg = config.config()
156 156 # Our files should not be touched. The pattern must be
157 157 # inserted first override a '** = native' pattern.
158 158 self.cfg.set('patterns', '.hg*', 'BIN', 'eol')
159 159 # We can then parse the user's patterns.
160 160 self.cfg.parse('.hgeol', data)
161 161
162 162 isrepolf = self.cfg.get('repository', 'native') != 'CRLF'
163 163 self._encode['NATIVE'] = isrepolf and 'to-lf' or 'to-crlf'
164 164 iswdlf = ui.config('eol', 'native', os.linesep) in ('LF', '\n')
165 165 self._decode['NATIVE'] = iswdlf and 'to-lf' or 'to-crlf'
166 166
167 167 include = []
168 168 exclude = []
169 169 for pattern, style in self.cfg.items('patterns'):
170 170 key = style.upper()
171 171 if key == 'BIN':
172 172 exclude.append(pattern)
173 173 else:
174 174 include.append(pattern)
175 175 # This will match the files for which we need to care
176 176 # about inconsistent newlines.
177 177 self.match = match.match(root, '', [], include, exclude)
178 178
179 179 def copytoui(self, ui):
180 180 for pattern, style in self.cfg.items('patterns'):
181 181 key = style.upper()
182 182 try:
183 183 ui.setconfig('decode', pattern, self._decode[key], 'eol')
184 184 ui.setconfig('encode', pattern, self._encode[key], 'eol')
185 185 except KeyError:
186 186 ui.warn(_("ignoring unknown EOL style '%s' from %s\n")
187 187 % (style, self.cfg.source('patterns', pattern)))
188 188 # eol.only-consistent can be specified in ~/.hgrc or .hgeol
189 189 for k, v in self.cfg.items('eol'):
190 190 ui.setconfig('eol', k, v, 'eol')
191 191
192 192 def checkrev(self, repo, ctx, files):
193 193 failed = []
194 194 for f in (files or ctx.files()):
195 195 if f not in ctx:
196 196 continue
197 197 for pattern, style in self.cfg.items('patterns'):
198 198 if not match.match(repo.root, '', [pattern])(f):
199 199 continue
200 200 target = self._encode[style.upper()]
201 201 data = ctx[f].data()
202 202 if (target == "to-lf" and "\r\n" in data
203 203 or target == "to-crlf" and singlelf.search(data)):
204 failed.append((str(ctx), target, f))
204 failed.append((f, target, str(ctx)))
205 205 break
206 206 return failed
207 207
208 208 def parseeol(ui, repo, nodes):
209 209 try:
210 210 for node in nodes:
211 211 try:
212 212 if node is None:
213 213 # Cannot use workingctx.data() since it would load
214 214 # and cache the filters before we configure them.
215 215 data = repo.wfile('.hgeol').read()
216 216 else:
217 217 data = repo[node]['.hgeol'].data()
218 218 return eolfile(ui, repo.root, data)
219 219 except (IOError, LookupError):
220 220 pass
221 221 except error.ParseError as inst:
222 222 ui.warn(_("warning: ignoring .hgeol file due to parse error "
223 223 "at %s: %s\n") % (inst.args[1], inst.args[0]))
224 224 return None
225 225
226 226 def _checkhook(ui, repo, node, headsonly):
227 227 # Get revisions to check and touched files at the same time
228 228 files = set()
229 229 revs = set()
230 230 for rev in xrange(repo[node].rev(), len(repo)):
231 231 revs.add(rev)
232 232 if headsonly:
233 233 ctx = repo[rev]
234 234 files.update(ctx.files())
235 235 for pctx in ctx.parents():
236 236 revs.discard(pctx.rev())
237 237 failed = []
238 238 for rev in revs:
239 239 ctx = repo[rev]
240 240 eol = parseeol(ui, repo, [ctx.node()])
241 241 if eol:
242 242 failed.extend(eol.checkrev(repo, ctx, files))
243 243
244 244 if failed:
245 245 eols = {'to-lf': 'CRLF', 'to-crlf': 'LF'}
246 246 msgs = []
247 for node, target, f in failed:
247 for f, target, node in sorted(failed):
248 248 msgs.append(_(" %s in %s should not have %s line endings") %
249 249 (f, node, eols[target]))
250 250 raise error.Abort(_("end-of-line check failed:\n") + "\n".join(msgs))
251 251
252 252 def checkallhook(ui, repo, node, hooktype, **kwargs):
253 253 """verify that files have expected EOLs"""
254 254 _checkhook(ui, repo, node, False)
255 255
256 256 def checkheadshook(ui, repo, node, hooktype, **kwargs):
257 257 """verify that files have expected EOLs"""
258 258 _checkhook(ui, repo, node, True)
259 259
260 260 # "checkheadshook" used to be called "hook"
261 261 hook = checkheadshook
262 262
263 263 def preupdate(ui, repo, hooktype, parent1, parent2):
264 264 repo.loadeol([parent1])
265 265 return False
266 266
267 267 def uisetup(ui):
268 268 ui.setconfig('hooks', 'preupdate.eol', preupdate, 'eol')
269 269
270 270 def extsetup(ui):
271 271 try:
272 272 extensions.find('win32text')
273 273 ui.warn(_("the eol extension is incompatible with the "
274 274 "win32text extension\n"))
275 275 except KeyError:
276 276 pass
277 277
278 278
279 279 def reposetup(ui, repo):
280 280 uisetup(repo.ui)
281 281
282 282 if not repo.local():
283 283 return
284 284 for name, fn in filters.iteritems():
285 285 repo.adddatafilter(name, fn)
286 286
287 287 ui.setconfig('patch', 'eol', 'auto', 'eol')
288 288
289 289 class eolrepo(repo.__class__):
290 290
291 291 def loadeol(self, nodes):
292 292 eol = parseeol(self.ui, self, nodes)
293 293 if eol is None:
294 294 return None
295 295 eol.copytoui(self.ui)
296 296 return eol.match
297 297
298 298 def _hgcleardirstate(self):
299 299 self._eolfile = self.loadeol([None, 'tip'])
300 300 if not self._eolfile:
301 301 self._eolfile = util.never
302 302 return
303 303
304 304 try:
305 305 cachemtime = os.path.getmtime(self.join("eol.cache"))
306 306 except OSError:
307 307 cachemtime = 0
308 308
309 309 try:
310 310 eolmtime = os.path.getmtime(self.wjoin(".hgeol"))
311 311 except OSError:
312 312 eolmtime = 0
313 313
314 314 if eolmtime > cachemtime:
315 315 self.ui.debug("eol: detected change in .hgeol\n")
316 316 wlock = None
317 317 try:
318 318 wlock = self.wlock()
319 319 for f in self.dirstate:
320 320 if self.dirstate[f] == 'n':
321 321 # all normal files need to be looked at
322 322 # again since the new .hgeol file might no
323 323 # longer match a file it matched before
324 324 self.dirstate.normallookup(f)
325 325 # Create or touch the cache to update mtime
326 326 self.vfs("eol.cache", "w").close()
327 327 wlock.release()
328 328 except error.LockUnavailable:
329 329 # If we cannot lock the repository and clear the
330 330 # dirstate, then a commit might not see all files
331 331 # as modified. But if we cannot lock the
332 332 # repository, then we can also not make a commit,
333 333 # so ignore the error.
334 334 pass
335 335
336 336 def commitctx(self, ctx, haserror=False):
337 337 for f in sorted(ctx.added() + ctx.modified()):
338 338 if not self._eolfile(f):
339 339 continue
340 340 fctx = ctx[f]
341 341 if fctx is None:
342 342 continue
343 343 data = fctx.data()
344 344 if util.binary(data):
345 345 # We should not abort here, since the user should
346 346 # be able to say "** = native" to automatically
347 347 # have all non-binary files taken care of.
348 348 continue
349 349 if inconsistenteol(data):
350 350 raise error.Abort(_("inconsistent newline style "
351 351 "in %s\n") % f)
352 352 return super(eolrepo, self).commitctx(ctx, haserror)
353 353 repo.__class__ = eolrepo
354 354 repo._hgcleardirstate()
@@ -1,218 +1,218
1 1 Test the EOL hook
2 2
3 3 $ hg init main
4 4 $ cat > main/.hg/hgrc <<EOF
5 5 > [hooks]
6 6 > pretxnchangegroup = python:hgext.eol.hook
7 7 > EOF
8 8 $ hg clone main fork
9 9 updating to branch default
10 10 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
11 11 $ cd fork
12 12
13 13 Create repo
14 14 $ cat > .hgeol <<EOF
15 15 > [patterns]
16 16 > mixed.txt = BIN
17 17 > crlf.txt = CRLF
18 18 > **.txt = native
19 19 > EOF
20 20 $ hg add .hgeol
21 21 $ hg commit -m 'Commit .hgeol'
22 22
23 23 $ printf "first\nsecond\nthird\n" > a.txt
24 24 $ hg add a.txt
25 25 $ hg commit -m 'LF a.txt'
26 26 $ hg push ../main
27 27 pushing to ../main
28 28 searching for changes
29 29 adding changesets
30 30 adding manifests
31 31 adding file changes
32 32 added 2 changesets with 2 changes to 2 files
33 33
34 34 $ printf "first\r\nsecond\r\nthird\n" > a.txt
35 35 $ hg commit -m 'CRLF a.txt'
36 36 $ hg push ../main
37 37 pushing to ../main
38 38 searching for changes
39 39 adding changesets
40 40 adding manifests
41 41 adding file changes
42 42 added 1 changesets with 1 changes to 1 files
43 43 error: pretxnchangegroup hook failed: end-of-line check failed:
44 44 a.txt in a8ee6548cd86 should not have CRLF line endings
45 45 transaction abort!
46 46 rollback completed
47 47 abort: end-of-line check failed:
48 48 a.txt in a8ee6548cd86 should not have CRLF line endings
49 49 [255]
50 50
51 51 $ printf "first\nsecond\nthird\n" > a.txt
52 52 $ hg commit -m 'LF a.txt (fixed)'
53 53 $ hg push ../main
54 54 pushing to ../main
55 55 searching for changes
56 56 adding changesets
57 57 adding manifests
58 58 adding file changes
59 59 added 2 changesets with 2 changes to 1 files
60 60
61 61 $ printf "first\nsecond\nthird\n" > crlf.txt
62 62 $ hg add crlf.txt
63 63 $ hg commit -m 'LF crlf.txt'
64 64 $ hg push ../main
65 65 pushing to ../main
66 66 searching for changes
67 67 adding changesets
68 68 adding manifests
69 69 adding file changes
70 70 added 1 changesets with 1 changes to 1 files
71 71 error: pretxnchangegroup hook failed: end-of-line check failed:
72 72 crlf.txt in 004ba2132725 should not have LF line endings
73 73 transaction abort!
74 74 rollback completed
75 75 abort: end-of-line check failed:
76 76 crlf.txt in 004ba2132725 should not have LF line endings
77 77 [255]
78 78
79 79 $ printf "first\r\nsecond\r\nthird\r\n" > crlf.txt
80 80 $ hg commit -m 'CRLF crlf.txt (fixed)'
81 81 $ hg push ../main
82 82 pushing to ../main
83 83 searching for changes
84 84 adding changesets
85 85 adding manifests
86 86 adding file changes
87 87 added 2 changesets with 2 changes to 1 files
88 88
89 89 $ printf "first\r\nsecond" > b.txt
90 90 $ hg add b.txt
91 91 $ hg commit -m 'CRLF b.txt'
92 92 $ hg push ../main
93 93 pushing to ../main
94 94 searching for changes
95 95 adding changesets
96 96 adding manifests
97 97 adding file changes
98 98 added 1 changesets with 1 changes to 1 files
99 99 error: pretxnchangegroup hook failed: end-of-line check failed:
100 100 b.txt in fbcf9b1025f5 should not have CRLF line endings
101 101 transaction abort!
102 102 rollback completed
103 103 abort: end-of-line check failed:
104 104 b.txt in fbcf9b1025f5 should not have CRLF line endings
105 105 [255]
106 106
107 107 $ hg up -r -2
108 108 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
109 109 $ printf "some\nother\nfile" > c.txt
110 110 $ hg add c.txt
111 111 $ hg commit -m "LF c.txt, b.txt doesn't exist here"
112 112 created new head
113 113 $ hg push -f ../main
114 114 pushing to ../main
115 115 searching for changes
116 116 adding changesets
117 117 adding manifests
118 118 adding file changes
119 119 added 2 changesets with 2 changes to 2 files (+1 heads)
120 120 error: pretxnchangegroup hook failed: end-of-line check failed:
121 121 b.txt in fbcf9b1025f5 should not have CRLF line endings
122 122 transaction abort!
123 123 rollback completed
124 124 abort: end-of-line check failed:
125 125 b.txt in fbcf9b1025f5 should not have CRLF line endings
126 126 [255]
127 127
128 128 Test checkheadshook alias
129 129
130 130 $ cat > ../main/.hg/hgrc <<EOF
131 131 > [hooks]
132 132 > pretxnchangegroup = python:hgext.eol.checkheadshook
133 133 > EOF
134 134 $ hg push -f ../main
135 135 pushing to ../main
136 136 searching for changes
137 137 adding changesets
138 138 adding manifests
139 139 adding file changes
140 140 added 2 changesets with 2 changes to 2 files (+1 heads)
141 141 error: pretxnchangegroup hook failed: end-of-line check failed:
142 142 b.txt in fbcf9b1025f5 should not have CRLF line endings
143 143 transaction abort!
144 144 rollback completed
145 145 abort: end-of-line check failed:
146 146 b.txt in fbcf9b1025f5 should not have CRLF line endings
147 147 [255]
148 148
149 149 We can fix the head and push again
150 150
151 151 $ hg up 6
152 152 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
153 153 $ printf "first\nsecond" > b.txt
154 154 $ hg ci -m "remove CRLF from b.txt"
155 155 $ hg push -f ../main
156 156 pushing to ../main
157 157 searching for changes
158 158 adding changesets
159 159 adding manifests
160 160 adding file changes
161 161 added 3 changesets with 3 changes to 2 files (+1 heads)
162 162 $ hg -R ../main rollback
163 163 repository tip rolled back to revision 5 (undo push)
164 164
165 165 Test it still fails with checkallhook
166 166
167 167 $ cat > ../main/.hg/hgrc <<EOF
168 168 > [hooks]
169 169 > pretxnchangegroup = python:hgext.eol.checkallhook
170 170 > EOF
171 171 $ hg push -f ../main
172 172 pushing to ../main
173 173 searching for changes
174 174 adding changesets
175 175 adding manifests
176 176 adding file changes
177 177 added 3 changesets with 3 changes to 2 files (+1 heads)
178 178 error: pretxnchangegroup hook failed: end-of-line check failed:
179 179 b.txt in fbcf9b1025f5 should not have CRLF line endings
180 180 transaction abort!
181 181 rollback completed
182 182 abort: end-of-line check failed:
183 183 b.txt in fbcf9b1025f5 should not have CRLF line endings
184 184 [255]
185 185
186 186 But we can push the clean head
187 187
188 188 $ hg push -r7 -f ../main
189 189 pushing to ../main
190 190 searching for changes
191 191 adding changesets
192 192 adding manifests
193 193 adding file changes
194 194 added 1 changesets with 1 changes to 1 files
195 195
196 196 Test multiple files/revisions output
197 197
198 198 $ printf "another\r\nbad\r\none" > d.txt
199 199 $ hg add d.txt
200 200 $ hg ci -m "add d.txt"
201 201 $ hg push -f ../main
202 202 pushing to ../main
203 203 searching for changes
204 204 adding changesets
205 205 adding manifests
206 206 adding file changes
207 207 added 3 changesets with 3 changes to 2 files (+1 heads)
208 208 error: pretxnchangegroup hook failed: end-of-line check failed:
209 b.txt in fbcf9b1025f5 should not have CRLF line endings
209 210 d.txt in a7040e68714f should not have CRLF line endings
210 b.txt in fbcf9b1025f5 should not have CRLF line endings
211 211 transaction abort!
212 212 rollback completed
213 213 abort: end-of-line check failed:
214 b.txt in fbcf9b1025f5 should not have CRLF line endings
214 215 d.txt in a7040e68714f should not have CRLF line endings
215 b.txt in fbcf9b1025f5 should not have CRLF line endings
216 216 [255]
217 217
218 218 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now