commit/commitdiff: show full first line of commit message.
[viewgit.git] / index.php
blob5e2ecac086bb043776c50a2c2c7966c1edf52472
1 <?php
2 error_reporting(E_ALL);
4 require_once('inc/config.php');
6 /**
7 * Formats "git diff" output into xhtml.
8 * @return array(array of filenames, xhtml)
9 */
10 function format_diff($text)
12 $files = array();
14 // match every "^diff --git a/<path> b/<path>$" line
15 foreach (explode("\n", $text) as $line) {
16 if (preg_match('#^diff --git a/(.*) b/(.*)$#', $line, $matches) > 0) {
17 $files[$matches[1]] = urlencode($matches[1]);
21 $text = htmlentities($text);
23 $text = preg_replace(
24 array(
25 '/^(\+.*)$/m',
26 '/^(-.*)$/m',
27 '/^(@.*)$/m',
28 '/^([^d\+-@].*)$/m',
30 array(
31 '<span class="add">$1</span>',
32 '<span class="del">$1</span>',
33 '<span class="pos">$1</span>',
34 '<span class="etc">$1</span>',
36 $text);
37 $text = preg_replace_callback('#^diff --git a/(.*) b/(.*)$#m',
38 create_function(
39 '$m',
40 'return "<span class=\"diffline\"><a name=\"". urlencode($m[1]) ."\">diff --git a/$m[1] b/$m[2]</a></span>";'
42 $text);
44 return array($files, $text);
47 function get_project_info($name)
49 global $conf;
51 $info = $conf['projects'][$name];
52 $info['name'] = $name;
53 $info['description'] = file_get_contents($info['repo'] .'/description');
55 $headinfo = git_get_commit_info($name, 'HEAD');
56 $info['head_stamp'] = $headinfo['author_utcstamp'];
57 $info['head_datetime'] = strftime($conf['datetime'], $headinfo['author_utcstamp']);
58 $info['head_hash'] = $headinfo['h'];
59 $info['head_tree'] = $headinfo['tree'];
61 return $info;
64 /**
65 * Get details of a commit: tree, parent, author/committer (name, mail, date), message
67 function git_get_commit_info($project, $hash = 'HEAD')
69 $info = array();
70 $info['h'] = $hash;
71 $info['message_full'] = '';
73 $output = run_git($project, "git rev-list --header --max-count=1 $hash");
74 // tree <h>
75 // parent <h>
76 // author <name> "<"<mail>">" <stamp> <timezone>
77 // committer
78 // <empty>
79 // <message>
80 $pattern = '/^(author|committer) ([^<]+) <([^>]*)> ([0-9]+) (.*)$/';
81 foreach ($output as $line) {
82 if (substr($line, 0, 4) === 'tree') {
83 $info['tree'] = substr($line, 5);
85 elseif (substr($line, 0, 6) === 'parent') {
86 $info['parent'] = substr($line, 7);
88 elseif (preg_match($pattern, $line, $matches) > 0) {
89 $info[$matches[1] .'_name'] = $matches[2];
90 $info[$matches[1] .'_mail'] = $matches[3];
91 $info[$matches[1] .'_stamp'] = $matches[4];
92 $info[$matches[1] .'_timezone'] = $matches[5];
93 $info[$matches[1] .'_utcstamp'] = $matches[4] - ((intval($matches[5]) / 100.0) * 3600);
95 elseif (substr($line, 0, 4) === ' ') {
96 $info['message_full'] .= substr($line, 4) ."\n";
97 if (!isset($info['message'])) {
98 $info['message'] = substr($line, 4, 40);
99 $info['message_firstline'] = substr($line, 4);
102 elseif (preg_match('/^[0-9a-f]{40}$/', $line) > 0) {
103 $info['h'] = $line;
107 return $info;
110 function git_get_heads($project)
112 $heads = array();
114 $output = run_git($project, 'git show-ref --heads');
115 foreach ($output as $line) {
116 $fullname = substr($line, 41);
117 $name = array_pop(explode('/', $fullname));
118 $heads[] = array('h' => substr($line, 0, 40), 'fullname' => "$fullname", 'name' => "$name");
121 return $heads;
124 function git_get_rev_list($project, $max_count = null, $start = 'HEAD')
126 $cmd = "git rev-list $start";
127 if (!is_null($max_count)) {
128 $cmd = "git rev-list --max-count=$max_count $start";
131 return run_git($project, $cmd);
134 function git_get_tags($project)
136 $tags = array();
138 $output = run_git($project, 'git show-ref --tags');
139 foreach ($output as $line) {
140 $fullname = substr($line, 41);
141 $name = array_pop(explode('/', $fullname));
142 $tags[] = array('h' => substr($line, 0, 40), 'fullname' => $fullname, 'name' => $name);
144 return $tags;
147 function git_ls_tree($project, $tree)
149 $entries = array();
150 $output = run_git($project, "git ls-tree $tree");
151 // 100644 blob 493b7fc4296d64af45dac64bceac2d9a96c958c1 .gitignore
152 // 040000 tree 715c78b1011dc58106da2a1af2fe0aa4c829542f doc
153 foreach ($output as $line) {
154 $parts = preg_split('/\s+/', $line, 4);
155 $entries[] = array('name' => $parts[3], 'mode' => $parts[0], 'type' => $parts[1], 'hash' => $parts[2]);
158 return $entries;
161 function makelink($dict)
163 $params = array();
164 foreach ($dict as $k => $v) {
165 $params[] = rawurlencode($k) .'='. str_replace('%2F', '/', rawurlencode($v));
167 if (count($params) > 0) {
168 return '?'. htmlentities(join('&', $params));
170 return '';
174 * Executes a git command in the project repo.
175 * @return array of output lines
177 function run_git($project, $command)
179 global $conf;
181 $output = array();
182 $cmd = "GIT_DIR=". $conf['projects'][$project]['repo'] ." $command";
183 exec($cmd, &$output);
184 return $output;
188 * Executes a git command in the project repo, sending output directly to the
189 * client.
191 function run_git_passthru($project, $command)
193 global $conf;
195 $cmd = "GIT_DIR=". $conf['projects'][$project]['repo'] ." $command";
196 $result = 0;
197 passthru($cmd, &$result);
198 return $result;
202 * Makes sure the given project is valid. If it's not, this function will
203 * die().
204 * @return the project
206 function validate_project($project)
208 global $conf;
210 if (!in_array($project, array_keys($conf['projects']))) {
211 die('Invalid project');
213 return $project;
217 * Makes sure the given hash is valid. If it's not, this function will die().
218 * @return the hash
220 function validate_hash($hash)
222 if (strlen($hash) != 40 || !preg_match('/^[0-9a-z]*$/', $hash)) {
223 die('Invalid hash');
226 return $hash;
229 $action = 'index';
230 $template = 'index';
231 $page['title'] = 'ViewGit';
233 if (isset($_REQUEST['a'])) {
234 $action = strtolower($_REQUEST['a']);
236 $page['action'] = $action;
238 if ($action === 'index') {
239 $template = 'index';
240 $page['title'] = 'List of projects - ViewGit';
242 foreach (array_keys($conf['projects']) as $p) {
243 $page['projects'][] = get_project_info($p);
246 elseif ($action === 'archive') {
247 $project = validate_project($_REQUEST['p']);
248 $tree = validate_hash($_REQUEST['h']);
249 $type = $_REQUEST['t'];
251 $basename = "$project-tree-$tree";
252 if (isset($_REQUEST['n'])) {
253 $basename = "$project-$_REQUEST[n]-". substr($tree, 0, 6);
256 if ($type === 'targz') {
257 header("Content-Type: application/x-tar-gz");
258 header("Content-Transfer-Encoding: binary");
259 header("Content-Disposition: attachment; filename=\"$basename.tar.gz\";");
260 run_git_passthru($project, "git archive --format=tar $tree |gzip");
262 elseif ($type === 'zip') {
263 header("Content-Type: application/x-zip");
264 header("Content-Transfer-Encoding: binary");
265 header("Content-Disposition: attachment; filename=\"$basename.zip\";");
266 run_git_passthru($project, "git archive --format=zip $tree");
268 else {
269 die('Invalid archive type requested');
272 die();
274 // blob: send a blob to browser with filename suggestion
275 elseif ($action === 'blob') {
276 $project = validate_project($_REQUEST['p']);
277 $hash = validate_hash($_REQUEST['h']);
278 $name = $_REQUEST['n'];
280 header('Content-type: application/octet-stream');
281 header("Content-Disposition: attachment; filename=$name"); // FIXME needs quotation
283 run_git_passthru($project, "git cat-file blob $hash");
284 die();
286 elseif ($action === 'commit') {
287 $template = 'commit';
288 $page['project'] = validate_project($_REQUEST['p']);
289 $page['title'] = "$page[project] - Commit - ViewGit";
290 $page['commit_id'] = validate_hash($_REQUEST['h']);
292 $info = git_get_commit_info($page['project'], $page['commit_id']);
294 $page['author_name'] = $info['author_name'];
295 $page['author_mail'] = $info['author_mail'];
296 $page['author_datetime'] = strftime($conf['datetime'], $info['author_utcstamp']);
297 $page['author_datetime_local'] = strftime($conf['datetime'], $info['author_stamp']) .' '. $info['author_timezone'];
298 $page['committer_name'] = $info['committer_name'];
299 $page['committer_mail'] = $info['committer_mail'];
300 $page['committer_datetime'] = strftime($conf['datetime'], $info['committer_utcstamp']);
301 $page['committer_datetime_local'] = strftime($conf['datetime'], $info['committer_stamp']) .' '. $info['committer_timezone'];
302 $page['tree_id'] = $info['tree'];
303 $page['parent'] = $info['parent'];
304 $page['message'] = $info['message'];
305 $page['message_firstline'] = $info['message_firstline'];
306 $page['message_full'] = $info['message_full'];
309 elseif ($action === 'commitdiff') {
310 $template = 'commitdiff';
311 $page['project'] = validate_project($_REQUEST['p']);
312 $page['title'] = "$page[project] - Commitdiff - ViewGit";
313 $hash = validate_hash($_REQUEST['h']);
314 $page['commit_id'] = $hash;
316 $info = git_get_commit_info($page['project'], $hash);
318 $page['tree_id'] = $info['tree'];
320 $page['message'] = $info['message'];
321 $page['message_firstline'] = $info['message_firstline'];
322 $page['message_full'] = $info['message_full'];
323 $page['author_name'] = $info['author_name'];
324 $page['author_mail'] = $info['author_mail'];
325 $page['author_datetime'] = strftime($conf['datetime'], $info['author_utcstamp']);
327 $text = join("\n", run_git($page['project'], "git diff $hash^..$hash"));
328 list($page['files'], $page['diffdata']) = format_diff($text);
329 //$page['diffdata'] = format_diff($text);
331 elseif ($action === 'shortlog') {
332 $template = 'shortlog';
333 $page['project'] = validate_project($_REQUEST['p']);
334 $page['title'] = "$page[project] - Shortlog - ViewGit";
335 if (isset($_REQUEST['h'])) {
336 $page['ref'] = validate_hash($_REQUEST['h']);
337 } else {
338 $page['ref'] = 'HEAD';
341 $info = git_get_commit_info($page['project']);
342 $page['commit_id'] = $info['h'];
343 $page['tree_id'] = $info['tree'];
345 // TODO merge the logic with 'summary' below
346 $revs = git_get_rev_list($page['project'], $conf['summary_shortlog'], $page['ref']); // TODO pass first rev as parameter
347 foreach ($revs as $rev) {
348 $info = git_get_commit_info($page['project'], $rev);
349 $page['shortlog'][] = array(
350 'author' => $info['author_name'],
351 'date' => strftime($conf['datetime'], $info['author_utcstamp']),
352 'message' => $info['message'],
353 'commit_id' => $rev,
354 'tree' => $info['tree'],
358 elseif ($action === 'summary') {
359 $template = 'summary';
360 $page['project'] = validate_project($_REQUEST['p']);
361 $page['title'] = "$page[project] - Summary - ViewGit";
363 $info = git_get_commit_info($page['project']);
364 $page['commit_id'] = $info['h'];
365 $page['tree_id'] = $info['tree'];
367 $revs = git_get_rev_list($page['project'], $conf['summary_shortlog']);
368 foreach ($revs as $rev) {
369 $info = git_get_commit_info($page['project'], $rev);
370 $page['shortlog'][] = array(
371 'author' => $info['author_name'],
372 'date' => strftime($conf['datetime'], $info['author_utcstamp']),
373 'message' => $info['message'],
374 'commit_id' => $rev,
375 'tree' => $info['tree'],
379 $tags = git_get_tags($page['project']);
380 $page['tags'] = array();
381 foreach ($tags as $tag) {
382 $info = git_get_commit_info($page['project'], $tag['h']);
383 $page['tags'][] = array(
384 'date' => strftime($conf['datetime'], $info['author_utcstamp']),
385 'h' => $tag['h'],
386 'fullname' => $tag['fullname'],
387 'name' => $tag['name'],
391 $heads = git_get_heads($page['project']);
392 $page['heads'] = array();
393 foreach ($heads as $h) {
394 $info = git_get_commit_info($page['project'], $h['h']);
395 $page['heads'][] = array(
396 'date' => strftime($conf['datetime'], $info['author_utcstamp']),
397 'h' => $h['h'],
398 'fullname' => $h['fullname'],
399 'name' => $h['name'],
404 * Shows a tree, with list of directories/files, links to them and download
405 * links to archives.
407 * @param p project
408 * @param h tree hash
409 * @param hb OPTIONAL base commit (trees can be part of multiple commits, this
410 * one denotes which commit the user navigated from)
412 elseif ($action === 'tree') {
413 $template = 'tree';
414 $page['project'] = validate_project($_REQUEST['p']);
415 $page['tree_id'] = validate_hash($_REQUEST['h']);
416 $page['title'] = "$page[project] - Tree - ViewGit";
418 // 'hb' optionally contains the commit_id this tree is related to
419 if (isset($_REQUEST['hb'])) {
420 $page['commit_id'] = validate_hash($_REQUEST['hb']);
422 else {
423 // for the header
424 $info = git_get_commit_info($page['project']);
425 $page['commit_id'] = $info['h'];
428 $page['path'] = '';
429 if (isset($_REQUEST['f'])) {
430 $page['path'] = $_REQUEST['f']; // TODO validate?
433 $page['entries'] = git_ls_tree($page['project'], $page['tree_id']);
436 * View a blob as inline, embedded on the page.
437 * @param p project
438 * @param h blob hash
439 * @param hb OPTIONAL base commit
441 elseif ($action === 'viewblob') {
442 $template = 'blob';
443 $page['project'] = validate_project($_REQUEST['p']);
444 $page['hash'] = validate_hash($_REQUEST['h']);
445 $page['title'] = "$page[project] - Blob - ViewGit";
446 if (isset($_REQUEST['hb'])) {
447 $page['commit_id'] = validate_hash($_REQUEST['hb']);
449 else {
450 $page['commit_id'] = 'HEAD';
453 $page['path'] = '';
454 if (isset($_REQUEST['f'])) {
455 $page['path'] = $_REQUEST['f']; // TODO validate?
458 // For the header's pagenav
459 $info = git_get_commit_info($page['project'], $page['commit_id']);
460 $page['commit_id'] = $info['h'];
461 $page['tree_id'] = $info['tree'];
463 $page['data'] = join("\n", run_git($page['project'], "git cat-file blob $page[hash]"));
465 else {
466 die('Invalid action');
469 require 'templates/header.php';
470 require "templates/$template.php";
471 require 'templates/footer.php';