##// END OF EJS Templates
eol: the hook no longer requires the extension to be loaded...
Patrick Mezard -
r13615:686dec75 default
parent child Browse files
Show More
@@ -1,299 +1,303 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 in your hgrc file
56 56 (not the ``.hgeol`` file) for settings that control the overall
57 57 behavior. There are two settings:
58 58
59 59 - ``eol.native`` (default ``os.linesep``) can be set to ``LF`` or
60 60 ``CRLF`` to override the default interpretation of ``native`` for
61 61 checkout. This can be used with :hg:`archive` on Unix, say, to
62 62 generate an archive where files have line endings for Windows.
63 63
64 64 - ``eol.only-consistent`` (default True) can be set to False to make
65 65 the extension convert files with inconsistent EOLs. Inconsistent
66 66 means that there is both ``CRLF`` and ``LF`` present in the file.
67 67 Such files are normally not touched under the assumption that they
68 68 have mixed EOLs on purpose.
69 69
70 70 The extension provides ``cleverencode:`` and ``cleverdecode:`` filters
71 71 like the deprecated win32text extension does. This means that you can
72 72 disable win32text and enable eol and your filters will still work. You
73 73 only need to these filters until you have prepared a ``.hgeol`` file.
74 74
75 75 The ``win32text.forbid*`` hooks provided by the win32text extension
76 76 have been unified into a single hook named ``eol.hook``. The hook will
77 77 lookup the expected line endings from the ``.hgeol`` file, which means
78 78 you must migrate to a ``.hgeol`` file first before using the hook.
79 Remember to enable the eol extension in the repository where you
80 install the hook.
81 79
82 80 See :hg:`help patterns` for more information about the glob patterns
83 81 used.
84 82 """
85 83
86 84 from mercurial.i18n import _
87 85 from mercurial import util, config, extensions, match, error
88 86 import re, os
89 87
90 88 # Matches a lone LF, i.e., one that is not part of CRLF.
91 89 singlelf = re.compile('(^|[^\r])\n')
92 90 # Matches a single EOL which can either be a CRLF where repeated CR
93 91 # are removed or a LF. We do not care about old Machintosh files, so a
94 92 # stray CR is an error.
95 93 eolre = re.compile('\r*\n')
96 94
97 95
98 96 def inconsistenteol(data):
99 97 return '\r\n' in data and singlelf.search(data)
100 98
101 99 def tolf(s, params, ui, **kwargs):
102 100 """Filter to convert to LF EOLs."""
103 101 if util.binary(s):
104 102 return s
105 103 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
106 104 return s
107 105 return eolre.sub('\n', s)
108 106
109 107 def tocrlf(s, params, ui, **kwargs):
110 108 """Filter to convert to CRLF EOLs."""
111 109 if util.binary(s):
112 110 return s
113 111 if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
114 112 return s
115 113 return eolre.sub('\r\n', s)
116 114
117 115 def isbinary(s, params):
118 116 """Filter to do nothing with the file."""
119 117 return s
120 118
121 119 filters = {
122 120 'to-lf': tolf,
123 121 'to-crlf': tocrlf,
124 122 'is-binary': isbinary,
125 123 # The following provide backwards compatibility with win32text
126 124 'cleverencode:': tolf,
127 125 'cleverdecode:': tocrlf
128 126 }
129 127
130 128 class eolfile(object):
131 129 def __init__(self, ui, root, data):
132 130 self._decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
133 131 self._encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
134 132
135 133 self.cfg = config.config()
136 134 # Our files should not be touched. The pattern must be
137 135 # inserted first override a '** = native' pattern.
138 136 self.cfg.set('patterns', '.hg*', 'BIN')
139 137 # We can then parse the user's patterns.
140 138 self.cfg.parse('.hgeol', data)
141 139
142 140 isrepolf = self.cfg.get('repository', 'native') != 'CRLF'
143 141 self._encode['NATIVE'] = isrepolf and 'to-lf' or 'to-crlf'
144 142 iswdlf = ui.config('eol', 'native', os.linesep) in ('LF', '\n')
145 143 self._decode['NATIVE'] = iswdlf and 'to-lf' or 'to-crlf'
146 144
147 145 include = []
148 146 exclude = []
149 147 for pattern, style in self.cfg.items('patterns'):
150 148 key = style.upper()
151 149 if key == 'BIN':
152 150 exclude.append(pattern)
153 151 else:
154 152 include.append(pattern)
155 153 # This will match the files for which we need to care
156 154 # about inconsistent newlines.
157 155 self.match = match.match(root, '', [], include, exclude)
158 156
159 157 def setfilters(self, ui):
160 158 for pattern, style in self.cfg.items('patterns'):
161 159 key = style.upper()
162 160 try:
163 161 ui.setconfig('decode', pattern, self._decode[key])
164 162 ui.setconfig('encode', pattern, self._encode[key])
165 163 except KeyError:
166 164 ui.warn(_("ignoring unknown EOL style '%s' from %s\n")
167 165 % (style, self.cfg.source('patterns', pattern)))
168 166
167 def checkrev(self, repo, ctx, files):
168 for f in files:
169 if f not in ctx:
170 continue
171 for pattern, style in self.cfg.items('patterns'):
172 if not match.match(repo.root, '', [pattern])(f):
173 continue
174 target = self._encode[style.upper()]
175 data = ctx[f].data()
176 if target == "to-lf" and "\r\n" in data:
177 raise util.Abort(_("%s should not have CRLF line endings")
178 % f)
179 elif target == "to-crlf" and singlelf.search(data):
180 raise util.Abort(_("%s should not have LF line endings")
181 % f)
182 # Ignore other rules for this file
183 break
184
169 185 def parseeol(ui, repo, nodes):
170 186 try:
171 187 for node in nodes:
172 188 try:
173 189 if node is None:
174 190 # Cannot use workingctx.data() since it would load
175 191 # and cache the filters before we configure them.
176 192 data = repo.wfile('.hgeol').read()
177 193 else:
178 194 data = repo[node]['.hgeol'].data()
179 195 return eolfile(ui, repo.root, data)
180 196 except (IOError, LookupError):
181 197 pass
182 198 except error.ParseError, inst:
183 199 ui.warn(_("warning: ignoring .hgeol file due to parse error "
184 200 "at %s: %s\n") % (inst.args[1], inst.args[0]))
185 201 return None
186 202
187 203 def hook(ui, repo, node, hooktype, **kwargs):
188 204 """verify that files have expected EOLs"""
189 205 files = set()
190 206 for rev in xrange(repo[node].rev(), len(repo)):
191 207 files.update(repo[rev].files())
192 208 tip = repo['tip']
193 for f in files:
194 if f not in tip:
195 continue
196 for pattern, target in ui.configitems('encode'):
197 if match.match(repo.root, '', [pattern])(f):
198 data = tip[f].data()
199 if target == "to-lf" and "\r\n" in data:
200 raise util.Abort(_("%s should not have CRLF line endings")
201 % f)
202 elif target == "to-crlf" and singlelf.search(data):
203 raise util.Abort(_("%s should not have LF line endings")
204 % f)
205 # Ignore other rules for this file
206 break
207
209 eol = parseeol(ui, repo, [tip.node()])
210 if eol:
211 eol.checkrev(repo, tip, files)
208 212
209 213 def preupdate(ui, repo, hooktype, parent1, parent2):
210 214 #print "preupdate for %s: %s -> %s" % (repo.root, parent1, parent2)
211 215 repo.loadeol([parent1])
212 216 return False
213 217
214 218 def uisetup(ui):
215 219 ui.setconfig('hooks', 'preupdate.eol', preupdate)
216 220
217 221 def extsetup(ui):
218 222 try:
219 223 extensions.find('win32text')
220 224 raise util.Abort(_("the eol extension is incompatible with the "
221 225 "win32text extension"))
222 226 except KeyError:
223 227 pass
224 228
225 229
226 230 def reposetup(ui, repo):
227 231 uisetup(repo.ui)
228 232 #print "reposetup for", repo.root
229 233
230 234 if not repo.local():
231 235 return
232 236 for name, fn in filters.iteritems():
233 237 repo.adddatafilter(name, fn)
234 238
235 239 ui.setconfig('patch', 'eol', 'auto')
236 240
237 241 class eolrepo(repo.__class__):
238 242
239 243 def loadeol(self, nodes):
240 244 eol = parseeol(self.ui, self, nodes)
241 245 if eol is None:
242 246 return None
243 247 eol.setfilters(self.ui)
244 248 return eol.match
245 249
246 250 def _hgcleardirstate(self):
247 251 self._eolfile = self.loadeol([None, 'tip'])
248 252 if not self._eolfile:
249 253 self._eolfile = util.never
250 254 return
251 255
252 256 try:
253 257 cachemtime = os.path.getmtime(self.join("eol.cache"))
254 258 except OSError:
255 259 cachemtime = 0
256 260
257 261 try:
258 262 eolmtime = os.path.getmtime(self.wjoin(".hgeol"))
259 263 except OSError:
260 264 eolmtime = 0
261 265
262 266 if eolmtime > cachemtime:
263 267 ui.debug("eol: detected change in .hgeol\n")
264 268 wlock = None
265 269 try:
266 270 wlock = self.wlock()
267 271 for f in self.dirstate:
268 272 if self.dirstate[f] == 'n':
269 273 # all normal files need to be looked at
270 274 # again since the new .hgeol file might no
271 275 # longer match a file it matched before
272 276 self.dirstate.normallookup(f)
273 277 # Touch the cache to update mtime.
274 278 self.opener("eol.cache", "w").close()
275 279 wlock.release()
276 280 except error.LockUnavailable:
277 281 # If we cannot lock the repository and clear the
278 282 # dirstate, then a commit might not see all files
279 283 # as modified. But if we cannot lock the
280 284 # repository, then we can also not make a commit,
281 285 # so ignore the error.
282 286 pass
283 287
284 288 def commitctx(self, ctx, error=False):
285 289 for f in sorted(ctx.added() + ctx.modified()):
286 290 if not self._eolfile(f):
287 291 continue
288 292 data = ctx[f].data()
289 293 if util.binary(data):
290 294 # We should not abort here, since the user should
291 295 # be able to say "** = native" to automatically
292 296 # have all non-binary files taken care of.
293 297 continue
294 298 if inconsistenteol(data):
295 299 raise util.Abort(_("inconsistent newline style "
296 300 "in %s\n" % f))
297 301 return super(eolrepo, self).commitctx(ctx, error)
298 302 repo.__class__ = eolrepo
299 303 repo._hgcleardirstate()
@@ -1,86 +1,83 b''
1 1 Test the EOL hook
2 2
3 3 $ hg init main
4 4 $ cat > main/.hg/hgrc <<EOF
5 > [extensions]
6 > eol =
7 >
8 5 > [hooks]
9 6 > pretxnchangegroup = python:hgext.eol.hook
10 7 > EOF
11 8 $ hg clone main fork
12 9 updating to branch default
13 10 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
14 11 $ cd fork
15 12
16 13 Create repo
17 14 $ cat > .hgeol <<EOF
18 15 > [patterns]
19 16 > mixed.txt = BIN
20 17 > crlf.txt = CRLF
21 18 > **.txt = native
22 19 > EOF
23 20 $ hg add .hgeol
24 21 $ hg commit -m 'Commit .hgeol'
25 22
26 23 $ printf "first\nsecond\nthird\n" > a.txt
27 24 $ hg add a.txt
28 25 $ hg commit -m 'LF a.txt'
29 26 $ hg push ../main
30 27 pushing to ../main
31 28 searching for changes
32 29 adding changesets
33 30 adding manifests
34 31 adding file changes
35 32 added 2 changesets with 2 changes to 2 files
36 33
37 34 $ printf "first\r\nsecond\r\nthird\n" > a.txt
38 35 $ hg commit -m 'CRLF a.txt'
39 36 $ hg push ../main
40 37 pushing to ../main
41 38 searching for changes
42 39 adding changesets
43 40 adding manifests
44 41 adding file changes
45 42 added 1 changesets with 1 changes to 1 files
46 43 error: pretxnchangegroup hook failed: a.txt should not have CRLF line endings
47 44 transaction abort!
48 45 rollback completed
49 46 abort: a.txt should not have CRLF line endings
50 47 [255]
51 48
52 49 $ printf "first\nsecond\nthird\n" > a.txt
53 50 $ hg commit -m 'LF a.txt (fixed)'
54 51 $ hg push ../main
55 52 pushing to ../main
56 53 searching for changes
57 54 adding changesets
58 55 adding manifests
59 56 adding file changes
60 57 added 2 changesets with 2 changes to 1 files
61 58
62 59 $ printf "first\nsecond\nthird\n" > crlf.txt
63 60 $ hg add crlf.txt
64 61 $ hg commit -m 'LF crlf.txt'
65 62 $ hg push ../main
66 63 pushing to ../main
67 64 searching for changes
68 65 adding changesets
69 66 adding manifests
70 67 adding file changes
71 68 added 1 changesets with 1 changes to 1 files
72 69 error: pretxnchangegroup hook failed: crlf.txt should not have LF line endings
73 70 transaction abort!
74 71 rollback completed
75 72 abort: crlf.txt should not have LF line endings
76 73 [255]
77 74
78 75 $ printf "first\r\nsecond\r\nthird\r\n" > crlf.txt
79 76 $ hg commit -m 'CRLF crlf.txt (fixed)'
80 77 $ hg push ../main
81 78 pushing to ../main
82 79 searching for changes
83 80 adding changesets
84 81 adding manifests
85 82 adding file changes
86 83 added 2 changesets with 2 changes to 1 files
General Comments 0
You need to be logged in to leave comments. Login now