win32text.py
245 lines
| 7.1 KiB
| text/x-python
|
PythonLexer
/ hgext / win32text.py
|
r6481 | # win32text.py - LF <-> CRLF/CR translation utilities for Windows/Mac users | ||
|
r5675 | # | ||
|
r47575 | # Copyright 2005, 2007-2009 Olivia Mackall <olivia@selenic.com> and others | ||
|
r8253 | # | ||
# This software may be used and distributed according to the terms of the | ||||
|
r10263 | # GNU General Public License version 2 or any later version. | ||
|
r8873 | |||
|
r20624 | '''perform automatic newline conversion (DEPRECATED) | ||
|
r8873 | |||
|
r11269 | Deprecation: The win32text extension requires each user to configure | ||
the extension again and again for each clone since the configuration | ||||
is not copied when cloning. | ||||
We have therefore made the ``eol`` as an alternative. The ``eol`` | ||||
uses a version controlled file for its configuration and each clone | ||||
will therefore use the right settings from the start. | ||||
|
r9217 | To perform automatic newline conversion, use:: | ||
|
r8873 | |||
|
r9217 | [extensions] | ||
|
r10112 | win32text = | ||
|
r9217 | [encode] | ||
** = cleverencode: | ||||
# or ** = macencode: | ||||
|
r8873 | |||
|
r9217 | [decode] | ||
** = cleverdecode: | ||||
# or ** = macdecode: | ||||
|
r8873 | |||
|
r9217 | If not doing conversion, to make sure you do not commit CRLF/CR by accident:: | ||
|
r8873 | |||
|
r9217 | [hooks] | ||
pretxncommit.crlf = python:hgext.win32text.forbidcrlf | ||||
# or pretxncommit.cr = python:hgext.win32text.forbidcr | ||||
|
r8873 | |||
To do the same check on a server to prevent CRLF/CR from being | ||||
|
r9217 | pushed or pulled:: | ||
|
r8873 | |||
|
r9217 | [hooks] | ||
pretxnchangegroup.crlf = python:hgext.win32text.forbidcrlf | ||||
# or pretxnchangegroup.cr = python:hgext.win32text.forbidcr | ||||
|
r8873 | ''' | ||
|
r5675 | |||
|
r29485 | |||
import re | ||||
|
r7225 | from mercurial.i18n import _ | ||
|
r43346 | from mercurial.node import short | ||
|
r29485 | from mercurial import ( | ||
r49211 | cmdutil, | |||
extensions, | ||||
|
r38806 | pycompat, | ||
|
r34186 | registrar, | ||
|
r37102 | ) | ||
|
r43346 | from mercurial.utils import stringutil | ||
|
r4859 | |||
|
r29841 | # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for | ||
|
r25186 | # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should | ||
# be specifying the version(s) of Mercurial they are tested with, or | ||||
# leave the attribute unspecified. | ||||
|
r43347 | testedwith = b'ships-with-hg-core' | ||
|
r16743 | |||
|
r34186 | configtable = {} | ||
configitem = registrar.configitem(configtable) | ||||
|
r43346 | configitem( | ||
|
r46554 | b'win32text', | ||
b'warn', | ||||
default=True, | ||||
|
r34186 | ) | ||
|
r4859 | # regexp for single LF without CR preceding. | ||
|
r43347 | re_single_lf = re.compile(b'(^|[^\r])\n', re.MULTILINE) | ||
|
r4859 | |||
|
r43347 | newlinestr = {b'\r\n': b'CRLF', b'\r': b'CR'} | ||
filterstr = {b'\r\n': b'clever', b'\r': b'mac'} | ||||
|
r6481 | |||
|
r43346 | |||
|
r6481 | def checknewline(s, newline, ui=None, repo=None, filename=None): | ||
# warn if already has 'newline' in repository. | ||||
|
r4859 | # it might cause unexpected eol conversion. | ||
# see issue 302: | ||||
|
r26420 | # https://bz.mercurial-scm.org/302 | ||
|
r6481 | if newline in s and ui and filename and repo: | ||
|
r43346 | ui.warn( | ||
_( | ||||
|
r43347 | b'WARNING: %s already has %s line endings\n' | ||
b'and does not need EOL conversion by the win32text plugin.\n' | ||||
b'Before your next commit, please reconsider your ' | ||||
b'encode/decode settings in \nMercurial.ini or %s.\n' | ||||
|
r43346 | ) | ||
|
r43347 | % (filename, newlinestr[newline], repo.vfs.join(b'hgrc')) | ||
|
r43346 | ) | ||
|
r6481 | |||
def dumbdecode(s, cmd, **kwargs): | ||||
|
r43347 | checknewline(s, b'\r\n', **kwargs) | ||
|
r4859 | # replace single LF to CRLF | ||
|
r43347 | return re_single_lf.sub(b'\\1\r\n', s) | ||
|
r4859 | |||
|
r43346 | |||
|
r4859 | def dumbencode(s, cmd): | ||
|
r43347 | return s.replace(b'\r\n', b'\n') | ||
|
r4859 | |||
|
r43346 | |||
|
r6481 | def macdumbdecode(s, cmd, **kwargs): | ||
|
r43347 | checknewline(s, b'\r', **kwargs) | ||
return s.replace(b'\n', b'\r') | ||||
|
r6481 | |||
|
r43346 | |||
|
r6481 | def macdumbencode(s, cmd): | ||
|
r43347 | return s.replace(b'\r', b'\n') | ||
|
r6481 | |||
|
r43346 | |||
|
r5967 | def cleverdecode(s, cmd, **kwargs): | ||
|
r37102 | if not stringutil.binary(s): | ||
|
r6473 | return dumbdecode(s, cmd, **kwargs) | ||
return s | ||||
|
r4859 | |||
|
r43346 | |||
|
r4859 | def cleverencode(s, cmd): | ||
|
r37102 | if not stringutil.binary(s): | ||
|
r6473 | return dumbencode(s, cmd) | ||
return s | ||||
|
r4859 | |||
|
r43346 | |||
|
r6481 | def macdecode(s, cmd, **kwargs): | ||
|
r37102 | if not stringutil.binary(s): | ||
|
r6484 | return macdumbdecode(s, cmd, **kwargs) | ||
return s | ||||
|
r6481 | |||
|
r43346 | |||
|
r6481 | def macencode(s, cmd): | ||
|
r37102 | if not stringutil.binary(s): | ||
|
r6484 | return macdumbencode(s, cmd) | ||
return s | ||||
|
r6481 | |||
|
r43346 | |||
|
r5966 | _filters = { | ||
|
r43347 | b'dumbdecode:': dumbdecode, | ||
b'dumbencode:': dumbencode, | ||||
b'cleverdecode:': cleverdecode, | ||||
b'cleverencode:': cleverencode, | ||||
b'macdumbdecode:': macdumbdecode, | ||||
b'macdumbencode:': macdumbencode, | ||||
b'macdecode:': macdecode, | ||||
b'macencode:': macencode, | ||||
|
r43346 | } | ||
|
r5675 | |||
|
r6483 | def forbidnewline(ui, repo, hooktype, node, newline, **kwargs): | ||
|
r5675 | halt = False | ||
|
r8150 | seen = set() | ||
|
r8147 | # we try to walk changesets in reverse order from newest to | ||
# oldest, so that if we see a file multiple times, we take the | ||||
# newest version as canonical. this prevents us from blocking a | ||||
# changegroup that contains an unacceptable commit followed later | ||||
# by a commit that fixes the problem. | ||||
|
r43347 | tip = repo[b'tip'] | ||
|
r43346 | for rev in pycompat.xrange( | ||
repo.changelog.tiprev(), repo[node].rev() - 1, -1 | ||||
): | ||||
|
r6747 | c = repo[rev] | ||
|
r5675 | for f in c.files(): | ||
|
r8147 | if f in seen or f not in tip or f not in c: | ||
|
r5675 | continue | ||
|
r8147 | seen.add(f) | ||
|
r5675 | data = c[f].data() | ||
|
r37102 | if not stringutil.binary(data) and newline in data: | ||
|
r5675 | if not halt: | ||
|
r43346 | ui.warn( | ||
_( | ||||
|
r43347 | b'attempt to commit or push text file(s) ' | ||
b'using %s line endings\n' | ||||
|
r43346 | ) | ||
% newlinestr[newline] | ||||
) | ||||
|
r43347 | ui.warn(_(b'in %s: %s\n') % (short(c.node()), f)) | ||
|
r5675 | halt = True | ||
|
r43347 | if halt and hooktype == b'pretxnchangegroup': | ||
|
r6481 | crlf = newlinestr[newline].lower() | ||
filter = filterstr[newline] | ||||
|
r43346 | ui.warn( | ||
_( | ||||
|
r43347 | b'\nTo prevent this mistake in your local repository,\n' | ||
b'add to Mercurial.ini or .hg/hgrc:\n' | ||||
b'\n' | ||||
b'[hooks]\n' | ||||
b'pretxncommit.%s = python:hgext.win32text.forbid%s\n' | ||||
b'\n' | ||||
b'and also consider adding:\n' | ||||
b'\n' | ||||
b'[extensions]\n' | ||||
b'win32text =\n' | ||||
b'[encode]\n' | ||||
b'** = %sencode:\n' | ||||
b'[decode]\n' | ||||
b'** = %sdecode:\n' | ||||
|
r43346 | ) | ||
% (crlf, crlf, filter, filter) | ||||
) | ||||
|
r5675 | return halt | ||
|
r5966 | |||
|
r43346 | |||
|
r6481 | def forbidcrlf(ui, repo, hooktype, node, **kwargs): | ||
|
r43347 | return forbidnewline(ui, repo, hooktype, node, b'\r\n', **kwargs) | ||
|
r6481 | |||
|
r43346 | |||
|
r6481 | def forbidcr(ui, repo, hooktype, node, **kwargs): | ||
|
r43347 | return forbidnewline(ui, repo, hooktype, node, b'\r', **kwargs) | ||
|
r6481 | |||
|
r43346 | |||
|
r5966 | def reposetup(ui, repo): | ||
if not repo.local(): | ||||
return | ||||
|
r49768 | for name, fn in _filters.items(): | ||
|
r5966 | repo.adddatafilter(name, fn) | ||
|
r43346 | |||
r49211 | def wrap_revert(orig, repo, ctx, names, uipathfn, actions, *args, **kwargs): | |||
# reset dirstate cache for file we touch | ||||
ds = repo.dirstate | ||||
with ds.parentchange(): | ||||
for filename in actions[b'revert'][0]: | ||||
entry = ds.get_entry(filename) | ||||
if entry is not None: | ||||
if entry.p1_tracked: | ||||
ds.update_file( | ||||
filename, | ||||
entry.tracked, | ||||
p1_tracked=True, | ||||
p2_info=entry.p2_info, | ||||
) | ||||
return orig(repo, ctx, names, uipathfn, actions, *args, **kwargs) | ||||
|
r12837 | def extsetup(ui): | ||
|
r25829 | # deprecated config: win32text.warn | ||
|
r43347 | if ui.configbool(b'win32text', b'warn'): | ||
|
r43346 | ui.warn( | ||
_( | ||||
|
r43347 | b"win32text is deprecated: " | ||
b"https://mercurial-scm.org/wiki/Win32TextExtension\n" | ||||
|
r43346 | ) | ||
) | ||||
r49211 | extensions.wrapfunction(cmdutil, '_performrevert', wrap_revert) | |||