# win32text.py - LF <-> CRLF/CR translation utilities for Windows/Mac users
#
#  Copyright 2005, 2007-2009 Matt Mackall <[email protected]> and others
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2, incorporated herein by reference.

'''perform automatic newline conversion

To perform automatic newline conversion, use::

 [extensions]
 hgext.win32text =
 [encode]
 ** = cleverencode:
 # or ** = macencode:

 [decode]
 ** = cleverdecode:
 # or ** = macdecode:

If not doing conversion, to make sure you do not commit CRLF/CR by accident::

 [hooks]
 pretxncommit.crlf = python:hgext.win32text.forbidcrlf
 # or pretxncommit.cr = python:hgext.win32text.forbidcr

To do the same check on a server to prevent CRLF/CR from being
pushed or pulled::

 [hooks]
 pretxnchangegroup.crlf = python:hgext.win32text.forbidcrlf
 # or pretxnchangegroup.cr = python:hgext.win32text.forbidcr
'''

from mercurial.i18n import _
from mercurial.node import short
from mercurial import util
import re

# regexp for single LF without CR preceding.
re_single_lf = re.compile('(^|[^\r])\n', re.MULTILINE)

newlinestr = {'\r\n': 'CRLF', '\r': 'CR'}
filterstr = {'\r\n': 'clever', '\r': 'mac'}

def checknewline(s, newline, ui=None, repo=None, filename=None):
   # warn if already has 'newline' in repository.
   # it might cause unexpected eol conversion.
   # see issue 302:
   #   http://mercurial.selenic.com/bts/issue302
   if newline in s and ui and filename and repo:
       ui.warn(_('WARNING: %s already has %s line endings\n'
                 'and does not need EOL conversion by the win32text plugin.\n'
                 'Before your next commit, please reconsider your '
                 'encode/decode settings in \nMercurial.ini or %s.\n') %
               (filename, newlinestr[newline], repo.join('hgrc')))

def dumbdecode(s, cmd, **kwargs):
   checknewline(s, '\r\n', **kwargs)
   # replace single LF to CRLF
   return re_single_lf.sub('\\1\r\n', s)

def dumbencode(s, cmd):
   return s.replace('\r\n', '\n')

def macdumbdecode(s, cmd, **kwargs):
   checknewline(s, '\r', **kwargs)
   return s.replace('\n', '\r')

def macdumbencode(s, cmd):
   return s.replace('\r', '\n')

def cleverdecode(s, cmd, **kwargs):
   if not util.binary(s):
       return dumbdecode(s, cmd, **kwargs)
   return s

def cleverencode(s, cmd):
   if not util.binary(s):
       return dumbencode(s, cmd)
   return s

def macdecode(s, cmd, **kwargs):
   if not util.binary(s):
       return macdumbdecode(s, cmd, **kwargs)
   return s

def macencode(s, cmd):
   if not util.binary(s):
       return macdumbencode(s, cmd)
   return s

_filters = {
   'dumbdecode:': dumbdecode,
   'dumbencode:': dumbencode,
   'cleverdecode:': cleverdecode,
   'cleverencode:': cleverencode,
   'macdumbdecode:': macdumbdecode,
   'macdumbencode:': macdumbencode,
   'macdecode:': macdecode,
   'macencode:': macencode,
   }

def forbidnewline(ui, repo, hooktype, node, newline, **kwargs):
   halt = False
   seen = set()
   # 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.
   tip = repo['tip']
   for rev in xrange(len(repo)-1, repo[node].rev()-1, -1):
       c = repo[rev]
       for f in c.files():
           if f in seen or f not in tip or f not in c:
               continue
           seen.add(f)
           data = c[f].data()
           if not util.binary(data) and newline in data:
               if not halt:
                   ui.warn(_('Attempt to commit or push text file(s) '
                             'using %s line endings\n') %
                             newlinestr[newline])
               ui.warn(_('in %s: %s\n') % (short(c.node()), f))
               halt = True
   if halt and hooktype == 'pretxnchangegroup':
       crlf = newlinestr[newline].lower()
       filter = filterstr[newline]
       ui.warn(_('\nTo prevent this mistake in your local repository,\n'
                 'add to Mercurial.ini or .hg/hgrc:\n'
                 '\n'
                 '[hooks]\n'
                 'pretxncommit.%s = python:hgext.win32text.forbid%s\n'
                 '\n'
                 'and also consider adding:\n'
                 '\n'
                 '[extensions]\n'
                 'hgext.win32text =\n'
                 '[encode]\n'
                 '** = %sencode:\n'
                 '[decode]\n'
                 '** = %sdecode:\n') % (crlf, crlf, filter, filter))
   return halt

def forbidcrlf(ui, repo, hooktype, node, **kwargs):
   return forbidnewline(ui, repo, hooktype, node, '\r\n', **kwargs)

def forbidcr(ui, repo, hooktype, node, **kwargs):
   return forbidnewline(ui, repo, hooktype, node, '\r', **kwargs)

def reposetup(ui, repo):
   if not repo.local():
       return
   for name, fn in _filters.iteritems():
       repo.adddatafilter(name, fn)