Show tree/blob path in nav.
[viewgit.git] / index.php
blob1def1dcda33cc37b4a59467c9bbdbae2077ffaf9
1 <?php
2 error_reporting(E_ALL);
4 require_once('inc/config.php');
6 /**
7 * Formats "git diff" output into xhtml.
8 */
9 function format_diff($text)
11 $text = htmlentities($text);
13 $text = preg_replace(
14 array(
15 '/^(\+.*)$/m',
16 '/^(-.*)$/m',
17 '/^(@.*)$/m',
18 '/^([^\+-@].*)$/m',
20 array(
21 '<span class="add">$1</span>',
22 '<span class="del">$1</span>',
23 '<span class="pos">$1</span>',
24 '<span class="etc">$1</span>',
26 $text);
28 return $text;
31 function get_project_info($name)
33 global $conf;
35 $info = $conf['projects'][$name];
36 $info['name'] = $name;
37 $info['description'] = file_get_contents($info['repo'] .'/description');
39 $headinfo = git_get_commit_info($name, 'HEAD');
40 $info['head_stamp'] = $headinfo['author_utcstamp'];
41 $info['head_datetime'] = strftime($conf['datetime'], $headinfo['author_utcstamp']);
42 $info['head_hash'] = $headinfo['h'];
43 $info['head_tree'] = $headinfo['tree'];
45 return $info;
48 /**
49 * Get details of a commit: tree, parent, author/committer (name, mail, date), message
51 function git_get_commit_info($project, $hash = 'HEAD')
53 $info = array();
54 $info['h'] = $hash;
55 $info['message_full'] = '';
57 $output = run_git($project, "git rev-list --header --max-count=1 $hash");
58 // tree <h>
59 // parent <h>
60 // author <name> "<"<mail>">" <stamp> <timezone>
61 // committer
62 // <empty>
63 // <message>
64 $pattern = '/^(author|committer) ([^<]+) <([^>]*)> ([0-9]+) (.*)$/';
65 foreach ($output as $line) {
66 if (substr($line, 0, 4) === 'tree') {
67 $info['tree'] = substr($line, 5);
69 elseif (substr($line, 0, 6) === 'parent') {
70 $info['parent'] = substr($line, 7);
72 elseif (preg_match($pattern, $line, $matches) > 0) {
73 $info[$matches[1] .'_name'] = $matches[2];
74 $info[$matches[1] .'_mail'] = $matches[3];
75 $info[$matches[1] .'_stamp'] = $matches[4];
76 $info[$matches[1] .'_timezone'] = $matches[5];
77 $info[$matches[1] .'_utcstamp'] = $matches[4] - ((intval($matches[5]) / 100.0) * 3600);
79 elseif (substr($line, 0, 4) === ' ') {
80 $info['message_full'] .= substr($line, 4) ."\n";
81 if (!isset($info['message'])) {
82 $info['message'] = substr($line, 4, 40);
85 elseif (preg_match('/^[0-9a-f]{40}$/', $line) > 0) {
86 $info['h'] = $line;
90 return $info;
93 function git_get_heads($project)
95 $heads = array();
97 $output = run_git($project, 'git show-ref --heads');
98 foreach ($output as $line) {
99 $fullname = substr($line, 41);
100 $name = array_pop(explode('/', $fullname));
101 $heads[] = array('h' => substr($line, 0, 40), 'fullname' => "$fullname", 'name' => "$name");
104 return $heads;
107 function git_get_rev_list($project, $max_count = null, $start = 'HEAD')
109 $cmd = "git rev-list $start";
110 if (!is_null($max_count)) {
111 $cmd = "git rev-list --max-count=$max_count $start";
114 return run_git($project, $cmd);
117 function git_get_tags($project)
119 $tags = array();
121 $output = run_git($project, 'git show-ref --tags');
122 foreach ($output as $line) {
123 $fullname = substr($line, 41);
124 $name = array_pop(explode('/', $fullname));
125 $tags[] = array('h' => substr($line, 0, 40), 'fullname' => $fullname, 'name' => $name);
127 return $tags;
130 function git_ls_tree($project, $tree)
132 $entries = array();
133 $output = run_git($project, "git ls-tree $tree");
134 // 100644 blob 493b7fc4296d64af45dac64bceac2d9a96c958c1 .gitignore
135 // 040000 tree 715c78b1011dc58106da2a1af2fe0aa4c829542f doc
136 foreach ($output as $line) {
137 $parts = preg_split('/\s+/', $line, 4);
138 $entries[] = array('name' => $parts[3], 'mode' => $parts[0], 'type' => $parts[1], 'hash' => $parts[2]);
141 return $entries;
144 function makelink($dict)
146 $params = array();
147 foreach ($dict as $k => $v) {
148 $params[] = rawurlencode($k) .'='. str_replace('%2F', '/', rawurlencode($v));
150 if (count($params) > 0) {
151 return '?'. htmlentities(join('&', $params));
153 return '';
157 * Executes a git command in the project repo.
158 * @return array of output lines
160 function run_git($project, $command)
162 global $conf;
164 $output = array();
165 $cmd = "GIT_DIR=". $conf['projects'][$project]['repo'] ." $command";
166 exec($cmd, &$output);
167 return $output;
171 * Executes a git command in the project repo, sending output directly to the
172 * client.
174 function run_git_passthru($project, $command)
176 global $conf;
178 $cmd = "GIT_DIR=". $conf['projects'][$project]['repo'] ." $command";
179 $result = 0;
180 passthru($cmd, &$result);
181 return $result;
185 * Makes sure the given project is valid. If it's not, this function will
186 * die().
187 * @return the project
189 function validate_project($project)
191 global $conf;
193 if (!in_array($project, array_keys($conf['projects']))) {
194 die('Invalid project');
196 return $project;
200 * Makes sure the given hash is valid. If it's not, this function will die().
201 * @return the hash
203 function validate_hash($hash)
205 if (strlen($hash) != 40 || !preg_match('/^[0-9a-z]*$/', $hash)) {
206 die('Invalid hash');
209 return $hash;
212 $action = 'index';
213 $template = 'index';
214 $page['title'] = 'ViewGit';
216 if (isset($_REQUEST['a'])) {
217 $action = strtolower($_REQUEST['a']);
219 $page['action'] = $action;
221 if ($action === 'index') {
222 $template = 'index';
223 $page['title'] = 'List of projects - ViewGit';
225 foreach (array_keys($conf['projects']) as $p) {
226 $page['projects'][] = get_project_info($p);
229 elseif ($action === 'archive') {
230 $project = validate_project($_REQUEST['p']);
231 $tree = validate_hash($_REQUEST['h']);
232 $type = $_REQUEST['t'];
234 $basename = "$project-tree-$tree";
235 if (isset($_REQUEST['n'])) {
236 $basename = "$project-$_REQUEST[n]-". substr($tree, 0, 6);
239 if ($type === 'targz') {
240 header("Content-Type: application/x-tar-gz");
241 header("Content-Transfer-Encoding: binary");
242 header("Content-Disposition: attachment; filename=\"$basename.tar.gz\";");
243 run_git_passthru($project, "git archive --format=tar $tree |gzip");
245 elseif ($type === 'zip') {
246 header("Content-Type: application/x-zip");
247 header("Content-Transfer-Encoding: binary");
248 header("Content-Disposition: attachment; filename=\"$basename.zip\";");
249 run_git_passthru($project, "git archive --format=zip $tree");
251 else {
252 die('Invalid archive type requested');
255 die();
257 // blob: send a blob to browser with filename suggestion
258 elseif ($action === 'blob') {
259 $project = validate_project($_REQUEST['p']);
260 $hash = validate_hash($_REQUEST['h']);
261 $name = $_REQUEST['n'];
263 header('Content-type: application/octet-stream');
264 header("Content-Disposition: attachment; filename=$name"); // FIXME needs quotation
266 run_git_passthru($project, "git cat-file blob $hash");
267 die();
269 elseif ($action === 'commit') {
270 $template = 'commit';
271 $page['project'] = validate_project($_REQUEST['p']);
272 $page['title'] = "$page[project] - Commit - ViewGit";
273 $page['commit_id'] = validate_hash($_REQUEST['h']);
275 $info = git_get_commit_info($page['project'], $page['commit_id']);
277 $page['author_name'] = $info['author_name'];
278 $page['author_mail'] = $info['author_mail'];
279 $page['author_datetime'] = strftime($conf['datetime'], $info['author_utcstamp']);
280 $page['author_datetime_local'] = strftime($conf['datetime'], $info['author_stamp']) .' '. $info['author_timezone'];
281 $page['committer_name'] = $info['committer_name'];
282 $page['committer_mail'] = $info['committer_mail'];
283 $page['committer_datetime'] = strftime($conf['datetime'], $info['committer_utcstamp']);
284 $page['committer_datetime_local'] = strftime($conf['datetime'], $info['committer_stamp']) .' '. $info['committer_timezone'];
285 $page['tree_id'] = $info['tree'];
286 $page['parent'] = $info['parent'];
287 $page['message'] = $info['message'];
288 $page['message_full'] = $info['message_full'];
291 elseif ($action === 'commitdiff') {
292 $template = 'commitdiff';
293 $page['project'] = validate_project($_REQUEST['p']);
294 $page['title'] = "$page[project] - Commitdiff - ViewGit";
295 $hash = validate_hash($_REQUEST['h']);
296 $page['commit_id'] = $hash;
298 $info = git_get_commit_info($page['project'], $hash);
300 $page['tree_id'] = $info['tree'];
302 $page['message'] = $info['message'];
303 $page['message_full'] = $info['message_full'];
304 $page['author_name'] = $info['author_name'];
305 $page['author_mail'] = $info['author_mail'];
306 $page['author_datetime'] = strftime($conf['datetime'], $info['author_utcstamp']);
308 $text = join("\n", run_git($page['project'], "git diff $hash^..$hash"));
309 $page['diffdata'] = format_diff($text);
311 elseif ($action === 'shortlog') {
312 $template = 'shortlog';
313 $page['project'] = validate_project($_REQUEST['p']);
314 $page['title'] = "$page[project] - Shortlog - ViewGit";
315 if (isset($_REQUEST['h'])) {
316 $page['ref'] = validate_hash($_REQUEST['h']);
317 } else {
318 $page['ref'] = 'HEAD';
321 $info = git_get_commit_info($page['project']);
322 $page['commit_id'] = $info['h'];
323 $page['tree_id'] = $info['tree'];
325 // TODO merge the logic with 'summary' below
326 $revs = git_get_rev_list($page['project'], $conf['summary_shortlog'], $page['ref']); // TODO pass first rev as parameter
327 foreach ($revs as $rev) {
328 $info = git_get_commit_info($page['project'], $rev);
329 $page['shortlog'][] = array(
330 'author' => $info['author_name'],
331 'date' => strftime($conf['datetime'], $info['author_utcstamp']),
332 'message' => $info['message'],
333 'commit_id' => $rev,
334 'tree' => $info['tree'],
338 elseif ($action === 'summary') {
339 $template = 'summary';
340 $page['project'] = validate_project($_REQUEST['p']);
341 $page['title'] = "$page[project] - Summary - ViewGit";
343 $info = git_get_commit_info($page['project']);
344 $page['commit_id'] = $info['h'];
345 $page['tree_id'] = $info['tree'];
347 $revs = git_get_rev_list($page['project'], $conf['summary_shortlog']);
348 foreach ($revs as $rev) {
349 $info = git_get_commit_info($page['project'], $rev);
350 $page['shortlog'][] = array(
351 'author' => $info['author_name'],
352 'date' => strftime($conf['datetime'], $info['author_utcstamp']),
353 'message' => $info['message'],
354 'commit_id' => $rev,
355 'tree' => $info['tree'],
359 $tags = git_get_tags($page['project']);
360 $page['tags'] = array();
361 foreach ($tags as $tag) {
362 $info = git_get_commit_info($page['project'], $tag['h']);
363 $page['tags'][] = array(
364 'date' => strftime($conf['datetime'], $info['author_utcstamp']),
365 'h' => $tag['h'],
366 'fullname' => $tag['fullname'],
367 'name' => $tag['name'],
371 $heads = git_get_heads($page['project']);
372 $page['heads'] = array();
373 foreach ($heads as $h) {
374 $info = git_get_commit_info($page['project'], $h['h']);
375 $page['heads'][] = array(
376 'date' => strftime($conf['datetime'], $info['author_utcstamp']),
377 'h' => $h['h'],
378 'fullname' => $h['fullname'],
379 'name' => $h['name'],
384 * Shows a tree, with list of directories/files, links to them and download
385 * links to archives.
387 * @param p project
388 * @param h tree hash
389 * @param hb OPTIONAL base commit (trees can be part of multiple commits, this
390 * one denotes which commit the user navigated from)
392 elseif ($action === 'tree') {
393 $template = 'tree';
394 $page['project'] = validate_project($_REQUEST['p']);
395 $page['tree_id'] = validate_hash($_REQUEST['h']);
396 $page['title'] = "$page[project] - Tree - ViewGit";
398 // 'hb' optionally contains the commit_id this tree is related to
399 if (isset($_REQUEST['hb'])) {
400 $page['commit_id'] = validate_hash($_REQUEST['hb']);
402 else {
403 // for the header
404 $info = git_get_commit_info($page['project']);
405 $page['commit_id'] = $info['h'];
408 $page['path'] = '';
409 if (isset($_REQUEST['f'])) {
410 $page['path'] = $_REQUEST['f']; // TODO validate?
413 $page['entries'] = git_ls_tree($page['project'], $page['tree_id']);
416 * View a blob as inline, embedded on the page.
417 * @param p project
418 * @param h blob hash
419 * @param hb OPTIONAL base commit
421 elseif ($action === 'viewblob') {
422 $template = 'blob';
423 $page['project'] = validate_project($_REQUEST['p']);
424 $page['hash'] = validate_hash($_REQUEST['h']);
425 $page['title'] = "$page[project] - Blob - ViewGit";
426 if (isset($_REQUEST['hb'])) {
427 $page['commit_id'] = validate_hash($_REQUEST['hb']);
429 else {
430 $page['commit_id'] = 'HEAD';
433 $page['path'] = '';
434 if (isset($_REQUEST['f'])) {
435 $page['path'] = $_REQUEST['f']; // TODO validate?
438 // For the header's pagenav
439 $info = git_get_commit_info($page['project'], $page['commit_id']);
440 $page['commit_id'] = $info['h'];
441 $page['tree_id'] = $info['tree'];
443 $page['data'] = join("\n", run_git($page['project'], "git cat-file blob $page[hash]"));
445 else {
446 die('Invalid action');
449 require 'templates/header.php';
450 require "templates/$template.php";
451 require 'templates/footer.php';