Browse code

Initial commit

Klaus Silveira authored on05/18/2012 04:38:33
Showing244 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,28 @@
1
+cache/
2
+*.diff
3
+*.err
4
+*.orig
5
+*.log
6
+*.rej
7
+*.swo
8
+*.swp
9
+*.zip
10
+*.vi
11
+*~
12
+*.sass-cache
13
+.DS_Store
14
+._*
15
+Thumbs.db
16
+.cache
17
+.project
18
+.settings
19
+.tmproj
20
+*.esproj
21
+nbproject
22
+*.sublime-project
23
+*.sublime-workspace
24
+.hg
25
+.svn
26
+.CVS
27
+.idea
28
+node_modules
0 29
new file mode 100644
... ...
@@ -0,0 +1,7 @@
1
+<IfModule mod_rewrite.c>
2
+    Options -MultiViews
3
+
4
+    RewriteEngine On
5
+    RewriteCond %{REQUEST_FILENAME} !-f
6
+    RewriteRule ^ index.php [L]
7
+</IfModule>
0 8
\ No newline at end of file
1 9
new file mode 100644
... ...
@@ -0,0 +1,5 @@
1
+language: php
2
+php:
3
+  - 5.3
4
+  - 5.4
5
+script: phpunit
0 6
new file mode 100644
... ...
@@ -0,0 +1,9 @@
1
+Copyright (c) 2012, Klaus Silveira and contributors
2
+All rights reserved.
3
+
4
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+
6
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+Neither the name of GitList nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0 10
new file mode 100644
... ...
@@ -0,0 +1,53 @@
1
+# GitList: an elegant and modern git repository viewer
2
+[![Build Status](https://secure.travis-ci.org/klaussilveira/GitList.png)](http://travis-ci.org/klaussilveira/GitList)
3
+
4
+GitList is an elegant and modern web interface for interacting with multiple git repositories. It allows you to browse repositories using your favorite browser, viewing files under different revisions, commit history, diffs. It also generates RSS feeds for each repository, allowing you to stay up-to-date with the latest changes anytime, anywhere. GitList was written in PHP, on top of the [Silex](http://silex.sensiolabs.org/) microframework and powered by the Twig template engine. This means that GitList is easy to install and easy to customize. Also, the GitList gorgeous interface was made possible due to [Bootstrap](http://twitter.github.com/bootstrap/). 
5
+
6
+## Features
7
+* Multiple repository support
8
+* Multiple branch support
9
+* Multiple tag support
10
+* Commit history, blame, diff
11
+* RSS feeds
12
+* Syntax highlighting
13
+* Repository statistics
14
+
15
+## Authors and contributors
16
+* [Klaus Silveira](http://www.klaussilveira.com) (Creator, developer)
17
+
18
+## License
19
+[New BSD license](http://www.opensource.org/licenses/bsd-license.php)
20
+
21
+## Todo
22
+* improve the current test code coverage
23
+* test the interface
24
+* error handling can be greatly improved during parsing
25
+* submodule support
26
+* multilanguage support
27
+
28
+## Requirements
29
+In order to run GitList on your server, you'll need:
30
+
31
+* git
32
+* Apache and mod_rewrite enabled
33
+* PHP 5.3.3
34
+
35
+## Installing
36
+Download the GitList latest package and decompress to your `/var/www/gitlist` folder, or anywhere else you want to place GitList. You can also clone the repository:
37
+
38
+```
39
+git clone https://github.com/klaussilveira/gitlist.git /var/www/gitlist
40
+```
41
+
42
+Now open up the `config.ini` and configure your installation. You'll have to provide where your repositories are located and the base GitList URL (in our case, http://localhost/gitlist). Now, let's create the cache folder and give the correct permissions:
43
+
44
+```
45
+cd /var/www/gitlist
46
+mkdir cache
47
+chmod 777 cache
48
+```
49
+
50
+That's it, installation complete!
51
+
52
+## Further information
53
+If you want to know more about customizing GitList, check the [Customization](https://github.com/klaussilveira/gitlist/wiki/Customizing) page on the wiki. Also, if you're having problems with GitList, check the [Troubleshooting](https://github.com/klaussilveira/gitlist/wiki/Customizing) page. Don't forget to report issues and suggest new features! :)
0 54
new file mode 100644
... ...
@@ -0,0 +1,6 @@
1
+[git]
2
+client = '/usr/bin/git' ; Your git executable path
3
+repositories = '/home/git/' ; Path to your repositories (with ending slash)
4
+
5
+[app]
6
+baseurl = 'http://localhost/gitlist' ; Base URL of the application (without ending slash)
0 7
new file mode 100644
... ...
@@ -0,0 +1,32 @@
1
+<?php
2
+
3
+$app->get('{repo}/blob/{branch}/{file}/', function($repo, $branch, $file) use($app) {
4
+    $repository = $app['git']->getRepository($app['git.repos'] . $repo);
5
+    $blob = $repository->getBlob("$branch:$file");
6
+    $breadcrumbs = $app['utils']->getBreadcrumbs("$repo/tree/$branch/$file");
7
+    $fileType = $app['utils']->getFileType($file);
8
+
9
+    return $app['twig']->render('file.twig', array(
10
+        'baseurl'        => $app['baseurl'],
11
+        'page'           => 'files',
12
+        'file'           => $file,
13
+        'fileType'       => $fileType,
14
+        'blob'           => $blob->output(),
15
+        'repo'           => $repo,
16
+        'branch'         => $branch,
17
+        'breadcrumbs'    => $breadcrumbs,
18
+        'branches'       => $repository->getBranches(),
19
+        'tags'           => $repository->getTags(),
20
+    ));
21
+})->assert('file', '.+')
22
+  ->assert('repo', '[\w-._]+')
23
+  ->assert('branch', '[\w-._]+');
24
+
25
+$app->get('{repo}/raw/{branch}/{file}', function($repo, $branch, $file) use($app) {
26
+    $repository = $app['git']->getRepository($app['git.repos'] . $repo);
27
+    $blob = $repository->getBlob("$branch:$file")->output();
28
+
29
+    return new Symfony\Component\HttpFoundation\Response($blob, 200, array('Content-Type' => 'text/plain'));
30
+})->assert('file', '.+')
31
+  ->assert('repo', '[\w-._]+')
32
+  ->assert('branch', '[\w-._]+');
0 33
\ No newline at end of file
1 34
new file mode 100644
... ...
@@ -0,0 +1,79 @@
1
+<?php
2
+
3
+$app->get('{repo}/commits/{branch}', function($repo, $branch) use($app) {
4
+    $repository = $app['git']->getRepository($app['git.repos'] . $repo);
5
+    $commits = $repository->getCommits();
6
+
7
+    foreach ($commits as $commit) {
8
+        $date = $commit->getDate();
9
+        $date = $date->format('m/d/Y');
10
+        $categorized[$date][] = $commit;
11
+    }
12
+
13
+    return $app['twig']->render('commits.twig', array(
14
+        'baseurl'        => $app['baseurl'],
15
+        'page'           => 'commits',
16
+        'repo'           => $repo,
17
+        'branch'         => $branch,
18
+        'branches'       => $repository->getBranches(),
19
+        'tags'           => $repository->getTags(),
20
+        'commits'        => $categorized,
21
+    ));
22
+})->assert('repo', '[\w-._]+')
23
+  ->assert('branch', '[\w-._]+')
24
+  ->value('branch', 'master');
25
+
26
+$app->get('{repo}/commits/{branch}/{file}/', function($repo, $branch, $file) use($app) {
27
+    $repository = $app['git']->getRepository($app['git.repos'] . $repo);
28
+    $commits = $repository->getCommits($file);
29
+
30
+    foreach ($commits as $commit) {
31
+        $date = $commit->getDate();
32
+        $date = $date->format('m/d/Y');
33
+        $categorized[$date][] = $commit;
34
+    }
35
+
36
+    return $app['twig']->render('commits.twig', array(
37
+        'baseurl'        => $app['baseurl'],
38
+        'page'           => 'commits',
39
+        'repo'           => $repo,
40
+        'branch'         => $branch,
41
+        'branches'       => $repository->getBranches(),
42
+        'tags'           => $repository->getTags(),
43
+        'commits'        => $categorized,
44
+    ));
45
+})->assert('repo', '[\w-._]+')
46
+  ->assert('file', '.+')
47
+  ->assert('branch', '[\w-._]+');
48
+
49
+$app->get('{repo}/commit/{commit}/', function($repo, $commit) use($app) {
50
+    $repository = $app['git']->getRepository($app['git.repos'] . $repo);
51
+    $commit = $repository->getCommit($commit);
52
+
53
+    return $app['twig']->render('commit.twig', array(
54
+        'baseurl'        => $app['baseurl'],
55
+        'page'           => 'commits',
56
+        'branch'         => 'master',
57
+        'repo'           => $repo,
58
+        'commit'         => $commit,
59
+    ));
60
+})->assert('repo', '[\w-._]+')
61
+  ->assert('commit', '[a-f0-9]+');
62
+
63
+$app->get('{repo}/blame/{branch}/{file}/', function($repo, $branch, $file) use($app) {
64
+    $repository = $app['git']->getRepository($app['git.repos'] . $repo);
65
+    $blames = $repository->getBlame($file);
66
+
67
+    return $app['twig']->render('blame.twig', array(
68
+        'baseurl'        => $app['baseurl'],
69
+        'page'           => 'commits',
70
+        'file'           => $file,
71
+        'repo'           => $repo,
72
+        'branch'         => $branch,
73
+        'branches'       => $repository->getBranches(),
74
+        'tags'           => $repository->getTags(),
75
+        'blames'         => $blames,
76
+    ));
77
+})->assert('repo', '[\w-._]+')
78
+  ->assert('file', '.+')
79
+  ->assert('branch', '[\w-._]+');
0 80
new file mode 100644
... ...
@@ -0,0 +1,10 @@
1
+<?php
2
+
3
+$app->get('/', function() use($app) {
4
+    $repositories = $app['git']->getRepositories($app['git.repos']);
5
+
6
+    return $app['twig']->render('index.twig', array(
7
+        'baseurl'        => $app['baseurl'],
8
+        'repositories'   => $repositories,
9
+    ));
10
+});
0 11
\ No newline at end of file
1 12
new file mode 100644
... ...
@@ -0,0 +1,16 @@
1
+<?php
2
+
3
+$app->get('{repo}/{branch}/rss/', function($repo, $branch) use($app) {
4
+    $repository = $app['git']->getRepository($app['git.repos'] . $repo);
5
+    $commits = $repository->getCommits();
6
+
7
+    $html = $app['twig']->render('rss.twig', array(
8
+        'baseurl'        => $app['baseurl'],
9
+        'repo'           => $repo,
10
+        'branch'         => $branch,
11
+        'commits'        => $commits,
12
+    ));
13
+
14
+    return new Symfony\Component\HttpFoundation\Response($html, 200, array('Content-Type' => 'application/rss+xml'));
15
+})->assert('repo', '[\w-._]+')
16
+  ->assert('branch', '[\w-._]+');
0 17
\ No newline at end of file
1 18
new file mode 100644
... ...
@@ -0,0 +1,20 @@
1
+<?php
2
+
3
+$app->get('{repo}/stats/{branch}', function($repo, $branch) use($app) {
4
+    $repository = $app['git']->getRepository($app['git.repos'] . $repo);
5
+    $stats = $repository->getStatistics($branch);
6
+    $authors = $repository->getAuthorStatistics();
7
+
8
+    return $app['twig']->render('stats.twig', array(
9
+        'baseurl'        => $app['baseurl'],
10
+        'page'           => 'stats',
11
+        'repo'           => $repo,
12
+        'branch'         => $branch,
13
+        'branches'       => $repository->getBranches(),
14
+        'tags'           => $repository->getTags(),
15
+        'stats'          => $stats,
16
+        'authors'         => $authors,
17
+    ));
18
+})->assert('repo', '[\w-._]+')
19
+  ->assert('branch', '[\w-._]+')
20
+  ->value('branch', 'master');
0 21
\ No newline at end of file
1 22
new file mode 100644
... ...
@@ -0,0 +1,67 @@
1
+<?php
2
+
3
+$app->get('{repo}/', function($repo) use($app) {
4
+    $repository = $app['git']->getRepository($app['git.repos'] . $repo);
5
+    $tree = $repository->getTree('master');
6
+    $breadcrumbs = $app['utils']->getBreadcrumbs("$repo/");
7
+
8
+    return $app['twig']->render('tree.twig', array(
9
+        'baseurl'        => $app['baseurl'],
10
+        'page'           => 'files',
11
+        'files'          => $tree->output(),
12
+        'repo'           => $repo,
13
+        'branch'         => 'master',
14
+        'path'           => '',
15
+        'parent'         => '',
16
+        'breadcrumbs'    => $breadcrumbs,
17
+        'branches'       => $repository->getBranches(),
18
+        'tags'           => $repository->getTags(),
19
+    ));
20
+})->assert('repo', '[\w-._]+');
21
+
22
+$app->get('{repo}/tree/{branch}/', function($repo, $branch) use($app) {
23
+    $repository = $app['git']->getRepository($app['git.repos'] . $repo);
24
+    $tree = $repository->getTree($branch);
25
+    $breadcrumbs = $app['utils']->getBreadcrumbs("$repo/");
26
+
27
+    return $app['twig']->render('tree.twig', array(
28
+        'baseurl'        => $app['baseurl'],
29
+        'page'           => 'files',
30
+        'files'          => $tree->output(),
31
+        'repo'           => $repo,
32
+        'branch'         => $branch,
33
+        'path'           => '',
34
+        'parent'         => '',
35
+        'breadcrumbs'    => $breadcrumbs,
36
+        'branches'       => $repository->getBranches(),
37
+        'tags'           => $repository->getTags(),
38
+    ));
39
+})->assert('repo', '[\w-._]+')
40
+  ->assert('branch', '[\w-._]+');
41
+
42
+$app->get('{repo}/tree/{branch}/{tree}/', function($repo, $branch, $tree) use($app) {
43
+    $repository = $app['git']->getRepository($app['git.repos'] . $repo);
44
+    $files = $repository->getTree("$branch:$tree/");
45
+    $breadcrumbs = $app['utils']->getBreadcrumbs("$repo/tree/$branch/$tree");
46
+
47
+    if (($slash = strrpos($tree, '/')) !== false) {
48
+        $parent = '/' . substr($tree, 0, $slash);
49
+    } else {
50
+        $parent = '/';
51
+    }
52
+
53
+    return $app['twig']->render('tree.twig', array(
54
+        'baseurl'        => $app['baseurl'],
55
+        'page'           => 'files',
56
+        'files'          => $files->output(),
57
+        'repo'           => $repo,
58
+        'branch'         => $branch,
59
+        'path'           => "$tree/",
60
+        'parent'         => $parent,
61
+        'breadcrumbs'    => $breadcrumbs,
62
+        'branches'       => $repository->getBranches(),
63
+        'tags'           => $repository->getTags(),
64
+    ));
65
+})->assert('tree', '.+')
66
+  ->assert('repo', '[\w-._]+')
67
+  ->assert('branch', '[\w-._]+');
0 68
\ No newline at end of file
1 69
new file mode 100644
... ...
@@ -0,0 +1,52 @@
1
+<?php
2
+
3
+/**
4
+ * GitList 0.1
5
+ * https://github.com/klaussilveira/gitlist
6
+ */
7
+
8
+$config = parse_ini_file('config.ini', true);
9
+
10
+if (empty($config['git']['repositories'])) {
11
+    die("Please, edit the config.ini file and provide your repositories directory");
12
+}
13
+
14
+require_once __DIR__.'/vendor/silex.phar';
15
+
16
+$app = new Silex\Application();
17
+$app['baseurl'] = $config['app']['baseurl'];
18
+
19
+// Register Git and Twig libraries
20
+$app['autoloader']->registerNamespace('Git', __DIR__.'/lib');
21
+$app['autoloader']->registerNamespace('Application', __DIR__.'/lib');
22
+$app->register(new Silex\Provider\TwigServiceProvider(), array(
23
+    'twig.path'       => __DIR__.'/views',
24
+    'twig.class_path' => __DIR__.'/vendor',
25
+    'twig.options'    => array('cache' => __DIR__.'/cache'),
26
+));
27
+$app->register(new Git\GitServiceProvider(), array(
28
+    'git.client'      => $config['git']['client'],
29
+    'git.repos'       => $config['git']['repositories'],
30
+));
31
+$app->register(new Application\UtilsServiceProvider());
32
+
33
+// Add the md5() function to Twig scope
34
+$app['twig']->addFilter('md5', new Twig_Filter_Function('md5'));
35
+
36
+// Load controllers
37
+include 'controllers/indexController.php';
38
+include 'controllers/treeController.php';
39
+include 'controllers/blobController.php';
40
+include 'controllers/commitController.php';
41
+include 'controllers/statsController.php';
42
+include 'controllers/rssController.php';
43
+
44
+// Error handling
45
+$app->error(function (\Exception $e, $code) use ($app) {
46
+    return $app['twig']->render('error.twig', array(
47
+        'baseurl'   => $app['baseurl'],
48
+        'message'   => $e->getMessage(),
49
+    ));
50
+});
51
+
52
+$app->run();
0 53
new file mode 100644
... ...
@@ -0,0 +1,206 @@
1
+<?php
2
+
3
+namespace Application;
4
+
5
+/**
6
+ * General helper class, mostly used for string parsing inside the application controllers
7
+ */
8
+class Utils
9
+{
10
+    /**
11
+     * Builds a breadcrumb array based on a path spec
12
+     * 
13
+     * @param string $spec Path spec
14
+     * @return array Array with parts of the breadcrumb
15
+     */
16
+    public function getBreadcrumbs($spec)
17
+    {
18
+        $paths = explode('/', $spec);
19
+        $last = '';
20
+
21
+        foreach ($paths as $path) {
22
+            $dir['dir'] = $path;
23
+            $dir['path'] = "$last/$path";
24
+            $breadcrumbs[] = $dir;
25
+            $last .= '/' . $path;
26
+        }
27
+
28
+        if (isset($paths[2])) {
29
+            $breadcrumbs[0]['path'] .= '/' . $paths[1] . '/' . $paths[2];
30
+        }
31
+        
32
+        unset($breadcrumbs[1], $breadcrumbs[2]);
33
+        return $breadcrumbs;
34
+    }
35
+
36
+    /**
37
+     * Returns the file type based on filename by treating the extension
38
+     *
39
+     * The file type is used by CodeMirror, a Javascript-based IDE implemented in
40
+     * GitList, to properly highlight the blob syntax (if it's a source-code)
41
+     * 
42
+     * @param string $spec File name
43
+     * @return string File type
44
+     */
45
+    public function getFileType($file)
46
+    {
47
+        if (($pos = strrpos($file, '.')) !== FALSE) {
48
+            $fileType = substr($file, $pos + 1);
49
+        } else {
50
+            return 'text';
51
+        }
52
+
53
+        switch ($fileType) {
54
+            case 'php':
55
+                return 'php';
56
+            case 'c':
57
+                return 'clike';
58
+            case 'h':
59
+                return 'clike';
60
+            case 'cpp':
61
+                return 'clike';
62
+            case 'cs':
63
+                return 'clike';
64
+            case 'm':
65
+                return 'clike';
66
+            case 'mm':
67
+                return 'clike';
68
+            case 'java':
69
+                return 'clike';
70
+            case 'clj':
71
+                return 'clojure';
72
+            case 'coffee':
73
+                return 'coffeescript';
74
+            case 'css':
75
+                return 'css';
76
+            case 'diff':
77
+                return 'diff';
78
+            case 'ecl':
79
+                return 'ecl';
80
+            case 'el':
81
+                return 'erlang';
82
+            case 'go':
83
+                return 'go';
84
+            case 'groovy':
85
+                return 'groovy';
86
+            case 'hs':
87
+                return 'haskell';
88
+            case 'lhs':
89
+                return 'haskell';
90
+            case 'jsp':
91
+                return 'htmlembedded';
92
+            case 'asp':
93
+                return 'htmlembedded';
94
+            case 'aspx':
95
+                return 'htmlembedded';
96
+            case 'html':
97
+                return 'htmlmixed';
98
+            case 'tpl':
99
+                return 'htmlmixed';
100
+            case 'js':
101
+                return 'javascript';
102
+            case 'json':
103
+                return 'javascript';
104
+            case 'less':
105
+                return 'less';
106
+            case 'lua':
107
+                return 'lua';
108
+            case 'md':
109
+                return 'markdown';
110
+            case 'markdown':
111
+                return 'markdown';
112
+            case 'sql':
113
+                return 'mysql';
114
+            case 'pl':
115
+                return 'perl';
116
+            case 'pm':
117
+                return 'perl';
118
+            case 'pas':
119
+                return 'pascal';
120
+            case 'ini':
121
+                return 'properties';
122
+            case 'cfg':
123
+                return 'properties';
124
+            case 'nt':
125
+                return 'ntriples';
126
+            case 'py':
127
+                return 'python';
128
+            case 'rb':
129
+                return 'ruby';
130
+            case 'rst':
131
+                return 'rst';
132
+            case 'r':
133
+                return 'r';
134
+            case 'sh':
135
+                return 'shell';
136
+            case 'ss':
137
+                return 'scheme';
138
+            case 'scm':
139
+                return 'scheme';
140
+            case 'sls':
141
+                return 'scheme';
142
+            case 'sps':
143
+                return 'scheme';
144
+            case 'rs':
145
+                return 'rust';
146
+            case 'st':
147
+                return 'smalltalk';
148
+            case 'tex':
149
+                return 'stex';
150
+            case 'vbs':
151
+                return 'vbscript';
152
+            case 'v':
153
+                return 'verilog';
154
+            case 'xml':
155
+                return 'xml';
156
+            case 'xsd':
157
+                return 'xml';
158
+            case 'xsl':
159
+                return 'xml';
160
+            case 'xul':
161
+                return 'xml';
162
+            case 'xlf':
163
+                return 'xml';
164
+            case 'xliff':
165
+                return 'xml';
166
+            case 'xaml':
167
+                return 'xml';
168
+            case 'wxs':
169
+                return 'xml';
170
+            case 'wxl':
171
+                return 'xml';
172
+            case 'wxi':
173
+                return 'xml';
174
+            case 'wsdl':
175
+                return 'xml';
176
+            case 'svg':
177
+                return 'xml';
178
+            case 'rss':
179
+                return 'xml';
180
+            case 'rdf':
181
+                return 'xml';
182
+            case 'plist':
183
+                return 'xml';
184
+            case 'mxml':
185
+                return 'xml';
186
+            case 'kml':
187
+                return 'xml';
188
+            case 'glade':
189
+                return 'xml';
190
+            case 'xq':
191
+                return 'xquery';
192
+            case 'xqm':
193
+                return 'xquery';
194
+            case 'xquery':
195
+                return 'xquery';
196
+            case 'xqy':
197
+                return 'xquery';
198
+            case 'yml':
199
+                return 'yaml';
200
+            case 'yaml':
201
+                return 'yaml';
202
+            default:
203
+                return 'text';
204
+        }
205
+    }
206
+}
0 207
\ No newline at end of file
1 208
new file mode 100644
... ...
@@ -0,0 +1,22 @@
1
+<?php
2
+
3
+namespace Application;
4
+
5
+use Silex\Application;
6
+use Silex\ServiceProviderInterface;
7
+
8
+class UtilsServiceProvider implements ServiceProviderInterface
9
+{
10
+    /**
11
+     * Register the Utils class on the Application ServiceProvider
12
+     * 
13
+     * @param Application $app Silex Application
14
+     * @return Utils Instance of the Utils class
15
+     */
16
+    public function register(Application $app)
17
+    {
18
+        $app['utils'] = function () use ($app) {
19
+            return new Utils;
20
+        };
21
+    }
22
+}
0 23
\ No newline at end of file
1 24
new file mode 100644
... ...
@@ -0,0 +1,122 @@
1
+<?php
2
+
3
+namespace Git;
4
+
5
+class Client
6
+{
7
+    protected $path;
8
+
9
+    public function __construct($path)
10
+    {
11
+        $this->setPath($path);
12
+    }
13
+
14
+    /**
15
+     * Creates a new repository on the specified path
16
+     * 
17
+     * @param string $path Path where the new repository will be created
18
+     * @return Repository Instance of Repository
19
+     */
20
+    public function createRepository($path)
21
+    {
22
+        if (file_exists($path . '/.git/HEAD') && !file_exists($path . '/HEAD')) {
23
+            throw new \RuntimeException('A GIT repository already exists at ' . $path);
24
+        }
25
+
26
+        $repository = new Repository($path, $this);
27
+        return $repository->create();
28
+    }
29
+
30
+    /**
31
+     * Opens a repository at the specified path
32
+     * 
33
+     * @param string $path Path where the repository is located
34
+     * @return Repository Instance of Repository
35
+     */
36
+    public function getRepository($path)
37
+    {
38
+        if (!file_exists($path) || !file_exists($path . '/.git/HEAD') && !file_exists($path . '/HEAD')) {
39
+            throw new \RuntimeException('There is no GIT repository at ' . $path);
40
+        }
41
+
42
+        return new Repository($path, $this);
43
+    }
44
+
45
+    /**
46
+     * Searches for valid repositories on the specified path
47
+     * 
48
+     * @param string $path Path where repositories will be searched
49
+     * @return array Found repositories, containing their name, path and description
50
+     */
51
+    public function getRepositories($path)
52
+    {
53
+        $dir = new \DirectoryIterator($path);
54
+
55
+        foreach ($dir as $file) {
56
+            $isBare = file_exists($file->getPathname() . '/HEAD');
57
+            $isRepository = file_exists($file->getPathname() . '/.git/HEAD');
58
+            
59
+            if ($file->isDir() && !$file->isDot() && $isRepository || $isBare) {
60
+                if ($isBare) {
61
+                    $description = file_get_contents($file->getPathname() . '/description');
62
+                } else {
63
+                    $description = file_get_contents($file->getPathname() . '/.git/description');
64
+                }
65
+                
66
+                $repositories[] = array('name' => $file->getFilename(), 'path' => $file->getPathname(), 'description' => $description);
67
+            }
68
+        }
69
+
70
+        if (!isset($repositories)) {
71
+            throw new \RuntimeException('There are no GIT repositories in ' . $path);
72
+        }
73
+        
74
+        sort($repositories);
75
+
76
+        return $repositories;
77
+    }
78
+
79
+    /**
80
+     * Execute a git command on the repository being manipulated
81
+     * 
82
+     * This method will start a new process on the current machine and
83
+     * run git commands. Once the command has been run, the method will 
84
+     * return the command line output.
85
+     * 
86
+     * @param Repository $repository Repository where the command will be run
87
+     * @param string $command Git command to be run
88
+     * @return string Returns the command output
89
+     */
90
+    public function run(Repository $repository, $command)
91
+    {
92
+        $descriptors = array(0 => array("pipe", "r"), 1 => array("pipe", "w"));
93
+        $process = proc_open($this->getPath() . ' ' . $command, $descriptors, $pipes, $repository->getPath());
94
+
95
+        if (is_resource($process)) {
96
+            $stdout = stream_get_contents($pipes[1]);
97
+            fclose($pipes[1]);
98
+            proc_close($process);
99
+            return $stdout;
100
+        }
101
+    }
102
+
103
+    /**
104
+     * Get the current Git binary path
105
+     * 
106
+     * @return string Path where the Git binary is located
107
+     */
108
+    protected function getPath()
109
+    {
110
+        return $this->path;
111
+    }
112
+
113
+    /**
114
+     * Set the current Git binary path
115
+     * 
116
+     * @param string $path Path where the Git binary is located
117
+     */
118
+    protected function setPath($path)
119
+    {
120
+        $this->path = $path;
121
+    }
122
+}
0 123
new file mode 100644
... ...
@@ -0,0 +1,35 @@
1
+<?php
2
+
3
+namespace Git\Commit;
4
+
5
+class Author
6
+{
7
+    protected $name;
8
+    protected $email;
9
+
10
+    public function __construct($name, $email)
11
+    {
12
+        $this->setName($name);
13
+        $this->setEmail($email);
14
+    }
15
+
16
+    public function getName()
17
+    {
18
+        return $this->name;
19
+    }
20
+
21
+    public function setName($name)
22
+    {
23
+        $this->name = $name;
24
+    }
25
+
26
+    public function getEmail()
27
+    {
28
+        return $this->email;
29
+    }
30
+
31
+    public function setEmail($email)
32
+    {
33
+        $this->email = $email;
34
+    }
35
+}
0 36
\ No newline at end of file
1 37
new file mode 100644
... ...
@@ -0,0 +1,148 @@
1
+<?php
2
+
3
+namespace Git\Commit;
4
+
5
+class Commit
6
+{
7
+    protected $hash;
8
+    protected $shortHash;
9
+    protected $treeHash;
10
+    protected $parentHash;
11
+    protected $author;
12
+    protected $date;
13
+    protected $commiter;
14
+    protected $commiterDate;
15
+    protected $message;
16
+    protected $diffs;
17
+
18
+    public function importData(array $data)
19
+    {
20
+        $this->setHash($data['hash']);
21
+        $this->setShortHash($data['short_hash']);
22
+        $this->setTreeHash($data['tree']);
23
+        $this->setParentHash($data['parent']);
24
+
25
+        $this->setAuthor(
26
+            new Author($data['author'], $data['author_email'])
27
+        );
28
+
29
+        $this->setDate(
30
+            new \DateTime('@' . $data['date'])
31
+        );
32
+
33
+        $this->setCommiter(
34
+            new Author($data['commiter'], $data['commiter_email'])
35
+        );
36
+
37
+        $this->setCommiterDate(
38
+            new \DateTime('@' . $data['commiter_date'])
39
+        );
40
+
41
+        $this->setMessage($data['message']);
42
+    }
43
+
44
+    public function getHash()
45
+    {
46
+        return $this->hash;
47
+    }
48
+
49
+    public function setHash($hash)
50
+    {
51
+        $this->hash = $hash;
52
+    }
53
+
54
+    public function getShortHash()
55
+    {
56
+        return $this->shortHash;
57
+    }
58
+
59
+    public function setShortHash($shortHash)
60
+    {
61
+        $this->shortHash = $shortHash;
62
+    }
63
+
64
+    public function getTreeHash()
65
+    {
66
+        return $this->treeHash;
67
+    }
68
+
69
+    public function setTreeHash($treeHash)
70
+    {
71
+        $this->treeHash = $treeHash;
72
+    }
73
+
74
+    public function getParentHash()
75
+    {
76
+        return $this->parentHash;
77
+    }
78
+
79
+    public function setParentHash($parentHash)
80
+    {
81
+        $this->parentHash = $parentHash;
82
+    }
83
+
84
+    public function getAuthor()
85
+    {
86
+        return $this->author;
87
+    }
88
+
89
+    public function setAuthor($author)
90
+    {
91
+        $this->author = $author;
92
+    }
93
+
94
+    public function getDate()
95
+    {
96
+        return $this->date;
97
+    }
98
+
99
+    public function setDate($date)
100
+    {
101
+        $this->date = $date;
102
+    }
103
+
104
+    public function getCommiter()
105
+    {
106
+        return $this->commiter;
107
+    }
108
+
109
+    public function setCommiter($commiter)
110
+    {
111
+        $this->commiter = $commiter;
112
+    }
113
+
114
+    public function getCommiterDate()
115
+    {
116
+        return $this->commiterDate;
117
+    }
118
+
119
+    public function setCommiterDate($commiterDate)
120
+    {
121
+        $this->commiterDate = $commiterDate;
122
+    }
123
+
124
+    public function getMessage()
125
+    {
126
+        return $this->message;
127
+    }
128
+
129
+    public function setMessage($message)
130
+    {
131
+        $this->message = $message;
132
+    }
133
+
134
+    public function getDiffs()
135
+    {
136
+        return $this->diffs;
137
+    }
138
+
139
+    public function setDiffs($diffs)
140
+    {
141
+        $this->diffs = $diffs;
142
+    }
143
+
144
+    public function getChangedFiles()
145
+    {
146
+        return sizeof($this->diffs);
147
+    }
148
+}
0 149
\ No newline at end of file
1 150
new file mode 100644
... ...
@@ -0,0 +1,23 @@
1
+<?php
2
+
3
+namespace Git;
4
+
5
+use Silex\Application;
6
+use Silex\ServiceProviderInterface;
7
+
8
+class GitServiceProvider implements ServiceProviderInterface
9
+{
10
+    /**
11
+     * Register the Git\Client on the Application ServiceProvider
12
+     * 
13
+     * @param Application $app Silex Application
14
+     * @return Git\Client Instance of the Git\Client
15
+     */
16
+    public function register(Application $app)
17
+    {
18
+        $app['git'] = function () use ($app) {
19
+            $default = $app['git.client'] ? $app['git.client'] : '/usr/bin/git';
20
+            return new Client($app['git.client']);
21
+        };
22
+    }
23
+}
0 24
\ No newline at end of file
1 25
new file mode 100644
... ...
@@ -0,0 +1,67 @@
1
+<?php
2
+
3
+namespace Git\Model;
4
+
5
+use Git\Client;
6
+use Git\Repository;
7
+use Git\ScopeAware;
8
+
9
+class Blob extends ScopeAware
10
+{
11
+    protected $mode;
12
+    protected $hash;
13
+    protected $name;
14
+    protected $size;
15
+
16
+    public function __construct($hash, Client $client, Repository $repository) {
17
+        $this->setClient($client);
18
+        $this->setRepository($repository);
19
+        $this->setHash($hash);
20
+    }
21
+
22
+    public function output()
23
+    {
24
+        $data = $this->getClient()->run($this->getRepository(), 'show ' . $this->getHash());
25
+        return $data;
26
+    }
27
+
28
+    public function getMode()
29
+    {
30
+        return $this->mode;
31
+    }
32
+
33
+    public function setMode($mode)
34
+    {
35
+        $this->mode = $mode;
36
+    }
37
+
38
+    public function getHash()
39
+    {
40
+        return $this->hash;
41
+    }
42
+
43
+    public function setHash($hash)
44
+    {
45
+        $this->hash = $hash;
46
+    }
47
+
48
+    public function getName()
49
+    {
50
+        return $this->name;
51
+    }
52
+
53
+    public function setName($name)
54
+    {
55
+        $this->name = $name;
56
+    }
57
+
58
+    public function getSize()
59
+    {
60
+        return $this->size;
61
+    }
62
+
63
+    public function setSize($size)
64
+    {
65
+        $this->size = $size;
66
+    }
67
+}
0 68
\ No newline at end of file
1 69
new file mode 100644
... ...
@@ -0,0 +1,60 @@
1
+<?php
2
+
3
+namespace Git\Model;
4
+
5
+use Git\Model\Line;
6
+
7
+class Diff
8
+{
9
+    protected $lines;
10
+    protected $index;
11
+    protected $old;
12
+    protected $new;
13
+    protected $file;
14
+
15
+    public function addLine($line)
16
+    {
17
+        $this->lines[] = new Line($line);
18
+    }
19
+
20
+    public function getLines()
21
+    {
22
+        return $this->lines;
23
+    }
24
+
25
+    public function setIndex($index)
26
+    {
27
+        $this->index = $index;
28
+    }
29
+
30
+    public function getIndex()
31
+    {
32
+        return $this->index;
33
+    }
34
+
35
+    public function setOld($old)
36
+    {
37
+        $this->old = $old;
38
+    }
39
+
40
+    public function getOld()
41
+    {
42
+        return $this->old;
43
+    }
44
+
45
+    public function setNew($new)
46
+    {
47
+        $this->new = $new;
48
+        $this->file = substr($new, 6);
49
+    }
50
+
51
+    public function getNew()
52
+    {
53
+        return $this->new;
54
+    }
55
+
56
+    public function getFile()
57
+    {
58
+        return $this->file;
59
+    }
60
+}
0 61
\ No newline at end of file
1 62
new file mode 100644
... ...
@@ -0,0 +1,48 @@
1
+<?php
2
+
3
+namespace Git\Model;
4
+
5
+class Line
6
+{
7
+    protected $line;
8
+    protected $type;
9
+
10
+    public function __construct($data)
11
+    {
12
+        if (!empty($data)) {
13
+            if ($data[0] == '@') {
14
+                $this->setType('chunk');
15
+            }
16
+
17
+            if ($data[0] == '-') {
18
+                $this->setType('old');
19
+            }
20
+
21
+            if ($data[0] == '+') {
22
+                $this->setType('new');
23
+            }
24
+        }
25
+
26
+        $this->setLine($data);
27
+    }
28
+
29
+    public function getLine()
30
+    {
31
+        return $this->line;
32
+    }
33
+
34
+    public function setLine($line)
35
+    {
36
+        $this->line = $line;
37
+    }
38
+
39
+    public function getType()
40
+    {
41
+        return $this->type;
42
+    }
43
+
44
+    public function setType($type)
45
+    {
46
+        $this->type = $type;
47
+    }
48
+}
0 49
\ No newline at end of file
1 50
new file mode 100644
... ...
@@ -0,0 +1,44 @@
1
+<?php
2
+
3
+namespace Git\Model;
4
+
5
+use Git\Client;
6
+use Git\Repository;
7
+use Git\ScopeAware;
8
+
9
+class Symlink
10
+{
11
+    protected $mode;
12
+    protected $name;
13
+    protected $path;
14
+
15
+    public function getMode()
16
+    {
17
+        return $this->mode;
18
+    }
19
+
20
+    public function setMode($mode)
21
+    {
22
+        $this->mode = $mode;
23
+    }
24
+
25
+    public function getName()
26
+    {
27
+        return $this->name;
28
+    }
29
+
30
+    public function setName($name)
31
+    {
32
+        $this->name = $name;
33
+    }
34
+
35
+    public function getPath()
36
+    {
37
+        return $this->path;
38
+    }
39
+
40
+    public function setPath($path)
41
+    {
42
+        $this->path = $path;
43
+    }
44
+}
0 45
\ No newline at end of file
1 46
new file mode 100644
... ...
@@ -0,0 +1,172 @@
1
+<?php
2
+
3
+namespace Git\Model;
4
+
5
+use Git\Client;
6
+use Git\Repository;
7
+use Git\ScopeAware;
8
+
9
+class Tree extends ScopeAware implements \RecursiveIterator
10
+{
11
+    protected $mode;
12
+    protected $hash;
13
+    protected $name;
14
+    protected $data;
15
+    protected $position = 0;
16
+    
17
+    public function __construct($hash, Client $client, Repository $repository) {
18
+        $this->setClient($client);
19
+        $this->setRepository($repository);
20
+        $this->setHash($hash);
21
+    }
22
+
23
+    public function parse()
24
+    {
25
+        $data = $this->getClient()->run($this->getRepository(), 'ls-tree -l ' . $this->getHash());
26
+        $lines = explode("\n", $data);
27
+        $files = array();
28
+        $root = array();
29
+
30
+        foreach ($lines as $key => $line) {
31
+            if (empty($line)) {
32
+                unset($lines[$key]);
33
+                continue;
34
+            }
35
+
36
+            $files[] = preg_split("/[\s]+/", $line);
37
+        }
38
+
39
+        foreach ($files as $file) {
40
+            if ($file[1] == 'commit') {
41
+                // submodule
42
+                continue;
43
+            }
44
+
45
+            if ($file[0] == '120000') {
46
+                $show = $this->getClient()->run($this->getRepository(), 'show ' . $file[2]);
47
+                $tree = new Symlink;
48
+                $tree->setMode($file[0]);
49
+                $tree->setName($file[4]);
50
+                $tree->setPath($show);
51
+                $root[] = $tree;
52
+                continue;
53
+            }
54
+
55
+            if ($file[1] == 'blob') {
56
+                $blob = new Blob($file[2], $this->getClient(), $this->getRepository());
57
+                $blob->setMode($file[0]);
58
+                $blob->setName($file[4]);
59
+                $blob->setSize($file[3]);
60
+                $root[] = $blob;
61
+                continue;
62
+            }
63
+
64
+            $tree = new Tree($file[2], $this->getClient(), $this->getRepository());
65
+            $tree->setMode($file[0]);
66
+            $tree->setName($file[4]);
67
+            $root[] = $tree;
68
+        }
69
+
70
+        $this->data = $root;
71
+    }
72
+
73
+    public function output()
74
+    {
75
+        $files = $folders = array();
76
+
77
+        foreach ($this as $node) {
78
+            if ($node instanceof Blob) {
79
+                $file['type'] = 'blob';
80
+                $file['name'] = $node->getName();
81
+                $file['size'] = $node->getSize();
82
+                $file['mode'] = $node->getMode();
83
+                $file['hash'] = $node->getHash();
84
+                $files[] = $file;
85
+                continue;
86
+            }
87
+
88
+            if ($node instanceof Tree) {
89
+                $folder['type'] = 'folder';
90
+                $folder['name'] = $node->getName();
91
+                $folder['size'] = '';
92
+                $folder['mode'] = $node->getMode();
93
+                $folder['hash'] = $node->getHash();
94
+                $folders[] = $folder;
95
+                continue;
96
+            }
97
+
98
+            if ($node instanceof Symlink) {
99
+                $folder['type'] = 'symlink';
100
+                $folder['name'] = $node->getName();
101
+                $folder['size'] = '';
102
+                $folder['mode'] = $node->getMode();
103
+                $folder['hash'] = '';
104
+                $folder['path'] = $node->getPath();
105
+                $folders[] = $folder;
106
+            }
107
+        }
108
+
109
+        // Little hack to make folders appear before files
110
+        $files = array_merge($folders, $files);
111
+
112
+        return $files;
113
+    }
114
+    
115
+    public function valid() {
116
+        return isset($this->data[$this->position]);
117
+    }
118
+    
119
+    public function hasChildren() {
120
+        return is_array($this->data[$this->position]);
121
+    }
122
+    
123
+    public function next() {
124
+        $this->position++;
125
+    }
126
+    
127
+    public function current() {
128
+        return $this->data[$this->position];
129
+    }
130
+    
131
+    public function getChildren() {
132
+        return $this->data[$this->position];
133
+    }
134
+    
135
+    public function rewind() {
136
+        $this->position = 0;
137
+    }
138
+    
139
+    public function key() {
140
+        return $this->position;
141
+    }
142
+
143
+    public function getMode()
144
+    {
145
+        return $this->mode;
146
+    }
147
+
148
+    public function setMode($mode)
149
+    {
150
+        $this->mode = $mode;
151
+    }
152
+
153
+    public function getHash()
154
+    {
155
+        return $this->hash;
156
+    }
157
+
158
+    public function setHash($hash)
159
+    {
160
+        $this->hash = $hash;
161
+    }
162
+
163
+    public function getName()
164
+    {
165
+        return $this->name;
166
+    }
167
+
168
+    public function setName($name)
169
+    {
170
+        $this->name = $name;
171
+    }
172
+}
0 173
\ No newline at end of file
1 174
new file mode 100644
... ...
@@ -0,0 +1,465 @@
1
+<?php
2
+
3
+namespace Git;
4
+
5
+use Git\Commit\Commit;
6
+use Git\Model\Tree;
7
+use Git\Model\Blob;
8
+use Git\Model\Diff;
9
+
10
+class Repository
11
+{
12
+    protected $path;
13
+    protected $client;
14
+
15
+    public function __construct($path, Client $client)
16
+    {
17
+        $this->setPath($path);
18
+        $this->setClient($client);
19
+    }
20
+
21
+    public function setClient(Client $client)
22
+    {
23
+        $this->client = $client;
24
+    }
25
+
26
+    public function getClient()
27
+    {
28
+        return $this->client;
29
+    }
30
+
31
+    public function create()
32
+    {
33
+        mkdir($this->getPath());
34
+        $this->getClient()->run($this, 'init');
35
+
36
+        return $this;
37
+    }
38
+
39
+    public function getConfig($key)
40
+    {
41
+        $key = $this->getClient()->run($this, 'config ' . $key);
42
+        return trim($key);
43
+    }
44
+
45
+    public function setConfig($key, $value)
46
+    {
47
+        $this->getClient()->run($this, "config $key \"$value\"");
48
+
49
+        return $this;
50
+    }
51
+    
52
+    /**
53
+     * Add untracked files
54
+     * 
55
+     * @access public
56
+     * @param mixed $files Files to be added to the repository
57
+     */
58
+    public function add($files = '.')
59
+    {
60
+        if(is_array($files)) {
61
+            $files = implode(' ', $files);
62
+        }
63
+        
64
+        $this->getClient()->run($this, "add $files");
65
+
66
+        return $this;
67
+    }
68
+
69
+    /**
70
+     * Add all untracked files
71
+     * 
72
+     * @access public
73
+     */
74
+    public function addAll()
75
+    {
76
+        $this->getClient()->run($this, "add -A");
77
+
78
+        return $this;
79
+    }
80
+    
81
+    /**
82
+     * Commit changes to the repository
83
+     * 
84
+     * @access public
85
+     * @param string $message Description of the changes made
86
+     */
87
+    public function commit($message)
88
+    {
89
+        $this->getClient()->run($this, "commit -m '$message'");
90
+
91
+        return $this;
92
+    }
93
+    
94
+    /**
95
+     * Checkout a branch
96
+     * 
97
+     * @access public
98
+     * @param string $branch Branch to be checked out
99
+     */
100
+    public function checkout($branch)
101
+    {
102
+        $this->getClient()->run($this, "checkout $branch");
103
+
104
+        return $this;
105
+    }
106
+
107
+    /**
108
+     * Pull repository changes
109
+     * 
110
+     * @access public
111
+     */
112
+    public function pull()
113
+    {
114
+        $this->getClient()->run($this, "pull");
115
+
116
+        return $this;
117
+    }
118
+    
119
+    /**
120
+     * Update remote references
121
+     * 
122
+     * @access public
123
+     * @param string $repository Repository to be pushed
124
+     * @param string $refspec Refspec for the push
125
+     */
126
+    public function push($repository = null, $refspec = null)
127
+    {
128
+        $command = "push";
129
+        
130
+        if($repository) {
131
+            $command .= " $repository";
132
+        } 
133
+        
134
+        if($refspec) {
135
+            $command .= " $refspec";
136
+        }
137
+        
138
+        $this->getClient()->run($this, $command);
139
+
140
+        return $this;
141
+    }
142
+    
143
+    /**
144
+     * Show a list of the repository branches
145
+     * 
146
+     * @access public
147
+     * @return array List of branches
148
+     */
149
+    public function getBranches()
150
+    {
151
+        $branches = $this->getClient()->run($this, "branch");
152
+        $branches = explode("\n", $branches);
153
+        $branches = array_filter(preg_replace('/[\*\s]/', '', $branches));
154
+        
155
+        return $branches;
156
+    }
157
+    
158
+    /**
159
+     * Show the current repository branch
160
+     * 
161
+     * @access public
162
+     * @return string Current repository branch
163
+     */
164
+    public function getCurrentBranch()
165
+    {
166
+        $branches = $this->getClient()->run($this, "branch");
167
+        $branches = explode("\n", $branches);
168
+        
169
+        foreach($branches as $branch) {
170
+            if($branch[0] == '*') {
171
+                return substr($branch, 2);
172
+            }
173
+        }
174
+    }
175
+    
176
+    /**
177
+     * Check if a specified branch exists
178
+     * 
179
+     * @access public
180
+     * @param string $branch Branch to be checked
181
+     * @return boolean True if the branch exists
182
+     */
183
+    public function hasBranch($branch)
184
+    {
185
+        $branches = $this->getBranches();
186
+        $status = in_array($branch, $branches);
187
+        return $status;
188
+    }
189
+
190
+    /**
191
+     * Create a new repository branch
192
+     * 
193
+     * @access public
194
+     * @param string $branch Branch name
195
+     */
196
+    public function createBranch($branch)
197
+    {
198
+        $this->getClient()->run($this, "branch $branch");
199
+    }
200
+    
201
+    /**
202
+     * Show a list of the repository tags
203
+     * 
204
+     * @access public
205
+     * @return array List of tags
206
+     */
207
+    public function getTags()
208
+    {
209
+        $tags = $this->getClient()->run($this, "tag");
210
+        $tags = explode("\n", $tags);
211
+
212
+        if (empty($tags[0])) {
213
+            return NULL;
214
+        }
215
+        
216
+        return $tags;
217
+    }
218
+    
219
+    /**
220
+     * Show the repository commit log
221
+     * 
222
+     * @access public
223
+     * @return array Commit log
224
+     */
225
+    public function getCommits($file = null)
226
+    {
227
+        $command = 'log --pretty=format:\'"%h": {"hash": "%H", "short_hash": "%h", "tree": "%T", "parent": "%P", "author": "%an", "author_email": "%ae", "date": "%at", "commiter": "%cn", "commiter_email": "%ce", "commiter_date": "%ct", "message": "%f"}\'';
228
+        
229
+        if ($file) {
230
+            $command .= " $file";
231
+        }
232
+
233
+        $logs = $this->getClient()->run($this, $command);
234
+        $logs = str_replace("\n", ',', $logs);
235
+        $logs = json_decode("{ $logs }", true);
236
+
237
+        foreach ($logs as $log) {
238
+            $log['message'] = str_replace('-', ' ', $log['message']);
239
+            $commit = new Commit;
240
+            $commit->importData($log);
241
+            $commits[] = $commit;
242
+        }
243
+
244
+        return $commits;
245
+    }
246
+
247
+    public function getRelatedCommits($hash)
248
+    {
249
+        $logs = $this->getClient()->run($this, 'log --pretty=format:\'"%h": {"hash": "%H", "short_hash": "%h", "tree": "%T", "parent": "%P", "author": "%an", "author_email": "%ae", "date": "%at", "commiter": "%cn", "commiter_email": "%ce", "commiter_date": "%ct", "message": "%f"}\'');
250
+        $logs = str_replace("\n", ',', $logs);
251
+        $logs = json_decode("{ $logs }", true);
252
+
253
+        foreach ($logs as $log) {
254
+            $log['message'] = str_replace('-', ' ', $log['message']);
255
+            $logTree = $this->getClient()->run($this, 'diff-tree -t -r ' . $log['hash']);
256
+            $lines = explode("\n", $logTree);
257
+            array_shift($lines);
258
+            $files = array();
259
+
260
+            foreach ($lines as $key => $line) {
261
+                if (empty($line)) {
262
+                    unset($lines[$key]);
263
+                    continue;
264
+                }
265
+
266
+                $files[] = preg_split("/[\s]+/", $line);
267
+            }
268
+
269
+            // Now let's find the commits who have our hash within them
270
+            foreach ($files as $file) {
271
+                if ($file[1] == 'commit') {
272
+                    continue;
273
+                }
274
+
275
+                if ($file[3] == $hash) {
276
+                    $commit = new Commit;
277
+                    $commit->importData($log);
278
+                    $commits[] = $commit;
279
+                    break;
280
+                }
281
+            }
282
+        }
283
+
284
+        return $commits;
285
+    }
286
+
287
+    public function getCommit($commit)
288
+    {
289
+        $logs = $this->getClient()->run($this, 'show --pretty=format:\'{"hash": "%H", "short_hash": "%h", "tree": "%T", "parent": "%P", "author": "%an", "author_email": "%ae", "date": "%at", "commiter": "%cn", "commiter_email": "%ce", "commiter_date": "%ct", "message": "%f"}\' ' . $commit);
290
+        $logs = explode("\n", $logs);
291
+
292
+        // Read commit metadata
293
+        $data = json_decode($logs[0], true);
294
+        $data['message'] = str_replace('-', ' ', $data['message']);
295
+        $commit = new Commit;
296
+        $commit->importData($data);
297
+        unset($logs[0]);
298
+
299
+        // Read diff logs
300
+        foreach ($logs as $log) {
301
+            if ('diff' === substr($log, 0, 4)) {
302
+                if (isset($diff)) {
303
+                    $diffs[] = $diff;
304
+                }
305
+
306
+                $diff = new Diff;
307
+                continue;
308
+            }
309
+
310
+            if ('index' === substr($log, 0, 5)) {
311
+                $diff->setIndex($log);
312
+                continue;
313
+            }
314
+
315
+            if ('---' === substr($log, 0, 3)) {
316
+                $diff->setOld($log);
317
+                continue;
318
+            }
319
+
320
+            if ('+++' === substr($log, 0, 3)) {
321
+                $diff->setNew($log);
322
+                continue;
323
+            }
324
+
325
+            $diff->addLine($log);
326
+        }
327
+
328
+        if (isset($diff)) {
329
+            $diffs[] = $diff;
330
+        }
331
+
332
+        $commit->setDiffs($diffs);
333
+
334
+        return $commit;
335
+    }
336
+
337
+    public function getAuthorStatistics()
338
+    {
339
+        $logs = $this->getClient()->run($this, 'log --pretty=format:\'%an||%ae\'');
340
+        $logs = explode("\n", $logs);
341
+        $logs = array_count_values($logs);
342
+        arsort($logs);
343
+
344
+        foreach ($logs as $user => $count) {
345
+            $user = explode('||', $user);
346
+            $data[] = array('name' => $user[0], 'email' => $user[1], 'commits' => $count); 
347
+        }
348
+        
349
+        return $data;
350
+    }
351
+
352
+    public function getStatistics($branch)
353
+    {
354
+        // Calculate amount of files, extensions and file size
355
+        $logs = $this->getClient()->run($this, 'ls-tree -r -l ' . $branch);
356
+        $lines = explode("\n", $logs);
357
+        $files = array();
358
+        $data['extensions'] = array();
359
+        $data['size'] = 0;
360
+        $data['files'] = 0;
361
+
362
+        foreach ($lines as $key => $line) {
363
+            if (empty($line)) {
364
+                unset($lines[$key]);
365
+                continue;
366
+            }
367
+
368
+            $files[] = preg_split("/[\s]+/", $line);
369
+        }
370
+
371
+        foreach ($files as $file) {
372
+            if ($file[1] == 'blob') {
373
+                $data['files']++;
374
+            }
375
+
376
+            if (is_numeric($file[3])) {
377
+                $data['size'] += $file[3];
378
+            }
379
+
380
+            if (($pos = strrpos($file[4], '.')) !== FALSE) {
381
+                $data['extensions'][] = substr($file[4], $pos);
382
+            }
383
+        }
384
+
385
+        $data['extensions'] = array_count_values($data['extensions']);
386
+        arsort($data['extensions']);
387
+
388
+        return $data;
389
+    }
390
+
391
+    /**
392
+     * Get the Tree for the provided folder
393
+     * 
394
+     * @param string $tree Folder that will be parsed
395
+     * @return Tree Instance of Tree for the provided folder
396
+     */
397
+    public function getTree($tree)
398
+    {
399
+        $tree = new Tree($tree, $this->getClient(), $this);
400
+        $tree->parse();
401
+        return $tree;
402
+    }
403
+
404
+    /**
405
+     * Get the Blob for the provided file
406
+     * 
407
+     * @param string $blob File that will be parsed
408
+     * @return Blob Instance of Blob for the provided file
409
+     */
410
+    public function getBlob($blob)
411
+    {
412
+        return new Blob($blob, $this->getClient(), $this);
413
+    }
414
+
415
+    /**
416
+     * Blames the provided file and parses the output
417
+     * 
418
+     * @param string $file File that will be blamed
419
+     * @return array Commits hashes containing the lines
420
+     */
421
+    public function getBlame($file)
422
+    {
423
+        $logs = $this->getClient()->run($this, "blame -s $file");
424
+        $logs = explode("\n", $logs);
425
+
426
+        foreach ($logs as $log) {
427
+            if ($log == '') {
428
+                continue;
429
+            }
430
+
431
+            $split = preg_split("/[a-zA-Z0-9^]{8}[\s]+[0-9]+\)/", $log);
432
+            preg_match_all("/([a-zA-Z0-9^]{8})[\s]+([0-9]+)\)/", $log, $match);
433
+
434
+            $commit = $match[1][0];
435
+
436
+            if (!isset($blame[$commit]['line'])) {
437
+                $blame[$commit]['line'] = '';
438
+            }
439
+
440
+            $blame[$commit]['line'] .= PHP_EOL . $split[1];
441
+        }
442
+
443
+        return $blame;
444
+    }
445
+
446
+    /**
447
+     * Get the current Repository path
448
+     * 
449
+     * @return string Path where the repository is located
450
+     */
451
+    public function getPath()
452
+    {
453
+        return $this->path;
454
+    }
455
+
456
+    /**
457
+     * Set the current Repository path
458
+     * 
459
+     * @param string $path Path where the repository is located
460
+     */
461
+    public function setPath($path)
462
+    {
463
+        $this->path = $path;
464
+    }
465
+}
0 466
new file mode 100644
... ...
@@ -0,0 +1,29 @@
1
+<?php
2
+
3
+namespace Git;
4
+
5
+class ScopeAware
6
+{
7
+    protected $client;
8
+    protected $repository;
9
+
10
+    public function setClient(Client $client)
11
+    {
12
+        $this->client = $client;
13
+    }
14
+
15
+    public function getClient()
16
+    {
17
+        return $this->client;
18
+    }
19
+
20
+    public function getRepository()
21
+    {
22
+        return $this->repository;
23
+    }
24
+
25
+    public function setRepository($repository)
26
+    {
27
+        $this->repository = $repository;
28
+    }
29
+}
0 30
\ No newline at end of file
1 31
new file mode 100644
... ...
@@ -0,0 +1,18 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+
3
+<phpunit backupGlobals="false"
4
+         backupStaticAttributes="false"
5
+         colors="true"
6
+         convertErrorsToExceptions="true"
7
+         convertNoticesToExceptions="true"
8
+         convertWarningsToExceptions="true"
9
+         processIsolation="false"
10
+         stopOnFailure="false"
11
+         syntaxCheck="true"
12
+>
13
+    <testsuites>
14
+        <testsuite name="GitList Test Suite">
15
+            <directory>./tests/</directory>
16
+        </testsuite>
17
+    </testsuites>
18
+</phpunit>
0 19
new file mode 100644
... ...
@@ -0,0 +1,371 @@
1
+<?php
2
+
3
+use Git\Client;
4
+use Git\Repository;
5
+
6
+spl_autoload_register(function($class)