Add Troubleshooting section to README.
[viewgit.git] / index.php
blobf2f8b30ecb6839d76b45c480d36f45653797cd14
1 <?php
2 header('Content-type: text/html; charset=UTF-8');
3 /** @file
4 * The main "controller" file of ViewGit.
6 * All requests come to this file. You can think of it as the controller in the
7 * Model-View-Controller pattern. It reads config, processes user input,
8 * fetches required data using git commandline, and finally passes the data to
9 * templates to be shown to the user.
11 error_reporting(E_ALL | E_STRICT);
13 require_once('inc/config.php');
14 require_once('inc/functions.php');
15 require_once('inc/plugins.php');
17 // Include all plugins
18 foreach (glob('plugins/*/main.php') as $plugin) {
19 require_once($plugin);
21 $parts = explode('/', $plugin);
22 $name = $parts[1];
24 $classname = "${name}plugin";
25 $inst = new $classname;
28 $old_error_handler = set_error_handler('vg_error_handler');
30 // Adjust error_reporting based on config.
31 if (!$conf['debug']) {
32 error_reporting(E_ALL ^ E_NOTICE);
35 // Timezone
36 date_default_timezone_set($conf['timezone']);
38 if (isset($conf['auth_lib'])){
39 require_once("inc/auth_{$conf['auth_lib']}.php");
40 auth_check();
43 if (isset($conf['projects_glob'])) {
44 if (!isset($conf['projects_exclude'])) {
45 $conf['projects_exclude'] = array();
47 foreach ($conf['projects_glob'] as $glob) {
48 foreach (glob($glob) as $path) {
49 // Get the last part of the path before .git
50 $name = preg_replace(array('#/?\.git$#', '#^.*/#'), array('', ''), $path);
52 // Workaround against name collisions; proj, proj1, proj2, ...
53 $i = '';
54 while (in_array($name . $i, array_keys($conf['projects']))) {
55 @$i++;
57 $name = $name . $i;
58 if (!in_array($name, $conf['projects_exclude'])) {
59 $conf['projects'][$name] = array('repo' => $path);
65 $action = 'index';
66 $template = 'index';
67 $page['title'] = 'ViewGit';
69 if (isset($_REQUEST['a'])) {
70 $action = strtolower($_REQUEST['a']);
72 $page['action'] = $action;
75 * index - list of projects
77 if ($action === 'index') {
78 $template = 'index';
79 $page['title'] = 'List of projects - ViewGit';
81 foreach (array_keys($conf['projects']) as $p) {
82 $page['projects'][] = get_project_info($p);
87 * archive - send a tree as an archive to client
88 * @param p project
89 * @param h tree hash
90 * @param hb OPTIONAL base commit (trees can be part of multiple commits, this
91 * one denotes which commit the user navigated from)
92 * @param t type, "targz" or "zip"
93 * @param n OPTIONAL name suggestion
95 elseif ($action === 'archive') {
96 $project = validate_project($_REQUEST['p']);
97 $info = get_project_info($project);
98 $tree = validate_hash($_REQUEST['h']);
99 $type = $_REQUEST['t'];
100 if (isset($_REQUEST['hb'])) {
101 $hb = validate_hash($_REQUEST['hb']);
102 $describe = git_describe($project, $hb);
105 // Archive prefix
106 $archive_prefix = '';
107 if (isset($info['archive_prefix'])) {
108 $archive_prefix = "{$info['archive_prefix']}";
110 elseif (isset($conf['archive_prefix'])) {
111 $archive_prefix = "{$conf['archive_prefix']}";
113 $archive_prefix = str_replace(array('{PROJECT}', '{DESCRIBE}'), array($project, $describe), $archive_prefix);
115 // Basename
116 $basename = "$project-tree-". substr($tree, 0, 7);
117 $basename = $archive_prefix;
118 if (isset($_REQUEST['n'])) {
119 $basename = "$basename-$_REQUEST[n]-". substr($tree, 0, 6);
122 $prefix_option = '';
123 if (isset($archive_prefix)) {
124 $prefix_option = "--prefix={$archive_prefix}/";
127 if ($type === 'targz') {
128 header("Content-Type: application/x-tar-gz");
129 header("Content-Transfer-Encoding: binary");
130 header("Content-Disposition: attachment; filename=\"$basename.tar.gz\";");
131 run_git_passthru($project, "archive --format=tar $prefix_option $tree |gzip");
133 elseif ($type === 'zip') {
134 header("Content-Type: application/x-zip");
135 header("Content-Transfer-Encoding: binary");
136 header("Content-Disposition: attachment; filename=\"$basename.zip\";");
137 run_git_passthru($project, "archive --format=zip $prefix_option $tree");
139 else {
140 die('Invalid archive type requested');
143 die();
147 * blob - send a blob to browser with filename suggestion
148 * @param p project
149 * @param h blob hash
150 * @param n filename
152 elseif ($action === 'blob') {
153 $project = validate_project($_REQUEST['p']);
154 $hash = validate_hash($_REQUEST['h']);
155 $name = $_REQUEST['n'];
157 header('Content-type: application/octet-stream');
158 header("Content-Disposition: attachment; filename=$name"); // FIXME needs quotation
160 run_git_passthru($project, "cat-file blob $hash");
161 die();
165 * co - git checkout. These requests come from mod_rewrite, see the .htaccess file.
166 * @param p project
167 * @param r path
169 elseif ($action === 'co') {
170 if (!$conf['allow_checkout']) { die('Checkout not allowed'); }
172 // For debugging
173 debug("Project: $_REQUEST[p] Request: $_REQUEST[r]");
175 // eg. info/refs, HEAD
176 $p = validate_project($_REQUEST['p']); // project
177 $r = $_REQUEST['r']; // path
179 $gitdir = $conf['projects'][$p]['repo'];
180 $filename = $gitdir .'/'. $r;
182 // make sure the request is legit (no reading of other files besides those under git projects)
183 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)) {
184 if (file_exists($filename)) {
185 debug('OK, sending');
186 readfile($filename);
187 } else {
188 debug('Not found');
189 header('HTTP/1.0 404 Not Found');
191 } else {
192 debug("Denied");
195 die();
199 * commit - view commit information
200 * @param p project
201 * @param h commit hash
203 elseif ($action === 'commit') {
204 $template = 'commit';
205 $page['project'] = validate_project($_REQUEST['p']);
206 $page['title'] = "$page[project] - Commit - ViewGit";
207 $page['commit_id'] = validate_hash($_REQUEST['h']);
208 $page['subtitle'] = "Commit ". substr($page['commit_id'], 0, 6);
210 $info = git_get_commit_info($page['project'], $page['commit_id']);
212 $page['author_name'] = $info['author_name'];
213 $page['author_mail'] = $info['author_mail'];
214 $page['author_datetime'] = $info['author_datetime'];
215 $page['author_datetime_local'] = $info['author_datetime_local'];
216 $page['committer_name'] = $info['committer_name'];
217 $page['committer_mail'] = $info['committer_mail'];
218 $page['committer_datetime'] = $info['committer_datetime'];
219 $page['committer_datetime_local'] = $info['committer_datetime_local'];
220 $page['tree_id'] = $info['tree'];
221 $page['parents'] = $info['parents'];
222 $page['message'] = $info['message'];
223 $page['message_firstline'] = $info['message_firstline'];
224 $page['message_full'] = $info['message_full'];
225 $page['affected_files'] = git_get_changed_paths($page['project'], $page['commit_id']);
230 * commitdiff - view diff of a commit
231 * @param p project
232 * @param h commit hash
234 elseif ($action === 'commitdiff') {
235 $template = 'commitdiff';
236 $page['project'] = validate_project($_REQUEST['p']);
237 $page['title'] = "$page[project] - Commitdiff - ViewGit";
238 $hash = validate_hash($_REQUEST['h']);
239 $page['commit_id'] = $hash;
240 $page['subtitle'] = "Commitdiff ". substr($page['commit_id'], 0, 6);
242 $info = git_get_commit_info($page['project'], $hash);
244 $page['tree_id'] = $info['tree'];
246 $page['message'] = $info['message'];
247 $page['message_firstline'] = $info['message_firstline'];
248 $page['message_full'] = $info['message_full'];
249 $page['author_name'] = $info['author_name'];
250 $page['author_mail'] = $info['author_mail'];
251 $page['author_datetime'] = $info['author_datetime'];
253 $text = fix_encoding(git_diff($page['project'], "$hash^", $hash));
254 list($page['files'], $page['diffdata']) = format_diff($text);
255 //$page['diffdata'] = format_diff($text);
258 elseif ($action === 'patch') {
259 $project = validate_project($_REQUEST['p']);
260 $hash = validate_hash($_REQUEST['h']);
261 $filename = "$project-". substr($hash, 0, 7) .".patch";
263 //header("Content-Type: text/x-diff");
264 header("Content-Type: application/octet-stream");
265 header("Content-Transfer-Encoding: binary");
266 // TODO git-style filename
267 header("Content-Disposition: attachment; filename=\"$filename\";");
269 run_git_passthru($project, "format-patch --stdout $hash^..$hash");
270 die();
274 * rss-log - RSS feed of project changes
275 * @param p project
276 * @param h OPTIONAL commit id to start showing log from
278 elseif ($action === 'rss-log') {
279 $page['project'] = validate_project($_REQUEST['p']);
281 $ext_url = 'http://'. $_SERVER['HTTP_HOST'] . dirname($_SERVER['SCRIPT_NAME']) .'/';
283 $page['rss_title'] = "Log for $page[project]";
284 $page['rss_link'] = $ext_url . makelink(array('a' => 'summary', 'p' => $page['project']));
285 $page['rss_description'] = "Git log for project $page[project], generated by ViewGit.";
286 $page['rss_pubDate'] = rss_pubdate(time());
287 $page['rss_ttl'] = $conf['rss_ttl'];
289 $page['rss_items'] = array();
291 $diffstat = strstr($conf['rss_item_description'], '{DIFFSTAT}');
293 if (isset($_REQUEST['h'])) {
294 $page['ref'] = validate_hash($_REQUEST['h']);
295 } else {
296 $page['ref'] = 'HEAD';
299 $revs = git_get_rev_list($page['project'], 0, $conf['rss_max_items'], $page['ref']);
300 foreach ($revs as $rev) {
301 $info = git_get_commit_info($page['project'], $rev);
302 $link = $ext_url . makelink(array('a' => 'commit', 'p' => $page['project'], 'h' => $rev));
303 if ($diffstat) {
304 $info['diffstat'] = git_diffstat($page['project'], $rev);
307 $page['rss_items'][] = array(
308 'title' => rss_item_format($conf['rss_item_title'], $info),
309 'guid' => $link,
310 'link' => $link,
311 'description' => rss_item_format($conf['rss_item_description'], $info),
312 'pubdate' => rss_pubdate($info['author_utcstamp']),
316 require('templates/rss.php');
317 die();
321 * search - search project history
322 * @param p project
323 * @param h branch
324 * @param st search type: commit,grep,author,committer,pickaxe
325 * @param s string to search for
327 elseif ($action === 'search') {
328 $template = 'shortlog';
330 $page['project'] = validate_project($_REQUEST['p']);
332 $info = git_get_commit_info($page['project']);
333 $page['commit_id'] = $info['h'];
334 $page['tree_id'] = $info['tree'];
336 $branch = validate_hash($_REQUEST['h']);
337 $type = $_REQUEST['st'];
338 $string = $_REQUEST['s'];
340 $page['search_t'] = $type;
341 $page['search_s'] = $string;
343 $commits = git_search_commits($page['project'], $branch, $type, $string);
344 $shortlog = array();
345 foreach ($commits as $c) {
346 $info = git_get_commit_info($page['project'], $c);
347 $shortlog[] = array(
348 'author' => $info['author_name'],
349 'date' => strftime($conf['datetime'], $info['author_utcstamp']),
350 'message' => $info['message'],
351 'commit_id' => $info['h'],
352 'tree' => $info['tree'],
353 'refs' => array(),
356 $page['shortlog_no_more'] = true;
357 $page['shortlog'] = $shortlog;
361 * shortlog - project shortlog entries
362 * @param p project
363 * @param h OPTIONAL commit id to start showing log from
365 elseif ($action === 'shortlog') {
366 $template = 'shortlog';
367 $page['project'] = validate_project($_REQUEST['p']);
368 $page['title'] = "$page[project] - Shortlog - ViewGit";
369 $page['subtitle'] = "Shortlog";
370 if (isset($_REQUEST['h'])) {
371 $page['ref'] = validate_hash($_REQUEST['h']);
372 } else {
373 $page['ref'] = 'HEAD';
375 if (isset($_REQUEST['pg'])) {
376 $page['pg'] = intval($_REQUEST['pg']);
377 } else {
378 $page['pg'] = 0;
381 $info = git_get_commit_info($page['project'], $page['ref']);
382 $page['commit_id'] = $info['h'];
383 $page['tree_id'] = $info['tree'];
385 $page['shortlog'] = handle_shortlog($page['project'], $page['ref'], $page['pg']);
387 elseif ($action === 'summary') {
388 $template = 'summary';
389 $page['project'] = validate_project($_REQUEST['p']);
390 $page['title'] = "$page[project] - Summary - ViewGit";
391 $page['subtitle'] = "Summary";
393 $info = git_get_commit_info($page['project']);
394 $page['commit_id'] = $info['h'];
395 $page['tree_id'] = $info['tree'];
397 $page['shortlog'] = handle_shortlog($page['project']);
399 $page['tags'] = handle_tags($page['project'], $conf['summary_tags']);
400 $page['ref'] = 'HEAD';
402 $heads = git_get_heads($page['project']);
403 $page['heads'] = array();
404 foreach ($heads as $h) {
405 $info = git_get_commit_info($page['project'], $h['h']);
406 $page['heads'][] = array(
407 'date' => strftime($conf['datetime'], $info['author_utcstamp']),
408 'h' => $h['h'],
409 'fullname' => $h['fullname'],
410 'name' => $h['name'],
414 elseif ($action === 'tags') {
415 $template = 'tags';
416 $page['project'] = validate_project($_REQUEST['p']);
417 $page['title'] = "$page[project] - Tags - ViewGit";
419 $info = git_get_commit_info($page['project']);
420 $page['commit_id'] = $info['h'];
421 $page['tree_id'] = $info['tree'];
423 $page['tags'] = handle_tags($page['project']);
426 * Shows a tree, with list of directories/files, links to them and download
427 * links to archives.
429 * @param p project
430 * @param h tree hash
431 * @param hb OPTIONAL base commit (trees can be part of multiple commits, this
432 * one denotes which commit the user navigated from)
433 * @param f OPTIONAL path the user has followed to view this tree
435 elseif ($action === 'tree') {
436 $template = 'tree';
437 $page['project'] = validate_project($_REQUEST['p']);
438 if (isset($_REQUEST['h'])) {
439 $page['tree_id'] = validate_hash($_REQUEST['h']);
442 else {
443 // TODO walk the tree
444 $page['tree_id'] = 'HEAD';
447 $page['title'] = "$page[project] - Tree - ViewGit";
449 // 'hb' optionally contains the commit_id this tree is related to
450 if (isset($_REQUEST['hb'])) {
451 $page['commit_id'] = validate_hash($_REQUEST['hb']);
453 else {
454 // for the header
455 $info = git_get_commit_info($page['project']);
456 $page['commit_id'] = $info['h'];
459 $page['path'] = '';
460 if (isset($_REQUEST['f'])) {
461 $page['path'] = $_REQUEST['f']; // TODO validate?
464 // get path info for the header
465 $page['pathinfo'] = git_get_path_info($page['project'], $page['commit_id'], $page['path']);
466 if (!isset($page['tree_id'])) {
467 // Take the last hash from the tree
468 if (count($page['pathinfo']) > 0) {
469 $page['tree_id'] = $page['pathinfo'][count($page['pathinfo']) - 1]['hash'];
470 } else {
471 $page['tree_id'] = 'HEAD';
475 $page['subtitle'] = "Tree ". substr($page['tree_id'], 0, 6);
476 $page['entries'] = git_ls_tree($page['project'], $page['tree_id']);
479 * View a blob as inline, embedded on the page.
480 * @param p project
481 * @param h blob hash
482 * @param hb OPTIONAL base commit
484 elseif ($action === 'viewblob') {
485 $template = 'blob';
486 $page['project'] = validate_project($_REQUEST['p']);
487 $page['hash'] = validate_hash($_REQUEST['h']);
488 $page['title'] = "$page[project] - Blob - ViewGit";
489 if (isset($_REQUEST['hb'])) {
490 $page['commit_id'] = validate_hash($_REQUEST['hb']);
492 else {
493 $page['commit_id'] = 'HEAD';
495 $page['subtitle'] = "Blob ". substr($page['hash'], 0, 6);
497 $page['path'] = '';
498 if (isset($_REQUEST['f'])) {
499 $page['path'] = $_REQUEST['f']; // TODO validate?
502 // For the header's pagenav
503 $info = git_get_commit_info($page['project'], $page['commit_id']);
504 $page['commit_id'] = $info['h'];
505 $page['tree_id'] = $info['tree'];
507 $page['pathinfo'] = git_get_path_info($page['project'], $page['commit_id'], $page['path']);
509 $page['data'] = fix_encoding(join("\n", run_git($page['project'], "cat-file blob $page[hash]")));
511 $page['lastlog'] = git_get_commit_info($page['project'], 'HEAD', $page['path']);
513 // GeSHi support
514 if ($conf['geshi'] && strpos($page['path'], '.')) {
515 $old_mask = error_reporting(E_ALL ^ E_NOTICE);
516 require_once($conf['geshi_path']);
517 $parts = explode('.', $page['path']);
518 $ext = array_pop($parts);
519 $geshi = new Geshi($page['data']);
520 $lang = $geshi->get_language_name_from_extension($ext);
521 if (strlen($lang) > 0) {
522 $geshi->set_language($lang);
523 if (is_int($conf['geshi_line_numbers'])) {
524 if ($conf['geshi_line_numbers'] == 0) {
525 $geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);
527 else {
528 $geshi->enable_line_numbers(GESHI_FANCY_LINE_NUMBERS, $conf['geshi_line_numbers']);
531 $page['html_data'] = $geshi->parse_code();
533 error_reporting($old_mask);
536 elseif (in_array($action, array_keys(VGPlugin::$plugin_actions))) {
537 VGPlugin::$plugin_actions[$action]->action($action);
538 die();
540 else {
541 die('Invalid action');
544 require 'templates/header.php';
545 require "templates/$template.php";
546 require 'templates/footer.php';