Commits

Anonymous committed 1cce7f9

initial import

Comments (0)

Files changed (2)

+syntax: regexp
+(^|/)\.DS_Store$
+^build$
+^docs/_build$
+^dist$
+^[^/]*.egg-info$
+^\.settings$
+~$
+\.orig$
+\.pyc$
+\.pyo$
+\.swp$
+\.tmp$
+^_generated_
+desktop\.ini$
+^nbproject$
+"""automatically manage newlines in repository files
+
+This extension behaves like the normal eol extension, but it comes with
+all the good settings hard-coded.
+"""
+
+from mercurial.i18n import _
+from mercurial import util, config, extensions, match, error
+import re, os
+
+testedwith = 'internal'
+
+# 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 Macintosh files, so a
+# stray CR is an error.
+eolre = re.compile('\r*\n')
+
+GLOBALEOL = """
+[patterns]
+.deps = native
+.dmrc = native
+.hgignore = native
+.ignorechecks = native
+**.c = native
+**.cpp = native
+**.css = native
+**.cu = native
+**.h = native
+**.hpp = native
+**.htm = native
+**.html = native
+**.java = native
+**.js = native
+**.json = native
+**.manifest = native
+**.py = native
+**.pyva = native
+**.ru = native
+**.sass = native
+**.scss = native
+**.tmpl = native
+**.txt = native
+**.xml = native
+**.yaml = native
+""".strip()
+
+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
+    if (ui.configbool('eol', 'fix-trailing-newline', False)
+        and s and s[-1] != '\n'):
+        s = s + '\n'
+    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
+    if (ui.configbool('eol', 'fix-trailing-newline', False)
+        and s and s[-1] != '\n'):
+        s = s + '\n'
+    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,
+    # The following provide backwards compatibility with win32text
+    'cleverencode:': tolf,
+    'cleverdecode:': tocrlf
+}
+
+class eolfile(object):
+    def __init__(self, ui, root, data):
+        self._decode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
+        self._encode = {'LF': 'to-lf', 'CRLF': 'to-crlf', 'BIN': 'is-binary'}
+
+        self.cfg = config.config()
+        # Our files should not be touched. The pattern must be
+        # inserted first override a '** = native' pattern.
+        self.cfg.set('patterns', '.hg*', 'BIN')
+        # We can then parse the user's patterns.
+        self.cfg.parse('GLOBALEOL', data)
+
+        isrepolf = self.cfg.get('repository', 'native') != 'CRLF'
+        self._encode['NATIVE'] = isrepolf and 'to-lf' or 'to-crlf'
+        iswdlf = ui.config('eol', 'native', os.linesep) in ('LF', '\n')
+        self._decode['NATIVE'] = iswdlf and 'to-lf' or 'to-crlf'
+
+        include = []
+        exclude = []
+        for pattern, style in self.cfg.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.
+        self.match = match.match(root, '', [], include, exclude)
+
+    def copytoui(self, ui):
+        for pattern, style in self.cfg.items('patterns'):
+            key = style.upper()
+            try:
+                ui.setconfig('decode', pattern, self._decode[key])
+                ui.setconfig('encode', pattern, self._encode[key])
+            except KeyError:
+                ui.warn(_("ignoring unknown EOL style '%s' from %s\n")
+                        % (style, self.cfg.source('patterns', pattern)))
+        # eol.only-consistent can be specified in ~/.hgrc or GLOBALEOL
+        for k, v in self.cfg.items('eol'):
+            ui.setconfig('eol', k, v)
+
+    def checkrev(self, repo, ctx, files):
+        failed = []
+        for f in (files or ctx.files()):
+            if f not in ctx:
+                continue
+            for pattern, style in self.cfg.items('patterns'):
+                if not match.match(repo.root, '', [pattern])(f):
+                    continue
+                target = self._encode[style.upper()]
+                data = ctx[f].data()
+                if (target == "to-lf" and "\r\n" in data
+                    or target == "to-crlf" and singlelf.search(data)):
+                    failed.append((str(ctx), target, f))
+                break
+        return failed
+
+def parseeol(ui, repo):
+    return eolfile(ui, repo.root, GLOBALEOL)
+
+def _checkhook(ui, repo, node, headsonly):
+    # Get revisions to check and touched files at the same time
+    files = set()
+    revs = set()
+    for rev in xrange(repo[node].rev(), len(repo)):
+        revs.add(rev)
+        if headsonly:
+            ctx = repo[rev]
+            files.update(ctx.files())
+            for pctx in ctx.parents():
+                revs.discard(pctx.rev())
+    failed = []
+    for rev in revs:
+        ctx = repo[rev]
+        eol = parseeol(ui, repo)
+        if eol:
+            failed.extend(eol.checkrev(repo, ctx, files))
+
+    if failed:
+        eols = {'to-lf': 'CRLF', 'to-crlf': 'LF'}
+        msgs = []
+        for node, target, f in failed:
+            msgs.append(_("  %s in %s should not have %s line endings") %
+                        (f, node, eols[target]))
+        raise util.Abort(_("end-of-line check failed:\n") + "\n".join(msgs))
+
+def checkallhook(ui, repo, node, hooktype, **kwargs):
+    """verify that files have expected EOLs"""
+    _checkhook(ui, repo, node, False)
+
+def checkheadshook(ui, repo, node, hooktype, **kwargs):
+    """verify that files have expected EOLs"""
+    _checkhook(ui, repo, node, True)
+
+# "checkheadshook" used to be called "hook"
+hook = checkheadshook
+
+def preupdate(ui, repo, hooktype, parent1, parent2):
+    repo.loadeol()
+    return False
+
+def uisetup(ui):
+    ui.setconfig('hooks', 'preupdate.eol', preupdate)
+
+def extsetup(ui):
+    try:
+        extensions.find('win32text')
+        ui.warn(_("the eol extension is incompatible with the "
+                  "win32text extension\n"))
+    except KeyError:
+        pass
+
+
+def reposetup(ui, repo):
+    uisetup(repo.ui)
+
+    if not repo.local():
+        return
+    for name, fn in filters.iteritems():
+        repo.adddatafilter(name, fn)
+
+    ui.setconfig('patch', 'eol', 'auto')
+
+    class eolrepo(repo.__class__):
+
+        def loadeol(self):
+            eol = parseeol(self.ui, self)
+            if eol is None:
+                return None
+            eol.copytoui(self.ui)
+            return eol.match
+
+        def _hgcleardirstate(self):
+            self._eolfile = self.loadeol()
+            if not self._eolfile:
+                self._eolfile = util.never
+                return
+
+            cachedpatterns = None
+            if os.path.exists(self.join('globaleol.cache')):
+                try:
+                    lock = self.lock()
+                    fp = self.opener('globaleol.cache', 'r')
+                    cachedpatterns = fp.read()
+                    fp.close()
+                    lock.release()
+                except error.LockUnavailable:
+                    pass
+                except OSError:
+                    pass
+
+            if cachedpatterns != GLOBALEOL:
+                self.ui.debug("eol: detected change in eol definition\n")
+                wlock = None
+                try:
+                    wlock = self.wlock()
+                    for f in self.dirstate:
+                        if self.dirstate[f] == 'n':
+                            # all normal files need to be looked at
+                            # again since the new GLOBALEOL might no
+                            # longer match a file it matched before
+                            self.dirstate.normallookup(f)
+                    # Update the cache
+                    fp = self.opener('globaleol.cache', 'w')
+                    fp.write(GLOBALEOL)
+                    fp.close()
+                    wlock.release()
+                except error.LockUnavailable:
+                    # If we cannot lock the repository and clear the
+                    # dirstate, then a commit might not see all files
+                    # as modified. But if we cannot lock the
+                    # repository, then we can also not make a commit,
+                    # so ignore the error.
+                    pass
+
+        def commitctx(self, ctx, error=False):
+            for f in sorted(ctx.added() + ctx.modified()):
+                if not self._eolfile(f):
+                    continue
+                try:
+                    data = ctx[f].data()
+                except IOError:
+                    continue
+                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()