--- a
+++ b/ForgeSCM/forgescm/lib/git.py
@@ -0,0 +1,109 @@
+import os
+import shutil
+import logging
+
+import pkg_resources
+import genshi
+from pymongo import bson
+
+from forgescm import model as M
+from .command import Command
+
+log = logging.getLogger(__name__)
+
+class init(Command):
+    base='git init'
+
+class clone(Command):
+    base='git clone'
+
+class scm_log(Command):
+    base='git log -p'
+
+def setup_gitweb(repo_name, repo_dir):
+    # Set up the GitWeb config file
+    tpl_fn = pkg_resources.resource_filename('forgescm', 'data/gitweb.conf_tmpl')
+    tpl_text = open(tpl_fn).read()
+    tt = genshi.template.NewTextTemplate(
+        tpl_text, filepath=os.path.dirname(tpl_fn), filename=tpl_fn)
+    cfg_strm = tt.generate(
+        my_uri='/_wsgi_/scm/' + repo_name,
+        site_name='GitWeb Interface for ' + repo_name,
+        project_root=repo_dir)
+    cfg_fn = os.path.join(repo_dir, 'gitweb.conf')
+    with open(cfg_fn, 'w') as fp:
+        fp.write(cfg_strm.render())
+
+class LogParser(object):
+
+    def __init__(self, repo_id):
+        self.repo_id = repo_id
+        self.result = []
+
+    def feed(self, line_iter):
+        cur_line = line_iter.next()
+        while True:
+            try:
+                if cur_line.startswith('commit'):
+                    cur_line = self.parse_header(cur_line, line_iter)
+                elif cur_line.startswith('diff --git'):
+                    cur_line = self.parse_diff(cur_line, line_iter)
+                elif cur_line.strip():
+                    self.result[-1].summary += cur_line
+                    # log.error('Unexpected line %r', cur_line)
+                    cur_line = line_iter.next()
+                else:
+                    cur_line = line_iter.next()
+            except StopIteration:
+                break
+        return self.result
+
+    def parse_header(self, cur_line, line_iter):
+        hash = cur_line.split()[-1].strip()
+        log.debug('Parsing changeset %s', hash)
+        r = M.Commit.make(dict(repository_id=self.repo_id,
+                               hash=hash,
+                               summary=''))
+        while cur_line != '\n':
+            cur_line = line_iter.next()
+            if cur_line == '\n': break
+            cmd, rest = cur_line.split(':', 1)
+            result = self.parse_line(rest)
+            if cur_line.startswith('Author:'):
+                r.user = result
+            elif cur_line.startswith('Date:'):
+                r.date = result
+            elif cur_line != '\n':
+                r.summary += cur_line
+            elif cur_line == '\n':
+                if r.summary: break
+                else: cur_line = line_iter.next()
+        r.m.save()
+        if self.result and not self.result[-1].parents:
+            self.result[-1].parents = [ r.hash ]
+            self.result[-1].m.save()
+        self.result.append(r)
+        if cur_line == '\n':
+            cur_line = line_iter.next()
+        return cur_line
+
+    def parse_line(self, rest):
+        return rest.lstrip()
+
+    def parse_diff(self, cur_line, line_iter):
+        cmdline = cur_line.split(' ')
+        log.debug('Begin diff %s', cmdline)
+        r = M.Patch.make(dict(repository_id=self.result[-1].repository_id,
+                              commit_id=self.result[-1]._id,
+                              filename=cmdline[2][2:]))
+        text_lines = []
+        while cur_line != '\n':
+            cur_line = line_iter.next()
+            if cur_line.startswith('diff'): break
+            if cur_line != '\n': text_lines.append(cur_line)
+        r.patch_text = bson.Binary(''.join(text_lines))
+        r.m.save()
+        if cur_line == '\n':
+            cur_line = line_iter.next()
+        return cur_line
+