--- scmtools/testdata/git_newfile.diff	(revision 0)
+++ scmtools/testdata/git_newfile.diff	(revision 0)
@@ -0,0 +1,3 @@
+diff --git a/IAMNEW b/IAMNEW
+new file mode 100644
+index 0000000..e69de29
--- scmtools/testdata/git_simple.diff	(revision 0)
+++ scmtools/testdata/git_simple.diff	(revision 0)
@@ -0,0 +1,14 @@
+diff --git a/cfg/testcase.ini b/cfg/testcase.ini
+index cc18ec8..5e70b73 100644
+--- a/cfg/testcase.ini
++++ b/cfg/testcase.ini
+@@ -1,6 +1,7 @@
++blah blah blah
+ [mysql]
+ host = localhost
+ port = 3306
+ user = user
+ pass = pass
+-db = pyunit
+\ No newline at end of file
++db = pyunit
--- scmtools/testdata/git_binary.diff	(revision 0)
+++ scmtools/testdata/git_binary.diff	(revision 0)
@@ -0,0 +1,4 @@
+diff --git a/pysvn-1.5.1.tar.gz b/pysvn-1.5.1.tar.gz
+new file mode 100644
+index 0000000..86b520c
+Binary files /dev/null and b/pysvn-1.5.1.tar.gz differ
--- scmtools/fixtures/test_scmtools.json	(revision 776)
+++ scmtools/fixtures/test_scmtools.json	(working copy)
@@ -1 +1 @@
-[{"pk": "1", "model": "scmtools.tool", "fields": {"class_name": "reviewboard.scmtools.svn.SVNTool", "name": "Subversion"}}, {"pk": "2", "model": "scmtools.tool", "fields": {"class_name": "reviewboard.scmtools.perforce.PerforceTool", "name": "Perforce"}}, {"pk": "1", "model": "scmtools.repository", "fields": {"username": "", "name": "Review Board SVN", "tool": 1, "bug_tracker": "http:\/\/code.google.com\/p\/reviewboard\/issues\/detail?id=%s", "path": "http:\/\/reviewboard.googlecode.com\/svn", "password": ""}}, {"pk": "2", "model": "scmtools.repository", "fields": {"username": "", "name": "Navi SVN", "tool": 1, "bug_tracker": "", "path": "http:\/\/svn.navi.cx\/misc", "password": ""}}]
+[{"pk": "1", "model": "scmtools.tool", "fields": {"class_name": "reviewboard.scmtools.svn.SVNTool", "name": "Subversion"}}, {"pk": "2", "model": "scmtools.tool", "fields": {"class_name": "reviewboard.scmtools.perforce.PerforceTool", "name": "Perforce"}}, {"pk": "3", "model": "scmtools.tool", "fields": {"name": "Git", "class_name": "reviewboard.scmtools.git.GitTool"}}, {"pk": "1", "model": "scmtools.repository", "fields": {"username": "", "name": "Review Board SVN", "tool": 1, "bug_tracker": "http:\/\/code.google.com\/p\/reviewboard\/issues\/detail?id=%s", "path": "http:\/\/reviewboard.googlecode.com\/svn", "password": ""}}, {"pk": "2", "model": "scmtools.repository", "fields": {"username": "", "name": "Navi SVN", "tool": 1, "bug_tracker": "", "path": "http:\/\/svn.navi.cx\/misc", "password": ""}}, {"pk": "3", "model": "scmtools.repository", "fields": {"username": "", "name": "Git test repo", "tool": 3, "bug_tracker": "", "path": "/tmp", "password": ""}}]
--- scmtools/tests.py	(revision 776)
+++ scmtools/tests.py	(working copy)
@@ -242,3 +242,58 @@
         self.assertEqual(changeset.summary, "Changes: Emma")
 
         self.assertEqual(changeset.branch, 'bfg-main')
+
+class GitTests(DjangoTestCase):
+    """
+    XXX Unittests for Git
+    For the moment we don't have tests for the actual talking 
+    to the repo, as i haven't worked out the best way for that.
+    """
+    fixtures = ['test_scmtools']
+
+    def setUp(self):
+        self.repository = Repository.objects.get(name="Git test repo")
+        try:
+            self.tool = self.repository.get_scmtool()
+        except ImportError:
+            raise nose.SkipTest
+
+    def testSimpleDiff(self):
+        """Testing parsing simple Git diff"""
+        diff = open( \
+               os.path.join(os.path.dirname(__file__), 'testdata/git_simple.diff'), \
+               'r').read()
+        file = self.tool.getParser(diff).parse()[0]
+        self.assertEqual(file.origFile, 'cfg/testcase.ini')
+        self.assertEqual(file.newFile, 'cfg/testcase.ini')
+        self.assertEqual(file.origInfo, 'cc18ec8')
+        self.assertEqual(file.newInfo, '5e70b73')
+        self.assertFalse(file.binary)
+        self.assertEqual(file.data.splitlines()[0], "diff --git a/cfg/testcase.ini b/cfg/testcase.ini")
+        self.assertEqual(file.data.splitlines()[-1], "+db = pyunit")
+
+    def testNewfileDiff(self):
+        """Testing parsing Git diff with new file"""
+        diff = open( \
+               os.path.join(os.path.dirname(__file__), 'testdata/git_newfile.diff'), \
+               'r').read()
+        file = self.tool.getParser(diff).parse()[0]
+        self.assertEqual(file.origFile, 'IAMNEW')
+        self.assertEqual(file.newFile, 'IAMNEW')
+        self.assertEqual(file.origInfo, '0000000')
+        self.assertEqual(file.newInfo, 'e69de29')
+        self.assertFalse(file.binary)
+        self.assertEqual(len(file.data), 0)
+
+    def testBinaryDiff(self):
+        """Testing parsing Git diff with binary"""
+        diff = open( \
+               os.path.join(os.path.dirname(__file__), 'testdata/git_binary.diff'), \
+               'r').read()
+        file = self.tool.getParser(diff).parse()[0]
+        self.assertEqual(file.origFile, 'pysvn-1.5.1.tar.gz')
+        self.assertEqual(file.newFile, 'pysvn-1.5.1.tar.gz')
+        self.assertEqual(file.origInfo, '0000000')
+        self.assertEqual(file.newInfo, '86b520c')
+        self.assertTrue(file.binary)
+        self.assertEqual(len(file.data), 0)
--- scmtools/git.py	(revision 0)
+++ scmtools/git.py	(revision 0)
@@ -0,0 +1,107 @@
+import re
+import subprocess
+
+from reviewboard.scmtools.core import \
+    FileNotFoundException, SCMTool, HEAD, PRE_CREATION
+from reviewboard.diffviewer.parser import \
+    File, DiffParser, DiffParserError
+
+class GitTool(SCMTool):
+    """
+        You can only use this tool with a locally available git repository.
+        The repository path should be to the .git directory (important if 
+        you do not have a bare repositry).
+    """
+    def __init__(self, repository):
+        self.client = GitClient(repository.path)
+        self.pre_creation_regexp = re.compile("^0{1,}$")
+        SCMTool.__init__(self, repository)
+
+    def get_file(self, path, revision=HEAD):
+        return self.client.cat_file(revision)
+
+    def file_exists(self, filename, revision):
+        # XXX not sure the best way to do this
+        try:
+            self.client.cat_file(revision)
+            return True
+        except:
+            return False
+
+    def parse_diff_revision(self, file_str, revision_str):
+        if self.pre_creation_regexp.match(revision_str):
+            return file_str, PRE_CREATION
+        return file_str, revision_str
+    
+    def get_diffs_use_absolute_paths(self):
+        return True
+
+    def get_fields(self):
+        return ['diff_path']
+
+    def getParser(self, data):
+        return GitDiffParser(data)
+
+
+class GitDiffParser(DiffParser):
+    """
+        This class is able to parse diffs created with Git
+    """
+    def __init__(self, data):
+        diffregexp = re.compile("^diff --git", re.MULTILINE)
+        self.diffs = diffregexp.split(data)
+
+    def parse(self):
+        self.files = []
+        # diffs are per file 
+        for diff in self.diffs:
+            if not diff:
+                continue
+            # split the lines for metadata parsing
+            diffparts = diff.splitlines(True)
+            file = File()
+            # get the filenames
+            filenames = diffparts[0].split(" b/", 1)
+            file.origFile, file.newFile = filenames[0][3:], filenames[1].strip()
+            for line in range(1, 3):
+                # we may have extra info if it is a new file, delete etc we don't need it
+                if not diffparts[line].startswith("index "):
+                    continue
+                # get the object ids
+                file.origInfo, file.newInfo = diffparts[line].split(None, 2)[1].split("..")
+                file.data = ""
+                try:
+                    if diffparts[line + 1].startswith("Binary files") or \
+                        diffparts[line + 1].startswith("GIT binary patch"):
+                        file.binary = True
+                        break
+                    # store the entire diff
+                    file.data = "diff --git" + diff
+                except IndexError:
+                    #this means we don't have more than a new empty file
+                    pass
+                # we have everything
+                break
+            self.files.append(file)
+        return self.files
+
+class GitClientException(Exception):
+    pass
+
+class GitClient:
+    def __init__(self, path):
+        self.path = path
+
+    def cat_file(self, commit, type=None):
+        p = subprocess.Popen(
+                ['git', '--git-dir=%s' % self.path, 'cat-file', 'blob', '%s' % commit], 
+                stderr=subprocess.PIPE,
+                stdout=subprocess.PIPE, close_fds=True
+        )
+        contents = p.stdout.read()
+        errmsg = p.stderr.read()
+        failure = p.wait()
+
+        if failure:
+            raise FileNotFoundException(errmsg)
+        return contents
