Allow showing a diffstat in the RSS feed.
[viewgit.git] / inc / functions.php
blobff7f05292df60b5aba1fb871ea6263536345c636
1 <?php
2 /** @file
3 * Functions used by ViewGit.
4 */
6 function debug($msg)
8 file_put_contents('/tmp/viewgit.log', strftime('%H:%M:%S') ." $_SERVER[REMOTE_ADDR]:$_SERVER[REMOTE_PORT] $msg\n", FILE_APPEND);
11 /**
12 * Formats "git diff" output into xhtml.
13 * @return array(array of filenames, xhtml)
15 function format_diff($text)
17 $files = array();
19 // match every "^diff --git a/<path> b/<path>$" line
20 foreach (explode("\n", $text) as $line) {
21 if (preg_match('#^diff --git a/(.*) b/(.*)$#', $line, $matches) > 0) {
22 $files[$matches[1]] = urlencode($matches[1]);
26 $text = htmlentities($text);
28 $text = preg_replace(
29 array(
30 '/^(\+.*)$/m',
31 '/^(-.*)$/m',
32 '/^(@.*)$/m',
33 '/^([^d\+-@].*)$/m',
35 array(
36 '<span class="add">$1</span>',
37 '<span class="del">$1</span>',
38 '<span class="pos">$1</span>',
39 '<span class="etc">$1</span>',
41 $text);
42 $text = preg_replace_callback('#^diff --git a/(.*) b/(.*)$#m',
43 create_function(
44 '$m',
45 'return "<span class=\"diffline\"><a name=\"". urlencode($m[1]) ."\">diff --git a/$m[1] b/$m[2]</a></span>";'
47 $text);
49 return array($files, $text);
52 /**
53 * Get project information from config and git, name/description and HEAD
54 * commit info are returned in an array.
56 function get_project_info($name)
58 global $conf;
60 $info = $conf['projects'][$name];
61 $info['name'] = $name;
62 $info['description'] = file_get_contents($info['repo'] .'/description');
64 $headinfo = git_get_commit_info($name, 'HEAD');
65 $info['head_stamp'] = $headinfo['author_utcstamp'];
66 $info['head_datetime'] = strftime($conf['datetime'], $headinfo['author_utcstamp']);
67 $info['head_hash'] = $headinfo['h'];
68 $info['head_tree'] = $headinfo['tree'];
70 return $info;
73 function git_diffstat($project, $commit, $commit_base = null)
75 if (is_null($commit_base)) {
76 $commit_base = "$commit^";
78 return join("\n", run_git($project, "git diff --stat $commit_base..$commit"));
81 /**
82 * Get details of a commit: tree, parents, author/committer (name, mail, date), message
84 function git_get_commit_info($project, $hash = 'HEAD')
86 global $conf;
88 $info = array();
89 $info['h_name'] = $hash;
90 $info['message_full'] = '';
91 $info['parents'] = array();
93 $output = run_git($project, "git rev-list --header --max-count=1 $hash");
94 // tree <h>
95 // parent <h>
96 // author <name> "<"<mail>">" <stamp> <timezone>
97 // committer
98 // <empty>
99 // <message>
100 $pattern = '/^(author|committer) ([^<]+) <([^>]*)> ([0-9]+) (.*)$/';
101 foreach ($output as $line) {
102 if (substr($line, 0, 4) === 'tree') {
103 $info['tree'] = substr($line, 5);
105 // may be repeated multiple times for merge/octopus
106 elseif (substr($line, 0, 6) === 'parent') {
107 $info['parents'][] = substr($line, 7);
109 elseif (preg_match($pattern, $line, $matches) > 0) {
110 $info[$matches[1] .'_name'] = $matches[2];
111 $info[$matches[1] .'_mail'] = $matches[3];
112 $info[$matches[1] .'_stamp'] = $matches[4];
113 $info[$matches[1] .'_timezone'] = $matches[5];
114 $info[$matches[1] .'_utcstamp'] = $matches[4] - ((intval($matches[5]) / 100.0) * 3600);
116 if (isset($conf['mail_filter'])) {
117 $info[$matches[1] .'_mail'] = $conf['mail_filter']($info[$matches[1] .'_mail']);
120 elseif (substr($line, 0, 4) === ' ') {
121 $info['message_full'] .= substr($line, 4) ."\n";
122 if (!isset($info['message'])) {
123 $info['message'] = substr($line, 4, $conf['commit_message_maxlen']);
124 $info['message_firstline'] = substr($line, 4);
127 elseif (preg_match('/^[0-9a-f]{40}$/', $line) > 0) {
128 $info['h'] = $line;
132 return $info;
136 * Get list of heads (branches) for a project.
138 function git_get_heads($project)
140 $heads = array();
142 $output = run_git($project, 'git show-ref --heads');
143 foreach ($output as $line) {
144 $fullname = substr($line, 41);
145 $name = array_pop(explode('/', $fullname));
146 $heads[] = array('h' => substr($line, 0, 40), 'fullname' => "$fullname", 'name' => "$name");
149 return $heads;
153 * Get array containing path information for parts, starting from root_hash.
155 * @param root_hash commit/tree hash for the root tree
156 * @param parts array of path fragments
158 function git_get_path_info($project, $root_hash, $parts)
160 $pathinfo = array();
162 $tid = $root_hash;
163 $pathinfo = array();
164 foreach ($parts as $p) {
165 $entry = git_ls_tree_part($project, $tid, $p);
166 $pathinfo[] = $entry;
167 $tid = $entry['hash'];
170 return $pathinfo;
174 * Get revision list starting from given commit.
175 * @param max_count number of commit hashes to return, or all if not given
176 * @param start revision to start from, or HEAD if not given
178 function git_get_rev_list($project, $max_count = null, $start = 'HEAD')
180 $cmd = "git rev-list $start";
181 if (!is_null($max_count)) {
182 $cmd = "git rev-list --max-count=$max_count $start";
185 return run_git($project, $cmd);
189 * Get list of tags for a project.
191 function git_get_tags($project)
193 $tags = array();
195 $output = run_git($project, 'git show-ref --tags');
196 foreach ($output as $line) {
197 $fullname = substr($line, 41);
198 $name = array_pop(explode('/', $fullname));
199 $tags[] = array('h' => substr($line, 0, 40), 'fullname' => $fullname, 'name' => $name);
201 return $tags;
205 * Get information about objects in a tree.
206 * @param tree tree or commit hash
207 * @return list of arrays containing name, mode, type, hash
209 function git_ls_tree($project, $tree)
211 $entries = array();
212 $output = run_git($project, "git ls-tree $tree");
213 // 100644 blob 493b7fc4296d64af45dac64bceac2d9a96c958c1 .gitignore
214 // 040000 tree 715c78b1011dc58106da2a1af2fe0aa4c829542f doc
215 foreach ($output as $line) {
216 $parts = preg_split('/\s+/', $line, 4);
217 $entries[] = array('name' => $parts[3], 'mode' => $parts[0], 'type' => $parts[1], 'hash' => $parts[2]);
220 return $entries;
224 * Get information about the given object in a tree, or null if not in the tree.
226 function git_ls_tree_part($project, $tree, $name)
228 $entries = git_ls_tree($project, $tree);
229 foreach ($entries as $entry) {
230 if ($entry['name'] === $name) {
231 return $entry;
234 return null;
238 * Return a URL that contains the given parameters.
240 function makelink($dict)
242 $params = array();
243 foreach ($dict as $k => $v) {
244 $params[] = rawurlencode($k) .'='. str_replace('%2F', '/', rawurlencode($v));
246 if (count($params) > 0) {
247 return '?'. htmlentities(join('&', $params));
249 return '';
253 * Obfuscate the e-mail address.
255 function obfuscate_mail($mail)
257 return str_replace(array('@', '.'), array(' at ', ' dot '), $mail);
261 * Used to format RSS item title and description.
263 * @param info commit info from git_get_commit_info()
265 function rss_item_format($format, $info)
267 return preg_replace(array(
268 '/{AUTHOR}/',
269 '/{AUTHOR_MAIL}/',
270 '/{SHORTLOG}/',
271 '/{LOG}/',
272 '/{COMMITTER}/',
273 '/{COMMITTER_MAIL}/',
274 '/{DIFFSTAT}/',
275 ), array(
276 $info['author_name'],
277 $info['author_mail'],
278 $info['message_firstline'],
279 $info['message_full'],
280 $info['committer_name'],
281 $info['committer_mail'],
282 isset($info['diffstat']) ? $info['diffstat'] : '',
283 ), $format);
286 function rss_pubdate($secs)
288 return date('D, d M Y H:i:s O', $secs);
292 * Executes a git command in the project repo.
293 * @return array of output lines
295 function run_git($project, $command)
297 global $conf;
299 $output = array();
300 $cmd = "GIT_DIR=". $conf['projects'][$project]['repo'] ." $command";
301 exec($cmd, &$output);
302 return $output;
306 * Executes a git command in the project repo, sending output directly to the
307 * client.
309 function run_git_passthru($project, $command)
311 global $conf;
313 $cmd = "GIT_DIR=". $conf['projects'][$project]['repo'] ." $command";
314 $result = 0;
315 passthru($cmd, &$result);
316 return $result;
320 * Makes sure the given project is valid. If it's not, this function will
321 * die().
322 * @return the project
324 function validate_project($project)
326 global $conf;
328 if (!in_array($project, array_keys($conf['projects']))) {
329 die('Invalid project');
331 return $project;
335 * Makes sure the given hash is valid. If it's not, this function will die().
336 * @return the hash
338 function validate_hash($hash)
340 if (!preg_match('/^[0-9a-z]{40}$/', $hash) && !preg_match('!^refs/(heads|tags)/[-.0-9a-z]+$!', $hash)) {
341 die('Invalid hash');
344 return $hash;