changeset 11249:0bb67503ad4b stable

eol: extension for managing file EOLs
author Martin Geisler <mg@lazybytes.net>
date Mon, 31 May 2010 21:37:01 +0200
parents 8f5ad12db28e
children ac6fec2af8c8 640d419725d0 8324a9fca62e
files hgext/eol.py tests/test-eol tests/test-eol-add tests/test-eol-add.out tests/test-eol-clone tests/test-eol-clone.out tests/test-eol-hook tests/test-eol-hook.out tests/test-eol-patch tests/test-eol-patch.out tests/test-eol-update tests/test-eol-update.out tests/test-eol.out
diffstat 13 files changed, 1547 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/eol.py	Mon May 31 21:37:01 2010 +0200
@@ -0,0 +1,256 @@
+"""automatically manage newlines in repository files
+
+This extension allows you to manage the type of line endings (CRLF or
+LF) that are used in the repository and in the local working
+directory. That way you can get CRLF line endings on Windows and LF on
+Unix/Mac, thereby letting everybody use their OS native line endings.
+
+The extension reads its configuration from a versioned ``.hgeol``
+configuration file every time you run an ``hg`` command. The
+``.hgeol`` file use the same syntax as all other Mercurial
+configuration files. It uses two sections, ``[patterns]`` and
+``[repository]``.
+
+The ``[patterns]`` section specifies the line endings used in the
+working directory. The format is specified by a file pattern. The
+first match is used, so put more specific patterns first. The
+available line endings are ``LF``, ``CRLF``, and ``BIN``.
+
+Files with the declared format of ``CRLF`` or ``LF`` are always
+checked out in that format and files declared to be binary (``BIN``)
+are left unchanged. Additionally, ``native`` is an alias for the
+platform's default line ending: ``LF`` on Unix (including Mac OS X)
+and ``CRLF`` on Windows. Note that ``BIN`` (do nothing to line
+endings) is Mercurial's default behaviour; it is only needed if you
+need to override a later, more general pattern.
+
+The optional ``[repository]`` section specifies the line endings to
+use for files stored in the repository. It has a single setting,
+``native``, which determines the storage line endings for files
+declared as ``native`` in the ``[patterns]`` section. It can be set to
+``LF`` or ``CRLF``. The default is ``LF``. For example, this means
+that on Windows, files configured as ``native`` (``CRLF`` by default)
+will be converted to ``LF`` when stored in the repository. Files
+declared as ``LF``, ``CRLF``, or ``BIN`` in the ``[patterns]`` section
+are always stored as-is in the repository.
+
+Example versioned ``.hgeol`` file::
+
+  [patterns]
+  **.py = native
+  **.vcproj = CRLF
+  **.txt = native
+  Makefile = LF
+  **.jpg = BIN
+
+  [repository]
+  native = LF
+
+The extension uses an optional ``[eol]`` section in your hgrc file
+(not the ``.hgeol`` file) for settings that control the overall
+behavior. There are two settings:
+
+- ``eol.native`` (default ``os.linesep``) can be set to ``LF`` or
+  ``CRLF`` override the default interpretation of ``native`` for
+  checkout. This can be used with :hg:`archive` on Unix, say, to
+  generate an archive where files have line endings for Windows.
+
+- ``eol.only-consistent`` (default True) can be set to False to make
+  the extension convert files with inconsistent EOLs. Inconsistent
+  means that there is both ``CRLF`` and ``LF`` present in the file.
+  Such files are normally not touched under the assumption that they
+  have mixed EOLs on purpose.
+
+See :hg:`help patterns` for more information about the glob patterns
+used.
+"""
+
+from mercurial.i18n import _
+from mercurial import util, config, extensions, commands, match, cmdutil
+import re, os
+
+# Matches a lone LF, i.e., one that is not part of CRLF.
+singlelf = re.compile('(^|[^\r])\n')
+# Matches a single EOL which can either be a CRLF where repeated CR
+# are removed or a LF. We do not care about old Machintosh files, so a
+# stray CR is an error.
+eolre = re.compile('\r*\n')
+
+
+def inconsistenteol(data):
+    return '\r\n' in data and singlelf.search(data)
+
+def tolf(s, params, ui, **kwargs):
+    """Filter to convert to LF EOLs."""
+    if util.binary(s):
+        return s
+    if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
+        return s
+    return eolre.sub('\n', s)
+
+def tocrlf(s, params, ui, **kwargs):
+    """Filter to convert to CRLF EOLs."""
+    if util.binary(s):
+        return s
+    if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
+        return s
+    return eolre.sub('\r\n', s)
+
+def isbinary(s, params):
+    """Filter to do nothing with the file."""
+    return s
+
+filters = {
+    'to-lf': tolf,
+    'to-crlf': tocrlf,
+    'is-binary': isbinary,
+}
+
+
+def hook(ui, repo, node, hooktype, **kwargs):
+    """verify that files have expected EOLs"""
+    files = set()
+    for rev in xrange(repo[node].rev(), len(repo)):
+        files.update(repo[rev].files())
+    tip = repo['tip']
+    for f in files:
+        if f not in tip:
+            continue
+        for pattern, target in ui.configitems('encode'):
+            if match.match(repo.root, '', [pattern])(f):
+                data = tip[f].data()
+                if target == "to-lf" and "\r\n" in data:
+                    raise util.Abort(_("%s should not have CRLF line endings")
+                                     % f)
+                elif target == "to-crlf" and singlelf.search(data):
+                    raise util.Abort(_("%s should not have LF line endings")
+                                     % f)
+
+
+def preupdate(ui, repo, hooktype, parent1, parent2):
+    #print "preupdate for %s: %s -> %s" % (repo.root, parent1, parent2)
+    repo.readhgeol(parent1)
+    return False
+
+def uisetup(ui):
+    ui.setconfig('hooks', 'preupdate.eol', preupdate)
+
+def extsetup(ui):
+    try:
+        extensions.find('win32text')
+        raise util.Abort(_("the eol extension is incompatible with the "
+                           "win32text extension"))
+    except KeyError:
+        pass
+
+
+def reposetup(ui, repo):
+    #print "reposetup for", repo.root
+
+    if not repo.local():
+        return
+    for name, fn in filters.iteritems():
+        repo.adddatafilter(name, fn)
+
+    ui.setconfig('patch', 'eol', 'auto')
+
+    class eolrepo(repo.__class__):
+
+        _decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
+        _encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
+
+        def readhgeol(self, node=None, data=None):
+            if data is None:
+                try:
+                    if node is None:
+                        data = self.wfile('.hgeol').read()
+                    else:
+                        data = self[node]['.hgeol'].data()
+                except (IOError, LookupError):
+                    return None
+
+            if self.ui.config('eol', 'native', os.linesep) in ('LF', '\n'):
+                self._decode['NATIVE'] = 'to-lf'
+            else:
+                self._decode['NATIVE'] = 'to-crlf'
+
+            eol = config.config()
+            eol.parse('.hgeol', data)
+
+            if eol.get('repository', 'native') == 'CRLF':
+                self._encode['NATIVE'] = 'to-crlf'
+            else:
+                self._encode['NATIVE'] = 'to-lf'
+
+            for pattern, style in eol.items('patterns'):
+                key = style.upper()
+                try:
+                    self.ui.setconfig('decode', pattern, self._decode[key])
+                    self.ui.setconfig('encode', pattern, self._encode[key])
+                except KeyError:
+                    self.ui.warn(_("ignoring unknown EOL style '%s' from %s\n")
+                                 % (style, eol.source('patterns', pattern)))
+
+            include = []
+            exclude = []
+            for pattern, style in eol.items('patterns'):
+                key = style.upper()
+                if key == 'BIN':
+                    exclude.append(pattern)
+                else:
+                    include.append(pattern)
+
+            # This will match the files for which we need to care
+            # about inconsistent newlines.
+            return match.match(self.root, '', [], include, exclude)
+
+        def _hgcleardirstate(self):
+            self._eolfile = self.readhgeol() or self.readhgeol('tip')
+
+            if not self._eolfile:
+                self._eolfile = util.never
+                return
+
+            try:
+                cachemtime = os.path.getmtime(self.join("eol.cache"))
+            except OSError:
+                cachemtime = 0
+
+            try:
+                eolmtime = os.path.getmtime(self.wjoin(".hgeol"))
+            except OSError:
+                eolmtime = 0
+
+            if eolmtime > cachemtime:
+                ui.debug("eol: detected change in .hgeol\n")
+                # TODO: we could introduce a method for this in dirstate.
+                wlock = None
+                try:
+                    wlock = self.wlock()
+                    for f, e in self.dirstate._map.iteritems():
+                        self.dirstate._map[f] = (e[0], e[1], -1, 0)
+                    self.dirstate._dirty = True
+                    # Touch the cache to update mtime. TODO: are we sure this
+                    # always enought to update the mtime, or should we write a
+                    # bit to the file?
+                    self.opener("eol.cache", "w").close()
+                finally:
+                    if wlock is not None:
+                        wlock.release()
+
+        def commitctx(self, ctx, error=False):
+            for f in sorted(ctx.added() + ctx.modified()):
+                if not self._eolfile(f):
+                    continue
+                data = ctx[f].data()
+                if util.binary(data):
+                    # We should not abort here, since the user should
+                    # be able to say "** = native" to automatically
+                    # have all non-binary files taken care of.
+                    continue
+                if inconsistenteol(data):
+                    raise util.Abort(_("inconsistent newline style "
+                                       "in %s\n" % f))
+            return super(eolrepo, self).commitctx(ctx, error)
+    repo.__class__ = eolrepo
+    repo._hgcleardirstate()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-eol	Mon May 31 21:37:01 2010 +0200
@@ -0,0 +1,180 @@
+#!/bin/sh
+
+cat > $HGRCPATH <<EOF
+[diff]
+git = True
+EOF
+
+cat > switch-eol.py <<EOF
+import sys
+
+try:
+    import os, msvcrt
+    msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
+    msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
+except ImportError:
+    pass
+
+(old, new) = sys.argv[1] == 'LF' and ('\n', '\r\n') or ('\r\n', '\n')
+print "%% switching encoding from %r to %r" % (old, new)
+for path in sys.argv[2:]:
+    data = file(path, 'rb').read()
+    data = data.replace(old, new)
+    file(path, 'wb').write(data)
+EOF
+
+seteol () {
+    if [ $1 = "LF" ]; then
+        EOL='\n'
+    else
+        EOL='\r\n'
+    fi
+}
+
+makerepo () {
+    seteol $1
+    echo "% setup $1 repository"
+    hg init repo
+    cd repo
+
+    cat > .hgeol <<EOF
+[repository]
+native = $1
+
+[patterns]
+mixed.txt = BIN
+**.txt = native
+EOF
+
+    printf "first${EOL}second${EOL}third${EOL}" > a.txt
+    hg commit --addremove -m 'checkin'
+    echo
+    cd ..
+}
+
+dotest () {
+    seteol $1
+    echo "% hg clone repo repo-$1"
+    hg clone --noupdate repo repo-$1
+    cd repo-$1
+
+    cat > .hg/hgrc <<EOF
+[extensions]
+eol =
+
+[eol]
+native = $1
+EOF
+
+    hg update
+    echo '% printrepr.py a.txt'
+    python $TESTDIR/printrepr.py < a.txt
+    echo '% hg cat a.txt'
+    hg cat a.txt | python $TESTDIR/printrepr.py
+
+    printf "fourth${EOL}" >> a.txt
+    echo '% printrepr.py a.txt'
+    python $TESTDIR/printrepr.py < a.txt
+    hg diff | python $TESTDIR/printrepr.py
+
+    python ../switch-eol.py $1 a.txt
+    echo '% hg diff only reports a single changed line:'
+    hg diff | python $TESTDIR/printrepr.py
+
+    echo "% reverting back to $1 format"
+    hg revert a.txt
+    python $TESTDIR/printrepr.py < a.txt
+
+    printf "first\r\nsecond\n" > mixed.txt
+    hg add mixed.txt
+    echo "% hg commit of inconsistent .txt file marked as binary (should work)"
+    hg commit -m 'binary file'
+
+    echo "% hg commit of inconsistent .txt file marked as native (should fail)"
+    printf "first\nsecond\r\nthird\nfourth\r\n" > a.txt
+    hg commit -m 'inconsistent file'
+
+    echo "% hg commit --config eol.only-consistent=False (should work)"
+    hg commit --config eol.only-consistent=False -m 'inconsistent file'
+
+    echo "% hg commit of binary .txt file marked as native (binary files always okay)"
+    printf "first${EOL}\0${EOL}third${EOL}" > a.txt
+    hg commit -m 'binary file'
+
+    cd ..
+    rm -r repo-$1
+}
+
+makerepo LF
+dotest LF
+dotest CRLF
+rm -r repo
+
+makerepo CRLF
+dotest LF
+dotest CRLF
+rm -r repo
+
+
+makemixedrepo () {
+    echo
+    echo "# setup $1 repository"
+    hg init mixed
+    cd mixed
+    printf "foo\r\nbar\r\nbaz\r\n" > win.txt
+    printf "foo\nbar\nbaz\n" > unix.txt
+    #printf "foo\r\nbar\nbaz\r\n" > mixed.txt
+
+    hg commit --addremove -m 'created mixed files'
+
+    echo "# setting repository-native EOLs to $1"
+    cat > .hgeol <<EOF
+[repository]
+native = $1
+
+[patterns]
+**.txt = native
+EOF
+    hg commit --addremove -m 'added .hgeol'
+    cd ..
+}
+
+testmixed () {
+    echo
+    echo "% hg clone mixed mixed-$1"
+    hg clone mixed mixed-$1
+    cd mixed-$1
+
+    echo '% hg status (eol extension not yet activated)'
+    hg status
+
+    cat > .hg/hgrc <<EOF
+[extensions]
+eol =
+
+[eol]
+native = $1
+EOF
+
+    echo '% hg status (eol activated)'
+    hg status
+    echo '% hg commit'
+    hg commit -m 'synchronized EOLs'
+
+    echo '% hg status'
+    hg status
+
+    cd ..
+    rm -r mixed-$1
+}
+
+makemixedrepo LF
+testmixed LF
+testmixed CRLF
+rm -r mixed
+
+makemixedrepo CRLF
+testmixed LF
+testmixed CRLF
+rm -r mixed
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-eol-add	Mon May 31 21:37:01 2010 +0200
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+cat > $HGRCPATH <<EOF
+[diff]
+git = 1
+EOF
+
+seteol () {
+    if [ $1 = "LF" ]; then
+        EOL='\n'
+    else
+        EOL='\r\n'
+    fi
+}
+
+makerepo () {
+    echo
+    echo "# ==== setup repository ===="
+    echo '% hg init'
+    hg init repo
+    cd repo
+
+    printf "first\nsecond\nthird\n" > a.txt
+    hg commit -d '100 0' --addremove -m 'LF commit'
+    cd ..
+}
+
+dotest () {
+    seteol $1
+
+    echo
+    echo "% hg clone repo repo-$1"
+    hg clone repo repo-$1
+    cd repo-$1
+
+    cat > .hg/hgrc <<EOF
+[extensions]
+eol =
+
+[eol]
+native = LF
+EOF
+
+    cat > .hgeol <<EOF
+[patterns]
+**.txt = native
+
+[repository]
+native = $1
+EOF
+
+    echo '% hg add .hgeol'
+    hg add .hgeol
+    echo '% hg status'
+    hg status
+
+    echo '% hg commit'
+    hg commit -d '200 0' -m 'Added .hgeol file'
+
+    echo '% hg status'
+    hg status
+
+    echo '% hg tip -p'
+    hg tip -p | python $TESTDIR/printrepr.py
+
+    cd ..
+    rm -r repo-$1
+}
+
+makerepo
+dotest LF
+dotest CRLF
+rm -r repo
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-eol-add.out	Mon May 31 21:37:01 2010 +0200
@@ -0,0 +1,69 @@
+
+# ==== setup repository ====
+% hg init
+adding a.txt
+
+% hg clone repo repo-LF
+updating to branch default
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% hg add .hgeol
+% hg status
+A .hgeol
+% hg commit
+% hg status
+% hg tip -p
+changeset:   1:34614fc6dc02
+tag:         tip
+user:        test
+date:        Thu Jan 01 00:03:20 1970 +0000
+summary:     Added .hgeol file
+
+diff --git a/.hgeol b/.hgeol
+new file mode 100644
+--- /dev/null
++++ b/.hgeol
+@@ -0,0 +1,5 @@
++[patterns]
++**.txt = native
++
++[repository]
++native = LF
+
+
+% hg clone repo repo-CRLF
+updating to branch default
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% hg add .hgeol
+% hg status
+M a.txt
+A .hgeol
+% hg commit
+% hg status
+% hg tip -p
+changeset:   1:4bbdacd3fe39
+tag:         tip
+user:        test
+date:        Thu Jan 01 00:03:20 1970 +0000
+summary:     Added .hgeol file
+
+diff --git a/.hgeol b/.hgeol
+new file mode 100644
+--- /dev/null
++++ b/.hgeol
+@@ -0,0 +1,5 @@
++[patterns]
++**.txt = native
++
++[repository]
++native = CRLF
+diff --git a/a.txt b/a.txt
+--- a/a.txt
++++ b/a.txt
+@@ -1,3 +1,3 @@
+-first
+-second
+-third
++first\r
++second\r
++third\r
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-eol-clone	Mon May 31 21:37:01 2010 +0200
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+cat > $HGRCPATH <<EOF
+[diff]
+git = True
+
+[extensions]
+eol =
+
+[eol]
+native = CRLF
+EOF
+
+echo "% setup repository"
+hg init repo
+cd repo
+
+cat > .hgeol <<EOF
+[patterns]
+**.txt = native
+EOF
+
+printf "first\r\nsecond\r\nthird\r\n" > a.txt
+hg commit --addremove -m 'checkin'
+cd ..
+
+echo "% hg clone repo repo-2"
+hg clone repo repo-2
+cd repo-2
+
+echo '% printrepr.py a.txt'
+python $TESTDIR/printrepr.py < a.txt
+echo '% hg cat a.txt'
+hg cat a.txt | python $TESTDIR/printrepr.py
+
+hg remove .hgeol
+hg commit -m 'remove eol'
+hg push --quiet
+
+cd ..
+
+# Test clone of repo with .hgeol in working dir, but no .hgeol in tip
+echo "% hg clone repo repo-3"
+hg clone repo repo-3
+cd repo-3
+
+echo '% printrepr.py a.txt'
+python $TESTDIR/printrepr.py < a.txt
+
+cd ..
+
+# Test clone of revision with .hgeol
+echo "% hg clone -r 1 repo repo-4"
+hg clone -r 0 repo repo-4
+cd repo-4
+
+echo '% cat .hgeol'
+cat .hgeol
+
+echo '% printrepr.py a.txt'
+python $TESTDIR/printrepr.py < a.txt
+
+cd ..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-eol-clone.out	Mon May 31 21:37:01 2010 +0200
@@ -0,0 +1,36 @@
+% setup repository
+adding .hgeol
+adding a.txt
+% hg clone repo repo-2
+updating to branch default
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% printrepr.py a.txt
+first\r
+second\r
+third\r
+% hg cat a.txt
+first
+second
+third
+% hg clone repo repo-3
+updating to branch default
+1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% printrepr.py a.txt
+first
+second
+third
+% hg clone -r 1 repo repo-4
+requesting all changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 2 changes to 2 files
+updating to branch default
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% cat .hgeol
+[patterns]
+**.txt = native
+% printrepr.py a.txt
+first\r
+second\r
+third\r
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-eol-hook	Mon May 31 21:37:01 2010 +0200
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+cat > $HGRCPATH <<EOF
+[diff]
+git = True
+EOF
+
+hg init main
+cat > main/.hg/hgrc <<EOF
+[extensions]
+eol =
+
+[hooks]
+pretxnchangegroup = python:hgext.eol.hook
+EOF
+
+hg clone main fork
+
+cd fork
+cat > .hgeol <<EOF
+[patterns]
+mixed.txt = BIN
+**.txt = native
+EOF
+
+hg add .hgeol
+hg commit -m 'Commit .hgeol'
+
+printf "first\nsecond\nthird\n" > a.txt
+hg add a.txt
+echo "% hg commit (LF a.txt)"
+hg commit -m 'LF a.txt'
+echo "% hg push"
+hg push ../main
+
+printf "first\r\nsecond\r\nthird\n" > a.txt
+echo "% hg commit (CRLF a.txt)"
+hg commit -m 'CRLF a.txt'
+echo "% hg push"
+hg push ../main
+
+
+echo "% hg commit (LF a.txt)"
+printf "first\nsecond\nthird\n" > a.txt
+hg commit -m 'LF a.txt (fixed)'
+echo "% hg push"
+hg push ../main
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-eol-hook.out	Mon May 31 21:37:01 2010 +0200
@@ -0,0 +1,30 @@
+updating to branch default
+0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% hg commit (LF a.txt)
+% hg push
+pushing to ../main
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 2 changesets with 2 changes to 2 files
+% hg commit (CRLF a.txt)
+% hg push
+pushing to ../main
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 1 changesets with 1 changes to 1 files
+error: pretxnchangegroup hook failed: a.txt should not have CRLF line endings
+transaction abort!
+rollback completed
+abort: a.txt should not have CRLF line endings
+% hg commit (LF a.txt)
+% hg push
+pushing to ../main
+searching for changes
+adding changesets
+adding manifests
+adding file changes
+added 2 changesets with 2 changes to 1 files
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-eol-patch	Mon May 31 21:37:01 2010 +0200
@@ -0,0 +1,103 @@
+#!/bin/sh
+
+cat > $HGRCPATH <<EOF
+[diff]
+git = 1
+EOF
+
+seteol () {
+    if [ $1 = "LF" ]; then
+        EOL='\n'
+    else
+        EOL='\r\n'
+    fi
+}
+
+makerepo () {
+    seteol $1
+    echo
+    echo "# ==== setup $1 repository ===="
+    echo '% hg init'
+    hg init repo
+    cd repo
+
+    cat > .hgeol <<EOF
+[repository]
+native = $1
+
+[patterns]
+unix.txt = LF
+win.txt = CRLF
+**.txt = native
+EOF
+
+    printf "first\r\nsecond\r\nthird\r\n" > win.txt
+    printf "first\nsecond\nthird\n" > unix.txt
+    printf "first${EOL}second${EOL}third${EOL}" > native.txt
+    hg commit --addremove -m 'checkin'
+    cd ..
+}
+
+dotest () {
+    seteol $1
+
+    echo
+    echo "% hg clone repo repo-$1"
+    hg clone --noupdate repo repo-$1
+    cd repo-$1
+
+    cat > .hg/hgrc <<EOF
+[extensions]
+eol =
+
+[eol]
+native = $1
+EOF
+
+    hg update
+    echo '% printrepr.py native.txt'
+    python $TESTDIR/printrepr.py < native.txt
+
+    echo '% printrepr.py unix.txt'
+    python $TESTDIR/printrepr.py < unix.txt
+
+    echo '% printrepr.py win.txt'
+    python $TESTDIR/printrepr.py < win.txt
+
+    printf "first${EOL}third${EOL}" > native.txt
+    printf "first\r\nthird\r\n" > win.txt
+    printf "first\nthird\n" > unix.txt
+
+    echo '% hg diff'
+    hg diff > p
+    python $TESTDIR/printrepr.py < p
+
+    echo '% hg revert'
+    hg revert --all
+
+    echo '% hg import'
+    hg import -m 'patch' p
+
+    echo '% printrepr.py native.txt'
+    python $TESTDIR/printrepr.py < native.txt
+    echo '% printrepr.py unix.txt'
+    python $TESTDIR/printrepr.py < unix.txt
+    echo '% printrepr.py win.txt'
+    python $TESTDIR/printrepr.py < win.txt
+
+    echo '% hg diff -c tip'
+    hg diff -c tip | python $TESTDIR/printrepr.py
+
+    cd ..
+    rm -r repo-$1
+}
+
+makerepo LF
+dotest LF
+dotest CRLF
+rm -r repo
+
+makerepo CRLF
+dotest LF
+dotest CRLF
+rm -r repo
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-eol-patch.out	Mon May 31 21:37:01 2010 +0200
@@ -0,0 +1,310 @@
+
+# ==== setup LF repository ====
+% hg init
+adding .hgeol
+adding native.txt
+adding unix.txt
+adding win.txt
+
+% hg clone repo repo-LF
+4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% printrepr.py native.txt
+first
+second
+third
+% printrepr.py unix.txt
+first
+second
+third
+% printrepr.py win.txt
+first\r
+second\r
+third\r
+% hg diff
+diff --git a/native.txt b/native.txt
+--- a/native.txt
++++ b/native.txt
+@@ -1,3 +1,2 @@
+ first
+-second
+ third
+diff --git a/unix.txt b/unix.txt
+--- a/unix.txt
++++ b/unix.txt
+@@ -1,3 +1,2 @@
+ first
+-second
+ third
+diff --git a/win.txt b/win.txt
+--- a/win.txt
++++ b/win.txt
+@@ -1,3 +1,2 @@
+ first\r
+-second\r
+ third\r
+% hg revert
+reverting native.txt
+reverting unix.txt
+reverting win.txt
+% hg import
+applying p
+% printrepr.py native.txt
+first
+third
+% printrepr.py unix.txt
+first
+third
+% printrepr.py win.txt
+first\r
+third\r
+% hg diff -c tip
+diff --git a/native.txt b/native.txt
+--- a/native.txt
++++ b/native.txt
+@@ -1,3 +1,2 @@
+ first
+-second
+ third
+diff --git a/unix.txt b/unix.txt
+--- a/unix.txt
++++ b/unix.txt
+@@ -1,3 +1,2 @@
+ first
+-second
+ third
+diff --git a/win.txt b/win.txt
+--- a/win.txt
++++ b/win.txt
+@@ -1,3 +1,2 @@
+ first\r
+-second\r
+ third\r
+
+% hg clone repo repo-CRLF
+4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% printrepr.py native.txt
+first\r
+second\r
+third\r
+% printrepr.py unix.txt
+first
+second
+third
+% printrepr.py win.txt
+first\r
+second\r
+third\r
+% hg diff
+diff --git a/native.txt b/native.txt
+--- a/native.txt
++++ b/native.txt
+@@ -1,3 +1,2 @@
+ first
+-second
+ third
+diff --git a/unix.txt b/unix.txt
+--- a/unix.txt
++++ b/unix.txt
+@@ -1,3 +1,2 @@
+ first
+-second
+ third
+diff --git a/win.txt b/win.txt
+--- a/win.txt
++++ b/win.txt
+@@ -1,3 +1,2 @@
+ first\r
+-second\r
+ third\r
+% hg revert
+reverting native.txt
+reverting unix.txt
+reverting win.txt
+% hg import
+applying p
+% printrepr.py native.txt
+first\r
+third\r
+% printrepr.py unix.txt
+first
+third
+% printrepr.py win.txt
+first\r
+third\r
+% hg diff -c tip
+diff --git a/native.txt b/native.txt
+--- a/native.txt
++++ b/native.txt
+@@ -1,3 +1,2 @@
+ first
+-second
+ third
+diff --git a/unix.txt b/unix.txt
+--- a/unix.txt
++++ b/unix.txt
+@@ -1,3 +1,2 @@
+ first
+-second
+ third
+diff --git a/win.txt b/win.txt
+--- a/win.txt
++++ b/win.txt
+@@ -1,3 +1,2 @@
+ first\r
+-second\r
+ third\r
+
+# ==== setup CRLF repository ====
+% hg init
+adding .hgeol
+adding native.txt
+adding unix.txt
+adding win.txt
+
+% hg clone repo repo-LF
+4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% printrepr.py native.txt
+first
+second
+third
+% printrepr.py unix.txt
+first
+second
+third
+% printrepr.py win.txt
+first\r
+second\r
+third\r
+% hg diff
+diff --git a/native.txt b/native.txt
+--- a/native.txt
++++ b/native.txt
+@@ -1,3 +1,2 @@
+ first\r
+-second\r
+ third\r
+diff --git a/unix.txt b/unix.txt
+--- a/unix.txt
++++ b/unix.txt
+@@ -1,3 +1,2 @@
+ first
+-second
+ third
+diff --git a/win.txt b/win.txt
+--- a/win.txt
++++ b/win.txt
+@@ -1,3 +1,2 @@
+ first\r
+-second\r
+ third\r
+% hg revert
+reverting native.txt
+reverting unix.txt
+reverting win.txt
+% hg import
+applying p
+% printrepr.py native.txt
+first
+third
+% printrepr.py unix.txt
+first
+third
+% printrepr.py win.txt
+first\r
+third\r
+% hg diff -c tip
+diff --git a/native.txt b/native.txt
+--- a/native.txt
++++ b/native.txt
+@@ -1,3 +1,2 @@
+ first\r
+-second\r
+ third\r
+diff --git a/unix.txt b/unix.txt
+--- a/unix.txt
++++ b/unix.txt
+@@ -1,3 +1,2 @@
+ first
+-second
+ third
+diff --git a/win.txt b/win.txt
+--- a/win.txt
++++ b/win.txt
+@@ -1,3 +1,2 @@
+ first\r
+-second\r
+ third\r
+
+% hg clone repo repo-CRLF
+4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% printrepr.py native.txt
+first\r
+second\r
+third\r
+% printrepr.py unix.txt
+first
+second
+third
+% printrepr.py win.txt
+first\r
+second\r
+third\r
+% hg diff
+diff --git a/native.txt b/native.txt
+--- a/native.txt
++++ b/native.txt
+@@ -1,3 +1,2 @@
+ first\r
+-second\r
+ third\r
+diff --git a/unix.txt b/unix.txt
+--- a/unix.txt
++++ b/unix.txt
+@@ -1,3 +1,2 @@
+ first
+-second
+ third
+diff --git a/win.txt b/win.txt
+--- a/win.txt
++++ b/win.txt
+@@ -1,3 +1,2 @@
+ first\r
+-second\r
+ third\r
+% hg revert
+reverting native.txt
+reverting unix.txt
+reverting win.txt
+% hg import
+applying p
+% printrepr.py native.txt
+first\r
+third\r
+% printrepr.py unix.txt
+first
+third
+% printrepr.py win.txt
+first\r
+third\r
+% hg diff -c tip
+diff --git a/native.txt b/native.txt
+--- a/native.txt
++++ b/native.txt
+@@ -1,3 +1,2 @@
+ first\r
+-second\r
+ third\r
+diff --git a/unix.txt b/unix.txt
+--- a/unix.txt
++++ b/unix.txt
+@@ -1,3 +1,2 @@
+ first
+-second
+ third
+diff --git a/win.txt b/win.txt
+--- a/win.txt
++++ b/win.txt
+@@ -1,3 +1,2 @@
+ first\r
+-second\r
+ third\r
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-eol-update	Mon May 31 21:37:01 2010 +0200
@@ -0,0 +1,83 @@
+#!/bin/sh
+
+cat > $HGRCPATH <<EOF
+[diff]
+git = 1
+EOF
+
+seteol () {
+    if [ $1 = "LF" ]; then
+        EOL='\n'
+    else
+        EOL='\r\n'
+    fi
+}
+
+makerepo () {
+    echo
+    echo "# ==== setup repository ===="
+    echo '% hg init'
+    hg init repo
+    cd repo
+
+    cat > .hgeol <<EOF
+[patterns]
+**.txt = LF
+EOF
+
+    printf "first\nsecond\nthird\n" > a.txt
+    hg commit --addremove -m 'LF commit'
+
+    cat > .hgeol <<EOF
+[patterns]
+**.txt = CRLF
+EOF
+
+    printf "first\r\nsecond\r\nthird\r\n" > a.txt
+    hg commit -m 'CRLF commit'
+
+    cd ..
+}
+
+dotest () {
+    seteol $1
+
+    echo
+    echo "% hg clone repo repo-$1"
+    hg clone --noupdate repo repo-$1
+    cd repo-$1
+
+    cat > .hg/hgrc <<EOF
+[extensions]
+eol =
+EOF
+
+    hg update
+
+    echo '% printrepr.py a.txt (before)'
+    python $TESTDIR/printrepr.py < a.txt
+
+    printf "first${EOL}third${EOL}" > a.txt
+
+    echo '% printrepr.py a.txt (after)'
+    python $TESTDIR/printrepr.py < a.txt
+    echo '% hg diff'
+    hg diff | python $TESTDIR/printrepr.py
+
+    echo '% hg update 0'
+    hg update 0
+
+    echo '% printrepr.py a.txt'
+    python $TESTDIR/printrepr.py < a.txt
+    echo '% hg diff'
+    hg diff | python $TESTDIR/printrepr.py
+
+
+    cd ..
+    rm -r repo-$1
+}
+
+makerepo
+dotest LF
+dotest CRLF
+rm -r repo
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-eol-update.out	Mon May 31 21:37:01 2010 +0200
@@ -0,0 +1,69 @@
+
+# ==== setup repository ====
+% hg init
+adding .hgeol
+adding a.txt
+
+% hg clone repo repo-LF
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% printrepr.py a.txt (before)
+first\r
+second\r
+third\r
+% printrepr.py a.txt (after)
+first
+third
+% hg diff
+diff --git a/a.txt b/a.txt
+--- a/a.txt
++++ b/a.txt
+@@ -1,3 +1,2 @@
+ first\r
+-second\r
+ third\r
+% hg update 0
+merging a.txt
+1 files updated, 1 files merged, 0 files removed, 0 files unresolved
+% printrepr.py a.txt
+first\r
+third\r
+% hg diff
+diff --git a/a.txt b/a.txt
+--- a/a.txt
++++ b/a.txt
+@@ -1,3 +1,2 @@
+ first
+-second
+ third
+
+% hg clone repo repo-CRLF
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% printrepr.py a.txt (before)
+first\r
+second\r
+third\r
+% printrepr.py a.txt (after)
+first\r
+third\r
+% hg diff
+diff --git a/a.txt b/a.txt
+--- a/a.txt
++++ b/a.txt
+@@ -1,3 +1,2 @@
+ first\r
+-second\r
+ third\r
+% hg update 0
+merging a.txt
+1 files updated, 1 files merged, 0 files removed, 0 files unresolved
+% printrepr.py a.txt
+first\r
+third\r
+% hg diff
+diff --git a/a.txt b/a.txt
+--- a/a.txt
++++ b/a.txt
+@@ -1,3 +1,2 @@
+ first
+-second
+ third
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-eol.out	Mon May 31 21:37:01 2010 +0200
@@ -0,0 +1,228 @@
+% setup LF repository
+adding .hgeol
+adding a.txt
+
+% hg clone repo repo-LF
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% printrepr.py a.txt
+first
+second
+third
+% hg cat a.txt
+first
+second
+third
+% printrepr.py a.txt
+first
+second
+third
+fourth
+diff --git a/a.txt b/a.txt
+--- a/a.txt
++++ b/a.txt
+@@ -1,3 +1,4 @@
+ first
+ second
+ third
++fourth
+% switching encoding from '\n' to '\r\n'
+% hg diff only reports a single changed line:
+diff --git a/a.txt b/a.txt
+--- a/a.txt
++++ b/a.txt
+@@ -1,3 +1,4 @@
+ first
+ second
+ third
++fourth
+% reverting back to LF format
+first
+second
+third
+% hg commit of inconsistent .txt file marked as binary (should work)
+% hg commit of inconsistent .txt file marked as native (should fail)
+abort: inconsistent newline style in a.txt
+
+% hg commit --config eol.only-consistent=False (should work)
+% hg commit of binary .txt file marked as native (binary files always okay)
+% hg clone repo repo-CRLF
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% printrepr.py a.txt
+first\r
+second\r
+third\r
+% hg cat a.txt
+first
+second
+third
+% printrepr.py a.txt
+first\r
+second\r
+third\r
+fourth\r
+diff --git a/a.txt b/a.txt
+--- a/a.txt
++++ b/a.txt
+@@ -1,3 +1,4 @@
+ first
+ second
+ third
++fourth
+% switching encoding from '\r\n' to '\n'
+% hg diff only reports a single changed line:
+diff --git a/a.txt b/a.txt
+--- a/a.txt
++++ b/a.txt
+@@ -1,3 +1,4 @@
+ first
+ second
+ third
++fourth
+% reverting back to CRLF format
+first\r
+second\r
+third\r
+% hg commit of inconsistent .txt file marked as binary (should work)
+% hg commit of inconsistent .txt file marked as native (should fail)
+abort: inconsistent newline style in a.txt
+
+% hg commit --config eol.only-consistent=False (should work)
+% hg commit of binary .txt file marked as native (binary files always okay)
+% setup CRLF repository
+adding .hgeol
+adding a.txt
+
+% hg clone repo repo-LF
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% printrepr.py a.txt
+first
+second
+third
+% hg cat a.txt
+first\r
+second\r
+third\r
+% printrepr.py a.txt
+first
+second
+third
+fourth
+diff --git a/a.txt b/a.txt
+--- a/a.txt
++++ b/a.txt
+@@ -1,3 +1,4 @@
+ first\r
+ second\r
+ third\r
++fourth\r
+% switching encoding from '\n' to '\r\n'
+% hg diff only reports a single changed line:
+diff --git a/a.txt b/a.txt
+--- a/a.txt
++++ b/a.txt
+@@ -1,3 +1,4 @@
+ first\r
+ second\r
+ third\r
++fourth\r
+% reverting back to LF format
+first
+second
+third
+% hg commit of inconsistent .txt file marked as binary (should work)
+% hg commit of inconsistent .txt file marked as native (should fail)
+abort: inconsistent newline style in a.txt
+
+% hg commit --config eol.only-consistent=False (should work)
+% hg commit of binary .txt file marked as native (binary files always okay)
+% hg clone repo repo-CRLF
+2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% printrepr.py a.txt
+first\r
+second\r
+third\r
+% hg cat a.txt
+first\r
+second\r
+third\r
+% printrepr.py a.txt
+first\r
+second\r
+third\r
+fourth\r
+diff --git a/a.txt b/a.txt
+--- a/a.txt
++++ b/a.txt
+@@ -1,3 +1,4 @@
+ first\r
+ second\r
+ third\r
++fourth\r
+% switching encoding from '\r\n' to '\n'
+% hg diff only reports a single changed line:
+diff --git a/a.txt b/a.txt
+--- a/a.txt
++++ b/a.txt
+@@ -1,3 +1,4 @@
+ first\r
+ second\r
+ third\r
++fourth\r
+% reverting back to CRLF format
+first\r
+second\r
+third\r
+% hg commit of inconsistent .txt file marked as binary (should work)
+% hg commit of inconsistent .txt file marked as native (should fail)
+abort: inconsistent newline style in a.txt
+
+% hg commit --config eol.only-consistent=False (should work)
+% hg commit of binary .txt file marked as native (binary files always okay)
+
+# setup LF repository
+adding unix.txt
+adding win.txt
+# setting repository-native EOLs to LF
+adding .hgeol
+
+% hg clone mixed mixed-LF
+updating to branch default
+3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% hg status (eol extension not yet activated)
+% hg status (eol activated)
+M win.txt
+% hg commit
+% hg status
+
+% hg clone mixed mixed-CRLF
+updating to branch default
+3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% hg status (eol extension not yet activated)
+% hg status (eol activated)
+M win.txt
+% hg commit
+% hg status
+
+# setup CRLF repository
+adding unix.txt
+adding win.txt
+# setting repository-native EOLs to CRLF
+adding .hgeol
+
+% hg clone mixed mixed-LF
+updating to branch default
+3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% hg status (eol extension not yet activated)
+% hg status (eol activated)
+M unix.txt
+% hg commit
+% hg status
+
+% hg clone mixed mixed-CRLF
+updating to branch default
+3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+% hg status (eol extension not yet activated)
+% hg status (eol activated)
+M unix.txt
+% hg commit
+% hg status