reactivated project (dev: http://git-dev.pk910.de)
[phpgitweb.git] / htdocs / lib / GitCommand.class.php
1 <?php
2 /* GitCommand.class.php - phpgitweb
3  * Copyright (C) 2011-2012  Philipp Kreil (pk910)
4  * 
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  * 
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License 
16  * along with this program. If not, see <http://www.gnu.org/licenses/>. 
17  */
18
19 class GitCommand {
20         private static $git = null;
21         private static $counter = 0;
22         
23         private static function command_header() {
24                 if(!self::$git) {
25                         //find git executable
26                         if(GitConfig::GIT_EXEC)
27                                 self::$git = GitConfig::GIT_EXEC;
28                         else
29                                 self::$git = str_replace(array("\n", "\r"), array("", ""), `which git`);
30                         if(!self::$git) {
31                                 trigger_error("Can not find git executable.", E_USER_ERROR);
32                                 self::$git = "git";
33                         }
34                 }
35                 $command = self::$git;
36                 
37                 return $command;
38         }
39         
40         private static function git_execute($params, $git_path = null) {
41                 $result = array();
42                 
43                 if($git_path) {
44                         $args = array("--git-dir=".$git_path);
45                         $args = array_merge($args, $params);
46                 } else
47                         $args = $params;
48                 
49                 $command = self::command_header();
50                 foreach($args as $arg) {
51                         $command .= ' '.escapeshellarg($arg);
52                 }
53                 
54                 exec($command, $result);
55                 self::$counter++;
56                 return implode("\n", $result);
57         }
58         
59         public static function core_version() {
60                 $args = array("--version");
61                 $version = self::git_execute($args);
62                 preg_match("/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/i", $version, $result);
63                 return $result[0];
64         }
65         
66         public static function last_activity($git_path, $ref = null) {
67                 $args = array("for-each-ref", "--format=%(committer)", "--sort=-committerdate", "--count=1");
68                 if($ref)
69                         $args[] = $ref;
70                 $age = self::git_execute($args, $git_path);
71                 if(preg_match("/[0-9]{9,}/i", $age, $result))
72                         return $result[0];
73                 else
74                         return -1;
75         }
76         
77         private static function parse_commit($commit_data) {
78                 $commit = array();
79                 $rev_lines = explode("\n", str_replace("\r", "", $commit_data));
80                 $commit['id'] = $rev_lines[0];
81                 $commit['parent'] = array();
82                 if(!preg_match("/^[a-f0-9]{40}$/i", $commit['id']))
83                         return null;
84                 foreach($rev_lines as $rev_line) {
85                         if(substr($rev_line, 0, 4) == "    ") {
86                                 if(array_key_exists('text', $commit))
87                                         $commit['text'] .= "\n";
88                                 else
89                                         $commit['text'] = '';
90                                 $commit['text'] .= substr($rev_line, 4);
91                         } else {
92                                 $opt = explode(" ", $rev_line, 2);
93                                 if($opt[0] == "tree")
94                                         $commit['tree'] = $opt[1];
95                                 else if($opt[0] == "parent")
96                                         $commit['parent'][] = $opt[1];
97                                 else if($opt[0] == "author") {
98                                         preg_match('/(.*) <([^>]*)> ([0-9]*) ([+\-0-9]{5})/i', $opt[1], $matches);
99                                         $commit['author'] = $matches[1];
100                                         $commit['author_mail'] = $matches[2];
101                                         $commit['author_time'] = $matches[3];
102                                         $commit['author_timezone'] = $matches[4];
103                                 } else if($opt[0] == "committer") {
104                                         preg_match('/(.*) <([^>]*)> ([0-9]*) ([+\-0-9]{5})/i', $opt[1], $matches);
105                                         $commit['committer'] = $matches[1];
106                                         $commit['committer_mail'] = $matches[2];
107                                         $commit['committer_time'] = $matches[3];
108                                         $commit['committer_timezone'] = $matches[4];
109                                 }
110                         }
111                 }
112                 return $commit;
113         }
114         
115         public static function get_commits($git_path, $head, $maxcount, $skip, $file = null) {
116                 $args = array("rev-list", "--header", "--max-count=".$maxcount, "--skip=".$skip, ($head ? $head : "--all"), "--");
117                 if($file)
118                         $args[] = $file;
119                 $commit_list = self::git_execute($args, $git_path);
120                 $commits = array();
121                 foreach(explode("\000", $commit_list) as $commit) {
122                         if($commit)
123                                 $commits[] = self::parse_commit($commit);
124                 }
125                 return $commits;
126         }
127         
128         public static function get_commit($git_path, $commit_id) {
129                 $args = array("rev-list", "--header", "--max-count=1", ($commit_id ? $commit_id : "--all"), "--");
130                 $commit_data = self::git_execute($args, $git_path);
131                 $commit = self::parse_commit($commit_data);
132                 return $commit;
133         }
134         
135         public static function get_hash($git_path, $ref) {
136                 $args = array("rev-parse", "--verify", "-q", $ref);
137                 $result = self::git_execute($args, $git_path);
138                 if(preg_match("#([a-f0-9]{40})#i", $result, $match))
139                         return $match[1];
140                 return null;
141         }
142         
143         private static function parse_difftree($line) {
144                 $entry = array();
145                 if(preg_match('/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/i', $line, $matches)) {
146                         $entry['parents'] = 1;
147                         $entry['from_mode'] = $matches[1];
148                         $entry['to_mode'] = $matches[2];
149                         $entry['from_id'] = $matches[3];
150                         $entry['to_id'] = $matches[4];
151                         $entry['status'] = $matches[5];
152                         $entry['similarity'] = $matches[6];
153                         $entry['file'] = $matches[7];
154                         if($entry['status'] == 'R' || $entry['status'] == 'C') { //renamed or copied
155                                 $files = explode("\t", $entry['file']);
156                                 $entry['from_file'] = $files[0];
157                                 $entry['to_file'] = $files[1];
158                         } else
159                                 $entry['from_file'] = $entry['to_file'] = $entry['file'];
160                 } else if(preg_match('/^(::+)((?:[0-7]{6} )+)((?:[0-9a-fA-F]{40} )+)([a-zA-Z]+)\t(.*)$/i', $line, $matches)) {
161                         $entry['parents'] = strlen($matches[1]);
162                         $from_modes = explode(" ", $matches[2]);
163                         $entry['from_mode'] = array_slice($from_modes, 1, -1);
164                         $entry['to_mode'] = $from_modes[0];
165                         $from_ids = explode(" ", $matches[3]);
166                         $entry['from_id'] = array_slice($from_ids, 1, -1);
167                         $entry['to_id'] = $from_ids[0];
168                         $entry['status'] = str_split($matches[4]);
169                         $entry['file'] = $entry['from_file'] = $entry['to_file'] = $matches[5];
170                 }
171                 return $entry;
172         }
173         
174         public static function get_commit_changes($git_path, $commit_id, $parents) {
175                 $args = array("diff-tree", "-r", "--no-commit-id");
176                 switch(GitConfig::DETECT_RENAME_LEVEL) {
177                 case 0:
178                         $args[] = "-M";
179                         break;
180                 case 1:
181                         $args[] = "-C";
182                         break;
183                 case 2:
184                         $args[] = "-C";
185                         $args[] = "--find-copies-harder";
186                         break;
187                 }
188                 if(GitConfig::DETECT_REWRITES)
189                         $args[] = "-B";
190                 if(is_array($parents) && count($parents) > 1)
191                         $args[] = "-c";
192                 else if(is_array($parents) && count($parents) == 1)
193                         $args[] = $parents[0];
194                 else
195                         $args[] = "--root";
196                 $args[] = $commit_id;
197                 $args[] = "--";
198                 $result = self::git_execute($args, $git_path);
199                 $tree = array();
200                 foreach(explode("\n", $result) as $line) {
201                         if($line == "")
202                                 continue;
203                         $tree[] = self::parse_difftree($line);
204                 }
205                 return $tree;
206         }
207         
208         public static function get_commit_diff($git_path, $commit_id, $parents) {
209                 $args = array("diff-tree", "-r", "--no-commit-id", "--patch-with-raw", "--full-index");
210                 switch(GitConfig::DETECT_RENAME_LEVEL) {
211                 case 0:
212                         $args[] = "-M";
213                         break;
214                 case 1:
215                         $args[] = "-C";
216                         break;
217                 case 2:
218                         $args[] = "-C";
219                         $args[] = "--find-copies-harder";
220                         break;
221                 }
222                 if(GitConfig::DETECT_REWRITES)
223                         $args[] = "-B";
224                 if(is_array($parents) && count($parents) > 1)
225                         $args[] = "--cc";
226                 else if(is_array($parents) && count($parents) == 1)
227                         $args[] = $parents[0];
228                 else
229                         $args[] = "--root";
230                 $args[] = $commit_id;
231                 $args[] = "--";
232                 $result = self::git_execute($args, $git_path);
233                 $tree = array();
234                 $diffs = array();
235                 $diff = null;
236                 $parse_difftree = true;
237                 foreach(explode("\n", $result) as $line) {
238                         if($line == "" && $parse_difftree)
239                                 $parse_difftree = false;
240                         else if($parse_difftree)
241                                 $tree[] = self::parse_difftree($line);
242                         else {
243                                 if(preg_match('/^diff/i', $line)) {
244                                         if($diff)
245                                                 $diffs[] = $diff;
246                                         $diff = array();
247                                 }
248                                 $diff[] = $line;
249                         }
250                 }
251                 if($diff)
252                         $diffs[] = $diff;
253                 $diff_data['tree'] = $tree;
254                 return array('tree' => $tree, 'diffs' => $diffs);
255         }
256 }