 README            |   15 +++++++++++----
 git-arr           |   44 +++++++++++++++++++++++++++++++++++---------
 git.py            |   12 +++++++++---
 utils.py          |   32 ++++++++++++++++++++++++++++----
 views/blob.html   |    2 +-
 views/commit.html |    2 +-
 6 files changed, 85 insertions(+), 22 deletions(-)

diff --git a/README b/README
index d51208d..b501adc 100644
--- a/README
+++ b/README
@@ -19,6 +19,13 @@ information.
 Getting started
 ---------------
 
+You will need Python, and the bottle.py framework (the package is usually
+called python-bottle in most distributions).
+
+If pygments is available, it will be used for syntax highlighting, otherwise
+everything will work fine, just in black and white.
+
+
 First, create a configuration file for your repositories. You can start by
 copying sample.conf, which has the list of the available options.
 
@@ -41,9 +48,9 @@ use, by running:
 That can be useful when making changes to the software itself.
 
 
-Where to report bugs
---------------------
+Contact
+-------
 
-If you want to report bugs, or have any questions or comments, just let me
-know at albertito@blitiri.com.ar.
+If you want to report bugs, send patches, or have any questions or comments,
+just let me know at albertito@blitiri.com.ar.
 
diff --git a/git-arr b/git-arr
index 8e8ae1f..3a44427 100755
--- a/git-arr
+++ b/git-arr
@@ -49,14 +49,10 @@ def load_config(path):
 
     # Do a first pass for general sanity checking and recursive expansion.
     for s in config.sections():
-        if not config.has_option(s, 'path'):
-            raise configparser.NoOptionError(
-                    '%s is missing the mandatory path' % s)
-
         if config.getboolean(s, 'recursive'):
             for path in os.listdir(config.get(s, 'path')):
-                fullpath = config.get(s, 'path') + '/' + path
-                if not os.path.exists(fullpath + '/HEAD'):
+                fullpath = find_git_dir(config.get(s, 'path') + '/' + path)
+                if not fullpath:
                     continue
 
                 if os.path.exists(fullpath + '/disable_gitweb'):
@@ -76,7 +72,13 @@ def load_config(path):
             config.remove_section(s)
 
     for s in config.sections():
-        fullpath = config.get(s, 'path')
+        fullpath = find_git_dir(config.get(s, 'path'))
+        if not fullpath:
+            raise ValueError(
+                    '%s: path %s is not a valid git repository' % (
+                        s, config.get(s, 'path')))
+
+        config.set(s, 'path', fullpath)
         config.set(s, 'name', s)
 
         desc = config.get(s, 'desc')
@@ -102,6 +104,29 @@ def load_config(path):
 
         repos[r.name] = r
 
+def find_git_dir(path):
+    """Returns the path to the git directory for the given repository.
+
+    This function takes a path to a git repository, and returns the path to
+    its git directory. If the repo is bare, it will be the same path;
+    otherwise it will be path + '.git/'.
+
+    An empty string is returned if the given path is not a valid repository.
+    """
+    def check(p):
+        """A dirty check for whether this is a git dir or not."""
+        # Note silent stderr because we expect this to fail and don't want the
+        # noise; and also we strip the final \n from the output.
+        return git.run_git(p,
+                ['rev-parse', '--git-dir'],
+                silent_stderr = True).read()[:-1]
+
+    for p in [ path, path + '/.git' ]:
+        if check(p):
+            return p
+
+    return ''
+
 
 def repo_filter(unused_conf):
     """Bottle route filter for repos."""
@@ -133,7 +158,7 @@ def with_utils(f):
     """
     utilities = {
         'shorten': utils.shorten,
-        'has_colorizer': utils.has_colorizer,
+        'can_colorize': utils.can_colorize,
         'colorize_diff': utils.colorize_diff,
         'colorize_blob': utils.colorize_blob,
         'abort': bottle.abort,
@@ -367,8 +392,9 @@ def main():
 
     try:
         load_config(opts.config)
-    except configparser.NoOptionError as e:
+    except (configparser.NoOptionError, ValueError) as e:
         print('Error parsing config:', e)
+        return
 
     if not args:
         parser.error('Must specify an action (serve|generate)')
diff --git a/git.py b/git.py
index 023f1a6..d005b13 100644
--- a/git.py
+++ b/git.py
@@ -41,7 +41,7 @@ class EncodeWrapper:
         return s.decode(self.encoding, errors = self.errors)
 
 
-def run_git(repo_path, params, stdin = None):
+def run_git(repo_path, params, stdin = None, silent_stderr = False):
     """Invokes git with the given parameters.
 
     This function invokes git with the given parameters, and returns a
@@ -49,11 +49,17 @@ def run_git(repo_path, params, stdin = None):
     """
     params = [GIT_BIN, '--git-dir=%s' % repo_path] + list(params)
 
+    stderr = None
+    if silent_stderr:
+        stderr = subprocess.PIPE
+
     if not stdin:
-        p = subprocess.Popen(params, stdin = None, stdout = subprocess.PIPE)
+        p = subprocess.Popen(params,
+                stdin = None, stdout = subprocess.PIPE, stderr = stderr)
     else:
         p = subprocess.Popen(params,
-                stdin = subprocess.PIPE, stdout = subprocess.PIPE)
+                stdin = subprocess.PIPE, stdout = subprocess.PIPE,
+                stderr = stderr)
         p.stdin.write(stdin)
         p.stdin.close()
 
diff --git a/utils.py b/utils.py
index 3bd281f..039d02b 100644
--- a/utils.py
+++ b/utils.py
@@ -18,8 +18,28 @@ def shorten(s, width = 60):
         return s
     return s[:57] + "..."
 
-def has_colorizer():
-    return pygments is not None
+def can_colorize(s):
+    """True if we can colorize the string, False otherwise."""
+    if pygments is None:
+        return False
+
+    # Pygments can take a huge amount of time with long files, or with very
+    # long lines; these are heuristics to try to avoid those situations.
+    if len(s) > (512 * 1024):
+        return False
+
+    # If any of the first 5 lines is over 300 characters long, don't colorize.
+    start = 0
+    for i in range(5):
+        pos = s.find('\n', start)
+        if pos == -1:
+            break
+
+        if pos - start > 300:
+            return False
+        start = pos + 1
+
+    return True
 
 def colorize_diff(s):
     lexer = lexers.DiffLexer(encoding = 'utf-8')
@@ -30,9 +50,13 @@ def colorize_diff(s):
 
 def colorize_blob(fname, s):
     try:
-        lexer = lexers.guess_lexer_for_filename(fname, s)
+        lexer = lexers.guess_lexer_for_filename(fname, s, encoding = 'utf-8')
     except lexers.ClassNotFound:
-        lexer = lexers.TextLexer(encoding = 'utf-8')
+        try:
+            lexer = lexers.guess_lexer(s[:200], encoding = 'utf-8')
+        except lexers.ClassNotFound:
+            lexer = lexers.TextLexer(encoding = 'utf-8')
+
     formatter = HtmlFormatter(encoding = 'utf-8',
                     cssclass = 'source_code',
                     linenos = 'table')
diff --git a/views/blob.html b/views/blob.html
index 4d5f7d0..283bbcd 100644
--- a/views/blob.html
+++ b/views/blob.html
@@ -36,7 +36,7 @@
     <a href="">{{!fname.html}}</a>
 </h3>
 
-% if has_colorizer():
+% if can_colorize(blob):
 {{!colorize_blob(fname.unicode, blob)}}
 % else:
 <pre class="blob-body">
diff --git a/views/commit.html b/views/commit.html
index 9a9e99d..bc3119c 100644
--- a/views/commit.html
+++ b/views/commit.html
@@ -55,7 +55,7 @@
 
 <hr/>
 
-% if has_colorizer():
+% if can_colorize(c.diff.body):
 {{!colorize_diff(c.diff.body)}}
 % else:
 <pre class="diff-body">
