Allow providing a path for git_get_commit_info().
[viewgit.git] / index.php
blob9716eccc4eb078e85b91bc9411b15fd3917bdf83
1 <?php
2 /** @file
3 * The main "controller" file of ViewGit.
5 * All requests come to this file. You can think of it as the controller in the
6 * Model-View-Controller pattern. It reads config, processes user input,
7 * fetches required data using git commandline, and finally passes the data to
8 * templates to be shown to the user.
9 */
10 error_reporting(E_ALL | E_STRICT);
12 require_once('inc/config.php');
13 require_once('inc/functions.php');
14 require_once('inc/plugins.php');
16 // Include all plugins
17 foreach (glob('plugins/*/main.php') as $plugin) {
18 require_once($plugin);
20 $parts = explode('/', $plugin);
21 $name = $parts[1];
23 $classname = "${name}plugin";
24 $inst = new $classname;
27 $old_error_handler = set_error_handler('vg_error_handler');
29 // Adjust error_reporting based on config.
30 if (!$conf['debug']) {
31 error_reporting(E_ALL ^ E_NOTICE);
34 if (isset($conf['auth_lib'])){
35 require_once("inc/auth_{$conf['auth_lib']}.php");
36 auth_check();
39 if (isset($conf['projects_glob'])) {
40 foreach ($conf['projects_glob'] as $glob) {
41 foreach (glob($glob) as $path) {
42 // Get the last part of the path before .git
43 $name = preg_replace(array('#/?\.git$#', '#^.*/#'), array('', ''), $path);
45 // Workaround against name collisions; proj, proj1, proj2, ...
46 $i = '';
47 while (in_array($name . $i, array_keys($conf['projects']))) {
48 @$i++;
50 $name = $name . $i;
51 $conf['projects'][$name] = array('repo' => $path);
56 $action = 'index';
57 $template = 'index';
58 $page['title'] = 'ViewGit';
60 if (isset($_REQUEST['a'])) {
61 $action = strtolower($_REQUEST['a']);
63 $page['action'] = $action;
66 * index - list of projects
68 if ($action === 'index') {
69 $template = 'index';
70 $page['title'] = 'List of projects - ViewGit';
72 foreach (array_keys($conf['projects']) as $p) {
73 $page['projects'][] = get_project_info($p);
78 * archive - send a tree as an archive to client
79 * @param p project
80 * @param h tree hash
81 * @param t type, "targz" or "zip"
82 * @param n OPTIONAL name suggestion
84 elseif ($action === 'archive') {
85 $project = validate_project($_REQUEST['p']);
86 $info = get_project_info($project);
87 $tree = validate_hash($_REQUEST['h']);
88 $type = $_REQUEST['t'];
90 $basename = "$project-tree-". substr($tree, 0, 7);
91 if (isset($_REQUEST['n'])) {
92 $basename = "$project-$_REQUEST[n]-". substr($tree, 0, 6);
94 $prefix_option = '';
95 if (isset($info['archive_prefix'])) {
96 $prefix_option = "--prefix={$info['archive_prefix']}/";
98 elseif (isset($conf['archive_prefix'])) {
99 $prefix_option = "--prefix={$conf['archive_prefix']}/";
101 $prefix_option = str_replace(array('{PROJECT}'), array($project), $prefix_option);
103 if ($type === 'targz') {
104 header("Content-Type: application/x-tar-gz");
105 header("Content-Transfer-Encoding: binary");
106 header("Content-Disposition: attachment; filename=\"$basename.tar.gz\";");
107 run_git_passthru($project, "archive --format=tar $prefix_option $tree |gzip");
109 elseif ($type === 'zip') {
110 header("Content-Type: application/x-zip");
111 header("Content-Transfer-Encoding: binary");
112 header("Content-Disposition: attachment; filename=\"$basename.zip\";");
113 run_git_passthru($project, "archive --format=zip $prefix_option $tree");
115 else {
116 die('Invalid archive type requested');
119 die();
123 * blob - send a blob to browser with filename suggestion
124 * @param p project
125 * @param h blob hash
126 * @param n filename
128 elseif ($action === 'blob') {
129 $project = validate_project($_REQUEST['p']);
130 $hash = validate_hash($_REQUEST['h']);
131 $name = $_REQUEST['n'];
133 header('Content-type: application/octet-stream');
134 header("Content-Disposition: attachment; filename=$name"); // FIXME needs quotation
136 run_git_passthru($project, "cat-file blob $hash");
137 die();
141 * co - git checkout. These requests come from mod_rewrite, see the .htaccess file.
142 * @param p project
143 * @param r path
145 elseif ($action === 'co') {
146 if (!$conf['allow_checkout']) { die('Checkout not allowed'); }
148 // For debugging
149 debug("Project: $_REQUEST[p] Request: $_REQUEST[r]");
151 // eg. info/refs, HEAD
152 $p = validate_project($_REQUEST['p']); // project
153 $r = $_REQUEST['r']; // path
155 $gitdir = $conf['projects'][$p]['repo'];
156 $filename = $gitdir .'/'. $r;
158 // make sure the request is legit (no reading of other files besides those under git projects)
159 if ($r === 'HEAD' || $r === 'info/refs' || preg_match('!^objects/info/(packs|http-alternates|alternates)$!', $r) > 0 || preg_match('!^objects/[0-9a-f]{2}/[0-9a-f]{38}$!', $r) > 0 || preg_match('!^objects/pack/pack-[0-9a-f]{40}\.(idx|pack)$!', $r)) {
160 if (file_exists($filename)) {
161 debug('OK, sending');
162 readfile($filename);
163 } else {
164 debug('Not found');
165 header('HTTP/1.0 404 Not Found');
167 } else {
168 debug("Denied");
171 die();
175 * commit - view commit information
176 * @param p project
177 * @param h commit hash
179 elseif ($action === 'commit') {
180 $template = 'commit';
181 $page['project'] = validate_project($_REQUEST['p']);
182 $page['title'] = "$page[project] - Commit - ViewGit";
183 $page['commit_id'] = validate_hash($_REQUEST['h']);
184 $page['subtitle'] = "Commit ". substr($page['commit_id'], 0, 6);
186 $info = git_get_commit_info($page['project'], $page['commit_id']);
188 $page['author_name'] = $info['author_name'];
189 $page['author_mail'] = $info['author_mail'];
190 $page['author_datetime'] = $info['author_datetime'];
191 $page['author_datetime_local'] = $info['author_datetime_local'];
192 $page['committer_name'] = $info['committer_name'];
193 $page['committer_mail'] = $info['committer_mail'];
194 $page['committer_datetime'] = $info['committer_datetime'];
195 $page['committer_datetime_local'] = $info['committer_datetime_local'];
196 $page['tree_id'] = $info['tree'];
197 $page['parents'] = $info['parents'];
198 $page['message'] = $info['message'];
199 $page['message_firstline'] = $info['message_firstline'];
200 $page['message_full'] = $info['message_full'];
205 * commitdiff - view diff of a commit
206 * @param p project
207 * @param h commit hash
209 elseif ($action === 'commitdiff') {
210 $template = 'commitdiff';
211 $page['project'] = validate_project($_REQUEST['p']);
212 $page['title'] = "$page[project] - Commitdiff - ViewGit";
213 $hash = validate_hash($_REQUEST['h']);
214 $page['commit_id'] = $hash;
215 $page['subtitle'] = "Commitdiff ". substr($page['commit_id'], 0, 6);
217 $info = git_get_commit_info($page['project'], $hash);
219 $page['tree_id'] = $info['tree'];
221 $page['message'] = $info['message'];
222 $page['message_firstline'] = $info['message_firstline'];
223 $page['message_full'] = $info['message_full'];
224 $page['author_name'] = $info['author_name'];
225 $page['author_mail'] = $info['author_mail'];
226 $page['author_datetime'] = gmstrftime($conf['datetime'], $info['author_utcstamp']);
228 $text = git_diff($page['project'], "$hash^", $hash);
229 list($page['files'], $page['diffdata']) = format_diff($text);
230 //$page['diffdata'] = format_diff($text);
233 elseif ($action === 'patch') {
234 $project = validate_project($_REQUEST['p']);
235 $hash = validate_hash($_REQUEST['h']);
236 $filename = "$project-". substr($hash, 0, 7) .".patch";
238 //header("Content-Type: text/x-diff");
239 header("Content-Type: application/octet-stream");
240 header("Content-Transfer-Encoding: binary");
241 // TODO git-style filename
242 header("Content-Disposition: attachment; filename=\"$filename\";");
244 run_git_passthru($project, "format-patch --stdout $hash^..$hash");
245 die();
249 * rss-log - RSS feed of project changes
250 * @param p project
252 elseif ($action === 'rss-log') {
253 $page['project'] = validate_project($_REQUEST['p']);
255 $ext_url = 'http://'. $_SERVER['HTTP_HOST'] . dirname($_SERVER['SCRIPT_NAME']) .'/';
257 $page['rss_title'] = "Log for $page[project]";
258 $page['rss_link'] = $ext_url . makelink(array('a' => 'summary', 'p' => $page['project']));
259 $page['rss_description'] = "Git log for project $page[project], generated by ViewGit.";
260 $page['rss_pubDate'] = rss_pubdate(time());
261 $page['rss_ttl'] = $conf['rss_ttl'];
263 $page['rss_items'] = array();
265 $diffstat = strstr($conf['rss_item_description'], '{DIFFSTAT}');
267 $revs = git_get_rev_list($page['project'], 0, $conf['rss_max_items']);
268 foreach ($revs as $rev) {
269 $info = git_get_commit_info($page['project'], $rev);
270 $link = $ext_url . makelink(array('a' => 'commit', 'p' => $page['project'], 'h' => $rev));
271 if ($diffstat) {
272 $info['diffstat'] = git_diffstat($page['project'], $rev);
275 $page['rss_items'][] = array(
276 'title' => rss_item_format($conf['rss_item_title'], $info),
277 'guid' => $link,
278 'link' => $link,
279 'description' => rss_item_format($conf['rss_item_description'], $info),
280 'pubdate' => rss_pubdate($info['author_utcstamp']),
284 require('templates/rss.php');
285 die();
289 * search - search project history
290 * @param p project
291 * @param h branch
292 * @param st search type: commit,grep,author,committer,pickaxe
293 * @param s string to search for
295 elseif ($action === 'search') {
296 $template = 'shortlog';
298 $page['project'] = validate_project($_REQUEST['p']);
300 $info = git_get_commit_info($page['project']);
301 $page['commit_id'] = $info['h'];
302 $page['tree_id'] = $info['tree'];
304 $branch = validate_hash($_REQUEST['h']);
305 $type = $_REQUEST['st'];
306 $string = $_REQUEST['s'];
308 $page['search_t'] = $type;
309 $page['search_s'] = $string;
311 $commits = git_search_commits($page['project'], $branch, $type, $string);
312 $shortlog = array();
313 foreach ($commits as $c) {
314 $info = git_get_commit_info($page['project'], $c);
315 $shortlog[] = array(
316 'author' => $info['author_name'],
317 'date' => gmstrftime($conf['datetime'], $info['author_utcstamp']),
318 'message' => $info['message'],
319 'commit_id' => $info['h'],
320 'tree' => $info['tree'],
321 'refs' => array(),
324 $page['shortlog_no_more'] = true;
325 $page['shortlog'] = $shortlog;
329 * shortlog - project shortlog entries
330 * @param p project
331 * @param h OPTIONAL commit id to start showing log from
333 elseif ($action === 'shortlog') {
334 $template = 'shortlog';
335 $page['project'] = validate_project($_REQUEST['p']);
336 $page['title'] = "$page[project] - Shortlog - ViewGit";
337 $page['subtitle'] = "Shortlog";
338 if (isset($_REQUEST['h'])) {
339 $page['ref'] = validate_hash($_REQUEST['h']);
340 } else {
341 $page['ref'] = 'HEAD';
343 if (isset($_REQUEST['pg'])) {
344 $page['pg'] = intval($_REQUEST['pg']);
345 } else {
346 $page['pg'] = 0;
349 $info = git_get_commit_info($page['project'], $page['ref']);
350 $page['commit_id'] = $info['h'];
351 $page['tree_id'] = $info['tree'];
353 $page['shortlog'] = handle_shortlog($page['project'], $page['ref'], $page['pg']);
355 elseif ($action === 'summary') {
356 $template = 'summary';
357 $page['project'] = validate_project($_REQUEST['p']);
358 $page['title'] = "$page[project] - Summary - ViewGit";
359 $page['subtitle'] = "Summary";
361 $info = git_get_commit_info($page['project']);
362 $page['commit_id'] = $info['h'];
363 $page['tree_id'] = $info['tree'];
365 $page['shortlog'] = handle_shortlog($page['project']);
367 $page['tags'] = handle_tags($page['project'], $conf['summary_tags']);
368 $page['ref'] = 'HEAD';
370 $heads = git_get_heads($page['project']);
371 $page['heads'] = array();
372 foreach ($heads as $h) {
373 $info = git_get_commit_info($page['project'], $h['h']);
374 $page['heads'][] = array(
375 'date' => gmstrftime($conf['datetime'], $info['author_utcstamp']),
376 'h' => $h['h'],
377 'fullname' => $h['fullname'],
378 'name' => $h['name'],
382 elseif ($action === 'tags') {
383 $template = 'tags';
384 $page['project'] = validate_project($_REQUEST['p']);
385 $page['title'] = "$page[project] - Tags - ViewGit";
387 $info = git_get_commit_info($page['project']);
388 $page['commit_id'] = $info['h'];
389 $page['tree_id'] = $info['tree'];
391 $page['tags'] = handle_tags($page['project']);
394 * Shows a tree, with list of directories/files, links to them and download
395 * links to archives.
397 * @param p project
398 * @param h tree hash
399 * @param hb OPTIONAL base commit (trees can be part of multiple commits, this
400 * one denotes which commit the user navigated from)
401 * @param f OPTIONAL path the user has followed to view this tree
403 elseif ($action === 'tree') {
404 $template = 'tree';
405 $page['project'] = validate_project($_REQUEST['p']);
406 if (isset($_REQUEST['h'])) {
407 $page['tree_id'] = validate_hash($_REQUEST['h']);
410 else {
411 // TODO walk the tree
412 $page['tree_id'] = 'HEAD';
415 $page['title'] = "$page[project] - Tree - ViewGit";
417 // 'hb' optionally contains the commit_id this tree is related to
418 if (isset($_REQUEST['hb'])) {
419 $page['commit_id'] = validate_hash($_REQUEST['hb']);
421 else {
422 // for the header
423 $info = git_get_commit_info($page['project']);
424 $page['commit_id'] = $info['h'];
427 $page['path'] = '';
428 if (isset($_REQUEST['f'])) {
429 $page['path'] = $_REQUEST['f']; // TODO validate?
432 // get path info for the header
433 $page['pathinfo'] = git_get_path_info($page['project'], $page['commit_id'], $page['path']);
434 if (!isset($page['tree_id'])) {
435 // Take the last hash from the tree
436 if (count($page['pathinfo']) > 0) {
437 $page['tree_id'] = $page['pathinfo'][count($page['pathinfo']) - 1]['hash'];
438 } else {
439 $page['tree_id'] = 'HEAD';
443 $page['subtitle'] = "Tree ". substr($page['tree_id'], 0, 6);
444 $page['entries'] = git_ls_tree($page['project'], $page['tree_id']);
447 * View a blob as inline, embedded on the page.
448 * @param p project
449 * @param h blob hash
450 * @param hb OPTIONAL base commit
452 elseif ($action === 'viewblob') {
453 $template = 'blob';
454 $page['project'] = validate_project($_REQUEST['p']);
455 $page['hash'] = validate_hash($_REQUEST['h']);
456 $page['title'] = "$page[project] - Blob - ViewGit";
457 if (isset($_REQUEST['hb'])) {
458 $page['commit_id'] = validate_hash($_REQUEST['hb']);
460 else {
461 $page['commit_id'] = 'HEAD';
463 $page['subtitle'] = "Blob ". substr($page['hash'], 0, 6);
465 $page['path'] = '';
466 if (isset($_REQUEST['f'])) {
467 $page['path'] = $_REQUEST['f']; // TODO validate?
470 // For the header's pagenav
471 $info = git_get_commit_info($page['project'], $page['commit_id']);
472 $page['commit_id'] = $info['h'];
473 $page['tree_id'] = $info['tree'];
475 $page['pathinfo'] = git_get_path_info($page['project'], $page['commit_id'], $page['path']);
477 $page['data'] = join("\n", run_git($page['project'], "cat-file blob $page[hash]"));
478 $link = makelink(array('a' => 'commit', 'p' => $page['project'], 'h' => ''));
479 $format = "--pretty=format:'commit <a href=$link%H>%H</a>%nAuthor: %an%nDate: %ad%n%n%s%n%n%b'";
480 $page['lastlog'] = join("<br />", run_git($page['project'], "log -n 1 $format -- $page[path]"));
482 // GeSHi support
483 if ($conf['geshi'] && strpos($page['path'], '.')) {
484 $old_mask = error_reporting(E_ALL ^ E_NOTICE);
485 require_once($conf['geshi_path']);
486 $parts = explode('.', $page['path']);
487 $ext = array_pop($parts);
488 $geshi = new Geshi($page['data']);
489 $lang = $geshi->get_language_name_from_extension($ext);
490 if (strlen($lang) > 0) {
491 $geshi->set_language($lang);
492 if (is_int($conf['geshi_line_numbers'])) {
493 if ($conf['geshi_line_numbers'] == 0) {
494 $geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);
496 else {
497 $geshi->enable_line_numbers(GESHI_FANCY_LINE_NUMBERS, $conf['geshi_line_numbers']);
500 $page['html_data'] = $geshi->parse_code();
502 error_reporting($old_mask);
505 elseif (in_array($action, array_keys(VGPlugin::$plugin_actions))) {
506 VGPlugin::$plugin_actions[$action]->action($action);
507 die();
509 else {
510 die('Invalid action');
513 require 'templates/header.php';
514 require "templates/$template.php";
515 require 'templates/footer.php';