104 pycompat, |
104 pycompat, |
105 registrar, |
105 registrar, |
106 scmutil, |
106 scmutil, |
107 util, |
107 util, |
108 ) |
108 ) |
109 from mercurial.utils import ( |
109 from mercurial.utils import stringutil |
110 stringutil, |
|
111 ) |
|
112 |
110 |
113 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for |
111 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for |
114 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should |
112 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should |
115 # be specifying the version(s) of Mercurial they are tested with, or |
113 # be specifying the version(s) of Mercurial they are tested with, or |
116 # leave the attribute unspecified. |
114 # leave the attribute unspecified. |
117 testedwith = 'ships-with-hg-core' |
115 testedwith = 'ships-with-hg-core' |
118 |
116 |
119 configtable = {} |
117 configtable = {} |
120 configitem = registrar.configitem(configtable) |
118 configitem = registrar.configitem(configtable) |
121 |
119 |
122 configitem('eol', 'fix-trailing-newline', |
120 configitem( |
123 default=False, |
121 'eol', 'fix-trailing-newline', default=False, |
124 ) |
122 ) |
125 configitem('eol', 'native', |
123 configitem( |
126 default=pycompat.oslinesep, |
124 'eol', 'native', default=pycompat.oslinesep, |
127 ) |
125 ) |
128 configitem('eol', 'only-consistent', |
126 configitem( |
129 default=True, |
127 'eol', 'only-consistent', default=True, |
130 ) |
128 ) |
131 |
129 |
132 # Matches a lone LF, i.e., one that is not part of CRLF. |
130 # Matches a lone LF, i.e., one that is not part of CRLF. |
133 singlelf = re.compile('(^|[^\r])\n') |
131 singlelf = re.compile('(^|[^\r])\n') |
134 |
132 |
|
133 |
135 def inconsistenteol(data): |
134 def inconsistenteol(data): |
136 return '\r\n' in data and singlelf.search(data) |
135 return '\r\n' in data and singlelf.search(data) |
|
136 |
137 |
137 |
138 def tolf(s, params, ui, **kwargs): |
138 def tolf(s, params, ui, **kwargs): |
139 """Filter to convert to LF EOLs.""" |
139 """Filter to convert to LF EOLs.""" |
140 if stringutil.binary(s): |
140 if stringutil.binary(s): |
141 return s |
141 return s |
142 if ui.configbool('eol', 'only-consistent') and inconsistenteol(s): |
142 if ui.configbool('eol', 'only-consistent') and inconsistenteol(s): |
143 return s |
143 return s |
144 if (ui.configbool('eol', 'fix-trailing-newline') |
144 if ( |
145 and s and not s.endswith('\n')): |
145 ui.configbool('eol', 'fix-trailing-newline') |
|
146 and s |
|
147 and not s.endswith('\n') |
|
148 ): |
146 s = s + '\n' |
149 s = s + '\n' |
147 return util.tolf(s) |
150 return util.tolf(s) |
|
151 |
148 |
152 |
149 def tocrlf(s, params, ui, **kwargs): |
153 def tocrlf(s, params, ui, **kwargs): |
150 """Filter to convert to CRLF EOLs.""" |
154 """Filter to convert to CRLF EOLs.""" |
151 if stringutil.binary(s): |
155 if stringutil.binary(s): |
152 return s |
156 return s |
153 if ui.configbool('eol', 'only-consistent') and inconsistenteol(s): |
157 if ui.configbool('eol', 'only-consistent') and inconsistenteol(s): |
154 return s |
158 return s |
155 if (ui.configbool('eol', 'fix-trailing-newline') |
159 if ( |
156 and s and not s.endswith('\n')): |
160 ui.configbool('eol', 'fix-trailing-newline') |
|
161 and s |
|
162 and not s.endswith('\n') |
|
163 ): |
157 s = s + '\n' |
164 s = s + '\n' |
158 return util.tocrlf(s) |
165 return util.tocrlf(s) |
|
166 |
159 |
167 |
160 def isbinary(s, params): |
168 def isbinary(s, params): |
161 """Filter to do nothing with the file.""" |
169 """Filter to do nothing with the file.""" |
162 return s |
170 return s |
|
171 |
163 |
172 |
164 filters = { |
173 filters = { |
165 'to-lf': tolf, |
174 'to-lf': tolf, |
166 'to-crlf': tocrlf, |
175 'to-crlf': tocrlf, |
167 'is-binary': isbinary, |
176 'is-binary': isbinary, |
168 # The following provide backwards compatibility with win32text |
177 # The following provide backwards compatibility with win32text |
169 'cleverencode:': tolf, |
178 'cleverencode:': tolf, |
170 'cleverdecode:': tocrlf |
179 'cleverdecode:': tocrlf, |
171 } |
180 } |
|
181 |
172 |
182 |
173 class eolfile(object): |
183 class eolfile(object): |
174 def __init__(self, ui, root, data): |
184 def __init__(self, ui, root, data): |
175 self._decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'} |
185 self._decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'} |
176 self._encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'} |
186 self._encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'} |
206 for pattern, key, m in self.patterns: |
216 for pattern, key, m in self.patterns: |
207 try: |
217 try: |
208 ui.setconfig('decode', pattern, self._decode[key], 'eol') |
218 ui.setconfig('decode', pattern, self._decode[key], 'eol') |
209 ui.setconfig('encode', pattern, self._encode[key], 'eol') |
219 ui.setconfig('encode', pattern, self._encode[key], 'eol') |
210 except KeyError: |
220 except KeyError: |
211 ui.warn(_("ignoring unknown EOL style '%s' from %s\n") |
221 ui.warn( |
212 % (key, self.cfg.source('patterns', pattern))) |
222 _("ignoring unknown EOL style '%s' from %s\n") |
|
223 % (key, self.cfg.source('patterns', pattern)) |
|
224 ) |
213 # eol.only-consistent can be specified in ~/.hgrc or .hgeol |
225 # eol.only-consistent can be specified in ~/.hgrc or .hgeol |
214 for k, v in self.cfg.items('eol'): |
226 for k, v in self.cfg.items('eol'): |
215 ui.setconfig('eol', k, v, 'eol') |
227 ui.setconfig('eol', k, v, 'eol') |
216 |
228 |
217 def checkrev(self, repo, ctx, files): |
229 def checkrev(self, repo, ctx, files): |
218 failed = [] |
230 failed = [] |
219 for f in (files or ctx.files()): |
231 for f in files or ctx.files(): |
220 if f not in ctx: |
232 if f not in ctx: |
221 continue |
233 continue |
222 for pattern, key, m in self.patterns: |
234 for pattern, key, m in self.patterns: |
223 if not m(f): |
235 if not m(f): |
224 continue |
236 continue |
225 target = self._encode[key] |
237 target = self._encode[key] |
226 data = ctx[f].data() |
238 data = ctx[f].data() |
227 if (target == "to-lf" and "\r\n" in data |
239 if ( |
228 or target == "to-crlf" and singlelf.search(data)): |
240 target == "to-lf" |
|
241 and "\r\n" in data |
|
242 or target == "to-crlf" |
|
243 and singlelf.search(data) |
|
244 ): |
229 failed.append((f, target, bytes(ctx))) |
245 failed.append((f, target, bytes(ctx))) |
230 break |
246 break |
231 return failed |
247 return failed |
|
248 |
232 |
249 |
233 def parseeol(ui, repo, nodes): |
250 def parseeol(ui, repo, nodes): |
234 try: |
251 try: |
235 for node in nodes: |
252 for node in nodes: |
236 try: |
253 try: |
242 data = repo[node]['.hgeol'].data() |
259 data = repo[node]['.hgeol'].data() |
243 return eolfile(ui, repo.root, data) |
260 return eolfile(ui, repo.root, data) |
244 except (IOError, LookupError): |
261 except (IOError, LookupError): |
245 pass |
262 pass |
246 except errormod.ParseError as inst: |
263 except errormod.ParseError as inst: |
247 ui.warn(_("warning: ignoring .hgeol file due to parse error " |
264 ui.warn( |
248 "at %s: %s\n") % (inst.args[1], inst.args[0])) |
265 _("warning: ignoring .hgeol file due to parse error " "at %s: %s\n") |
|
266 % (inst.args[1], inst.args[0]) |
|
267 ) |
249 return None |
268 return None |
|
269 |
250 |
270 |
251 def ensureenabled(ui): |
271 def ensureenabled(ui): |
252 """make sure the extension is enabled when used as hook |
272 """make sure the extension is enabled when used as hook |
253 |
273 |
254 When eol is used through hooks, the extension is never formally loaded and |
274 When eol is used through hooks, the extension is never formally loaded and |
282 |
303 |
283 if failed: |
304 if failed: |
284 eols = {'to-lf': 'CRLF', 'to-crlf': 'LF'} |
305 eols = {'to-lf': 'CRLF', 'to-crlf': 'LF'} |
285 msgs = [] |
306 msgs = [] |
286 for f, target, node in sorted(failed): |
307 for f, target, node in sorted(failed): |
287 msgs.append(_(" %s in %s should not have %s line endings") % |
308 msgs.append( |
288 (f, node, eols[target])) |
309 _(" %s in %s should not have %s line endings") |
|
310 % (f, node, eols[target]) |
|
311 ) |
289 raise errormod.Abort(_("end-of-line check failed:\n") + "\n".join(msgs)) |
312 raise errormod.Abort(_("end-of-line check failed:\n") + "\n".join(msgs)) |
|
313 |
290 |
314 |
291 def checkallhook(ui, repo, node, hooktype, **kwargs): |
315 def checkallhook(ui, repo, node, hooktype, **kwargs): |
292 """verify that files have expected EOLs""" |
316 """verify that files have expected EOLs""" |
293 _checkhook(ui, repo, node, False) |
317 _checkhook(ui, repo, node, False) |
294 |
318 |
|
319 |
295 def checkheadshook(ui, repo, node, hooktype, **kwargs): |
320 def checkheadshook(ui, repo, node, hooktype, **kwargs): |
296 """verify that files have expected EOLs""" |
321 """verify that files have expected EOLs""" |
297 _checkhook(ui, repo, node, True) |
322 _checkhook(ui, repo, node, True) |
298 |
323 |
|
324 |
299 # "checkheadshook" used to be called "hook" |
325 # "checkheadshook" used to be called "hook" |
300 hook = checkheadshook |
326 hook = checkheadshook |
|
327 |
301 |
328 |
302 def preupdate(ui, repo, hooktype, parent1, parent2): |
329 def preupdate(ui, repo, hooktype, parent1, parent2): |
303 p1node = scmutil.resolvehexnodeidprefix(repo, parent1) |
330 p1node = scmutil.resolvehexnodeidprefix(repo, parent1) |
304 repo.loadeol([p1node]) |
331 repo.loadeol([p1node]) |
305 return False |
332 return False |
306 |
333 |
|
334 |
307 def uisetup(ui): |
335 def uisetup(ui): |
308 ui.setconfig('hooks', 'preupdate.eol', preupdate, 'eol') |
336 ui.setconfig('hooks', 'preupdate.eol', preupdate, 'eol') |
|
337 |
309 |
338 |
310 def extsetup(ui): |
339 def extsetup(ui): |
311 try: |
340 try: |
312 extensions.find('win32text') |
341 extensions.find('win32text') |
313 ui.warn(_("the eol extension is incompatible with the " |
342 ui.warn( |
314 "win32text extension\n")) |
343 _( |
|
344 "the eol extension is incompatible with the " |
|
345 "win32text extension\n" |
|
346 ) |
|
347 ) |
315 except KeyError: |
348 except KeyError: |
316 pass |
349 pass |
317 |
350 |
318 |
351 |
319 def reposetup(ui, repo): |
352 def reposetup(ui, repo): |
412 # We should not abort here, since the user should |
444 # We should not abort here, since the user should |
413 # be able to say "** = native" to automatically |
445 # be able to say "** = native" to automatically |
414 # have all non-binary files taken care of. |
446 # have all non-binary files taken care of. |
415 continue |
447 continue |
416 if inconsistenteol(data): |
448 if inconsistenteol(data): |
417 raise errormod.Abort(_("inconsistent newline style " |
449 raise errormod.Abort( |
418 "in %s\n") % f) |
450 _("inconsistent newline style " "in %s\n") % f |
|
451 ) |
419 return super(eolrepo, self).commitctx(ctx, error, origctx) |
452 return super(eolrepo, self).commitctx(ctx, error, origctx) |
|
453 |
420 repo.__class__ = eolrepo |
454 repo.__class__ = eolrepo |
421 repo._hgcleardirstate() |
455 repo._hgcleardirstate() |