Implement Blob, Tree and Lazy, add tests for Tree.
authorEdward Z. Yang <edwardzyang@thewritingpot.com>
Sun, 20 Jul 2008 00:06:49 +0000 (19 20:06 -0400)
committerEdward Z. Yang <edwardzyang@thewritingpot.com>
Sun, 20 Jul 2008 00:06:49 +0000 (19 20:06 -0400)
Lots of changes this commit. More in-depth:
* Added text fixtures from Git Python
* Add Git->getDir
* Add Git::$executeKwords (doesn't do anything yet, we need to merge upstream
  changes)
* Implemented Git_Tree expansion in Git_Commit
* Make Git_Repo properties public, as they are not lazy-loaded
* Implement Git_Repo->tree()
* Implement GitHarness, for easy access to fixtures
* Setup Mocks

Signed-off-by: Edward Z. Yang <edwardzyang@thewritingpot.com>
36 files changed:
.gitattributes [new file with mode: 0644]
library/Git.php
library/Git/Blob.php [new file with mode: 0644]
library/Git/Commit.php
library/Git/Lazy.php [new file with mode: 0644]
library/Git/Repo.php
library/Git/Tree.php [new file with mode: 0644]
tests/Git/TreeTest.php [new file with mode: 0644]
tests/GitHarness.php [new file with mode: 0644]
tests/fixtures/blame [new file with mode: 0644]
tests/fixtures/cat_file_blob [new file with mode: 0644]
tests/fixtures/cat_file_blob_size [new file with mode: 0644]
tests/fixtures/diff_2 [new file with mode: 0644]
tests/fixtures/diff_2f [new file with mode: 0644]
tests/fixtures/diff_f [new file with mode: 0644]
tests/fixtures/diff_i [new file with mode: 0644]
tests/fixtures/diff_mode_only [new file with mode: 0644]
tests/fixtures/diff_new_mode [new file with mode: 0644]
tests/fixtures/diff_numstat [new file with mode: 0644]
tests/fixtures/diff_p [new file with mode: 0644]
tests/fixtures/for_each_ref [new file with mode: 0644]
tests/fixtures/for_each_ref_tags [new file with mode: 0644]
tests/fixtures/ls_tree_a [new file with mode: 0644]
tests/fixtures/ls_tree_b [new file with mode: 0644]
tests/fixtures/ls_tree_commit [new file with mode: 0644]
tests/fixtures/rev_list [new file with mode: 0644]
tests/fixtures/rev_list_commit_diffs [new file with mode: 0644]
tests/fixtures/rev_list_commit_idabbrev [new file with mode: 0644]
tests/fixtures/rev_list_commit_stats [new file with mode: 0644]
tests/fixtures/rev_list_count [new file with mode: 0644]
tests/fixtures/rev_list_delta_a [new file with mode: 0644]
tests/fixtures/rev_list_delta_b [new file with mode: 0644]
tests/fixtures/rev_list_single [new file with mode: 0644]
tests/fixtures/rev_parse [new file with mode: 0644]
tests/fixtures/show_empty_commit [new file with mode: 0644]
tests/index.php

diff --git a/.gitattributes b/.gitattributes
new file mode 100644 (file)
index 0000000..5f2af4e
--- /dev/null
@@ -0,0 +1 @@
+/tests/fixtures/* -crlf
\ No newline at end of file
index 4e3cdcd..2f098f4 100644 (file)
@@ -1,9 +1,12 @@
 <?php
 
-require_once 'Git/Actor.php';
-require_once 'Git/Commit.php';
-require_once 'Git/Exception.php';
-require_once 'Git/Repo.php';
+require_once dirname(__FILE__) . '/Git/Lazy.php';
+require_once dirname(__FILE__) . '/Git/Actor.php';
+require_once dirname(__FILE__) . '/Git/Commit.php';
+require_once dirname(__FILE__) . '/Git/Exception.php';
+require_once dirname(__FILE__) . '/Git/Repo.php';
+require_once dirname(__FILE__) . '/Git/Tree.php';
+require_once dirname(__FILE__) . '/Git/Blob.php';
 
 /**
  * Wrapper for Git executable. Lowest level interface.
@@ -13,6 +16,11 @@ class Git
 
     const S = DIRECTORY_SEPARATOR;
 
+    static protected $executeKwargs = array(
+        'istream', 'with_keep_cwd', 'with_extended_output',
+        'with_exceptions', 'with_raw_output'
+    );
+
     /**
      * Git executable to invoke. You can replace this with a full path
      * to your Git executable/wrapper script, but be sure to escape
@@ -26,13 +34,18 @@ class Git
     protected $dir;
 
     /**
-     * @param $dir Current working directory
+     * @param $dir Current working directory.
      */
     public function __construct($dir) {
         $this->dir = $dir;
     }
 
     /**
+     * Returns current working directory.
+     */
+    public function getDir() {return $this->dir;}
+
+    /**
      * Executes a command on shell and returns output.
      */
     public function execute($command) {
diff --git a/library/Git/Blob.php b/library/Git/Blob.php
new file mode 100644 (file)
index 0000000..9ba2bf4
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * Represents a binary large object in Git, usually a file in a tree.
+ */
+class Git_Blob
+{
+    const DEFAULT_MIME_TYPE = 'text/plain';
+    /**
+     * Instance of Git_Repo.
+     */
+    public $repo;
+    public $id;
+    public $mode;
+    public $name;
+    private $_size;
+    private $_data;
+    /**
+     * Creates an unbaked Blob containing just the specified attributes.
+     */
+    public function __construct($repo, $kwargs) {
+        $this->repo = $repo;
+        foreach ($kwargs as $k => $v) {
+            $this->$k = $v;
+        }
+    }
+    /**
+     * Size of this blob in bytes.
+     */
+    public function size() {
+        if (is_null($this->_size)) {
+            $this->_size = (int) $this->repo->git->catFile($this->id, array('s' => true));
+        }
+        return $this->_size;
+    }
+    /**
+     * Returns the binary contents of this blob.
+     */
+    public function data() {
+        if (is_null($this->_data)) {
+            $this->_data = $this->repo->git->catFile($this->id, array('p' => true));
+        }
+        return $this->_data;
+    }
+    // This function isn't implemented because we don't really have a
+    // good MIME implementation by default on PHP. Will implement after
+    // more investigation.
+    // public function mimeType() {
+    /**
+     * Basename of this blob.
+     */
+    public function basename() {
+        return basename($this->__get('name'));
+    }
+    // public static function blame($repo, $commit, $file) {
+    public function __toString() {
+        return '(Git_Blob "' . $this->id . '")';
+    }
+    public function __get($name) {
+        if (method_exists($this, $name)) return $this->$name();
+        else return $this->$name;
+    }
+}
index 967feb6..3029b46 100644 (file)
@@ -29,9 +29,7 @@ class Git_Commit {
                 }
                 continue;
             } elseif ($k == 'tree') {
-                //$this->tree = new Git_Tree($repo, array('id' => $v));
-                // :TODO: Implement Git_Tree
-                $this->tree = $v;
+                $this->tree = new Git_Tree($repo, array('id' => $v));
                 continue;
             }
             $this->$k = $v;
diff --git a/library/Git/Lazy.php b/library/Git/Lazy.php
new file mode 100644 (file)
index 0000000..7cdde2f
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * Implements lazy loading of properties.
+ *
+ * In the Python implementation, this is implemented as a
+ * mix-in. However, we don't have this capability in PHP, so
+ * we implement is as a super-class (as multiple mix-ins
+ * are not used). If this ends up being a problem, rewrite
+ * this class to be a decorator, and create a factory for
+ * Git objects.
+ * 
+ * @warning Subclasses should be careful to call __get manually
+ *          if they're not sure if a particular property will
+ *          be initialized or not; because they have full access
+ *          __get will not be called automatically.
+ */
+abstract class Git_Lazy {
+    /**
+     * Whether or not this class's contents have been loaded.
+     */
+    private $_baked = false;
+    /**
+     * Marks the class as baked; use this if you loaded the
+     * contents yourself.
+     */
+    protected function markBaked() {
+        $this->_baked = true;
+    }
+    /**
+     * Implements public lazy loading.
+     */
+    public function __get($name) {
+        if ($name[0] == '_') throw new Git_Exception('Cannot access private property ' . $name);
+        // Quick and easy way of emulating Python's @property decorator
+        if (method_exists($this, $name)) {
+            return $this->$name();
+        }
+        if (isset($this->$name)) return $this->$name;
+        if (!$this->_baked) {
+            $this->bake();
+            $this->_baked = true;
+        }
+        return $this->$name;
+    }
+    /**
+     * Implements the loading operation; after this method is
+     * called the class should be ready to go.
+     */
+    abstract protected function bake();
+}
index 8c380dc..69181ae 100644 (file)
@@ -11,17 +11,17 @@ class Git_Repo {
     /**
      * Path to this repository.
      */
-    protected $path;
+    public $path;
 
     /**
      * Whether or not this is a bare repository.
      */
-    protected $bare;
+    public $bare;
 
     /**
      * Interface to Git itself.
      */
-    protected $git;
+    public $git;
 
     /**
      * @param $path Path to the repository (either working copy or bare)
@@ -52,7 +52,15 @@ class Git_Repo {
     // public function commitCount($start = 'master') {
     // * public function commit($id) {
     // public function commitDeltasFrom($other_repo, $ref = 'master', $other_ref = 'master') {
-    // * public function tree($treeish = 'master', $paths = array()) {
+    /**
+     * Returns the Git_Tree object for a given treeish reference.
+     * @param $treeish Reference to retrieve, can be branch name, or filename
+     * @param $paths Optional array of directory paths to restrict to.
+     * @return Git_Tree for that path.
+     */
+    public function tree($treeish = 'master', $paths = array()) {
+        return Git_Tree::construct($this, $treeish, $paths);
+    }
     // * public function blob($id) {
 
     /**
diff --git a/library/Git/Tree.php b/library/Git/Tree.php
new file mode 100644 (file)
index 0000000..dbe2a38
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * Represents a tree file structure in Git.
+ */
+class Git_Tree extends Git_Lazy {
+    /**
+     * Instance of Git_Repo.
+     */
+    public $repo;
+    /**
+     * Treeish identifier of the tree. Can be a SHA-1 sum.
+     */
+    protected $id;
+    protected $mode;
+    protected $name;
+    protected $contents;
+    public function __construct($repo, $kwargs = array()) {
+        $this->repo = $repo;
+        foreach ($kwargs as $k => $v) {
+            $this->$k = $v;
+        }
+    }
+    /**
+     * Lazy loads the tree's contents.
+     */
+    protected function bake() {
+        $tmp = Git_Tree::construct($this->repo, $this->id);
+        $this->contents = $tmp->contents;
+    }
+    /**
+     * Constructs and fully initializes a tree.
+     */
+    public static function construct($repo, $treeish, $paths = array()) {
+        $output = $repo->git->lsTree($treeish, $paths);
+        $tree = new Git_Tree($repo, array('id' => $treeish));
+        $tree->constructInitialize($repo, $treeish, $output);
+        return $tree;
+    }
+    /**
+     * Initializes a tree object based on the output of the ls-tree command.
+     */
+    public function constructInitialize($repo, $id, $text) {
+        $this->repo = $repo;
+        $this->id = $id;
+        $this->contents = array();
+        foreach (explode("\n", $text) as $line) {
+            $out = self::contentFromString($repo, $line);
+            if (!$out) continue;
+            $this->contents[] = $out;
+        }
+        $this->markBaked();
+    }
+    /**
+     * Creates a content object (blob or tree) based on the ls-tree command.
+     */
+    public function contentFromString($repo, $text) {
+        $text = str_replace("\t", ' ', $text);
+        $bits = explode(' ', $text);
+        if (count($bits) != 4) return;
+        list($mode, $type, $id, $name) = $bits;
+        switch ($type) {
+            case 'tree':
+                return new Git_Tree($repo, compact('id', 'mode', 'name'));
+            case 'blob':
+                return new Git_Blob($repo, compact('id', 'mode', 'name'));
+            case 'commit':
+                return;
+            default:
+                throw new Git_Exception('Invalid type: ' . $type);
+        }
+    }
+    /**
+     * Retrieves a named object in this tree's contents.
+     * @param Filename of object to return. This function does NOT search
+     *        recursively.
+     * @return Null if file not found, otherwise Git_Tree or Git_Commit file.
+     */
+    public function get($file) {
+        foreach ($this->contents as $c) {
+            if ($c->name == $file) return $c;
+        }
+    }
+    /**
+     * Retrieves the basename of this tree.
+     */
+    public function basename() {
+        return basename($this->__get('name'));
+    }
+    public function __toString() {
+        return '(Git_Tree "'.$this->id.'")';
+    }
+}
diff --git a/tests/Git/TreeTest.php b/tests/Git/TreeTest.php
new file mode 100644 (file)
index 0000000..2b17459
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+
+class Git_TreeTest extends GitHarness {
+    protected $repo, $tree, $git;
+    public function setUp() {
+        $this->repo = new Git_Repo(GIT_REPO);
+        $this->git = $this->repo->git = new GitMock();
+        $this->tree = new Git_Tree($this->repo);
+    }
+    function testContentsShouldCache() {
+        $this->git->setReturnValue('__call', $this->fixture('ls_tree_a') . $this->fixture('ls_tree_b'));
+        $this->git->expectCallCount('__call', 2);
+        $this->git->expectAt(0, '__call', array('lsTree', array('master', array())));
+        $this->git->expectAt(1, '__call', array('lsTree', array('34868e6e7384cb5ee51c543a8187fdff2675b5a7', array())));
+        $tree = $this->repo->tree('master');
+        list($child) = array_slice($tree->contents, -1);
+        $child->contents;
+        $child->contents;
+    }
+    function testContentFromStringTreeShouldReturnTree() {
+        list($text) = array_slice(explode("\n", trim($this->fixture('ls_tree_a'))), -1);
+        $tree = $this->tree->contentFromString(null, $text);
+        $this->assertIsA($tree, 'Git_Tree');
+        $this->assertIdentical('650fa3f0c17f1edb4ae53d8dcca4ac59d86e6c44', $tree->id);
+        $this->assertIdentical('040000', $tree->mode);
+        $this->assertIdentical('test', $tree->name);
+    }
+    function testContentFromStringTreeShouldReturnBlob() {
+        list($text) = explode("\n", trim($this->fixture('ls_tree_b')));
+        $blob = $this->tree->contentFromString(null, $text);
+        $this->assertIsA($blob, 'Git_Blob');
+        $this->assertIdentical('aa94e396335d2957ca92606f909e53e7beaf3fbb', $blob->id);
+        $this->assertIdentical('100644', $blob->mode);
+        $this->assertIdentical('grit.rb', $blob->name);
+    }
+    function testContentFromStringTreeShouldReturnCommit() {
+        list($x, $text) = explode("\n", trim($this->fixture('ls_tree_commit')));
+        $tree = $this->tree->contentFromString(null, $text);
+        $this->assertIdentical($tree, null);
+    }
+    function testContentFromStringInvalidTypeShouldThrowException() {
+        $this->expectException(new Git_Exception('Invalid type: bogus'));
+        $this->tree->contentFromString(null, "040000 bogus 650fa3f0c17f1edb4ae53d8dcca4ac59d86e6c44\ttest");
+    }
+    function testGet() {
+        // Python implementation patched blob here, but it doesn't seem
+        // to ever be called. Weird. They then go on to test with zero-length 
+        // files, which shouldn't have any bearing either. We have condensed
+        // those tests.
+        $this->git->setReturnValue('__call', $this->fixture('ls_tree_a'));
+        $this->git->expectOnce('__call', array('lsTree', array('master', array())));
+        $tree = $this->repo->tree('master');
+        $this->assertIdentical('aa06ba24b4e3f463b3c4a85469d0fb9e5b421cf8', $tree->get('lib')->id);
+        $this->assertIdentical('8b1e02c0fb554eed2ce2ef737a68bb369d7527df', $tree->get('README.txt')->id);
+    }
+    function testGetWithCommits() {
+        $this->git->setReturnValue('__call', $this->fixture('ls_tree_commit'));
+        $this->git->expectOnce('__call', array('lsTree', array('master', array())));
+        $tree = $this->repo->tree('master');
+        $this->assertIdentical($tree->get('bar'), null);
+        $this->assertIdentical('2afb47bcedf21663580d5e6d2f406f08f3f65f19', $tree->get('foo')->id);
+        $this->assertIdentical('f623ee576a09ca491c4a27e48c0dfe04be5f4a2e', $tree->get('baz')->id);
+    }
+    function testToString() {
+        $tree = new Git_Tree($this->repo, array('id' => 'abc'));
+        $this->assertIdentical('(Git_Tree "abc")', (string) $tree);
+    }
+}
diff --git a/tests/GitHarness.php b/tests/GitHarness.php
new file mode 100644 (file)
index 0000000..28ceef0
--- /dev/null
@@ -0,0 +1,8 @@
+<?php
+
+class GitHarness extends UnitTestCase
+{
+    public function fixture($name) {
+        return file_get_contents(GIT_FIXTURES . '/' . $name);
+    }
+}
diff --git a/tests/fixtures/blame b/tests/fixtures/blame
new file mode 100644 (file)
index 0000000..10c141d
--- /dev/null
@@ -0,0 +1,131 @@
+634396b2f541a9f2d58b00be1a07f0c358b999b3 1 1 7
+author Tom Preston-Werner
+author-mail <tom@mojombo.com>
+author-time 1191997100
+author-tz -0700
+committer Tom Preston-Werner
+committer-mail <tom@mojombo.com>
+committer-time 1191997100
+committer-tz -0700
+filename lib/grit.rb
+summary initial grit setup
+boundary
+       $:.unshift File.dirname(__FILE__)     # For use/testing when no gem is installed
+634396b2f541a9f2d58b00be1a07f0c358b999b3 2 2
+       
+634396b2f541a9f2d58b00be1a07f0c358b999b3 3 3
+       # core
+634396b2f541a9f2d58b00be1a07f0c358b999b3 4 4
+       
+634396b2f541a9f2d58b00be1a07f0c358b999b3 5 5
+       # stdlib
+634396b2f541a9f2d58b00be1a07f0c358b999b3 6 6
+       
+634396b2f541a9f2d58b00be1a07f0c358b999b3 7 7
+       # internal requires
+3b1930208a82457747d76729ae088e90edca4673 8 8 1
+author Tom Preston-Werner
+author-mail <tom@mojombo.com>
+author-time 1192267241
+author-tz -0700
+committer Tom Preston-Werner
+committer-mail <tom@mojombo.com>
+committer-time 1192267241
+committer-tz -0700
+filename lib/grit.rb
+summary big refactor to do lazy loading
+       require 'grit/lazy'
+4c8124ffcf4039d292442eeccabdeca5af5c5017 8 9 1
+author Tom Preston-Werner
+author-mail <tom@mojombo.com>
+author-time 1191999972
+author-tz -0700
+committer Tom Preston-Werner
+committer-mail <tom@mojombo.com>
+committer-time 1191999972
+committer-tz -0700
+filename lib/grit.rb
+summary implement Grit#heads
+       require 'grit/errors'
+d01a4cfad6ea50285c4710243e3cbe019d381eba 9 10 1
+author Tom Preston-Werner
+author-mail <tom@mojombo.com>
+author-time 1192032303
+author-tz -0700
+committer Tom Preston-Werner
+committer-mail <tom@mojombo.com>
+committer-time 1192032303
+committer-tz -0700
+filename lib/grit.rb
+summary convert to Grit module, refactor to be more OO
+       require 'grit/git'
+4c8124ffcf4039d292442eeccabdeca5af5c5017 9 11 1
+       require 'grit/head'
+a47fd41f3aa4610ea527dcc1669dfdb9c15c5425 10 12 1
+author Tom Preston-Werner
+author-mail <tom@mojombo.com>
+author-time 1192002639
+author-tz -0700
+committer Tom Preston-Werner
+committer-mail <tom@mojombo.com>
+committer-time 1192002639
+committer-tz -0700
+filename lib/grit.rb
+summary add more comments throughout
+       require 'grit/commit'
+b17b974691f0a26f26908495d24d9c4c718920f8 13 13 1
+author Tom Preston-Werner
+author-mail <tom@mojombo.com>
+author-time 1192271832
+author-tz -0700
+committer Tom Preston-Werner
+committer-mail <tom@mojombo.com>
+committer-time 1192271832
+committer-tz -0700
+filename lib/grit.rb
+summary started implementing Tree
+       require 'grit/tree'
+74fd66519e983a0f29e16a342a6059dbffe36020 14 14 1
+author Tom Preston-Werner
+author-mail <tom@mojombo.com>
+author-time 1192317005
+author-tz -0700
+committer Tom Preston-Werner
+committer-mail <tom@mojombo.com>
+committer-time 1192317005
+committer-tz -0700
+filename lib/grit.rb
+summary add Blob
+       require 'grit/blob'
+d01a4cfad6ea50285c4710243e3cbe019d381eba 12 15 1
+       require 'grit/repo'
+634396b2f541a9f2d58b00be1a07f0c358b999b3 9 16 1
+       
+d01a4cfad6ea50285c4710243e3cbe019d381eba 14 17 1
+       module Grit
+b6e1b765e0c15586a2c5b9832854f95defd71e1f 18 18 6
+author Tom Preston-Werner
+author-mail <tom@mojombo.com>
+author-time 1192860483
+author-tz -0700
+committer Tom Preston-Werner
+committer-mail <tom@mojombo.com>
+committer-time 1192860483
+committer-tz -0700
+filename lib/grit.rb
+summary implement Repo.init_bare
+         class << self
+b6e1b765e0c15586a2c5b9832854f95defd71e1f 19 19
+           attr_accessor :debug
+b6e1b765e0c15586a2c5b9832854f95defd71e1f 20 20
+         end
+b6e1b765e0c15586a2c5b9832854f95defd71e1f 21 21
+         
+b6e1b765e0c15586a2c5b9832854f95defd71e1f 22 22
+         self.debug = false
+b6e1b765e0c15586a2c5b9832854f95defd71e1f 23 23
+         
+634396b2f541a9f2d58b00be1a07f0c358b999b3 11 24 2
+         VERSION = '1.0.0'
+634396b2f541a9f2d58b00be1a07f0c358b999b3 12 25
+       end
\ No newline at end of file
diff --git a/tests/fixtures/cat_file_blob b/tests/fixtures/cat_file_blob
new file mode 100644 (file)
index 0000000..70c379b
--- /dev/null
@@ -0,0 +1 @@
+Hello world
\ No newline at end of file
diff --git a/tests/fixtures/cat_file_blob_size b/tests/fixtures/cat_file_blob_size
new file mode 100644 (file)
index 0000000..b4de394
--- /dev/null
@@ -0,0 +1 @@
+11
diff --git a/tests/fixtures/diff_2 b/tests/fixtures/diff_2
new file mode 100644 (file)
index 0000000..1f060c7
--- /dev/null
@@ -0,0 +1,54 @@
+diff --git a/lib/grit/commit.rb b/lib/grit/commit.rb
+index a093bb1db8e884cccf396b297259181d1caebed4..80fd3d527f269ecbd570b65b8e21fd85baedb6e9 100644
+--- a/lib/grit/commit.rb
++++ b/lib/grit/commit.rb
+@@ -156,12 +156,8 @@ module Grit
+     def diffs
+       if parents.empty?
+-        diff = @repo.git.show({:full_index => true, :pretty => 'raw'}, @id)
+-        if diff =~ /diff --git a/
+-          diff = diff.sub(/.+?(diff --git a)/m, '\1')
+-        else
+-          diff = ''
+-        end
++        diff = @repo.git.show({:full_index => true, :pretty => 'raw'}, @id) 
++        diff = diff.sub(/.+?(diff --git a)/m, '\1')
+         Diff.list_from_string(@repo, diff)
+       else
+         self.class.diff(@repo, parents.first.id, @id) 
+diff --git a/test/fixtures/show_empty_commit b/test/fixtures/show_empty_commit
+deleted file mode 100644
+index ea25e32a409fdf74c1b9268820108d1c16dcc553..0000000000000000000000000000000000000000
+--- a/test/fixtures/show_empty_commit
++++ /dev/null
+@@ -1,6 +0,0 @@
+-commit 1e3824339762bd48316fe87bfafc853732d43264
+-tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
+-author Tom Preston-Werner <tom@mojombo.com> 1157392833 +0000
+-committer Tom Preston-Werner <tom@mojombo.com> 1157392833 +0000
+-
+-    initial directory structure
+diff --git a/test/test_commit.rb b/test/test_commit.rb
+index fdeb9000089b052f0b31a845e0173e9b089e06a0..bdbc450e08084d7d611e985cfa12fb424cab29b2 100644
+--- a/test/test_commit.rb
++++ b/test/test_commit.rb
+@@ -98,18 +98,6 @@ class TestCommit < Test::Unit::TestCase
+     assert_equal true, diffs[5].new_file
+   end
+   
+-  def test_diffs_on_initial_import_with_empty_commit
+-    Git.any_instance.expects(:show).with(
+-      {:full_index => true, :pretty => 'raw'}, 
+-      '634396b2f541a9f2d58b00be1a07f0c358b999b3'
+-    ).returns(fixture('show_empty_commit'))
+-    
+-    @c = Commit.create(@r, :id => '634396b2f541a9f2d58b00be1a07f0c358b999b3')
+-    diffs = @c.diffs
+-    
+-    assert_equal [], diffs
+-  end
+-  
+   # to_s
+   
+   def test_to_s
diff --git a/tests/fixtures/diff_2f b/tests/fixtures/diff_2f
new file mode 100644 (file)
index 0000000..5246cd6
--- /dev/null
@@ -0,0 +1,19 @@
+diff --git a/lib/grit/commit.rb b/lib/grit/commit.rb
+index a093bb1db8e884cccf396b297259181d1caebed4..80fd3d527f269ecbd570b65b8e21fd85baedb6e9 100644
+--- a/lib/grit/commit.rb
++++ b/lib/grit/commit.rb
+@@ -156,12 +156,8 @@ module Grit
+     def diffs
+       if parents.empty?
+-        diff = @repo.git.show({:full_index => true, :pretty => 'raw'}, @id)
+-        if diff =~ /diff --git a/
+-          diff = diff.sub(/.+?(diff --git a)/m, '\1')
+-        else
+-          diff = ''
+-        end
++        diff = @repo.git.show({:full_index => true, :pretty => 'raw'}, @id) 
++        diff = diff.sub(/.+?(diff --git a)/m, '\1')
+         Diff.list_from_string(@repo, diff)
+       else
+         self.class.diff(@repo, parents.first.id, @id) 
diff --git a/tests/fixtures/diff_f b/tests/fixtures/diff_f
new file mode 100644 (file)
index 0000000..48a4925
--- /dev/null
@@ -0,0 +1,15 @@
+diff --git a/lib/grit/diff.rb b/lib/grit/diff.rb
+index 537955bb86a8ceaa19aea89e75ccbea5ce6f2698..00b0b4a67eca9242db5f8991e99625acd55f040c 100644
+--- a/lib/grit/diff.rb
++++ b/lib/grit/diff.rb
+@@ -27,6 +27,10 @@ module Grit
+       while !lines.empty?
+         m, a_path, b_path = *lines.shift.match(%r{^diff --git a/(\S+) b/(\S+)$})
+         
++        if lines.first =~ /^old mode/
++          2.times { lines.shift }
++        end
++        
+         new_file = false
+         deleted_file = false
+         
diff --git a/tests/fixtures/diff_i b/tests/fixtures/diff_i
new file mode 100644 (file)
index 0000000..cec64e1
--- /dev/null
@@ -0,0 +1,201 @@
+commit 634396b2f541a9f2d58b00be1a07f0c358b999b3
+Author: Tom Preston-Werner <tom@mojombo.com>
+Date:   Tue Oct 9 23:18:20 2007 -0700
+
+    initial grit setup
+
+diff --git a/History.txt b/History.txt
+new file mode 100644
+index 0000000000000000000000000000000000000000..81d2c27608b352814cbe979a6acd678d30219678
+--- /dev/null
++++ b/History.txt
+@@ -0,0 +1,5 @@
++== 1.0.0 / 2007-10-09
++
++* 1 major enhancement
++  * Birthday!
++
+diff --git a/Manifest.txt b/Manifest.txt
+new file mode 100644
+index 0000000000000000000000000000000000000000..641972d82c6d1b51122274ae8f6a0ecdfb56ee22
+--- /dev/null
++++ b/Manifest.txt
+@@ -0,0 +1,7 @@
++History.txt
++Manifest.txt
++README.txt
++Rakefile
++bin/grit
++lib/grit.rb
++test/test_grit.rb
+\ No newline at end of file
+diff --git a/README.txt b/README.txt
+new file mode 100644
+index 0000000000000000000000000000000000000000..8b1e02c0fb554eed2ce2ef737a68bb369d7527df
+--- /dev/null
++++ b/README.txt
+@@ -0,0 +1,48 @@
++grit
++    by FIX (your name)
++    FIX (url)
++
++== DESCRIPTION:
++  
++FIX (describe your package)
++
++== FEATURES/PROBLEMS:
++  
++* FIX (list of features or problems)
++
++== SYNOPSIS:
++
++  FIX (code sample of usage)
++
++== REQUIREMENTS:
++
++* FIX (list of requirements)
++
++== INSTALL:
++
++* FIX (sudo gem install, anything else)
++
++== LICENSE:
++
++(The MIT License)
++
++Copyright (c) 2007 FIX
++
++Permission is hereby granted, free of charge, to any person obtaining
++a copy of this software and associated documentation files (the
++'Software'), to deal in the Software without restriction, including
++without limitation the rights to use, copy, modify, merge, publish,
++distribute, sublicense, and/or sell copies of the Software, and to
++permit persons to whom the Software is furnished to do so, subject to
++the following conditions:
++
++The above copyright notice and this permission notice shall be
++included in all copies or substantial portions of the Software.
++
++THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
++EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
++MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
++IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
++CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
++TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
++SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+diff --git a/Rakefile b/Rakefile
+new file mode 100644
+index 0000000000000000000000000000000000000000..ff69c3684a18592c741332b290492aa39d980e02
+--- /dev/null
++++ b/Rakefile
+@@ -0,0 +1,17 @@
++# -*- ruby -*-
++
++require 'rubygems'
++require 'hoe'
++require './lib/grit.rb'
++
++Hoe.new('grit', GitPython.VERSION) do |p|
++  p.rubyforge_name = 'grit'
++  # p.author = 'FIX'
++  # p.email = 'FIX'
++  # p.summary = 'FIX'
++  # p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
++  # p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
++  p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
++end
++
++# vim: syntax=Ruby
+diff --git a/bin/grit b/bin/grit
+new file mode 100644
+index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
+diff --git a/lib/grit.rb b/lib/grit.rb
+new file mode 100644
+index 0000000000000000000000000000000000000000..32cec87d1e78946a827ddf6a8776be4d81dcf1d1
+--- /dev/null
++++ b/lib/grit.rb
+@@ -0,0 +1,12 @@
++$:.unshift File.dirname(__FILE__)     # For use/testing when no gem is installed
++
++# core
++
++# stdlib
++
++# internal requires
++require 'grit/grit'
++
++class Grit
++  VERSION = '1.0.0'
++end
+\ No newline at end of file
+diff --git a/lib/grit/errors.rb b/lib/grit/errors.rb
+new file mode 100644
+index 0000000000000000000000000000000000000000..b3be31553741937607a89be8b6a2ab1df208852e
+--- /dev/null
++++ b/lib/grit/errors.rb
+@@ -0,0 +1,4 @@
++class Grit
++  class InvalidGitRepositoryError < StandardError
++  end
++end
+\ No newline at end of file
+diff --git a/lib/grit/grit.rb b/lib/grit/grit.rb
+new file mode 100644
+index 0000000000000000000000000000000000000000..48fd36e16081ec09903f7a0e2253b3d16f9efb01
+--- /dev/null
++++ b/lib/grit/grit.rb
+@@ -0,0 +1,24 @@
++class Grit
++  attr_accessor :path
++  
++  # Create a new Grit instance
++  #   +path+ is the path to either the root git directory or the bare git repo
++  #
++  # Examples
++  #   g = Grit.new("/Users/tom/dev/grit")
++  #   g = Grit.new("/Users/tom/public/grit.git")
++  def initialize(path)
++    if File.exist?(File.join(path, '.git'))
++      self.path = File.join(path, '.git')
++    elsif File.exist?(path) && path =~ /\.git$/
++      self.path = path
++    else
++      raise InvalidGitRepositoryError.new(path) unless File.exist?(path)
++    end
++  end
++  
++  # Return the project's description. Taken verbatim from REPO/description
++  def description
++    File.open(File.join(self.path, 'description')).read.chomp
++  end
++end
+\ No newline at end of file
+diff --git a/test/helper.rb b/test/helper.rb
+new file mode 100644
+index 0000000000000000000000000000000000000000..56e21da6b4ce3021d2754775dfa589947a4e37e5
+--- /dev/null
++++ b/test/helper.rb
+@@ -0,0 +1,5 @@
++require File.join(File.dirname(__FILE__), *%w[.. lib grit])
++
++require 'test/unit'
++
++GRIT_REPO = File.join(File.dirname(__FILE__), *%w[..])
+diff --git a/test/test_grit.rb b/test/test_grit.rb
+new file mode 100644
+index 0000000000000000000000000000000000000000..93aa481b37629797df739380306ae689e13f2855
+--- /dev/null
++++ b/test/test_grit.rb
+@@ -0,0 +1,11 @@
++require File.dirname(__FILE__) + '/helper'
++
++class TestGrit < Test::Unit::TestCase
++  def setup
++    @g = Grit.new(GRIT_REPO)
++  end
++  
++  def test_description
++    assert_equal "Grit is a ruby library for interfacing with git repositories.", @g.description
++  end
++end
+\ No newline at end of file
diff --git a/tests/fixtures/diff_mode_only b/tests/fixtures/diff_mode_only
new file mode 100644 (file)
index 0000000..6fc18f6
--- /dev/null
@@ -0,0 +1,1152 @@
+diff --git a/bin/merb b/bin/merb
+old mode 100644
+new mode 100755
+diff --git a/lib/merb.rb b/lib/merb.rb
+index 76cb3e269e46fdf9b63cda7cb563c6cf40fdcb15..a2ab4ed47f9cb2ab942da5c46a2b561758a0d704 100644
+--- a/lib/merb.rb
++++ b/lib/merb.rb
+@@ -15,7 +15,7 @@ require 'merb_core/core_ext'
+ require 'merb_core/gem_ext/erubis'
+ require 'merb_core/logger'
+ require 'merb_core/version'
+-
++require 'merb_core/controller/mime'
+ module Merb
+   class << self
+@@ -23,6 +23,7 @@ module Merb
+     def start(argv=ARGV)
+       Merb::Config.parse_args(argv)
+       BootLoader.run
++      
+       case Merb::Config[:adapter]
+       when "mongrel"
+         adapter = Merb::Rack::Mongrel
+diff --git a/lib/merb_core/boot/bootloader.rb b/lib/merb_core/boot/bootloader.rb
+index d873924860bf4da06ac93db5c6a188f63dd1c3cc..57da75f05e28e8a256922bf345ccd3902e0a0b02 100644
+--- a/lib/merb_core/boot/bootloader.rb
++++ b/lib/merb_core/boot/bootloader.rb
+@@ -20,7 +20,7 @@ module Merb
+       end
+       
+       def run
+-        subclasses.each {|klass| Object.full_const_get(klass).new.run }
++        subclasses.each {|klass| Object.full_const_get(klass).run }
+       end
+       
+       def after(klass)
+@@ -37,95 +37,128 @@ module Merb
+   
+ end
+-class Merb::BootLoader::BuildFramework < Merb::BootLoader
+-  def run
+-    build_framework
++class Merb::BootLoader::LoadInit < Merb::BootLoader
++  def self.run
++    if Merb::Config[:init_file]
++      require Merb.root / Merb::Config[:init_file]
++    elsif File.exists?(Merb.root / "config" / "merb_init.rb")
++      require Merb.root / "config" / "merb_init"
++    elsif File.exists?(Merb.root / "merb_init.rb")
++      require Merb.root / "merb_init"
++    elsif File.exists?(Merb.root / "application.rb")
++      require Merb.root / "application"
++    end
++  end
++end
++
++class Merb::BootLoader::Environment < Merb::BootLoader
++  def self.run
++    Merb.environment = Merb::Config[:environment]
++  end
++end
++
++class Merb::BootLoader::Logger < Merb::BootLoader
++  def self.run
++    Merb.logger = Merb::Logger.new(Merb.dir_for(:log) / "test_log")
++    Merb.logger.level = Merb::Logger.const_get(Merb::Config[:log_level].upcase) rescue Merb::Logger::INFO    
+   end
++end
++
++class Merb::BootLoader::BuildFramework < Merb::BootLoader
++  class << self
++    def run
++      build_framework
++    end
+   
+-  # This method should be overridden in merb_init.rb before Merb.start to set up a different
+-  # framework structure
+-  def build_framework
+-    %[view model controller helper mailer part].each do |component|
+-      Merb.push_path(component.to_sym, Merb.root_path("app/#{component}s"))
++    # This method should be overridden in merb_init.rb before Merb.start to set up a different
++    # framework structure
++    def build_framework
++      %w[view model controller helper mailer part].each do |component|
++        Merb.push_path(component.to_sym, Merb.root_path("app/#{component}s"))
++      end
++      Merb.push_path(:application,    Merb.root_path("app/controllers/application.rb"))
++      Merb.push_path(:config,         Merb.root_path("config/router.rb"))
++      Merb.push_path(:lib,            Merb.root_path("lib"))    
+     end
+-    Merb.push_path(:application,    Merb.root_path("app/controllers/application.rb"))
+-    Merb.push_path(:config,         Merb.root_path("config/router.rb"))
+-    Merb.push_path(:lib,            Merb.root_path("lib"))    
+   end
+ end
+ class Merb::BootLoader::LoadPaths < Merb::BootLoader
+   LOADED_CLASSES = {}
+   
+-  def run
+-    # Add models, controllers, and lib to the load path
+-    $LOAD_PATH.unshift Merb.load_paths[:model].first      if Merb.load_paths[:model]
+-    $LOAD_PATH.unshift Merb.load_paths[:controller].first if Merb.load_paths[:controller]
+-    $LOAD_PATH.unshift Merb.load_paths[:lib].first        if Merb.load_paths[:lib]
++  class << self
++    def run
++      # Add models, controllers, and lib to the load path
++      $LOAD_PATH.unshift Merb.load_paths[:model].first      if Merb.load_paths[:model]
++      $LOAD_PATH.unshift Merb.load_paths[:controller].first if Merb.load_paths[:controller]
++      $LOAD_PATH.unshift Merb.load_paths[:lib].first        if Merb.load_paths[:lib]
+     
+-    # Require all the files in the registered load paths
+-    puts Merb.load_paths.inspect
+-    Merb.load_paths.each do |name, path|
+-      Dir[path.first / path.last].each do |file| 
+-        klasses = ObjectSpace.classes.dup
+-        require f
+-        LOADED_CLASSES[file] = ObjectSpace.classes - klasses
++      # Require all the files in the registered load paths
++      puts Merb.load_paths.inspect
++      Merb.load_paths.each do |name, path|
++        Dir[path.first / path.last].each do |file| 
++          klasses = ObjectSpace.classes.dup
++          require file
++          LOADED_CLASSES[file] = ObjectSpace.classes - klasses
++        end
+       end
+     end
+-  end
+-  def reload(file)
+-    if klasses = LOADED_CLASSES[file]
+-      klasses.each do |klass|
+-        remove_constant(klass)
++    def reload(file)
++      if klasses = LOADED_CLASSES[file]
++        klasses.each do |klass|
++          remove_constant(klass)
++        end
+       end
++      load file
+     end
+-    load file
+-  end
+   
+-  def remove_constant(const)
+-    # This is to support superclasses (like AbstractController) that track
+-    # their subclasses in a class variable. Classes that wish to use this
+-    # functionality are required to alias it to _subclasses_list. Plugins
+-    # for ORMs and other libraries should keep this in mind.
+-    if klass.superclass.respond_to?(:_subclasses_list)
+-      klass.superclass.send(:_subclasses_list).delete(klass)
+-      klass.superclass.send(:_subclasses_list).delete(klass.to_s)          
+-    end
++    def remove_constant(const)
++      # This is to support superclasses (like AbstractController) that track
++      # their subclasses in a class variable. Classes that wish to use this
++      # functionality are required to alias it to _subclasses_list. Plugins
++      # for ORMs and other libraries should keep this in mind.
++      if klass.superclass.respond_to?(:_subclasses_list)
++        klass.superclass.send(:_subclasses_list).delete(klass)
++        klass.superclass.send(:_subclasses_list).delete(klass.to_s)          
++      end
+   
+-    parts = const.to_s.split("::")
+-    base = parts.size == 1 ? Object : Object.full_const_get(parts[0..-2].join("::"))
+-    object = parts[-1].intern
+-    Merb.logger.debugger("Removing constant #{object} from #{base}")
+-    base.send(:remove_const, object) if object
++      parts = const.to_s.split("::")
++      base = parts.size == 1 ? Object : Object.full_const_get(parts[0..-2].join("::"))
++      object = parts[-1].intern
++      Merb.logger.debugger("Removing constant #{object} from #{base}")
++      base.send(:remove_const, object) if object
++    end
+   end
+   
+ end
+ class Merb::BootLoader::Templates < Merb::BootLoader
+-  def run
+-    template_paths.each do |path|
+-      Merb::Template.inline_template(path)
++  class << self
++    def run
++      template_paths.each do |path|
++        Merb::Template.inline_template(path)
++      end
+     end
+-  end
+   
+-  def template_paths
+-    extension_glob = "{#{Merb::Template::EXTENSIONS.keys.join(',')}}"
++    def template_paths
++      extension_glob = "{#{Merb::Template::EXTENSIONS.keys.join(',')}}"
+-    # This gets all templates set in the controllers template roots        
+-    # We separate the two maps because most of controllers will have
+-    # the same _template_root, so it's silly to be globbing the same
+-    # path over and over.
+-    template_paths = Merb::AbstractController._abstract_subclasses.map do |klass| 
+-      Object.full_const_get(klass)._template_root
+-    end.uniq.map {|path| Dir["#{path}/**/*.#{extension_glob}"] }
++      # This gets all templates set in the controllers template roots        
++      # We separate the two maps because most of controllers will have
++      # the same _template_root, so it's silly to be globbing the same
++      # path over and over.
++      template_paths = Merb::AbstractController._abstract_subclasses.map do |klass| 
++        Object.full_const_get(klass)._template_root
++      end.uniq.compact.map {|path| Dir["#{path}/**/*.#{extension_glob}"] }
+     
+-    # This gets the templates that might be created outside controllers
+-    # template roots.  eg app/views/shared/*
+-    template_paths << Dir["#{Merb.dir_for(:view)}/**/*.#{extension_glob}"] if Merb.dir_for(:view)
++      # This gets the templates that might be created outside controllers
++      # template roots.  eg app/views/shared/*
++      template_paths << Dir["#{Merb.dir_for(:view)}/**/*.#{extension_glob}"] if Merb.dir_for(:view)
+     
+-    template_paths.flatten.compact.uniq
+-  end  
++      template_paths.flatten.compact.uniq
++    end
++  end
+ end
+ class Merb::BootLoader::Libraries < Merb::BootLoader
+@@ -145,18 +178,41 @@ class Merb::BootLoader::Libraries < Merb::BootLoader
+   def self.add_libraries(hsh)
+     @@libraries.merge!(hsh)
+   end
+-  
+-  def run
++
++  def self.run
+     @@libraries.each do |exclude, choices|
+       require_first_working(*choices) unless Merb::Config[exclude]
+     end
+   end
+-  
+-  def require_first_working(first, *rest)
++
++  def self.require_first_working(first, *rest)
+     p first, rest
+     require first
+   rescue LoadError
+     raise LoadError if rest.empty?
+     require_first_working rest.unshift, *rest
+   end
++end
++
++class Merb::BootLoader::MimeTypes < Merb::BootLoader
++  def self.run
++    # Sets the default mime-types
++    # 
++    # By default, the mime-types include:
++    # :all:: no transform, */*
++    # :yaml:: to_yaml, application/x-yaml or text/yaml
++    # :text:: to_text, text/plain
++    # :html:: to_html, text/html or application/xhtml+xml or application/html
++    # :xml:: to_xml, application/xml or text/xml or application/x-xml, adds "Encoding: UTF-8" response header
++    # :js:: to_json, text/javascript ot application/javascript or application/x-javascript
++    # :json:: to_json, application/json or text/x-json
++    Merb.available_mime_types.clear
++    Merb.add_mime_type(:all,  nil,      %w[*/*])
++    Merb.add_mime_type(:yaml, :to_yaml, %w[application/x-yaml text/yaml])
++    Merb.add_mime_type(:text, :to_text, %w[text/plain])
++    Merb.add_mime_type(:html, :to_html, %w[text/html application/xhtml+xml application/html])
++    Merb.add_mime_type(:xml,  :to_xml,  %w[application/xml text/xml application/x-xml], :Encoding => "UTF-8")
++    Merb.add_mime_type(:js,   :to_json, %w[text/javascript application/javascript application/x-javascript])
++    Merb.add_mime_type(:json, :to_json, %w[application/json text/x-json])      
++  end
+ end
+\ No newline at end of file
+diff --git a/lib/merb_core/config.rb b/lib/merb_core/config.rb
+index c92f2e6f071c234551ecb16a4716d47fa92f6c7b..ab0864e0174b54833c758f9f22a840d3b53c7653 100644
+--- a/lib/merb_core/config.rb
++++ b/lib/merb_core/config.rb
+@@ -92,6 +92,10 @@ module Merb
+              options[:cluster] = nodes
+            end
++           opts.on("-I", "--init-file FILE", "Name of the file to load first") do |init_file|
++             options[:init_file] = init_file
++           end
++
+            opts.on("-p", "--port PORTNUM", "Port to run merb on, defaults to 4000.") do |port|
+              options[:port] = port
+            end
+@@ -261,29 +265,29 @@ module Merb
+          @configuration = Merb::Config.apply_configuration_from_file options, environment_merb_yml
+          
+-         case Merb::Config[:environment].to_s
+-         when 'production'
+-           Merb::Config[:reloader] = Merb::Config.fetch(:reloader, false)
+-           Merb::Config[:exception_details] = Merb::Config.fetch(:exception_details, false)
+-           Merb::Config[:cache_templates] = true
+-         else
+-           Merb::Config[:reloader] = Merb::Config.fetch(:reloader, true)
+-           Merb::Config[:exception_details] = Merb::Config.fetch(:exception_details, true)
+-         end
+-
+-         Merb::Config[:reloader_time] ||= 0.5 if Merb::Config[:reloader] == true
+-
+-
+-         if Merb::Config[:reloader]
+-           Thread.abort_on_exception = true
+-           Thread.new do
+-             loop do
+-               sleep( Merb::Config[:reloader_time] )
+-               ::Merb::BootLoader.reload if ::Merb::BootLoader.app_loaded?
+-             end
+-             Thread.exit
+-           end
+-         end
++         # case Merb::Config[:environment].to_s
++         # when 'production'
++         #   Merb::Config[:reloader] = Merb::Config.fetch(:reloader, false)
++         #   Merb::Config[:exception_details] = Merb::Config.fetch(:exception_details, false)
++         #   Merb::Config[:cache_templates] = true
++         # else
++         #   Merb::Config[:reloader] = Merb::Config.fetch(:reloader, true)
++         #   Merb::Config[:exception_details] = Merb::Config.fetch(:exception_details, true)
++         # end
++         # 
++         # Merb::Config[:reloader_time] ||= 0.5 if Merb::Config[:reloader] == true
++         # 
++         # 
++         # if Merb::Config[:reloader]
++         #   Thread.abort_on_exception = true
++         #   Thread.new do
++         #     loop do
++         #       sleep( Merb::Config[:reloader_time] )
++         #       ::Merb::BootLoader.reload if ::Merb::BootLoader.app_loaded?
++         #     end
++         #     Thread.exit
++         #   end
++         # end
+          @configuration
+        end
+        
+diff --git a/lib/merb_core/controller/abstract_controller.rb b/lib/merb_core/controller/abstract_controller.rb
+index fbf83372793da6da4b803b799994f0e341fddf88..f5e9a59057d67a6d56377a516a726cf51aa03d6f 100644
+--- a/lib/merb_core/controller/abstract_controller.rb
++++ b/lib/merb_core/controller/abstract_controller.rb
+@@ -96,7 +96,7 @@ class Merb::AbstractController
+   # the superclass.
+   #---
+   # @public
+-  def _template_location(action, controller = controller_name, type = nil)
++  def _template_location(action, type = nil, controller = controller_name)
+     "#{controller}/#{action}"
+   end
+   
+@@ -106,6 +106,8 @@ class Merb::AbstractController
+   # own subclasses. We're using a Set so we don't have to worry about
+   # uniqueness.
+   self._abstract_subclasses = Set.new
++  self._template_root = Merb.dir_for(:view)
++  
+   def self.subclasses_list() _abstract_subclasses end
+   
+   class << self
+@@ -114,7 +116,6 @@ class Merb::AbstractController
+     #   The controller that is being inherited from Merb::AbstractController
+     def inherited(klass)
+       _abstract_subclasses << klass.to_s
+-      klass._template_root ||= Merb.dir_for(:view)
+       super
+     end
+     
+diff --git a/lib/merb_core/controller/merb_controller.rb b/lib/merb_core/controller/merb_controller.rb
+index 7283f006bb0501b29f825da129600cf045264b62..98af6ef3330a6b3f46d7bb1f8643261e28155ae5 100644
+--- a/lib/merb_core/controller/merb_controller.rb
++++ b/lib/merb_core/controller/merb_controller.rb
+@@ -71,6 +71,10 @@ class Merb::Controller < Merb::AbstractController
+     end
+   end
+   
++  def _template_location(action, type = nil, controller = controller_name)
++    "#{controller}/#{action}.#{type}"
++  end  
++  
+   # Sets the variables that came in through the dispatch as available to
+   # the controller. This is called by .build, so see it for more
+   # information.
+@@ -107,9 +111,7 @@ class Merb::Controller < Merb::AbstractController
+         request.cookies[_session_id_key] = request.params[_session_id_key]
+       end
+     end
+-    @_request, @_response, @_status, @_headers = 
+-      request, response, status, headers
+-
++    @request, @response, @status, @headers = request, response, status, headers
+     nil
+   end
+   
+@@ -135,7 +137,8 @@ class Merb::Controller < Merb::AbstractController
+     @_benchmarks[:action_time] = Time.now - start
+   end
+   
+-  _attr_reader :request, :response, :status, :headers
++  attr_reader :request, :response, :headers
++  attr_accessor :status
+   def params()  request.params  end
+   def cookies() request.cookies end
+   def session() request.session end
+diff --git a/lib/merb_core/controller/mime.rb b/lib/merb_core/controller/mime.rb
+index d17570786ca318cff7201c4b1e947ae229b01de8..ff9abe4d1c452aeabfcf5f7dc7a2c7cdd3f67035 100644
+--- a/lib/merb_core/controller/mime.rb
++++ b/lib/merb_core/controller/mime.rb
+@@ -8,7 +8,7 @@ module Merb
+     # Any specific outgoing headers should be included here.  These are not
+     # the content-type header but anything in addition to it.
+-    # +tranform_method+ should be set to a symbol of the method used to
++    # +transform_method+ should be set to a symbol of the method used to
+     # transform a resource into this mime type.
+     # For example for the :xml mime type an object might be transformed by
+     # calling :to_xml, or for the :js mime type, :to_json.
+@@ -71,27 +71,6 @@ module Merb
+     def mime_by_request_header(header)
+       available_mime_types.find {|key,info| info[request_headers].include?(header)}.first
+     end
+-
+-    # Resets the default mime-types
+-    # 
+-    # By default, the mime-types include:
+-    # :all:: no transform, */*
+-    # :yaml:: to_yaml, application/x-yaml or text/yaml
+-    # :text:: to_text, text/plain
+-    # :html:: to_html, text/html or application/xhtml+xml or application/html
+-    # :xml:: to_xml, application/xml or text/xml or application/x-xml, adds "Encoding: UTF-8" response header
+-    # :js:: to_json, text/javascript ot application/javascript or application/x-javascript
+-    # :json:: to_json, application/json or text/x-json
+-    def reset_default_mime_types!
+-      available_mime_types.clear
+-      Merb.add_mime_type(:all,  nil,      %w[*/*])
+-      Merb.add_mime_type(:yaml, :to_yaml, %w[application/x-yaml text/yaml])
+-      Merb.add_mime_type(:text, :to_text, %w[text/plain])
+-      Merb.add_mime_type(:html, :to_html, %w[text/html application/xhtml+xml application/html])
+-      Merb.add_mime_type(:xml,  :to_xml,  %w[application/xml text/xml application/x-xml], :Encoding => "UTF-8")
+-      Merb.add_mime_type(:js,   :to_json, %w[text/javascript application/javascript application/x-javascript])
+-      Merb.add_mime_type(:json, :to_json, %w[application/json text/x-json])      
+-    end
+     
+   end
+ end
+\ No newline at end of file
+diff --git a/lib/merb_core/controller/mixins/render.rb b/lib/merb_core/controller/mixins/render.rb
+index 8e096546d4647bb597ab2e00a4b15d09db35e9c9..a298263af7d655d9ce43007554f3827046831287 100644
+--- a/lib/merb_core/controller/mixins/render.rb
++++ b/lib/merb_core/controller/mixins/render.rb
+@@ -51,21 +51,22 @@ module Merb::RenderMixin
+     
+     # If you don't specify a thing to render, assume they want to render the current action
+     thing ||= action_name.to_sym
+-    
++
+     # Content negotiation
+     opts[:format] ? (self.content_type = opts[:format]) : content_type 
+     
+     # Do we have a template to try to render?
+     if thing.is_a?(Symbol) || opts[:template]
+-      
++
+       # Find a template path to look up (_template_location adds flexibility here)
+-      template_location = _template_root / (opts[:template] || _template_location(thing))
++      template_location = _template_root / (opts[:template] || _template_location(thing, content_type))
++      
+       # Get the method name from the previously inlined list
+       template_method = Merb::Template.template_for(template_location)
+       # Raise an error if there's no template
+       raise TemplateNotFound, "No template found at #{template_location}" unless 
+-        self.respond_to?(template_method)
++        template_method && self.respond_to?(template_method)
+       # Call the method in question and throw the content for later consumption by the layout
+       throw_content(:for_layout, self.send(template_method))
+diff --git a/lib/merb_core/controller/mixins/responder.rb b/lib/merb_core/controller/mixins/responder.rb
+index e910b2b32c844ab51cf2a10d0ad26c314dbb3631..5ac67fb907aaf9f95effc7eb3cbb07b8963ce022 100644
+--- a/lib/merb_core/controller/mixins/responder.rb
++++ b/lib/merb_core/controller/mixins/responder.rb
+@@ -97,6 +97,8 @@ module Merb
+   # and none of the provides methods can be used.
+   module ResponderMixin
+     
++    TYPES = {}
++    
+     class ContentTypeAlreadySet < StandardError; end
+     
+     # ==== Parameters
+@@ -105,6 +107,7 @@ module Merb
+       base.extend(ClassMethods)
+       base.class_eval do
+         class_inheritable_accessor :class_provided_formats
++        self.class_provided_formats = []
+       end
+       base.reset_provides
+     end
+@@ -178,171 +181,253 @@ module Merb
+       def reset_provides
+         only_provides(:html)
+       end
+-      
+-      # ==== Returns
+-      # The current list of formats provided for this instance of the controller. 
+-      # It starts with what has been set in the controller (or :html by default) 
+-      # but can be modifed on a per-action basis.      
+-      def _provided_formats
+-        @_provided_formats ||= class_provided_formats.dup
++    end
++
++    # ==== Returns
++    # The current list of formats provided for this instance of the controller. 
++    # It starts with what has been set in the controller (or :html by default) 
++    # but can be modifed on a per-action basis.      
++    def _provided_formats
++      @_provided_formats ||= class_provided_formats.dup
++    end
++    
++    # Sets the provided formats for this action.  Usually, you would
++    # use a combination of +provides+, +only_provides+ and +does_not_provide+
++    # to manage this, but you can set it directly.
++    # 
++    # ==== Parameters
++    # *formats<Symbol>:: A list of formats to be passed to provides
++    #
++    # ==== Raises
++    # Merb::ResponderMixin::ContentTypeAlreadySet::
++    #   Content negotiation already occured, and the content_type is set.
++    #
++    # ==== Returns
++    # Array:: List of formats passed in
++    def _set_provided_formats(*formats)
++      if @_content_type
++        raise ContentTypeAlreadySet, "Cannot modify provided_formats because content_type has already been set"
+       end
+-      
+-      # Sets the provided formats for this action.  Usually, you would
+-      # use a combination of +provides+, +only_provides+ and +does_not_provide+
+-      # to manage this, but you can set it directly.
+-      # 
+-      # ==== Parameters
+-      # *formats<Symbol>:: A list of formats to be passed to provides
+-      #
+-      # ==== Raises
+-      # Merb::ResponderMixin::ContentTypeAlreadySet::
+-      #   Content negotiation already occured, and the content_type is set.
+-      #
+-      # ==== Returns
+-      # Array:: List of formats passed in
+-      def _set_provided_formats(*formats)
+-        if @_content_type
+-          raise ContentTypeAlreadySet, "Cannot modify provided_formats because content_type has already been set"
+-        end
+-        @_provided_formats = []
+-        provides(*formats)
++      @_provided_formats = []
++      provides(*formats)
++    end
++    alias :_provided_formats= :_set_provided_formats   
++    
++    # Adds formats to the list of provided formats for this particular 
++    # request. Usually used to add formats to a single action. See also
++    # the controller-level provides that affects all actions in a controller.
++    #
++    # ==== Parameters
++    # *formats<Symbol>:: A list of formats to add to the per-action list
++    #                    of provided formats
++    #
++    # ==== Raises
++    # Merb::ResponderMixin::ContentTypeAlreadySet::
++    #   Content negotiation already occured, and the content_type is set.
++    #
++    # ==== Returns
++    # Array:: List of formats passed in
++    #
++    #---
++    # @public
++    def provides(*formats)
++      if @_content_type
++        raise ContentTypeAlreadySet, "Cannot modify provided_formats because content_type has already been set"
+       end
+-      alias :_provided_formats= :_set_provided_formats   
+-      
+-      # Adds formats to the list of provided formats for this particular 
+-      # request. Usually used to add formats to a single action. See also
+-      # the controller-level provides that affects all actions in a controller.
+-      #
+-      # ==== Parameters
+-      # *formats<Symbol>:: A list of formats to add to the per-action list
+-      #                    of provided formats
+-      #
+-      # ==== Raises
+-      # Merb::ResponderMixin::ContentTypeAlreadySet::
+-      #   Content negotiation already occured, and the content_type is set.
+-      #
+-      # ==== Returns
+-      # Array:: List of formats passed in
+-      #
+-      #---
+-      # @public
+-      def provides(*formats)
+-        if @_content_type
+-          raise ContentTypeAlreadySet, "Cannot modify provided_formats because content_type has already been set"
+-        end
+-        formats.each do |fmt|
+-          _provided_formats << fmt unless _provided_formats.include?(fmt)
+-        end
++      formats.each do |fmt|
++        _provided_formats << fmt unless _provided_formats.include?(fmt)
+       end
++    end
+-      # Sets list of provided formats for this particular 
+-      # request. Usually used to limit formats to a single action. See also
+-      # the controller-level only_provides that affects all actions
+-      # in a controller.      
+-      # 
+-      # ==== Parameters
+-      # *formats<Symbol>:: A list of formats to use as the per-action list
+-      #                    of provided formats
+-      #
+-      # ==== Returns
+-      # Array:: List of formats passed in
+-      #
+-      #---
+-      # @public
+-      def only_provides(*formats)
+-        self._provided_formats = *formats
+-      end
+-      
+-      # Removes formats from the list of provided formats for this particular 
+-      # request. Usually used to remove formats from a single action.  See
+-      # also the controller-level does_not_provide that affects all actions in a
+-      # controller.
+-      #
+-      # ==== Parameters
+-      # *formats<Symbol>:: Registered mime-type
+-      # 
+-      # ==== Returns
+-      # Array:: List of formats that remain after removing the ones not to provide
+-      #
+-      #---
+-      # @public      
+-      def does_not_provide(*formats)
+-        formats.flatten!
+-        self._provided_formats -= formats
+-      end
+-      
+-      # Do the content negotiation:
+-      # 1. if params[:format] is there, and provided, use it
+-      # 2. Parse the Accept header
+-      # 3. If it's */*, use the first provided format
+-      # 4. Look for one that is provided, in order of request
+-      # 5. Raise 406 if none found
+-      def _perform_content_negotiation # :nodoc:
+-        raise Merb::ControllerExceptions::NotAcceptable if provided_formats.empty?
+-        if fmt = params[:format]
+-          return fmt.to_sym if provided_formats.include?(fmt.to_sym)
+-        else
+-          accepts = Responder.parse(request.accept).map {|t| t.to_sym}
+-          return provided_formats.first if accepts.include?(:all)
+-          return accepts.each { |type| break type if provided_formats.include?(type) }
+-        end
+-        raise Merb::ControllerExceptions::NotAcceptable          
++    # Sets list of provided formats for this particular 
++    # request. Usually used to limit formats to a single action. See also
++    # the controller-level only_provides that affects all actions
++    # in a controller.      
++    # 
++    # ==== Parameters
++    # *formats<Symbol>:: A list of formats to use as the per-action list
++    #                    of provided formats
++    #
++    # ==== Returns
++    # Array:: List of formats passed in
++    #
++    #---
++    # @public
++    def only_provides(*formats)
++      self._provided_formats = *formats
++    end
++    
++    # Removes formats from the list of provided formats for this particular 
++    # request. Usually used to remove formats from a single action.  See
++    # also the controller-level does_not_provide that affects all actions in a
++    # controller.
++    #
++    # ==== Parameters
++    # *formats<Symbol>:: Registered mime-type
++    # 
++    # ==== Returns
++    # Array:: List of formats that remain after removing the ones not to provide
++    #
++    #---
++    # @public      
++    def does_not_provide(*formats)
++      formats.flatten!
++      self._provided_formats -= formats
++    end
++    
++    # Do the content negotiation:
++    # 1. if params[:format] is there, and provided, use it
++    # 2. Parse the Accept header
++    # 3. If it's */*, use the first provided format
++    # 4. Look for one that is provided, in order of request
++    # 5. Raise 406 if none found
++    def _perform_content_negotiation # :nodoc:
++      raise Merb::ControllerExceptions::NotAcceptable if _provided_formats.empty?
++      if fmt = params[:format] && _provided_formats.include?(fmt.to_sym)
++        return fmt.to_sym
+       end
++      accepts = Responder.parse(request.accept).map {|t| t.to_sym}
++      return _provided_formats.first if accepts.include?(:all)
++      (accepts & _provided_formats).first || (raise Merb::ControllerExceptions::NotAcceptable)
++    end
+-      # Returns the output format for this request, based on the 
+-      # provided formats, <tt>params[:format]</tt> and the client's HTTP
+-      # Accept header.
+-      #
+-      # The first time this is called, it triggers content negotiation
+-      # and caches the value.  Once you call +content_type+ you can
+-      # not set or change the list of provided formats.
+-      #
+-      # Called automatically by +render+, so you should only call it if
+-      # you need the value, not to trigger content negotiation. 
+-      # 
+-      # ==== Parameters
+-      # fmt<String?>:: 
+-      #   An optional format to use instead of performing content negotiation.
+-      #   This can be used to pass in the values of opts[:format] from the 
+-      #   render function to short-circuit content-negotiation when it's not
+-      #   necessary. This optional parameter should not be considered part
+-      #   of the public API.
+-      #
+-      # ==== Returns
+-      # Symbol:: The content-type that will be used for this controller.
+-      #
+-      #---
+-      # @public
+-      def content_type(fmt = nil)
+-        self.content_type = (fmt || _perform_content_negotiation) unless @_content_type
+-        @_content_type
++    # Returns the output format for this request, based on the 
++    # provided formats, <tt>params[:format]</tt> and the client's HTTP
++    # Accept header.
++    #
++    # The first time this is called, it triggers content negotiation
++    # and caches the value.  Once you call +content_type+ you can
++    # not set or change the list of provided formats.
++    #
++    # Called automatically by +render+, so you should only call it if
++    # you need the value, not to trigger content negotiation. 
++    # 
++    # ==== Parameters
++    # fmt<String?>:: 
++    #   An optional format to use instead of performing content negotiation.
++    #   This can be used to pass in the values of opts[:format] from the 
++    #   render function to short-circuit content-negotiation when it's not
++    #   necessary. This optional parameter should not be considered part
++    #   of the public API.
++    #
++    # ==== Returns
++    # Symbol:: The content-type that will be used for this controller.
++    #
++    #---
++    # @public
++    def content_type(fmt = nil)
++      @_content_type = (fmt || _perform_content_negotiation) unless @_content_type
++      @_content_type
++    end
++    
++    # Sets the content type of the current response to a value based on 
++    # a passed in key. The Content-Type header will be set to the first
++    # registered header for the mime-type.
++    #
++    # ==== Parameters
++    # type<Symbol>:: A type that is in the list of registered mime-types.
++    #
++    # ==== Raises
++    # ArgumentError:: "type" is not in the list of registered mime-types.
++    #
++    # ==== Returns
++    # Symbol:: The content-type that was passed in.
++    #
++    #---
++    # @semipublic
++    def content_type=(type)
++      unless Merb.available_mime_types.has_key?(type)
++        raise Merb::ControllerExceptions::NotAcceptable.new("Unknown content_type for response: #{type}") 
++      end        
++      headers['Content-Type'] = Merb.available_mime_types[type].first        
++      @_content_type = type
++    end
++    
++  end
++
++  class Responder
++  
++    protected
++    def self.parse(accept_header)
++      # parse the raw accept header into a unique, sorted array of AcceptType objects
++      list = accept_header.to_s.split(/,/).enum_for(:each_with_index).map do |entry,index|
++        AcceptType.new(entry,index += 1)
++      end.sort.uniq
++      # firefox (and possibly other browsers) send broken default accept headers.
++      # fix them up by sorting alternate xml forms (namely application/xhtml+xml)
++      # ahead of pure xml types (application/xml,text/xml).
++      if app_xml = list.detect{|e| e.super_range == 'application/xml'}
++        list.select{|e| e.to_s =~ /\+xml/}.each { |acc_type|
++          list[list.index(acc_type)],list[list.index(app_xml)] = 
++            list[list.index(app_xml)],list[list.index(acc_type)] }
+       end
+-      
+-      # Sets the content type of the current response to a value based on 
+-      # a passed in key. The Content-Type header will be set to the first
+-      # registered header for the mime-type.
+-      #
+-      # ==== Parameters
+-      # type<Symbol>:: A type that is in the list of registered mime-types.
+-      #
+-      # ==== Raises
+-      # ArgumentError:: "type" is not in the list of registered mime-types.
+-      #
+-      # ==== Returns
+-      # Symbol:: The content-type that was passed in.
+-      #
+-      #---
+-      # @semipublic
+-      def content_type=(type)
+-        unless Merb.available_mime_types.has_key?(type)
+-          raise Merb::ControllerExceptions::NotAcceptable.new("Unknown content_type for response: #{type}") 
+-        end        
+-        headers['Content-Type'] = Merb.available_mime_types[type].first        
+-        @_content_type = type
++      list
++    end
++    
++    public
++    def self.params_to_query_string(value, prefix = nil)
++      case value
++      when Array
++        value.map { |v|
++          params_to_query_string(v, "#{prefix}[]")
++        } * "&"
++      when Hash
++        value.map { |k, v|
++          params_to_query_string(v, prefix ? "#{prefix}[#{Merb::Request.escape(k)}]" : Merb::Request.escape(k))
++        } * "&"
++      else
++        "#{prefix}=#{Merb::Request.escape(value)}"
+       end
++    end      
+       
+-    end
++  end
++
++  class AcceptType
++
++    attr_reader :media_range, :quality, :index, :type, :sub_type
+     
++    def initialize(entry,index)
++      @index = index
++      @media_range, quality = entry.split(/;\s*q=/).map{|a| a.strip }
++      @type, @sub_type = @media_range.split(/\//)
++      quality ||= 0.0 if @media_range == '*/*'
++      @quality = ((quality || 1.0).to_f * 100).to_i
++    end
++  
++    def <=>(entry)
++      c = entry.quality <=> quality
++      c = index <=> entry.index if c == 0
++      c
++    end
++  
++    def eql?(entry)
++      synonyms.include?(entry.media_range)
++    end
++  
++    def ==(entry); eql?(entry); end
++  
++    def hash; super_range.hash; end
++  
++    def synonyms
++      @syns ||= Merb.available_mime_types.values.map do |e| 
++        e[:request_headers] if e[:request_headers].include?(@media_range)
++      end.compact.flatten
++    end
++  
++    def super_range
++      synonyms.first || @media_range
++    end
++  
++    def to_sym
++      Merb.available_mime_types.select{|k,v| 
++        v[:request_headers] == synonyms || v[:request_headers][0] == synonyms[0]}.flatten.first
++    end
++  
++    def to_s
++      @media_range
++    end
++  
+   end
++
+     
+ end
+\ No newline at end of file
+diff --git a/lib/merb_core/dispatch/dispatcher.rb b/lib/merb_core/dispatch/dispatcher.rb
+index c458c9f9ad454d3b0c3055d6b2a8e88b17712b44..f7fed0f539a20f9cce08b72c551725ad0563bf37 100644
+--- a/lib/merb_core/dispatch/dispatcher.rb
++++ b/lib/merb_core/dispatch/dispatcher.rb
+@@ -33,10 +33,10 @@ class Merb::Dispatcher
+     # this is the custom dispatch_exception; it allows failures to still be dispatched
+     # to the error controller
+-    rescue => exception
+-      Merb.logger.error(Merb.exception(exception))
+-      exception = controller_exception(exception)
+-      dispatch_exception(request, response, exception)
++    # rescue => exception
++    #   Merb.logger.error(Merb.exception(exception))
++    #   exception = controller_exception(exception)
++    #   dispatch_exception(request, response, exception)
+     end
+     
+     private
+@@ -49,10 +49,10 @@ class Merb::Dispatcher
+     def dispatch_action(klass, action, request, response, status=200)
+       # build controller
+       controller = klass.build(request, response, status)
+-      if @@use_mutex
+-        @@mutex.synchronize { controller.dispatch(action) }
++      if use_mutex
++        @@mutex.synchronize { controller._dispatch(action) }
+       else
+-        controller.dispatch(action)
++        controller._dispatch(action)
+       end
+       [controller, action]
+     end
+diff --git a/lib/merb_core/rack/adapter.rb b/lib/merb_core/rack/adapter.rb
+index ffc7117e9733e83b0567bbe4a43fac7663800b7d..217399a5382d0b3878aaea3d3e302173c5b5f119 100644
+--- a/lib/merb_core/rack/adapter.rb
++++ b/lib/merb_core/rack/adapter.rb
+@@ -40,7 +40,7 @@ module Merb
+         begin
+           controller, action = ::Merb::Dispatcher.handle(request, response)
+         rescue Object => e
+-          return [500, {"Content-Type"=>"text/html"}, "Internal Server Error"]
++          return [500, {"Content-Type"=>"text/html"}, e.message + "<br/>" + e.backtrace.join("<br/>")]
+         end
+         [controller.status, controller.headers, controller.body]
+       end
+diff --git a/lib/merb_core/test/request_helper.rb b/lib/merb_core/test/request_helper.rb
+index 10a9fb3ace56eaf1db0fa300df3fb2ab88a7118a..f302a3b71539182ba142cd208fe6d6aae171b1a1 100644
+--- a/lib/merb_core/test/request_helper.rb
++++ b/lib/merb_core/test/request_helper.rb
+@@ -26,8 +26,10 @@ module Merb::Test::RequestHelper
+     Merb::Test::FakeRequest.new(env, StringIO.new(req))
+   end
+   
+-  def dispatch_to(controller_klass, action, env = {}, opt = {}, &blk)
+-    request = fake_request(env, opt)
++  def dispatch_to(controller_klass, action, params = {}, env = {}, &blk)
++    request = fake_request(env, 
++      :query_string => Merb::Responder.params_to_query_string(params))
++
+     controller = controller_klass.build(request)
+     controller.instance_eval(&blk) if block_given?
+     controller._dispatch(action)
+diff --git a/spec/public/abstract_controller/spec_helper.rb b/spec/public/abstract_controller/spec_helper.rb
+index df759008d14e7572b5c44de24f77f828f83f1682..694cee2592a210a5c1fa40ca7846beeaa09725fe 100644
+--- a/spec/public/abstract_controller/spec_helper.rb
++++ b/spec/public/abstract_controller/spec_helper.rb
+@@ -1,12 +1,10 @@
+ __DIR__ = File.dirname(__FILE__)
+ require File.join(__DIR__, "..", "..", "spec_helper")
+-# The framework structure *must* be set up before loading in framework
+-# files.
+ require File.join(__DIR__, "controllers", "filters")
+ require File.join(__DIR__, "controllers", "render")
+-Merb::BootLoader::Templates.new.run
++Merb::BootLoader::Templates.run
+ module Merb::Test::Behaviors
+   def dispatch_should_make_body(klass, body, action = :index)
+diff --git a/spec/public/controller/base_spec.rb b/spec/public/controller/base_spec.rb
+index 1709e612629ed2c2b6af4579a8b89684aca9aa3c..5bcdb59948cc22592639b1aee9bd233ff2c306fa 100644
+--- a/spec/public/controller/base_spec.rb
++++ b/spec/public/controller/base_spec.rb
+@@ -10,11 +10,11 @@ describe Merb::Controller, " callable actions" do
+   end
+   
+   it "should dispatch to callable actions" do
+-    dispatch_to(Merb::Test::Fixtures::TestFoo, :index).body.should == "index"
++    dispatch_to(Merb::Test::Fixtures::TestBase, :index).body.should == "index"
+   end
+   it "should not dispatch to hidden actions" do
+-    calling { dispatch_to(Merb::Test::Fixtures::TestFoo, :hidden) }.
++    calling { dispatch_to(Merb::Test::Fixtures::TestBase, :hidden) }.
+       should raise_error(Merb::ControllerExceptions::ActionNotFound)
+   end
+     
+diff --git a/spec/public/controller/controllers/base.rb b/spec/public/controller/controllers/base.rb
+index a1b3beb27899df781d943427d9b23945f02e14de..c4b69a440a9da3c3486208d2cb95ccb8bdb974b9 100644
+--- a/spec/public/controller/controllers/base.rb
++++ b/spec/public/controller/controllers/base.rb
+@@ -3,7 +3,7 @@ module Merb::Test::Fixtures
+     self._template_root = File.dirname(__FILE__) / "views"
+   end
+   
+-  class TestFoo < ControllerTesting
++  class TestBase < ControllerTesting
+     def index
+       "index"
+     end
+diff --git a/spec/public/controller/controllers/responder.rb b/spec/public/controller/controllers/responder.rb
+new file mode 100644
+index 0000000000000000000000000000000000000000..867192e8f6e995a43fd5cd3daffa0ec11b3d31e5
+--- /dev/null
++++ b/spec/public/controller/controllers/responder.rb
+@@ -0,0 +1,25 @@
++module Merb::Test::Fixtures
++  class ControllerTesting < Merb::Controller
++    self._template_root = File.dirname(__FILE__) / "views"
++  end
++  
++  class TestResponder < ControllerTesting
++    def index
++      render
++    end
++  end
++  
++  class TestHtmlDefault < TestResponder; end
++  
++  class TestClassProvides < TestResponder; 
++    provides :xml
++  end
++  
++  class TestLocalProvides < TestResponder; 
++    def index
++      provides :xml
++      render
++    end
++  end
++  
++end
+\ No newline at end of file
+diff --git a/spec/public/controller/controllers/views/merb/test/fixtures/test_class_provides/index.html.erb b/spec/public/controller/controllers/views/merb/test/fixtures/test_class_provides/index.html.erb
+new file mode 100644
+index 0000000000000000000000000000000000000000..1bfb77d4a44c444bba6888ae7740f7df4b074c58
+--- /dev/null
++++ b/spec/public/controller/controllers/views/merb/test/fixtures/test_class_provides/index.html.erb
+@@ -0,0 +1 @@
++This should not be rendered
+\ No newline at end of file
+diff --git a/spec/public/controller/controllers/views/merb/test/fixtures/test_class_provides/index.xml.erb b/spec/public/controller/controllers/views/merb/test/fixtures/test_class_provides/index.xml.erb
+new file mode 100644
+index 0000000000000000000000000000000000000000..7c91f633987348e87e5e34e1d9e87d9dd0e5100c
+--- /dev/null
++++ b/spec/public/controller/controllers/views/merb/test/fixtures/test_class_provides/index.xml.erb
+@@ -0,0 +1 @@
++<XML:Class provides='true' />
+\ No newline at end of file
+diff --git a/spec/public/controller/controllers/views/merb/test/fixtures/test_html_default/index.html.erb b/spec/public/controller/controllers/views/merb/test/fixtures/test_html_default/index.html.erb
+new file mode 100644
+index 0000000000000000000000000000000000000000..eb4b52bf5a7aaba8f1706de419f42789c05684a2
+--- /dev/null
++++ b/spec/public/controller/controllers/views/merb/test/fixtures/test_html_default/index.html.erb
+@@ -0,0 +1 @@
++HTML: Default
+\ No newline at end of file
+diff --git a/spec/public/controller/controllers/views/merb/test/fixtures/test_local_provides/index.html.erb b/spec/public/controller/controllers/views/merb/test/fixtures/test_local_provides/index.html.erb
+new file mode 100644
+index 0000000000000000000000000000000000000000..a3a841a89c62e6174038935a42da9cd24ff54413
+--- /dev/null
++++ b/spec/public/controller/controllers/views/merb/test/fixtures/test_local_provides/index.html.erb
+@@ -0,0 +1 @@
++This should not render
+\ No newline at end of file
+diff --git a/spec/public/controller/controllers/views/merb/test/fixtures/test_local_provides/index.xml.erb b/spec/public/controller/controllers/views/merb/test/fixtures/test_local_provides/index.xml.erb
+new file mode 100644
+index 0000000000000000000000000000000000000000..c1384ec6af0357b585cc367035d1bc3a30347ade
+--- /dev/null
++++ b/spec/public/controller/controllers/views/merb/test/fixtures/test_local_provides/index.xml.erb
+@@ -0,0 +1 @@
++<XML:Local provides='true' />
+\ No newline at end of file
+diff --git a/spec/public/controller/responder_spec.rb b/spec/public/controller/responder_spec.rb
+index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bcf18532442e5965cf6ca8501770d7b7a1eb2429 100644
+--- a/spec/public/controller/responder_spec.rb
++++ b/spec/public/controller/responder_spec.rb
+@@ -0,0 +1,31 @@
++require File.join(File.dirname(__FILE__), "spec_helper")
++
++describe Merb::Controller, " responds" do
++  
++  before do
++    Merb.push_path(:layout, File.dirname(__FILE__) / "controllers" / "views" / "layouts")    
++    Merb::Router.prepare do |r|
++      r.default_routes
++    end
++  end
++  
++  it "should default the mime-type to HTML" do
++    dispatch_to(Merb::Test::Fixtures::TestHtmlDefault, :index).body.should == "HTML: Default"
++  end
++
++  it "should use other mime-types if they are provided on the class level" do
++    controller = dispatch_to(Merb::Test::Fixtures::TestClassProvides, :index, {}, :http_accept => "application/xml")
++    controller.body.should == "<XML:Class provides='true' />"
++  end
++
++  it "should fail if none of the acceptable mime-types are available" do
++    calling { dispatch_to(Merb::Test::Fixtures::TestClassProvides, :index, {}, :http_accept => "application/json") }.
++      should raise_error(Merb::ControllerExceptions::NotAcceptable)
++  end
++  
++  it "should use mime-types that are provided at the local level" do
++    controller = dispatch_to(Merb::Test::Fixtures::TestLocalProvides, :index, {}, :http_accept => "application/xml")
++    controller.body.should == "<XML:Local provides='true' />"    
++  end
++    
++end
+\ No newline at end of file
+diff --git a/spec/public/controller/spec_helper.rb b/spec/public/controller/spec_helper.rb
+index f68628a63740f4ce0235a15d71c5889e55ecaf78..e360194c1fbaf72c3298c61543c2d3a19b512b41 100644
+--- a/spec/public/controller/spec_helper.rb
++++ b/spec/public/controller/spec_helper.rb
+@@ -1,4 +1,10 @@
+ __DIR__ = File.dirname(__FILE__)
++require 'ruby-debug'
++
+ require File.join(__DIR__, "..", "..", "spec_helper")
+-require File.join(__DIR__, "controllers", "base")
+\ No newline at end of file
++require File.join(__DIR__, "controllers", "base")
++require File.join(__DIR__, "controllers", "responder")
++
++Merb::BootLoader::Templates.run
++Merb::BootLoader::MimeTypes.run
+\ No newline at end of file
diff --git a/tests/fixtures/diff_new_mode b/tests/fixtures/diff_new_mode
new file mode 100644 (file)
index 0000000..663c909
--- /dev/null
@@ -0,0 +1,14 @@
+diff --git a/conf/global_settings.py b/conf/global_settings.py
+old mode 100644
+new mode 100755
+index 9ec1bac..1c4f83b
+--- a/conf/global_settings.py
++++ b/conf/global_settings.py
+@@ -58,6 +58,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
+ )
+ MIDDLEWARE_CLASSES = (
++    "django.middleware.cache.CacheMiddleware",
+     "django.middleware.common.CommonMiddleware",
+     "django.contrib.sessions.middleware.SessionMiddleware",
+     "django.contrib.auth.middleware.AuthenticationMiddleware",
diff --git a/tests/fixtures/diff_numstat b/tests/fixtures/diff_numstat
new file mode 100644 (file)
index 0000000..44c6ca2
--- /dev/null
@@ -0,0 +1,2 @@
+29     18      a.txt
+0      5       b.txt
diff --git a/tests/fixtures/diff_p b/tests/fixtures/diff_p
new file mode 100644 (file)
index 0000000..af4759e
--- /dev/null
@@ -0,0 +1,610 @@
+diff --git a/.gitignore b/.gitignore
+index 4ebc8aea50e0a67e000ba29a30809d0a7b9b2666..2dd02534615434d88c51307beb0f0092f21fd103 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -1 +1,2 @@
+ coverage
++pkg
+diff --git a/Manifest.txt b/Manifest.txt
+index 641972d82c6d1b51122274ae8f6a0ecdfb56ee22..38bf80c54a526e76d74820a0f48606fe1ca7b1be 100644
+--- a/Manifest.txt
++++ b/Manifest.txt
+@@ -4,4 +4,31 @@ README.txt
+ Rakefile
+ bin/grit
+ lib/grit.rb
+-test/test_grit.rb
+\ No newline at end of file
++lib/grit/actor.rb
++lib/grit/blob.rb
++lib/grit/commit.rb
++lib/grit/errors.rb
++lib/grit/git.rb
++lib/grit/head.rb
++lib/grit/lazy.rb
++lib/grit/repo.rb
++lib/grit/tree.rb
++test/fixtures/blame
++test/fixtures/cat_file_blob
++test/fixtures/cat_file_blob_size
++test/fixtures/for_each_ref
++test/fixtures/ls_tree_a
++test/fixtures/ls_tree_b
++test/fixtures/rev_list
++test/fixtures/rev_list_single
++test/helper.rb
++test/profile.rb
++test/suite.rb
++test/test_actor.rb
++test/test_blob.rb
++test/test_commit.rb
++test/test_git.rb
++test/test_head.rb
++test/test_reality.rb
++test/test_repo.rb
++test/test_tree.rb
+diff --git a/README.txt b/README.txt
+index 8b1e02c0fb554eed2ce2ef737a68bb369d7527df..fca94f84afd7d749c62626011f972a509f6a5ac6 100644
+--- a/README.txt
++++ b/README.txt
+@@ -1,32 +1,185 @@
+ grit
+-    by FIX (your name)
+-    FIX (url)
++    by Tom Preston-Werner
++    grit.rubyforge.org
+ == DESCRIPTION:
++
++Grit is a Ruby library for extracting information from a git repository in and
++object oriented manner.
++
++== REQUIREMENTS:
++
++* git (http://git.or.cz) tested with 1.5.3.4
++
++== INSTALL:
++
++sudo gem install grit
++
++== USAGE:
++
++Grit gives you object model access to your git repository. Once you have
++created a repository object, you can traverse it to find parent commit(s),
++trees, blobs, etc.
++
++= Initialize a Repo object
++
++The first step is to create a GitPython.Repo object to represent your repo. I
++include the Grit module so reduce typing.
++
++  include Grit
++  repo = Repo.new("/Users/tom/dev/grit")
+   
+-FIX (describe your package)
++In the above example, the directory /Users/tom/dev/grit is my working
++repo and contains the .git directory. You can also initialize Grit with a 
++bare repo.
+-== FEATURES/PROBLEMS:
++  repo = Repo.new("/var/git/grit.git")
+   
+-* FIX (list of features or problems)
++= Getting a list of commits
+-== SYNOPSIS:
++From the Repo object, you can get a list of commits as an array of Commit
++objects.
+-  FIX (code sample of usage)
++  repo.commits
++  # => [#<GitPython.Commit "e80bbd2ce67651aa18e57fb0b43618ad4baf7750">,
++        #<GitPython.Commit "91169e1f5fa4de2eaea3f176461f5dc784796769">,
++        #<GitPython.Commit "038af8c329ef7c1bae4568b98bd5c58510465493">,
++        #<GitPython.Commit "40d3057d09a7a4d61059bca9dca5ae698de58cbe">,
++        #<GitPython.Commit "4ea50f4754937bf19461af58ce3b3d24c77311d9">]
++        
++Called without arguments, Repo#commits returns a list of up to ten commits
++reachable by the master branch (starting at the latest commit). You can ask
++for commits beginning at a different branch, commit, tag, etc.
+-== REQUIREMENTS:
++  repo.commits('mybranch')
++  repo.commits('40d3057d09a7a4d61059bca9dca5ae698de58cbe')
++  repo.commits('v0.1')
++  
++You can specify the maximum number of commits to return.
+-* FIX (list of requirements)
++  repo.commits('master', 100)
++  
++If you need paging, you can specify a number of commits to skip.
+-== INSTALL:
++  repo.commits('master', 10, 20)
++  
++The above will return commits 21-30 from the commit list.
++        
++= The Commit object
++
++Commit objects contain information about that commit.
++
++  head = repo.commits.first
++  
++  head.id
++  # => "e80bbd2ce67651aa18e57fb0b43618ad4baf7750"
++  
++  head.parents
++  # => [#<GitPython.Commit "91169e1f5fa4de2eaea3f176461f5dc784796769">]
++  
++  head.tree
++  # => #<GitPython.Tree "3536eb9abac69c3e4db583ad38f3d30f8db4771f">
++  
++  head.author
++  # => #<GitPython.Actor "Tom Preston-Werner <tom@mojombo.com>">
++  
++  head.authored_date
++  # => Wed Oct 24 22:02:31 -0700 2007
++  
++  head.committer
++  # => #<GitPython.Actor "Tom Preston-Werner <tom@mojombo.com>">
++  
++  head.committed_date
++  # => Wed Oct 24 22:02:31 -0700 2007
++  
++  head.message
++  # => "add Actor inspect"
++  
++You can traverse a commit's ancestry by chaining calls to #parents.
++
++  repo.commits.first.parents[0].parents[0].parents[0]
++  
++The above corresponds to master^^^ or master~3 in git parlance.
++
++= The Tree object
++
++A tree records pointers to the contents of a directory. Let's say you want
++the root tree of the latest commit on the master branch.
++
++  tree = repo.commits.first.tree
++  # => #<GitPython.Tree "3536eb9abac69c3e4db583ad38f3d30f8db4771f">
++  
++  tree.id
++  # => "3536eb9abac69c3e4db583ad38f3d30f8db4771f"
++  
++Once you have a tree, you can get the contents.
++
++  contents = tree.contents
++  # => [#<GitPython.Blob "4ebc8aea50e0a67e000ba29a30809d0a7b9b2666">,
++        #<GitPython.Blob "81d2c27608b352814cbe979a6acd678d30219678">,
++        #<GitPython.Tree "c3d07b0083f01a6e1ac969a0f32b8d06f20c62e5">,
++        #<GitPython.Tree "4d00fe177a8407dbbc64a24dbfc564762c0922d8">]
++
++This tree contains two Blob objects and two Tree objects. The trees are
++subdirectories and the blobs are files. Trees below the root have additional
++attributes.
++
++  contents.last.name
++  # => "lib"
++  
++  contents.last.mode
++  # => "040000"
++  
++There is a convenience method that allows you to get a named sub-object
++from a tree.
++
++  tree/"lib"
++  # => #<GitPython.Tree "e74893a3d8a25cbb1367cf241cc741bfd503c4b2">
++  
++You can also get a tree directly from the repo if you know its name.
++
++  repo.tree
++  # => #<GitPython.Tree "master">
++  
++  repo.tree("91169e1f5fa4de2eaea3f176461f5dc784796769")
++  # => #<GitPython.Tree "91169e1f5fa4de2eaea3f176461f5dc784796769">
++  
++= The Blob object
++
++A blob represents a file. Trees often contain blobs.
++
++  blob = tree.contents.first
++  # => #<GitPython.Blob "4ebc8aea50e0a67e000ba29a30809d0a7b9b2666">
++  
++A blob has certain attributes.
++
++  blob.id
++  # => "4ebc8aea50e0a67e000ba29a30809d0a7b9b2666"
++  
++  blob.name
++  # => "README.txt"
++  
++  blob.mode
++  # => "100644"
++  
++  blob.size
++  # => 7726
++  
++You can get the data of a blob as a string.
++
++  blob.data
++  # => "Grit is a library to ..."
++  
++You can also get a blob directly from the repo if you know its name.
+-* FIX (sudo gem install, anything else)
++  repo.blob("4ebc8aea50e0a67e000ba29a30809d0a7b9b2666")
++  # => #<GitPython.Blob "4ebc8aea50e0a67e000ba29a30809d0a7b9b2666">
+ == LICENSE:
+ (The MIT License)
+-Copyright (c) 2007 FIX
++Copyright (c) 2007 Tom Preston-Werner
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+diff --git a/Rakefile b/Rakefile
+index 5bfb62163af455ca54422fd0b2e723ba1021ad12..72fde8c9ca87a1c992ce992bab13c3c4f13cddb9 100644
+--- a/Rakefile
++++ b/Rakefile
+@@ -4,11 +4,11 @@ require './lib/grit.rb'
+ Hoe.new('grit', GitPython.VERSION) do |p|
+   p.rubyforge_name = 'grit'
+-  # p.author = 'FIX'
+-  # p.email = 'FIX'
+-  # p.summary = 'FIX'
+-  # p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
+-  # p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
++  p.author = 'Tom Preston-Werner'
++  p.email = 'tom@rubyisawesome.com'
++  p.summary = 'Object model interface to a git repo'
++  p.description = p.paragraphs_of('README.txt', 2..2).join("\n\n")
++  p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[2..-1].map { |u| u.strip }
+   p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
+ end
+diff --git a/lib/grit.rb b/lib/grit.rb
+index ae0792ae39d4891ebc1af996102a4f9df703394d..ae55fd7961ac49233f6ca515622a61e90d516044 100644
+--- a/lib/grit.rb
++++ b/lib/grit.rb
+@@ -1,4 +1,4 @@
+-$:.unshift File.dirname(__FILE__)     # For use/testing when no gem is installed
++$:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
+ # core
+@@ -12,6 +12,8 @@ require 'grit/head'
+ require 'grit/commit'
+ require 'grit/tree'
+ require 'grit/blob'
++require 'grit/actor'
++require 'grit/diff'
+ require 'grit/repo'
+ module Grit
+@@ -21,5 +23,5 @@ module Grit
+   
+   self.debug = false
+   
+-  VERSION = '1.0.0'
++  VERSION = '0.1.0'
+ end
+\ No newline at end of file
+diff --git a/lib/grit/actor.rb b/lib/grit/actor.rb
+new file mode 100644
+index 0000000000000000000000000000000000000000..f733bce6b57c0e5e353206e692b0e3105c2527f4
+--- /dev/null
++++ b/lib/grit/actor.rb
+@@ -0,0 +1,35 @@
++module Grit
++  
++  class Actor
++    attr_reader :name
++    attr_reader :email
++    
++    def initialize(name, email)
++      @name = name
++      @email = email
++    end
++    
++    # Create an Actor from a string.
++    #   +str+ is the string, which is expected to be in regular git format
++    #
++    # Format
++    #   John Doe <jdoe@example.com>
++    #
++    # Returns Actor
++    def self.from_string(str)
++      case str
++        when /<.+>/
++          m, name, email = *str.match(/(.*) <(.+?)>/)
++          return self.new(name, email)
++        else
++          return self.new(str, nil)
++      end
++    end
++    
++    # Pretty object inspection
++    def inspect
++      %Q{#<GitPython.Actor "#{@name} <#{@email}>">}
++    end
++  end # Actor
++  
++end # Grit
+\ No newline at end of file
+diff --git a/lib/grit/blob.rb b/lib/grit/blob.rb
+index c863646d4278bfee2a7bcb64caace6b31f89ef03..87d43fab37844afdc2f8814dba3abdaa791f1370 100644
+--- a/lib/grit/blob.rb
++++ b/lib/grit/blob.rb
+@@ -81,9 +81,9 @@ module Grit
+             c = commits[info[:id]]
+             unless c
+               c = Commit.create(repo, :id => info[:id],
+-                                      :author => info[:author],
++                                      :author => Actor.from_string(info[:author] + ' ' + info[:author_email]),
+                                       :authored_date => info[:author_date],
+-                                      :committer => info[:committer],
++                                      :committer => Actor.from_string(info[:committer] + ' ' + info[:committer_email]),
+                                       :committed_date => info[:committer_date],
+                                       :message => info[:summary])
+               commits[info[:id]] = c
+@@ -102,11 +102,6 @@ module Grit
+     def inspect
+       %Q{#<GitPython.Blob "#{@id}">}
+     end
+-    
+-    # private
+-    
+-    def self.read_
+-    end
+   end # Blob
+   
+ end # Grit
+\ No newline at end of file
+diff --git a/lib/grit/commit.rb b/lib/grit/commit.rb
+index c2a9e2f81657b19925fe9bab4bc5d7ac130e5880..cd9c3e3184c97e83a8982fab9499cad3aec339f6 100644
+--- a/lib/grit/commit.rb
++++ b/lib/grit/commit.rb
+@@ -136,6 +136,11 @@ module Grit
+       commits
+     end
+     
++    def self.diff(repo, id)
++      text = repo.git.diff({:full_index => true}, id)
++      Diff.list_from_string(repo, text)
++    end
++    
+     # Convert this Commit to a String which is just the SHA1 id
+     def to_s
+       @id
+@@ -153,7 +158,7 @@ module Grit
+     # Returns [String (actor name and email), Time (acted at time)]
+     def self.actor(line)
+       m, actor, epoch = *line.match(/^.+? (.*) (\d+) .*$/)
+-      [actor, Time.at(epoch.to_i)]
++      [Actor.from_string(actor), Time.at(epoch.to_i)]
+     end
+   end # Commit
+   
+diff --git a/lib/grit/git.rb b/lib/grit/git.rb
+index 1d5251d40fb65ac89184ec662a3e1b04d0c24861..98eeddda5ed2b0e215e21128112393bdc9bc9039 100644
+--- a/lib/grit/git.rb
++++ b/lib/grit/git.rb
+@@ -13,17 +13,6 @@ module Grit
+       self.git_dir = git_dir
+     end
+     
+-    # Converstion hash from Ruby style options to git command line
+-    # style options
+-    TRANSFORM = {:max_count => "--max-count=",
+-                 :skip => "--skip=",
+-                 :pretty => "--pretty=",
+-                 :sort => "--sort=",
+-                 :format => "--format=",
+-                 :since => "--since=",
+-                 :p => "-p",
+-                 :s => "-s"}
+-    
+     # Run the given git command with the specified arguments and return
+     # the result as a String
+     #   +cmd+ is the command
+@@ -52,12 +41,19 @@ module Grit
+     def transform_options(options)
+       args = []
+       options.keys.each do |opt|
+-        if TRANSFORM[opt]
++        if opt.to_s.size == 1
++          if options[opt] == true
++            args << "-#{opt}"
++          else
++            val = options.delete(opt)
++            args << "-#{opt.to_s} #{val}"
++          end
++        else
+           if options[opt] == true
+-            args << TRANSFORM[opt]
++            args << "--#{opt.to_s.gsub(/_/, '-')}"
+           else
+             val = options.delete(opt)
+-            args << TRANSFORM[opt] + val.to_s
++            args << "--#{opt.to_s.gsub(/_/, '-')}=#{val}"
+           end
+         end
+       end
+diff --git a/lib/grit/repo.rb b/lib/grit/repo.rb
+index 624991d07e240ae66ff2a0dc55e2f2b5e262c75b..63bf03b839374c96a3d42a07d56681a797f52a71 100644
+--- a/lib/grit/repo.rb
++++ b/lib/grit/repo.rb
+@@ -93,6 +93,17 @@ module Grit
+     def blob(id)
+       Blob.create(self, :id => id)
+     end
++
++    # The commit log for a treeish
++    #
++    # Returns GitPython.Commit[]
++    def log(commit = 'master', path = nil, options = {})
++      default_options = {:pretty => "raw"}
++      actual_options  = default_options.merge(options)
++      arg = path ? "#{commit} -- #{path}" : commit
++      commits = self.git.log(actual_options, arg)
++      Commit.list_from_string(self, commits)
++    end
+     
+     # The diff from commit +a+ to commit +b+, optionally restricted to the given file(s)
+     #   +a+ is the base commit
+@@ -121,4 +132,4 @@ module Grit
+     end
+   end # Repo
+   
+-end # Grit
+\ No newline at end of file
++end # Grit
+diff --git a/test/test_actor.rb b/test/test_actor.rb
+new file mode 100644
+index 0000000000000000000000000000000000000000..08391f12336831d048122c8d13bc8404f27e6b91
+--- /dev/null
++++ b/test/test_actor.rb
+@@ -0,0 +1,28 @@
++require File.dirname(__FILE__) + '/helper'
++
++class TestActor < Test::Unit::TestCase
++  def setup
++    
++  end
++  
++  # from_string
++  
++  def test_from_string_should_separate_name_and_email
++    a = Actor.from_string("Tom Werner <tom@example.com>")
++    assert_equal "Tom Werner", a.name
++    assert_equal "tom@example.com", a.email
++  end
++  
++  def test_from_string_should_handle_just_name
++    a = Actor.from_string("Tom Werner")
++    assert_equal "Tom Werner", a.name
++    assert_equal nil, a.email
++  end
++  
++  # inspect
++  
++  def test_inspect
++    a = Actor.from_string("Tom Werner <tom@example.com>")
++    assert_equal %Q{#<GitPython.Actor "Tom Werner <tom@example.com>">}, a.inspect
++  end
++end
+\ No newline at end of file
+diff --git a/test/test_blob.rb b/test/test_blob.rb
+index 6fa087d785661843034d03c7e0b917a8a80d5d8c..9ef84cc14266141b070771706b8aeebc3dfbef82 100644
+--- a/test/test_blob.rb
++++ b/test/test_blob.rb
+@@ -40,9 +40,11 @@ class TestBlob < Test::Unit::TestCase
+     c = b.first.first
+     c.expects(:__bake__).times(0)
+     assert_equal '634396b2f541a9f2d58b00be1a07f0c358b999b3', c.id
+-    assert_equal 'Tom Preston-Werner', c.author
++    assert_equal 'Tom Preston-Werner', c.author.name
++    assert_equal 'tom@mojombo.com', c.author.email
+     assert_equal Time.at(1191997100), c.authored_date
+-    assert_equal 'Tom Preston-Werner', c.committer
++    assert_equal 'Tom Preston-Werner', c.committer.name
++    assert_equal 'tom@mojombo.com', c.committer.email
+     assert_equal Time.at(1191997100), c.committed_date
+     assert_equal 'initial grit setup', c.message
+     # c.expects(:__bake__).times(1)
+diff --git a/test/test_commit.rb b/test/test_commit.rb
+index 3bd6af75deda05725900eb7fd06e8107df14c655..0936c90e5b29ede2b5214d6dc26d256a8c6646f4 100644
+--- a/test/test_commit.rb
++++ b/test/test_commit.rb
+@@ -10,9 +10,28 @@ class TestCommit < Test::Unit::TestCase
+   def test_bake
+     Git.any_instance.expects(:rev_list).returns(fixture('rev_list_single'))
+     @c = Commit.create(@r, :id => '4c8124ffcf4039d292442eeccabdeca5af5c5017')
+-    @c.author # cause bake-age
++    @c.author # bake
+     
+-    assert_equal "Tom Preston-Werner <tom@mojombo.com>", @c.author
++    assert_equal "Tom Preston-Werner", @c.author.name
++    assert_equal "tom@mojombo.com", @c.author.email
++  end
++  
++  # diff
++  
++  def test_diff
++    Git.any_instance.expects(:diff).returns(fixture('diff_p'))
++    diffs = Commit.diff(@r, 'master')
++    
++    assert_equal 15, diffs.size
++    
++    assert_equal '.gitignore', diffs.first.a_path
++    assert_equal '.gitignore', diffs.first.b_path
++    assert_equal '4ebc8ae', diffs.first.a_commit
++    assert_equal '2dd0253', diffs.first.b_commit
++    assert_equal '100644', diffs.first.mode
++    assert_equal false, diffs.first.new_file
++    assert_equal false, diffs.first.deleted_file
++    assert_equal "--- a/.gitignore\n+++ b/.gitignore\n@@ -1 +1,2 @@\n coverage\n+pkg", diffs.first.diff
+   end
+   
+   # to_s
+diff --git a/test/test_git.rb b/test/test_git.rb
+index e615a035d096b6cbc984e2f4213c06d0ac785321..72a18ec424f078f6daee75dbc62265c02ba7a892 100644
+--- a/test/test_git.rb
++++ b/test/test_git.rb
+@@ -10,6 +10,12 @@ class TestGit < Test::Unit::TestCase
+   end
+   
+   def test_transform_options
++    assert_equal ["-s"], @git.transform_options({:s => true})
++    assert_equal ["-s 5"], @git.transform_options({:s => 5})
++    
++    assert_equal ["--max-count"], @git.transform_options({:max_count => true})
+     assert_equal ["--max-count=5"], @git.transform_options({:max_count => 5})
++    
++    assert_equal ["-t", "-s"], @git.transform_options({:s => true, :t => true})
+   end
+ end
+\ No newline at end of file
+diff --git a/test/test_repo.rb b/test/test_repo.rb
+index d53476a51e3286be270c7b515ec1d65e5c1716e0..114a4464fa248550be10cc4abe0735d6025b5fca 100644
+--- a/test/test_repo.rb
++++ b/test/test_repo.rb
+@@ -59,9 +59,11 @@ class TestRepo < Test::Unit::TestCase
+     assert_equal '4c8124ffcf4039d292442eeccabdeca5af5c5017', c.id
+     assert_equal ["634396b2f541a9f2d58b00be1a07f0c358b999b3"], c.parents.map { |p| p.id }
+     assert_equal "672eca9b7f9e09c22dcb128c283e8c3c8d7697a4", c.tree.id
+-    assert_equal "Tom Preston-Werner <tom@mojombo.com>", c.author
++    assert_equal "Tom Preston-Werner", c.author.name
++    assert_equal "tom@mojombo.com", c.author.email
+     assert_equal Time.at(1191999972), c.authored_date
+-    assert_equal "Tom Preston-Werner <tom@mojombo.com>", c.committer
++    assert_equal "Tom Preston-Werner", c.committer.name
++    assert_equal "tom@mojombo.com", c.committer.email
+     assert_equal Time.at(1191999972), c.committed_date
+     assert_equal "implement Grit#heads", c.message
+     
+@@ -125,4 +127,18 @@ class TestRepo < Test::Unit::TestCase
+   def test_inspect
+     assert_equal %Q{#<GitPython.Repo "#{File.expand_path(GRIT_REPO)}/.git">}, @r.inspect
+   end
+-end
+\ No newline at end of file
++
++  # log
++
++  def test_log
++    Git.any_instance.expects(:log).times(2).with({:pretty => 'raw'}, 'master').returns(fixture('rev_list'))
++
++    assert_equal '4c8124ffcf4039d292442eeccabdeca5af5c5017', @r.log.first.id
++    assert_equal 'ab25fd8483882c3bda8a458ad2965d2248654335', @r.log.last.id
++  end
++
++  def test_log_with_path_and_options
++    Git.any_instance.expects(:log).with({:pretty => 'raw', :max_count => 1}, 'master -- file.rb').returns(fixture('rev_list'))
++    @r.log('master', 'file.rb', :max_count => 1)
++  end
++end
diff --git a/tests/fixtures/for_each_ref b/tests/fixtures/for_each_ref
new file mode 100644 (file)
index 0000000..e56f526
Binary files /dev/null and b/tests/fixtures/for_each_ref differ
diff --git a/tests/fixtures/for_each_ref_tags b/tests/fixtures/for_each_ref_tags
new file mode 100644 (file)
index 0000000..c4df85c
Binary files /dev/null and b/tests/fixtures/for_each_ref_tags differ
diff --git a/tests/fixtures/ls_tree_a b/tests/fixtures/ls_tree_a
new file mode 100644 (file)
index 0000000..69b76f4
--- /dev/null
@@ -0,0 +1,7 @@
+100644 blob 81d2c27608b352814cbe979a6acd678d30219678   History.txt
+100644 blob 641972d82c6d1b51122274ae8f6a0ecdfb56ee22   Manifest.txt
+100644 blob 8b1e02c0fb554eed2ce2ef737a68bb369d7527df   README.txt
+100644 blob 735d7338b7cb208563aa282f0376c5c4049453a7   Rakefile
+040000 tree c3d07b0083f01a6e1ac969a0f32b8d06f20c62e5   bin
+040000 tree aa06ba24b4e3f463b3c4a85469d0fb9e5b421cf8   lib
+040000 tree 650fa3f0c17f1edb4ae53d8dcca4ac59d86e6c44   test
diff --git a/tests/fixtures/ls_tree_b b/tests/fixtures/ls_tree_b
new file mode 100644 (file)
index 0000000..329aff3
--- /dev/null
@@ -0,0 +1,2 @@
+100644 blob aa94e396335d2957ca92606f909e53e7beaf3fbb   grit.rb
+040000 tree 34868e6e7384cb5ee51c543a8187fdff2675b5a7   grit
diff --git a/tests/fixtures/ls_tree_commit b/tests/fixtures/ls_tree_commit
new file mode 100644 (file)
index 0000000..d97aca0
--- /dev/null
@@ -0,0 +1,3 @@
+040000 tree 2afb47bcedf21663580d5e6d2f406f08f3f65f19   foo
+160000 commit d35b34c6e931b9da8f6941007a92c9c9a9b0141a bar
+040000 tree f623ee576a09ca491c4a27e48c0dfe04be5f4a2e   baz
diff --git a/tests/fixtures/rev_list b/tests/fixtures/rev_list
new file mode 100644 (file)
index 0000000..95a1ebf
--- /dev/null
@@ -0,0 +1,24 @@
+commit 4c8124ffcf4039d292442eeccabdeca5af5c5017
+tree 672eca9b7f9e09c22dcb128c283e8c3c8d7697a4
+parent 634396b2f541a9f2d58b00be1a07f0c358b999b3
+author Tom Preston-Werner <tom@mojombo.com> 1191999972 -0700
+committer Tom Preston-Werner <tom@mojombo.com> 1191999972 -0700
+
+    implement Grit#heads
+
+commit 634396b2f541a9f2d58b00be1a07f0c358b999b3
+tree b35b4bf642d667fdd613eebcfe4e17efd420fb8a
+author Tom Preston-Werner <tom@mojombo.com> 1191997100 -0700
+committer Tom Preston-Werner <tom@mojombo.com> 1191997100 -0700
+
+    initial grit setup
+
+commit ab25fd8483882c3bda8a458ad2965d2248654335
+tree c20b5ec543bde1e43a931449b196052c06ed8acc
+parent 6e64c55896aabb9a7d8e9f8f296f426d21a78c2c
+parent 7f874954efb9ba35210445be456c74e037ba6af2
+author Tom Preston-Werner <tom@mojombo.com> 1182645538 -0700
+committer Tom Preston-Werner <tom@mojombo.com> 1182645538 -0700
+
+    Merge branch 'site'
+    Some other stuff
diff --git a/tests/fixtures/rev_list_commit_diffs b/tests/fixtures/rev_list_commit_diffs
new file mode 100644 (file)
index 0000000..20397e2
--- /dev/null
@@ -0,0 +1,8 @@
+commit 91169e1f5fa4de2eaea3f176461f5dc784796769
+tree 802ed53edbf6f02ad664af3f7e5900f514024b2f
+parent 038af8c329ef7c1bae4568b98bd5c58510465493
+author Tom Preston-Werner <tom@mojombo.com> 1193200199 -0700
+committer Tom Preston-Werner <tom@mojombo.com> 1193200199 -0700
+
+    fix some initialization warnings
+
diff --git a/tests/fixtures/rev_list_commit_idabbrev b/tests/fixtures/rev_list_commit_idabbrev
new file mode 100644 (file)
index 0000000..9385ba7
--- /dev/null
@@ -0,0 +1,8 @@
+commit 80f136f500dfdb8c3e8abf4ae716f875f0a1b57f
+tree 3fffd0fce0655433c945e6bdc5e9f338b087b211
+parent 44f82e5ac93ba322161019dce44b78c5bd1fdce2
+author tom <tom@taco.(none)> 1195608462 -0800
+committer tom <tom@taco.(none)> 1195608462 -0800
+
+    fix tests on other machines
+
diff --git a/tests/fixtures/rev_list_commit_stats b/tests/fixtures/rev_list_commit_stats
new file mode 100644 (file)
index 0000000..60aa8cf
--- /dev/null
@@ -0,0 +1,7 @@
+commit 634396b2f541a9f2d58b00be1a07f0c358b999b3
+tree b35b4bf642d667fdd613eebcfe4e17efd420fb8a
+author Tom Preston-Werner <tom@mojombo.com> 1191997100 -0700
+committer Tom Preston-Werner <tom@mojombo.com> 1191997100 -0700
+
+    initial grit setup
+
diff --git a/tests/fixtures/rev_list_count b/tests/fixtures/rev_list_count
new file mode 100644 (file)
index 0000000..a802c13
--- /dev/null
@@ -0,0 +1,655 @@
+72223ed47d7792924083f1966e550694a0259d36
+f7cd338ee316482c478805aa8b636a33df3e4299
+994566139b90fffdc449c3f1104f42626e90f89f
+e34590b7a2d186b3bb9a1170d02d52b36c791c78
+8977833d74f8681aa0d9a5e84b0dd3d81519774d
+6f5561530cb3a94e4c86454e84732197325be172
+ee419e04a961543444be6db66aef52e6e37936d6
+d845de9d438e1a249a0c2fcb778e8ea3b7e06cef
+0bba4a6c10060405a94d52533af2f9bdacd4f29c
+77711c0722964ead965e0ba2ee9ed4a03cb3d292
+501d23cac6dd911511f15d091ee031a15b90ebde
+07c9bd0abcd47cf9ca68af5d2403e28de33154f1
+103ca320fc8bd48fed16e074df6ace6562bed4b5
+55544624fb9be54a3b9f9e2ec85ef59e08bd0376
+e5c8246dec64eccad0c095c67f5a8bbea7f11aca
+1b54d9f82ee6f3f2129294c85fad910178bef185
+36062a1634fb25de2c4b8f6b406ae3643805baf5
+0896bb9b8d2217163e78b5f1f75022a330d9ddc8
+6646dfce607b043ab7bbe36e51321422673b7c56
+f0bad592abc255fabe6c6d6c62b604b3de5cdce2
+5705e15c71f9e10ca617c0a234b37609cfb74d23
+b006d8b73912eb028355c49e7bfe53a29f97ce7c
+b21eb6173dbe07cac63f4571e353188dde46f049
+a3256f3150ccec73c50b61b85d54e30e39a65848
+c5a32e937166d65757f3dd4c1b6fd4f5ecc10970
+1e90e2c654aab0d81088f615c090d6d46f03ca4c
+924e7685fcd62d83aac19480837e4edd9c4bae5e
+489e1468aea658a333481132957069708127c69f
+970b6b13726889f6883e4347e34d8f9d66deb7c9
+df74c45e8fdb4d29a7de265ac08d0bff76b78a83
+936aa0b946155b2d47528073433fc08b17a4c7cc
+3b6a5e8f12b6269a0a3e0eaeede81abfb3fc4896
+8e0f306dae96d78aa1ea9a08e82647fd95fc1a74
+5eb099e5e274be44c0fd27ce8928d2dc8483dab7
+050fbed693d4806ac6c03460103777b2a4befcf8
+c5d4b6dac74e323d474fa8878a7ea0c233d57019
+8e5daf911943d5ef025025c137fcf97164467141
+bcdf7c2421302b15f4ee4ebbdeae7b644a4518e7
+e2874a42835cbb2fe8856a398f5c4b49a9cd8d30
+f50ea97159e4ae7132e057fbf5ea1e75ec961282
+5dbd614c20e9473240082239894d99c24de42282
+0490e1ac1ffafcb9117029286b224ab39671a015
+ad3620d47f0ea96f24904227d3c7a7f9548c34dd
+fd37e7191ae3d312ced0877a1492cd2ea4578275
+b7f8cc51c9056a469006b5601a4993b67c07e099
+1d849af5083073b8864487938a9a2a8e21d71529
+26d0bb4c9ee3d8591fe291c86f495b2d1900bf9b
+7a25e3056a7225c1ff8542c2c2c1cf6f3a8e13d4
+d0e0de0b13b9c81d2bcf9d54eecdb20591fd6d2f
+0bf82343ade1e07c0aebd14ee66df688a4cc0e87
+d81de0fb6a19342a90cdba9a664659da66296162
+9105667175797bbadea80879e98a5cf849a40065
+12f5af2a169c658cfae1677ceafd527d3775873f
+00ae94689600b5949bd0fcf68205f31f95a36aa4
+8f5d34224e4620c51c16c01578786e76567d025d
+3385eb31651c84384b4c7e93d82bc5b592edf9fb
+eda9179b9af0275d62c4074480e7a0103d356435
+982c2d1e55165fddb4f4c69065e2c4ac39542c84
+7117495ef012719769582939ea59a5533077fc8f
+b7dae75dab5b59a320b8df8a67060d238fed3a8e
+37c684e1a46599fe4d34d1601875685a70b1b879
+0a694fa0cb044a31bb844564776b490c151ac317
+e77c6b459f01ce078aa59183189226a6d48fdf38
+dd0c0eaefdebc38610bb1986e51324a0392e829a
+d8bc2414e9504172da640f29db1b2d29d834a94b
+a9f1119667dd0f5aa9413dec23965a747d1dac05
+f52775f6bc21d999382f4b9b38b108b814114ea1
+e82c77ac679887140867e471a9f47fd3b2d95039
+2db3fff5673bbd4bfcc8139d8398727d243c9efd
+c1805c000c6233a20ac0824cad21c1fe40f93100
+83f7807585cf70018a9a06179af9d89d4a8204b2
+730c326beb29cc6d2624915b125633792a40ca36
+bea422b653d74dd03ec068dcce938169149aa822
+586a57666618299464519c450033eecc3ce89664
+82fba8cf4796f2adbec5ad336bd750ad60a075fd
+9d9b899f836a199fe854075e99091d1ef083de24
+4670357c662596aa2c2922d826de84abd9f877ea
+9b562567430544c74009ea4a6173f44ddb4a44e5
+013d51fbb5f3a60bc748449b1ab73158da9a3203
+3fe67cb90fca9ea76292deb793cb480f4eb5e8d6
+91c80e489fee08e71a79bfbea79fcc28e1aa27f2
+dd9104095bdb08fe399af46d91b334e760986ddd
+a9198904586546a038f855bc6fc0e7cc413722fb
+574a7bad1017d9ed466474881e1f068f892207f4
+f95acec9297b7816284d8b24e984cd5c82104c89
+3907dac65a125b7759172a8eae959b0e70220299
+e5b44576eb2182b16c7b6770fab5977eedbc03c6
+9f4aad9833d0f9a609dd2556e7db784ba813d8fa
+579309c96651a1fed75fdd18f80019db8e6624ec
+5e1a9a48e6c96099d6a0c3aff1e31c9be16b7b8d
+cae4b811038f4e0dd4a8e68122c3db955e10ae81
+fccee1c818f5af5fce593de0949f5a8ecd35b443
+d4187d5a5f9ffe1f882c74f6ced7e0ba1c260ff2
+02ff197aa41d892e623dc668b0055806294bd6c0
+3f81af24214761a6ed77fd4dcd6e45a651dd8f84
+5cb08c5232a669a881606a6d8c4a4cd23aad6755
+5212b25869e0b9aff485af6f5371a159e89f8f07
+a778322bb60f8438a68112a73df78e05a97093ff
+b55c30c3992a766628dfc4a7e22db4d8d9e46b5f
+1d3e4a32e0407f16f641be316c745c1a48f16e2b
+7f35ca3333944165e0ec82a3a95c185f67fba904
+ef6c5bbe2dffe76e4a9698df03b8ee08af702033
+aeb90405ed696c1efcb29d0664b43a52a2bf824f
+e0b8bebd78172229666dfd425933f9bc21863928
+2a71a55154edf75ab51dcb4f2f7dc63592410e16
+a5d25352d326c77d083a2e089c2d80b4ea923624
+a3fbc38b9f1b86bb5f5e6540048083fc1dc6062b
+bbe67e1bdadf4aff186769145a40727f78e39e01
+a02a58c6c6d04001873ba91ac3dc902275879d0f
+eb5281d4f40e18b0e21d275ee5c5964bbbcc855c
+19e9939a098b9cb93c8c1d0d069d46861afb507a
+7a72471f9a4587cc4a7d37da0d26122b0eadaddc
+c6a043eb057cd544130b77bf99f39b7738e0a204
+723b6223726c6772e034d9f4ba5c710e66a1991f
+25b4ff1a26dd3694a98c1ef2eba04a5a500c0b28
+7c571ac2c35a7e1f399651242e904596c93beeb0
+0c90015733521720688bfcb59ad2a3978b2fbbc3
+d6b99183122a97a590e4e54f4617b58f13b90df8
+6b663271af39d69082422866e61ff7801c2b3fa7
+2e9e6ab7651e4c215110eb381678e0ea2bc0f7d8
+967b91e045661c9b6d2a5f011ec152da391db7ec
+7fda8d15bdb3d3d61fce49413153a216849721f9
+f7d7e83ee1cec103a768ddc9f68b6d5075849894
+925953da542a9c21a3fde1ab0891175fb6212a12
+ea2f54455427506583437391cbaf470a1ef4edeb
+f0bdb2cdddefb3a391ec2e3fa9b78ed06d7c874a
+8d289566fa92a96a83ff3c2e24c1f3d12b1718ed
+7fb102615532952c6833e87389668831b37a13d6
+7f7bbe8473158ab606a89ad66d607ffd0e5ba1f7
+a98ea5a00d19406f3e644448039f13db496cefd5
+39f03072d9d84d622ae974b09dd11cf7a2515a7c
+e2050a1c488fff4b114614d7f75096dd0a149f5b
+d2851f113530fbe211b3e948b6181152d30d1fa3
+1eef0fe740f6db35a91e790fe77d4ba1c9065e99
+9608403b012908cc58223db44962553704cab8af
+4911a005ea6b55f34f8b0f504a6a0934c0df896a
+a4400fb8e7d0f1261634dbb89588da86b8b6c93f
+f310729583f6733ee60f534a9732b7a3a9e414d4
+49e78793487ce4d8d7e624b5245fca8a9cc1ba66
+2f2501ce5d28e5ada6018504ee8dcecbbee70428
+f1e127253e1eb07b537b221e9cc96beb16333790
+8bf1684ca9b5a37d91671dd0d63d0ac59bea987a
+24838a6042a134b11fe945bbaa5ab1b2b3fc6eb0
+f53c57af21fded3735fd479b3785fcf7adf80268
+aa8d0a63d61d13524b1395972563b516cd263f05
+16803d64332412a66121ef3fd10cd0d88598d3be
+5f2715ed4d9416fa4940c2cd29b5ca18b6a79b8c
+851ede1f8dceef7d681f35e4769e5693160c0a04
+5264588c6c20c38d54394059eef0a854683aa3fc
+111800d8e66ff86f0757df7eb6533fc62040a22f
+b04de89d31003e468c191cd08dd2a4629d99c38e
+6aef629094e9ee6b4fac2431897844c4dddf2f57
+d1168c999fdae7d1eaac8c163b2b1190afb1815c
+6afc3257929528d9f4de964e8828822d2fa2c93a
+436f30ce1b562efe4f34696def45b0145eb98304
+9afbf904be0e6154f6c424377ad596e86ea38807
+a3cf657305d9283525711e867e03684a2e4b39cc
+5813b4d04b25c385359af4437027b4fe763cd2ba
+0fa594594c97a0d3579312f4ec073304c1436106
+cb7b36c28adb38b1e597fa3f3b5c24c988a25b0e
+5b0c867cbda81ce34df1b5fb67557b556ea24e9e
+44090e9c550c7c5ded01dc2a681a7c934ba901b6
+9ccc89b61736c4a9c02faaa679e97a9ec063dd29
+7828d6d18115b0720888a45e3b547b697910c59e
+618497e48e46fdc00dee67c07cd0f40860e819f9
+69a14ed4f36d880e8322a530d8c5bfd9888a8c13
+0a0cd655e40903abff4840c23b57628fb1a88122
+cb262098646f47e1d80a89662f1480c216bfd81b
+d60e59fce6f698a8bb97e2b4a935c069584621b1
+ca77ba0d6d301cee1d45edb24742dc5cdabd4b83
+17b598510967922690f5181903f20ddae5758e86
+30ad3d9f3164966afb2974640f772387fb796b7d
+48964c5dcc94234dea1737d7fa23220f9eab0fb7
+0fe241f4db12f455c2f5976c6bf6497cc485f503
+04953aca41bd372d990da7f68cc795f4a8b78d94
+2dc9a061595a291d8c53168c42da8d14da52d610
+68b15d34903038e3f2e327f03f0486b2d38784bc
+30ceaaf39b10f9f9c7b4362505144d1650035a40
+e75891a5760f6a51f54a33b671951c16fbce1558
+b2a35989ad3392f26e070b158f89d1d8b75327f2
+8468830b8b37f7c1cdda926a966c0aba2893a7c0
+6a6112e8cde1bafebfa12e4c863dab5834c38e12
+eafcd2ffc25d17fce41eff2afd5c4521730a23ab
+f7eda0752f45c3a4eb13e075b24b86d7e7dd5016
+b634d0d48d0a113bc060a47972b10c9353621428
+49f95235a174f0a056e44bb5c704fea4ab345353
+6eec70a31a6376ffd7d6b143be0968a195ad59d6
+7c9ae1a71aa39efe28a678c18c8a03d759deabed
+a19fd6f13c16720dc38a1f572eebf960022015ad
+87052ac2cbaec195094e1d1a2bad4ac480bd111e
+2cde1b0e69f97a8a67bb47d729c53af3ba8e5700
+91a06d1a4efb6376959c3b444a536fe6b4fd4d6b
+07f73b465b6c509b337c2776fe7a73b56ee178ec
+15218bab55236d62fb8b911c2ae1ee05dde1ee60
+900180ff2aa70e7d857403412753df6384553d26
+a9c43cbeb0542cf6538fe8025edc8863d2526c68
+d7d8f0c9b7d56f554d5a5cf5759f30cc3d36771c
+d703e5d9ac82b8601b8f4bfe402567b5ce3ebbf9
+3905a12ad511ffe157cb893e7769f79335e64181
+73a933454b09ee48ffc873b0ee767e0229f2d609
+c2c91403aa9d95efa09843bffe83ace4d52d3446
+c90f480010097efa3fb7046abe7fac3c9b8b3975
+13e888d5624e8087ea63638de7f4375f5c13ac55
+19344e551c8c5e56e91de14253b4f08ca05f9e69
+b1b8f098bb1e2f0f08cf82805d7bd29d2931f63e
+3a3e025bbb2f3e49facac00e270d8afa5d31b962
+195116405307f7bd9575f9621fd93344304341d1
+31252094210748399f7e43e7b6149190990f4e8c
+357e549bf43126e371a1f85c393d2560597cb32d
+df1f8ab23f915420e9c48744decbc45375f180d3
+f96c2eedf6800b8fc31032a02caf0d2b469ba9ec
+73405f0505813ec1bd25f03f2825315f3520bcca
+7e2447536c35ae67e3737a031fa1ac57026475a0
+970d4c4854dbcc3b0bf9b16edf1d47eabf3be242
+3c73519e6b54d3559555ffac40072957041f62d4
+46d461676fc1fb16fd7dee027065441d9a8b87d5
+f11f64bb55240dcc1767a1ec823aecd3531f1d20
+038e91a424078c5d81cba6c820cd981f0be6086b
+157d6e98ba894cba5582caeb15b429ca0dcbf2d9
+2c768cf9d1bdb6d3d84f662a847966b69c898f59
+4fd0f29459ec3ea65625b943b147df85e5826cd9
+c7e90c64e580ce5f95147eb4e117b56b5cda254b
+cd4f2496b274b0d55b7c48388c2ec0365d9bc266
+68b5e288a29ebbcd65e6d0a8eed47702ee4e689c
+22abd4a7ed7b061364e002f1fe08857850a309ad
+4c3b38be6fda8ba32fe6f29226539e03bd0c55ce
+355e946ca8b8a5e4c17317446b12fc374399810a
+1fc5c0122fffdada1630febc1f2e42952cdc7e2e
+8db042e1faef7be24d62b9287fd3b9add7a1b4cb
+1cbea023ce354939ae9082a62810b46f38ab1cd8
+f5edf5b99d1bae09314b9680e58766a4e3c1bbc0
+58a5ef79958b58736603f47cf211494fe5819601
+8f2038bec169ae6d62885f522202d8171e3f5f5c
+5488e29e68684648b4d733e90c6e3188d3bd5bad
+84c88e813117db46c6ac68b16a7739018eb99e24
+789c3655197585ba8771ce68c0117cbdd41ea390
+0510404a3c0d337763e90e5315548043bac65b06
+2a665d7c6cab59ea8e3bb7fc65249ee947e51fec
+d53423de534d3b5e68a7644d4218d835a8bfe6ce
+73f2a3f332f23579a29e090f70825dcf84dcdbac
+f79ae7f27e750c97c139cdbdd7c3223b39ed1a70
+c84a75f7a4b274c5c133b1df3648a5a24ed9f687
+cdf8e5a49192b81bcd39d9f4e39aa4812b58b80c
+1180461f564674e373222fec3b4fe8c2861ea6a6
+150d93bd910597b85500e74b97b96e7eb4bce2f6
+ec3b819ffe3392bf193483fea94d4404c88966b1
+729fc8ffd38c02a9576640b56376c36b49edf52e
+2ee31128fbd86244d547e3ff66b802dda699210f
+f87f28c563ad602cba605e84bee95693b77b8840
+9e92c5fb59af58867acf5512e95138fc368f7dae
+76b1489042e1bb45909832f7064f9a5437b68b18
+66f5d86face564c095b3c95848f070f50fe4688a
+f9b2b3ec52b88dbd68b2f2c6b246bf07f632b40c
+14f689f05c4fae52ac8bb95762ff43b9f7f4e567
+5ca84af5f7a3f4533b353c43a332b552cb2fc5e4
+c5f33e9eb55201c41691e14fff0d45e32c989a42
+9f83cf471949164a6352cb9e3a201b8bb317b89a
+5532c7b06a2f02e9cafd6673d5099798c4144690
+0d28c20ab4f03b5d8579132048c060affc36c466
+cddce1dfd9d4d7f1fc49003aa211f018bf8fad2a
+169617e3672bac804a271c0aaff9cdbac7b4b45e
+fdebb28d6ae398ccba88f3e2e63ef6d7f10f62f4
+0651bfe384a8d5865d6cab808ca0ce803af93878
+de89eb007459fd5400cd344dddf240fc33fd0b65
+c6a14beb887170d8c901e522f2f4dce3bf0b9ed8
+13dd0647b3ee39fae1140f8eff2b15d7f63ee546
+9f89105c1462f2a80e620ada1b95c3d08a121c3e
+1ed6496751273cf472538779266dcc3dc9797192
+4e8dffa66fc7be8f864cb48cf26abcef4cc32379
+5543fce145ff28a1c424b730b376fd4e3cfa0956
+bd951a4a8574baac21b7e1f3a09d1265aa51850a
+3fd1c12fa880ee45b0ff7b794238a8894306a790
+830ec14bc9edbd2c6522ff46ed0acfe477e7e32a
+e68c3109a709e2b732d0945f860495de161754a2
+1e0f4fda735167ff6d27c76a67b8b4a4ab31aaf6
+c6c40dd0ff4420708c2e0f5a0e0dadde93eae336
+baf0c18ac24acb9ac3d1a7c0030ad5675eeb64d7
+8d30906e9f2f68024eb716be9f482de5cec5b302
+ec9fce551828795e1dace26a11f57f9aaf1af37a
+28fb918d7e9840a7118b7aa0b6151b496f1fc1f0
+b9e58c5b98f7c89054ed5c0a0226066ba9d93c8d
+0c5db457cdd3852182ce70b96cb376337b8ad7ad
+36a48168274cbb6f31c35777a74ee16c06e1a853
+07ef3ad40bb01bc7798b241c88fda2eaba7aad19
+02aa9f2ba871e9639891986a97618e0917955fc8
+5f776d3c74ad532f36ab75a71bcbece6a62c831d
+f31ea9eeea91106481e1b2d30026b601555b6699
+c3d7f6bac18fbf8041662fbdda4f04e3f3b25e3a
+6280c4bcf1195c011d7a7abb5bf689df11d66419
+45fc4ef9adaf514bbe21f496cdea8869a147c81b
+fa1160786e34c057cf1212efd59a72c3931eb2a3
+09b285cc7d7c8768917c7d4e5513e3e73d752b68
+a8da5db6094c887f1087162c5ddfddf601560523
+b6134a31d236c376193e969a2df65c8427d280a0
+793e0d19fef38f8a151622257b13edf6786e469e
+e40e6a17b4df5be46a2cafaa3fca5f4c3cec5001
+4d82e160cb874da6dbddc27af7dfd1036772b8f6
+745ee8e3e74dc0674dd8018999707f025a9634f5
+f507baf298549096f08dc33de22f7301e9799814
+bd7ebd663da867692f2316b94db73c42c0f9a5d1
+697f07726d209cac519b528018559f8957c56069
+2297b5172c0c1c83f2d78fc726fac0803be6eeb9
+91e3543f82039a446c5be8293d5a79ec767d1444
+e997169214440256b5b759f6e7e255a302838c97
+77d174ae14afbc6e212eb7d957b11a231a036d96
+3e81ee29892006f16d5f1f26d9d6b341a8958fb1
+59957e1d84f8fe8117d9697154c3951ba2959480
+96c6fa03962edb98a9b6aa7793be4ff54e79bfd5
+068a293fd6b4fcf216fb84ca982699095613af37
+b3b1804ffad1b7d274bc3f8f5aa11b15049ac030
+63e394c13a50de0d9f6cef55a8c91830200c3dac
+e7ed33eba96d590bbc7179fd26db707c910d1dc5
+6b2084340a988f4123e71c6e30817806ec4cf3a3
+da721d3f48f821faa90d1a4778d77b03fd3dcdeb
+a433cb8d56a4fcd50bfc74b0204c916e08c9d5e6
+067fae6fd778d5b1d6b6436aedc0d25db58334d1
+e34c192a5aef80c7e83c78c2372602830671ca5a
+861a44dc56a983262caebf909be96c62254930cf
+417ed493a824863e30922deda64b9729b1c6d6e7
+2df6a0d803ac21f0d20ae9fce0a970b35b3663ec
+44bedcfc59292d3ff6b36759b324812fcb779b2f
+c620f7e60c8ce4ddee8fc1072b2a161fee862545
+82ce5a39b422aec7572d9a773f85be8eaecb1618
+dc0ea6defad83a0569896a9c23f11f5052a48107
+e1c15f1da71a3aabdd43a8ad669d2a755f315c77
+c78ee1aaa5c499019948c9a3dfca3aaa2f897860
+e66d0d34c541c6588da3ae06c6aff7e7c9ef5745
+c24d513d46b3db5b4c53b36b7e43ce5fdfd5a2e5
+a75d0a4bf6d2e1a9c4026586cb707f254691eb3b
+41e98ebf4526e78d78ab16182b503f237e77fbd7
+2182dce8c27c33f9452e7c910f59750d1e58b1e3
+6b7aa9fdbdd0160ec29b6b3b591169c627fd0f01
+b39470063e41ee5773f47de325a845666d0721ce
+c7941bdc8822ae1842d2a2f42924f31d2d37e864
+fad6e836009429e88c788ab7e7a679d422d8cae1
+a478917ebcf70c5dd6f56c7cd139832108696189
+4101b1ae3b17e229c1a80d9c302b74d215d98f04
+b051ec4e69a99e26d6a6e5d7a393014f841eed6a
+5298ce551a104605b7d5d9872387f3eb704fe5e9
+b14a12bed26d53eaccd1a2c172ca4a38773e1d45
+4ae0790397d05a758013e0496ba2c2b23363361f
+431f01bf3aea6f8baaf06669172561a3ec9e82db
+12476263aa193c7e921ad4b183fc648bd73d2a1e
+8e937050fb12a62e99b0cf685578213552774cc4
+b85a487787454f6dac84be59f905b8c929f0ee94
+dbb2116e0f03fe6d84d2158d67ddd02761938bda
+57186ad57242ad0bcd737c4ca4ecb7c063979a95
+cdb4a295593cb3ad424b4ab86d74154d7bbb97bd
+8e9e1ae0edb776f0a490005b838f8ef82b368be7
+73f8f21a69a03cdc2b1031bca214a6b84f4c867c
+b913c6d878ac5cf570e6f8ba9b5ff022ed601a8b
+b98879530fd51f328441d33c64c6c5f311097e15
+325b21b5370a0c179b40fd596b9daea00b3615c3
+6e722a5c5393dda24172de6f8e08138bcbfb10b8
+44b396caab82c97a6270eb7391d6f96502c9fcf9
+4e6ed6d22079b68551bbb83e5dd797517796a438
+b611fe79daa20893683475cc459dff98b2d4892b
+017d40f9b23f4a4379c74ceeb89ce7b4bccb7460
+a31b0a7fc7190218136d6ff6ffb3ee6af3244135
+861bd42abb90a61ed757728e1fef7cee2d6aa081
+6e9ff586de744d166a9f6f213b609e7386692472
+a790ca7384982e872092766c036d6faa86bff71a
+13485c50ca4dfd885d516154421bdb23cb034230
+c5471e696f3166942a245e77796bcddafe6a607c
+600308daf62d0d651fcfc874110e7bd4f5de648a
+bada607744ec7f37ba9d05c09bb8f41e7fc3d06a
+d3b230b209fd7c3f4a39db965b239ab600fac1fe
+6d730b7ae0b662b1f987101e8ccf9c1828554d69
+f0757668fcd3f8d1f2fe83ce9f0e2355b6be75f9
+40819d9a5631a184a17d38e36240d1171a6fc923
+8a6847ca68ec998df0543c4b5bd5c709c05d5f12
+d8eb0646ae1360b5b984ba7d99bc64e00dd67016
+761bf1cc1e2b86437e71c9a106fc9c341097c3bd
+3b620d960d29fa7719f95cf945163b04e43d2dad
+6be8590f72c2ef158202486e75f273d8598be6bc
+d7f22a15d66139efd65bae28ba780b0bf8d1a914
+e1ebbf612cf9d49cb08d0e0770ac1678ce1436ab
+4db9912f07ce63e4519053f52dbe521ec95c0fba
+b9fd4f4760ef65934b5d38e8b7c0eb2f77822861
+0e0178ecfacd553526afd221734607971b6911f1
+8cd4823a8ac9f846930408ad1759da4496384f9d
+e96cf22a972cc3185739ef1c1ce74a978ab71d11
+a9d63829aa54049801d37429b597eb04c9e1412d
+2519d617e18fa35974e20d10414f1262013501bb
+d02fc8d8483903871d9f65261b32c6acd2e4362d
+569456505d5c97934344d4f989a08fdcdb522de9
+f56d4c60ffb8df8fc1516d32a0512def0b6f8296
+745e899452ec746d3ffbb7b082995b7939a85387
+8c11f9ca2433bf9381840696218c245ec700666c
+bc2a868d1ba12b485a6eac460cefee67bd9ee899
+e628b072d054d982ecbbd7aa7fec628e0d9ee8d6
+e3390afd65e721dd8ef228f48fd4244228de2986
+35102507bd653296eaaa5e7d475405cc1feafbe3
+e2e5342f92148238391665fba101b1ca7dad5582
+621f4743f0165c6ca3f1571773867d2e0da67961
+5f558819695a49bbb92d5d1e07b9f12072874024
+eb45e9da84875e2d1325b78157d2f9e96374bdae
+bc0ab7e4f643e779cf9554f03e567d4f4708bd4d
+fd55e896d6df035cba49a20e26ed6ddd2d7b6024
+dcb9d95840c9a0514f8fd0a7b3b17cc228950c7e
+0bedc3d7a01f9819171c0b664e16900d9965c3ae
+94f6e372fd90e96cfd9a393a5952aa850485de66
+0b889a9cf37997c58a9f8979850da1f4bc84de9b
+b70ec5facdea7fc681c2a10dfb14ca0d8fed6f1d
+03e0192fb34134f25784a2b14791fbfdf69461bf
+9266cc52df3725107edf513aea4a02c131aa153c
+0820a412fdc9941567d86cba02793ca6a6378275
+f1a72254956f63393f6039a7d5da5fca943fcd2c
+abeb9e16d924c1a87c5b525ed12c43031ef1cb2f
+d5813fad322c97bc31d7dd37f838c7442aa68f35
+428b26fdca0ba98a3a01e89629bdb778dec9e8ab
+19ff672db65a7ee25ee0d48baa3f9bbf2d145ecd
+d1eb6283ece7d9c814b0d3d5223207b905d3d720
+9ba934b83a40d26ebc5e8d7304ff29c32541e82d
+9b600cbf0209ad6079d00dd5d6a5270d858d5929
+0f22868d790bfab8a41894fc7eca161256ab6854
+fba092070b6e03432f6d47154f5ae4734e935a05
+11b1bf011fc24c2ca6dd8c81206c7338ac2b2915
+d93c82b17d7416e4c57ed036d6b75a323859d837
+27f762e8d3f1ed8bd0254800c121d0f16e914c2e
+e252d9d270330072e9e5e91257e90f255e7e968d
+e55c3c30785eb50b5dc36f9568e6b6ae39e6de11
+63491807090d814bd7ffccfe44cb05795830eb3b
+8111dbaeb71c53132229c4064c34247746a3769e
+8fe37ca0d79dd1f8132e9add06aa206d371964e3
+eb32fae4665b9f11ffd06a342e763b9d212e1353
+4e923698ee5566143fc6d32fdcc6fb46fcda2d23
+2e3910e29142382f9bbd1705ab9c605d1937a1ae
+533cc5f884885f771d3f6df4164fbfa29bae0e6d
+3fea0404fc58822cfc60d4f10ca404e3223f82a4
+733404a081eda804707c3dde1d6b8161e7a34b3d
+d2be0ea2923344abed57aa21f13dc816d4537eda
+7884465bb9da51c8b6e95a1cbc9888ed696ff68d
+6e63e5a03bfbba52dc3b4f504e6bc41951f56707
+44a9d3ed75c44e817a6e4b56e30be06a15f453c9
+91e12aff0f988bf414e64b97a8c20b9699440309
+008119e510f6a7f8714e63d2ec33ca7cd7776ea2
+27822b01ad020374ff6169428649fd667abf7f8b
+0c972fb8903c656cb7e750b1d5c1ea1f26bd8c50
+3d8f3e1fae697a905e87250aa5c0ae1f6c60ad66
+744421b6f1d3aa30c7558570da8aa1d52f11d39d
+ac017796cd3a5558dd78f73ecb82a6b961d8a3ec
+e11f534a2fd666ecd841f657faf0751d5fe02034
+eca5d275376911916c3e018c2d163cb8eb914263
+a3144ddce360b6ac1b55fc27d19a318be1f224c4
+84fc7d68bf3a309b3687da768f0dc206e647e653
+fd5132bf8e99230a9074ce9bb3d950cd26b3d25b
+720ceb5e566d26803db85af3ef69fc4fa14d355e
+e97f338a79e2248afd3a2b9077d8ac1c334cdf38
+0173ccf8d04014bcc4cc53df4d6574540f4231e4
+52da09b8812d96c14d3e57a77784d56e5749a8ae
+5169648c7429788c777947e21527e121d35aebe9
+41c8c94cdd1c646296946a00dc72dff8fcb6556f
+9b341f77b72b55674a030ad0209ac297e41c5570
+6aacd7b9b8fc571e930d18da63efc8be46e31bdc
+9875e5d15c0750b6ee4c41b0e1321e1dc0bb7810
+fd60909d92b0e124957aa0783ea03471c73fd732
+2f299011d707ffd8502e5a597f38f0d25ab3099b
+6c10423816abd3b0f327863c9b8fcf55cd6265bf
+14cc60568455ac2210f00ccb238ae41ddb473fcb
+74cf0e9a42bf241d3f76f25aaed46e4b6550d842
+9a0eacdab0398ced7d729f5c7a9b173eada2dcaf
+3057f2e5ac8cd11cd018780c062da7c2bb11d2f7
+dab224a6b259d9d7e16af4cf7e2718af8ba4a74c
+fe6dc165cde8c826a3935b536c8cfd1c10ba7d62
+7f3572bca7fd48b66649d761a054412b8369deba
+2ea30dde468795a3ccb307343cd50eb7041f5ee3
+5d4099ededa31d823a355d4ef0e53bed6b833539
+69eb5257143b2de63c8c7471216ba6f025b6d7ef
+e4c7387b32e314cca7e0ee2b1df197340272fad1
+01f14dd38700098d97f933008327c8456c75af34
+94040e25d5aacae0e55c3e9a91fe24d7daaaaaca
+cd64f093886bf092b8d88c75ccd2e2f9118d3ba9
+ceb96f9512f80188fafc61ec8d8d61c93d51a5c2
+9a4e9bf98bd371cee2b69ef62a1189c24cd8baa4
+dd861f56b65404a625538978d50819924f384a60
+b2960c129e39d30f446d27e38f726975bef7b4f0
+8351c6b1293bb0cc4a2e1235995c16433c84c463
+008ba61116504d01558fe8afea0d5b3e90944b76
+cce20d2824a877ffed6a912e3f22d7db3d8e5043
+5e02e12edf58e1dfe37ed770fb32171e64993a81
+7966a56b3a3c9c9ac6db5b9355ba5e96558ea7b6
+5dea2f86730665894cf03f2b1fac98c1217a9fb4
+451a4d8118d2c9c746c687efceaacac799e67ad9
+059dfb5adcde569a19a9260c2ff85c7b47f8c516
+da7449db2898c567fcfb40c595c0c21536c901b8
+db97ce996b09b15049a9f818ce27a680e585bd11
+e1f95b9a8fe2394e1cfb41fe83f130bdb68fe6b4
+fc2c03e29a331cafc8b08abd5eade336904f40dc
+385b11a95469f7477bdcf5b9c743982c4a866c65
+d7e31d19b9ed766048ccf9129723ebe36b4842dc
+9c9af56fb29f510ef75221a39964c128448526bd
+83e3c642af5648aaaa119cce34dfef6ef3c560bc
+a831fc506ca30a11c9d9b33c9cb2c43f6f01a446
+62c5ebf183a0cc2332f04c1ee3323005a9878438
+6bb31edda343bbbc4410e2f780c432129e610b47
+846ef94e8af8f09340a740d11c93157c81079bc0
+47aec581139d8a3ab4f2969b481868c1485e2ac6
+e3f68d2cd84e15063c4f73c8420a444f9fb64a7a
+3db1240470361a7314ea096f63c0fde74810caba
+ae951371c666cc605ef69b5ca3f5f31d0cd30298
+8ec035e739f01aeaa09742a92154f02ab3dbfe93
+4737a65f7c1e125ba37ef35acbc6e99c4db2bed6
+7005d4cae81a16a5a860fcd3c259d6ec07597072
+d98807cb107ad2e9bf95138ee4bfb566bf75cb50
+1e8cbd548f12e1ec861f3aed5fa9f080cf2782c4
+25c2b2cad9cf873edc80747cd2df5874034282aa
+676749cf8f76eadb469289b1d918cb5e485cd56b
+8cba76ab8a5034ee21e95a99196f257b7e527b49
+0151aa85f5a178da21ddf7d5e81398fff87604dc
+f881500552171b5a8a8c3ec7a2dc06e493a1ebbe
+8d39edf2ae13ed33d0529164d4e172bd4d060d7a
+b5c3f29c81e524e860e5f9ebefdc573f83fc600d
+b686bc7a882e461987ffb7bf1a25bdc6f82ccdd3
+ebc1f42a059e7863adb57890562878f652922b56
+b30835cea58d0b827cb56aaf9e4d5f6e673a1bf1
+a2cf1028df49cbf53c57d0f599083fec59cc38b7
+6efa045dbdfb4272f075255411f54fe436c31b8a
+0c3f085a4044e9231287c11e34504624b04ee7cf
+b8e628fdc2a7627283e0601ebfe8e978e91dfc00
+d84e30103d59d6bace53223fc0d5787f03d7f028
+2e0e70d0466bde79d134a215a399b20c2a9d0981
+142de640101e2bee71fe2dc98e567d688c7e3aa7
+8b02a5e91092f7363443a1cf96933dc445f0ce51
+753c065260b1659c0d8d247b62f6b0fbe986c7b2
+1113b6978475c9941be9b140e8cd6bc267469657
+0a01d10b21c039484410c7898250afc4079db28d
+b9bd23fa584a8f1900ada4addb96eeb750ef0a68
+5ecc9b675c4cc5c1bdcd8f84e1a52457ad30144d
+d91b0a31122b251998915b4eb274350fd42a841e
+a829cb9c850cc75546547aa95fa3ca6100ce16f7
+4b9bba5d1063d986be6463e4c5740eb18befc7b6
+ffb2f17926143e242efc18b32ee0c630b5447687
+3feb18fbff52f17a541abb1ebbb4894beec18d55
+4acbde9bdb24bd802ba5bb0ebe19d71c8d753240
+c9dba689c67ad7b16c8f6b1bf1bd382369fdec4e
+ff956cafd71e4787e9ef7b64725142fe8838a65a
+e2c090f1ca171b51d08e6ecbb74b27410bdfa7eb
+73aa4812a2effb88bb64a42f93713a54a88e1ccb
+8e0e0c69b0adb9a65098b18a7b96d6ed3a43940a
+5dc8620cb17c3e606b635f8f95ecebdd66af04ee
+18f8afd6fc87b3731145f61818f23b4b766da703
+0d2d0bd0680557dc28f4f7b23562495cdbb3afc0
+94da53667213590ad9767b335a9f2e51fe1e2c5d
+c6cb97a42dcea5461a2931b097ddfd53b9cc5870
+62a3d5192232ed847f3c7810344c43607a361e68
+aa6992567e763a0b081e6bce753cc42bc287e9d3
+1d67358d33250d456040091d8b29083b1b47d9bb
+65d399a4ac7dc36df20b8b2bc773bbc6fa67f43b
+acf7ea014fd1b7eb351dc6946b199ad2cc98f845
+7e4dcbb7f0fc2b051e33b555c4fdc67796dbbab9
+a07916245a9c21f3874a7b8c898638ca3b65df42
+bb7368d9b07b02aecfbca6d01788a7327743ffed
+60454c29275aac27c450323f0141d60ea8202842
+c4d0ff10c85ca4c12ddfda1830cee475408205d5
+a5da3671524fb761552a4eb5c1e27dd433f80fe4
+43142e711f392ae1bcdade749dbaa9dd98664228
+7aa0bdd118c78d8929e737392457d14f87d625ae
+be921331245c4e04ef9f0ff7e359907e2d101cac
+d6f654de1b8c27f84e34fbff12aadffb30342465
+fef2680b335ffd861021ceff2a2637f5a360f037
+79de53d3b87469e21d510ed6ddb33d809c05a3f6
+475b10017d25db725e73eef11ca789ad7dfcf4ac
+d14f3734dc27ecccfdb4683cf7ef3334a5a70b3f
+f0c394dd6a109b97ba4a9ab16cc71b789d9ee38b
+a57cd5c8278e1fd6fae6f02947c13880be4f3b62
+83c6e4b636f3bf115955b6eeb3f91a5689e7f00b
+b881752a8cc16f49ca605bf6a35af106e7e19c9a
+8362e4bcc30e73460ae1b9731bc545fd2b12d8f0
+b01216229149bed7c110221551353b54ff8e4704
+10ad0e68785b27bab975868b83bc463b9c9c9153
+7a66612abaa223ef0410fae66727a8abac3add03
+8a7bdba957536b078f0421faf5dfaf8d65ff5add
+defd6d03526345a410437eda15cbd067124f9c2c
+f7e6c29aa4d1f7a607e0c87ea20105afeee0372a
+751363e461257a4036a8f2aa740195401883c1ea
+a8d66b5855eda5abf699ebf9c6dd721928007fb8
+35ca716114bdf87a89857f2d633be3f4b13cbc70
+cf319abfba8fc1b33de4c6a6f99e21864cc72563
+4fd36e634e762ff2f94e9d66f24ceabe164f9e26
+d0364113a1b57ed5017dbea6126b0cc5a5c2886d
+9b3d7bf551d20acae4ee943a86c3cf898b6280ba
+b35351d566efdde005747503c7f121d49e864848
+57b1dc2b20f2e67c3313f0c6127b05041d125fb4
+fadcdf4c98e9167f8f06a45dafa08d3acce7a741
+3bcfcb7717bfc0e50c5b8f5c7beaed9f3ddf5478
+b8388b7b5973dd3e84902c25c5378f9a412d6147
+814f07ea363eb0464380ccfce7b4cf5209f1dcb2
+b33315c8551bede3fb867efb3fdb1134cdff5115
+c7bade1e7cc239e8fceb2c0b06f880e60eb8ebec
+bb193f4f0f5b1b8bdc9cc72967f8fa6387faf7c4
+b727e8d9f4a4987cbad41c75c630cfdb445c37a0
+a2103d7fe328871d8231f8e07ba5dc9182f637b3
+e36d269d16660db5bba028746564b5699721def5
+35f9c486cc26bdff903241f4ab2b1dac2536059f
+cd5314af7e8e120bceda896a3c17daa8eeedd528
+200e09df8f0f7b94eb8941136482cf7c60fffb0d
+17a618f241a6236c93af5ba2e09238369fc7d784
+15aeb2bb0401d428cb7058e1d6554e20369ed352
+40b0a406cc23467af8bb63d9a62378fa871e2031
+7abd7f4cb237ef33b9e019f4529b6fb05b84284f
+ac614b7506e820457417c3ea15ba99fbc8146155
+8afd5a714da3f45389e0e4edeb64f49576c57c76
+77d10571047d8b4153180e7a89d5c9aae6a84060
+35479ce1706725f73bfe99428c43e8fe2e3f9157
+360a0864ece712571d3df95e86251d6883bcdf7d
+b5cd910848f592e33efb6de3226c07ae545a2aad
+f5a9c28ca029ec5d1c5d3c594afa09374adf04e5
+b9fce5928a1c5056f66706b67c01cd564e6c0a90
+e5a2250e35706127304cd5ed86b81575f2636b5b
+f30cffe4cef93aa190bcb1caf407ca0767107d06
+45535f6e0af6785676531c81b4a2a3c480a98e70
+740bd201b23beded9ade92a93301cddc67c4d106
+70460e9e601171276dd6844cd6addd8db5eb2465
+44dff2c35acb4736b183cef9e46155386f579716
+46ea31f673bc9365fcca558f15c862ef6a899018
+34556caf76c2422a76be3d1cecd223fcf435d93c
+fea67ed9483b5cc76dc55eb4dd6f52baf445394d
+31b1897ece6222826f379c1aebda891384b4b63a
+80dcf3713b85b78979d4eb443fce9e992675b5c0
+11993c742658321c0c5c200f48231583216d636c
+7b5a089ed3007252e61df0aee3fc17c14d051745
+890881c9a552c22f4be01dee16ee902c88f6700f
+401ba79da09dded82a73996c8e0609a87cbd728b
+e06313f41971de730085dcddf640a4549fc54fc3
+054d52e86a954a615ed1f5add7f9d6842737d965
+d8a60982c456a9cae3de745a37dc3f5985814f7f
+2b39f575a510cf581aa828df494e633cc76fafa6
+e11d353191175b329b3c9f9af7fa33e3ef9f837d
+32ac2659ce98765aaae9c10cc7216d1f1faf155e
+5f7f801227868c7abcce7e58dee3eff855011955
+a013eaf0fe38d8689e27278bddd4ebf87ac5476b
+401b3f3d2d96fa785c5321bb64c97cfb17c509e3
+1fa4fd4321fa708b3db5cfb514e2192b00672aff
+77976b24ff839c59c3b20d80cb28351ccb5e59a8
+09b76d2966e2370a78ed37a31c2f7c23d08609c3
+7000b24511618a21d40b39ee213d397e1d29497d
+c2a6adfcd18c0d95dbed6ea62ac9c9a912d18123
+6ba3609953d5c46a76ca1d0d3d83018be61454e6
+3dff6074fe205e36fae219f277ef87aab097e236
+1cdc8437fa6c621d96c4dfa5f6370c8fdb9cbc3d
+d471720bc8f7ce7109276b49dd9c76b6163007d9
+a67b1bdd027629dfc38601b21dc564272e28712c
+20125a6d37d5c1614ffe1de94ca064095968e7f0
+2b642751ef86265a1c953186810e118740f8bd2d
+e562c1d74e2b6744572184e66a0673e55f9ba0b8
+ba9687b5d746dda28d4a19c5c96d0679d7c77b15
+f39d7d293c3e342b4f447bb440a9b6f72d2d20cc
+95750ad9e700efd15d137963ba0dc443e6c9b6b0
+0f76d8445048dc0bfcaf05e30b61b338a08f0e48
+1a9a4c61d6a371d9e95eaef44fa2452d17a09d22
+912b41aad5983d9735379d322eae8f6d40d8bdca
+eea0b559472874ff48c34f16bb805108967e6489
+ad4e7ba4032e6b1c047230b3144848dbcf66a127
+b6d93107393dee6eebb05376a67f2e4dfcb44311
diff --git a/tests/fixtures/rev_list_delta_a b/tests/fixtures/rev_list_delta_a
new file mode 100644 (file)
index 0000000..023c551
--- /dev/null
@@ -0,0 +1,8 @@
+e34590b7a2d186b3bb9a1170d02d52b36c791c78
+8977833d74f8681aa0d9a5e84b0dd3d81519774d
+6f5561530cb3a94e4c86454e84732197325be172
+ee419e04a961543444be6db66aef52e6e37936d6
+d845de9d438e1a249a0c2fcb778e8ea3b7e06cef
+0bba4a6c10060405a94d52533af2f9bdacd4f29c
+77711c0722964ead965e0ba2ee9ed4a03cb3d292
+501d23cac6dd911511f15d091ee031a15b90ebde
diff --git a/tests/fixtures/rev_list_delta_b b/tests/fixtures/rev_list_delta_b
new file mode 100644 (file)
index 0000000..aea7187
--- /dev/null
@@ -0,0 +1,11 @@
+4c8124ffcf4039d292442eeccabdeca5af5c5017
+634396b2f541a9f2d58b00be1a07f0c358b999b3
+ab25fd8483882c3bda8a458ad2965d2248654335
+e34590b7a2d186b3bb9a1170d02d52b36c791c78
+8977833d74f8681aa0d9a5e84b0dd3d81519774d
+6f5561530cb3a94e4c86454e84732197325be172
+ee419e04a961543444be6db66aef52e6e37936d6
+d845de9d438e1a249a0c2fcb778e8ea3b7e06cef
+0bba4a6c10060405a94d52533af2f9bdacd4f29c
+77711c0722964ead965e0ba2ee9ed4a03cb3d292
+501d23cac6dd911511f15d091ee031a15b90ebde
diff --git a/tests/fixtures/rev_list_single b/tests/fixtures/rev_list_single
new file mode 100644 (file)
index 0000000..d8c6431
--- /dev/null
@@ -0,0 +1,7 @@
+commit 4c8124ffcf4039d292442eeccabdeca5af5c5017
+tree 672eca9b7f9e09c22dcb128c283e8c3c8d7697a4
+parent 634396b2f541a9f2d58b00be1a07f0c358b999b3
+author Tom Preston-Werner <tom@mojombo.com> 1191999972 -0700
+committer Tom Preston-Werner <tom@mojombo.com> 1191999972 -0700
+
+    implement Grit#heads
diff --git a/tests/fixtures/rev_parse b/tests/fixtures/rev_parse
new file mode 100644 (file)
index 0000000..a639d89
--- /dev/null
@@ -0,0 +1 @@
+80f136f
diff --git a/tests/fixtures/show_empty_commit b/tests/fixtures/show_empty_commit
new file mode 100644 (file)
index 0000000..ea25e32
--- /dev/null
@@ -0,0 +1,6 @@
+commit 1e3824339762bd48316fe87bfafc853732d43264
+tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904
+author Tom Preston-Werner <tom@mojombo.com> 1157392833 +0000
+committer Tom Preston-Werner <tom@mojombo.com> 1157392833 +0000
+
+    initial directory structure
index 9ba843b..487c0ee 100644 (file)
@@ -1,17 +1,28 @@
 <?php
 
+error_reporting(E_ALL | E_STRICT);
+
 chdir(dirname(__FILE__));
 $simpletest = '..';
 require '../test-settings.php';
 require_once $simpletest . '/unit_tester.php';
 require_once $simpletest . '/mock_objects.php';
 
+// Easy repository to run tests out ofs
+define('GIT_REPO', realpath(dirname(__FILE__) . '/..'));
+define('GIT_FIXTURES', realpath(dirname(__FILE__) . '/fixtures'));
+
 require_once '../library/Git.php';
+
+Mock::generate('Git', 'GitMock');
+
+require_once 'GitHarness.php';
 require_once 'GitTest.php';
+require_once 'Git/TreeTest.php';
 
 $loader = new SimpleFileLoader();
 $suite = $loader->createSuiteFromClasses('PHPGit Tests', array(
-    'GitTest'
+    'GitTest', 'Git_TreeTest',
 ));
 $result = $suite->run(new DefaultReporter());
 if (SimpleReporter::inCli()) {