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