Updated todo + tweaks.
[viewgit.git] / inc / functions.php
bloba12a6dee40ca7404ed2d24a14f36e6a3f76b3e44
1 <?php
2 /** @file
3 * Functions used by ViewGit.
4 */
6 function debug($msg)
8 global $conf;
10 if ($conf['debug']) {
11 file_put_contents('/tmp/viewgit.log', strftime('%H:%M:%S') ." $_SERVER[REMOTE_ADDR]:$_SERVER[REMOTE_PORT] $msg\n", FILE_APPEND);
15 /**
16 * Formats "git diff" output into xhtml.
17 * @return array(array of filenames, xhtml)
19 function format_diff($text)
21 $files = array();
23 // match every "^diff --git a/<path> b/<path>$" line
24 foreach (explode("\n", $text) as $line) {
25 if (preg_match('#^diff --git a/(.*) b/(.*)$#', $line, $matches) > 0) {
26 $files[$matches[1]] = urlencode($matches[1]);
30 $text = htmlentities($text);
32 $text = preg_replace(
33 array(
34 '/^(\+.*)$/m',
35 '/^(-.*)$/m',
36 '/^(@.*)$/m',
37 '/^([^d\+-@].*)$/m',
39 array(
40 '<span class="add">$1</span>',
41 '<span class="del">$1</span>',
42 '<span class="pos">$1</span>',
43 '<span class="etc">$1</span>',
45 $text);
46 $text = preg_replace_callback('#^diff --git a/(.*) b/(.*)$#m',
47 create_function(
48 '$m',
49 'return "<span class=\"diffline\"><a name=\"". urlencode($m[1]) ."\">diff --git a/$m[1] b/$m[2]</a></span>";'
51 $text);
53 return array($files, $text);
56 /**
57 * Get project information from config and git, name/description and HEAD
58 * commit info are returned in an array.
60 function get_project_info($name)
62 global $conf;
64 $info = $conf['projects'][$name];
65 $info['name'] = $name;
66 $info['description'] = file_get_contents($info['repo'] .'/description');
68 $headinfo = git_get_commit_info($name, 'HEAD');
69 $info['head_stamp'] = $headinfo['author_utcstamp'];
70 $info['head_datetime'] = strftime($conf['datetime'], $headinfo['author_utcstamp']);
71 $info['head_hash'] = $headinfo['h'];
72 $info['head_tree'] = $headinfo['tree'];
74 return $info;
77 function git_diffstat($project, $commit, $commit_base = null)
79 if (is_null($commit_base)) {
80 $commit_base = "$commit^";
82 return join("\n", run_git($project, "git diff --stat $commit_base..$commit"));
85 /**
86 * Get details of a commit: tree, parents, author/committer (name, mail, date), message
88 function git_get_commit_info($project, $hash = 'HEAD')
90 global $conf;
92 $info = array();
93 $info['h_name'] = $hash;
94 $info['message_full'] = '';
95 $info['parents'] = array();
97 $output = run_git($project, "git rev-list --header --max-count=1 $hash");
98 // tree <h>
99 // parent <h>
100 // author <name> "<"<mail>">" <stamp> <timezone>
101 // committer
102 // <empty>
103 // <message>
104 $pattern = '/^(author|committer) ([^<]+) <([^>]*)> ([0-9]+) (.*)$/';
105 foreach ($output as $line) {
106 if (substr($line, 0, 4) === 'tree') {
107 $info['tree'] = substr($line, 5);
109 // may be repeated multiple times for merge/octopus
110 elseif (substr($line, 0, 6) === 'parent') {
111 $info['parents'][] = substr($line, 7);
113 elseif (preg_match($pattern, $line, $matches) > 0) {
114 $info[$matches[1] .'_name'] = $matches[2];
115 $info[$matches[1] .'_mail'] = $matches[3];
116 $info[$matches[1] .'_stamp'] = $matches[4];
117 $info[$matches[1] .'_timezone'] = $matches[5];
118 $info[$matches[1] .'_utcstamp'] = $matches[4] - ((intval($matches[5]) / 100.0) * 3600);
120 if (isset($conf['mail_filter'])) {
121 $info[$matches[1] .'_mail'] = $conf['mail_filter']($info[$matches[1] .'_mail']);
124 elseif (substr($line, 0, 4) === ' ') {
125 $info['message_full'] .= substr($line, 4) ."\n";
126 if (!isset($info['message'])) {
127 $info['message'] = substr($line, 4, $conf['commit_message_maxlen']);
128 $info['message_firstline'] = substr($line, 4);
131 elseif (preg_match('/^[0-9a-f]{40}$/', $line) > 0) {
132 $info['h'] = $line;
136 return $info;
140 * Get list of heads (branches) for a project.
142 function git_get_heads($project)
144 $heads = array();
146 $output = run_git($project, 'git show-ref --heads');
147 foreach ($output as $line) {
148 $fullname = substr($line, 41);
149 $name = array_pop(explode('/', $fullname));
150 $heads[] = array('h' => substr($line, 0, 40), 'fullname' => "$fullname", 'name' => "$name");
153 return $heads;
157 * Get array containing path information for parts, starting from root_hash.
159 * @param root_hash commit/tree hash for the root tree
160 * @param path path
162 function git_get_path_info($project, $root_hash, $path)
164 if (strlen($path) > 0) {
165 $parts = explode('/', $path);
166 } else {
167 $parts = array();
170 $pathinfo = array();
172 $tid = $root_hash;
173 $pathinfo = array();
174 foreach ($parts as $p) {
175 $entry = git_ls_tree_part($project, $tid, $p);
176 $pathinfo[] = $entry;
177 $tid = $entry['hash'];
180 return $pathinfo;
184 * Get revision list starting from given commit.
185 * @param max_count number of commit hashes to return, or all if not given
186 * @param start revision to start from, or HEAD if not given
188 function git_get_rev_list($project, $max_count = null, $start = 'HEAD')
190 $cmd = "git rev-list $start";
191 if (!is_null($max_count)) {
192 $cmd = "git rev-list --max-count=$max_count $start";
195 return run_git($project, $cmd);
199 * Get list of tags for a project.
201 function git_get_tags($project)
203 $tags = array();
205 $output = run_git($project, 'git show-ref --tags');
206 foreach ($output as $line) {
207 $fullname = substr($line, 41);
208 $name = array_pop(explode('/', $fullname));
209 $tags[] = array('h' => substr($line, 0, 40), 'fullname' => $fullname, 'name' => $name);
211 return $tags;
215 * Get information about objects in a tree.
216 * @param tree tree or commit hash
217 * @return list of arrays containing name, mode, type, hash
219 function git_ls_tree($project, $tree)
221 $entries = array();
222 $output = run_git($project, "git ls-tree $tree");
223 // 100644 blob 493b7fc4296d64af45dac64bceac2d9a96c958c1 .gitignore
224 // 040000 tree 715c78b1011dc58106da2a1af2fe0aa4c829542f doc
225 foreach ($output as $line) {
226 $parts = preg_split('/\s+/', $line, 4);
227 $entries[] = array('name' => $parts[3], 'mode' => $parts[0], 'type' => $parts[1], 'hash' => $parts[2]);
230 return $entries;
234 * Get information about the given object in a tree, or null if not in the tree.
236 function git_ls_tree_part($project, $tree, $name)
238 $entries = git_ls_tree($project, $tree);
239 foreach ($entries as $entry) {
240 if ($entry['name'] === $name) {
241 return $entry;
244 return null;
248 * Get shortlog entries for the given project.
250 function handle_shortlog($project, $hash = 'HEAD')
252 global $conf;
254 $result = array();
255 $revs = git_get_rev_list($project, $conf['summary_shortlog'], $hash);
256 foreach ($revs as $rev) {
257 $info = git_get_commit_info($project, $rev);
258 $result[] = array(
259 'author' => $info['author_name'],
260 'date' => strftime($conf['datetime'], $info['author_utcstamp']),
261 'message' => $info['message'],
262 'commit_id' => $rev,
263 'tree' => $info['tree'],
267 return $result;
271 * Fetch tags data, newest first.
273 * @param limit maximum number of tags to return
275 function handle_tags($project, $limit = 0)
277 global $conf;
279 $tags = git_get_tags($project);
280 $result = array();
281 foreach ($tags as $tag) {
282 $info = git_get_commit_info($project, $tag['h']);
283 $result[] = array(
284 'stamp' => $info['author_utcstamp'],
285 'date' => strftime($conf['datetime'], $info['author_utcstamp']),
286 'h' => $tag['h'],
287 'fullname' => $tag['fullname'],
288 'name' => $tag['name'],
292 // sort tags newest first
293 // aka. two more reasons to hate PHP (figuring those out is your homework:)
294 usort($result, create_function(
295 '$x, $y',
296 '$a = $x["stamp"]; $b = $y["stamp"]; return ($a == $b ? 0 : ($a > $b ? -1 : 1));'
299 // TODO optimize this some way, currently all tags are fetched when only a
300 // few are shown. The problem is that without fetching the commit info
301 // above, we can't sort using dates, only by tag name...
302 if ($limit > 0) {
303 $result = array_splice($result, 0, $limit);
306 return $result;
310 * Return a URL that contains the given parameters.
312 function makelink($dict)
314 $params = array();
315 foreach ($dict as $k => $v) {
316 $params[] = rawurlencode($k) .'='. str_replace('%2F', '/', rawurlencode($v));
318 if (count($params) > 0) {
319 return '?'. htmlentities(join('&', $params));
321 return '';
325 * Obfuscate the e-mail address.
327 function obfuscate_mail($mail)
329 return str_replace(array('@', '.'), array(' at ', ' dot '), $mail);
333 * Used to format RSS item title and description.
335 * @param info commit info from git_get_commit_info()
337 function rss_item_format($format, $info)
339 return preg_replace(array(
340 '/{AUTHOR}/',
341 '/{AUTHOR_MAIL}/',
342 '/{SHORTLOG}/',
343 '/{LOG}/',
344 '/{COMMITTER}/',
345 '/{COMMITTER_MAIL}/',
346 '/{DIFFSTAT}/',
347 ), array(
348 $info['author_name'],
349 $info['author_mail'],
350 $info['message_firstline'],
351 $info['message_full'],
352 $info['committer_name'],
353 $info['committer_mail'],
354 isset($info['diffstat']) ? $info['diffstat'] : '',
355 ), $format);
358 function rss_pubdate($secs)
360 return date('D, d M Y H:i:s O', $secs);
364 * Executes a git command in the project repo.
365 * @return array of output lines
367 function run_git($project, $command)
369 global $conf;
371 $output = array();
372 $cmd = "GIT_DIR=". $conf['projects'][$project]['repo'] ." $command";
373 exec($cmd, &$output);
374 return $output;
378 * Executes a git command in the project repo, sending output directly to the
379 * client.
381 function run_git_passthru($project, $command)
383 global $conf;
385 $cmd = "GIT_DIR=". $conf['projects'][$project]['repo'] ." $command";
386 $result = 0;
387 passthru($cmd, &$result);
388 return $result;
392 * Makes sure the given project is valid. If it's not, this function will
393 * die().
394 * @return the project
396 function validate_project($project)
398 global $conf;
400 if (!in_array($project, array_keys($conf['projects']))) {
401 die('Invalid project');
403 return $project;
407 * Makes sure the given hash is valid. If it's not, this function will die().
408 * @return the hash
410 function validate_hash($hash)
412 if (!preg_match('/^[0-9a-z]{40}$/', $hash) && !preg_match('!^refs/(heads|tags)/[-.0-9a-z]+$!', $hash) && $hash !== 'HEAD') {
413 die('Invalid hash');
416 return $hash;