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