<?php
-/* config.example.php - phpwebirc
+/* config.example.php - phpgitweb
* Copyright (C) 2011-2012 Philipp Kreil (pk910)
*
* This program is free software: you can redistribute it and/or modify
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+/* Global configuration */
+/* GIT configuration */
+class GitConfig {
+ /* PHPGitWeb Title / Name */
+ const GITWEB_TITLE = "PHPGitWeb";
+
+ /* path to git projects (<project>.git) */
+ const PROJECT_ROOT = "/srv/gitosis/repositories/";
+
+ /* Point to projects.list file generated by gitosis. */
+ //const PROJECT_LIST = NULL;
+ const PROJECT_LIST = "/srv/gitosis/gitosis/projects.list";
+
+ /* Only allow Projects from PROJECT_LIST being viewed */
+ const STRICT_EXPORT = true;
+
+ /* Only allow Projects with this file within their PROJECT_ROOT being viewed */
+ //const EXPORT_FILE = "export-ok";
+ const EXPORT_FILE = NULL;
+
+ /* Override Project Owner to this one */
+ const PROJECT_OWNER = NULL;
+
+ /* Template name */
+ const TEMPLATE_NAME = NULL;
+
+ /* Git executable */
+ const GIT_EXEC = NULL; /* autodetect */
+}
+
?>
\ No newline at end of file
--- /dev/null
+body {
+ font-family: sans-serif;
+ font-size: small;
+ border: solid #d9d8d1;
+ border-width: 1px;
+ margin: 10px;
+ background-color: #ffffff;
+ color: #000000;
+}
+
+a {
+ color: #0000cc;
+}
+
+a:hover, a:visited, a:active {
+ color: #880000;
+}
+
+span.cntrl {
+ border: dashed #aaaaaa;
+ border-width: 1px;
+ padding: 0px 2px 0px 2px;
+ margin: 0px 2px 0px 2px;
+}
+
+img.logo {
+ float: right;
+ border-width: 0px;
+}
+
+img.avatar {
+ vertical-align: middle;
+}
+
+a.list img.avatar {
+ border-style: none;
+}
+
+div.page_header {
+ height: 25px;
+ padding: 8px;
+ font-size: 150%;
+ font-weight: bold;
+ background-color: #d9d8d1;
+}
+
+div.page_header a:visited, a.header {
+ color: #0000cc;
+}
+
+div.page_header a:hover {
+ color: #880000;
+}
+
+div.page_nav {
+ padding: 8px;
+}
+
+div.page_nav a:visited {
+ color: #0000cc;
+}
+
+div.page_path {
+ padding: 8px;
+ font-weight: bold;
+ border: solid #d9d8d1;
+ border-width: 0px 0px 1px;
+}
+
+div.page_footer {
+ height: 17px;
+ padding: 4px 8px;
+ background-color: #d9d8d1;
+}
+
+div.page_footer_text {
+ float: left;
+ color: #555555;
+ font-style: italic;
+}
+
+div#generating_info {
+ margin: 4px;
+ font-size: smaller;
+ text-align: center;
+ color: #505050;
+}
+
+div.page_body {
+ padding: 8px;
+ font-family: monospace;
+}
+
+div.title, a.title {
+ display: block;
+ padding: 6px 8px;
+ font-weight: bold;
+ background-color: #edece6;
+ text-decoration: none;
+ color: #000000;
+}
+
+div.readme {
+ padding: 8px;
+}
+
+a.title:hover {
+ background-color: #d9d8d1;
+}
+
+div.title_text {
+ padding: 6px 0px;
+ border: solid #d9d8d1;
+ border-width: 0px 0px 1px;
+ font-family: monospace;
+}
+
+div.log_body {
+ padding: 8px 8px 8px 150px;
+}
+
+span.age {
+ position: relative;
+ float: left;
+ width: 142px;
+ font-style: italic;
+}
+
+span.signoff {
+ color: #888888;
+}
+
+div.log_link {
+ padding: 0px 8px;
+ font-size: 70%;
+ font-family: sans-serif;
+ font-style: normal;
+ position: relative;
+ float: left;
+ width: 136px;
+}
+
+div.list_head {
+ padding: 6px 8px 4px;
+ border: solid #d9d8d1;
+ border-width: 1px 0px 0px;
+ font-style: italic;
+}
+
+.author_date, .author {
+ font-style: italic;
+}
+
+div.author_date {
+ padding: 8px;
+ border: solid #d9d8d1;
+ border-width: 0px 0px 1px 0px;
+}
+
+a.list {
+ text-decoration: none;
+ color: #000000;
+}
+
+a.subject, a.name {
+ font-weight: bold;
+}
+
+table.tags a.subject {
+ font-weight: normal;
+}
+
+a.list:hover {
+ text-decoration: underline;
+ color: #880000;
+}
+
+a.text {
+ text-decoration: none;
+ color: #0000cc;
+}
+
+a.text:visited {
+ text-decoration: none;
+ color: #880000;
+}
+
+a.text:hover {
+ text-decoration: underline;
+ color: #880000;
+}
+
+table {
+ padding: 8px 4px;
+ border-spacing: 0;
+}
+
+table.shortlog td {
+ padding: 0px 8px;
+ vertical-align: middle;
+ height:20px;
+}
+
+img.graph {
+ padding: 0px;
+ margin: 0px;
+ display: block;
+}
+
+table.diff_tree {
+ font-family: monospace;
+}
+
+table.combined.diff_tree th {
+ text-align: center;
+}
+
+table.combined.diff_tree td {
+ padding-right: 24px;
+}
+
+table.combined.diff_tree th.link,
+table.combined.diff_tree td.link {
+ padding: 0px 2px;
+}
+
+table.combined.diff_tree td.nochange a {
+ color: #6666ff;
+}
+
+table.combined.diff_tree td.nochange a:hover,
+table.combined.diff_tree td.nochange a:visited {
+ color: #d06666;
+}
+
+table.blame {
+ border-collapse: collapse;
+}
+
+table.blame td {
+ padding: 0px 5px;
+ font-size: 100%;
+ vertical-align: top;
+}
+
+th {
+ padding: 2px 5px;
+ font-size: 100%;
+ text-align: left;
+}
+
+/* do not change row style on hover for 'blame' view */
+tr.light,
+table.blame .light:hover {
+ background-color: #ffffff;
+}
+
+tr.dark,
+table.blame .dark:hover {
+ background-color: #f6f6f0;
+}
+
+tr.header {
+ background-color: #d9d8d1;
+}
+
+tr.header td {
+ vertical-align: center;
+}
+
+/* currently both use the same, but it can change */
+tr.light:hover,
+tr.dark:hover {
+ background-color: #edece6;
+}
+
+/* boundary commits in 'blame' view */
+/* and commits without "previous" */
+tr.boundary td.sha1,
+tr.no-previous td.linenr {
+ font-weight: bold;
+}
+
+/* for 'blame_incremental', during processing */
+tr.color1 { background-color: #f6fff6; }
+tr.color2 { background-color: #f6f6ff; }
+tr.color3 { background-color: #fff6f6; }
+
+td {
+ padding: 2px 5px;
+ font-size: 100%;
+ vertical-align: top;
+}
+
+td.link, td.selflink {
+ padding: 2px 5px;
+ font-family: sans-serif;
+ font-size: 70%;
+}
+
+td.selflink {
+ padding-right: 0px;
+}
+
+td.sha1 {
+ font-family: monospace;
+}
+
+.error {
+ color: red;
+ background-color: yellow;
+}
+
+td.current_head {
+ text-decoration: underline;
+}
+
+table.diff_tree span.file_status.new {
+ color: #008000;
+}
+
+table.diff_tree span.file_status.deleted {
+ color: #c00000;
+}
+
+table.diff_tree span.file_status.moved,
+table.diff_tree span.file_status.mode_chnge {
+ color: #777777;
+}
+
+table.diff_tree span.file_status.copied {
+ color: #70a070;
+}
+
+/* noage: "No commits" */
+table.project_list td.noage {
+ color: #808080;
+ font-style: italic;
+}
+
+/* age2: 60*60*24*2 <= age */
+table.project_list td.age2, table.blame td.age2 {
+ font-style: italic;
+}
+
+/* age1: 60*60*2 <= age < 60*60*24*2 */
+table.project_list td.age1 {
+ color: #009900;
+ font-style: italic;
+}
+
+table.blame td.age1 {
+ color: #009900;
+ background: transparent;
+}
+
+/* age0: age < 60*60*2 */
+table.project_list td.age0 {
+ color: #009900;
+ font-style: italic;
+ font-weight: bold;
+}
+
+table.blame td.age0 {
+ color: #009900;
+ background: transparent;
+ font-weight: bold;
+}
+
+td.pre, div.pre, div.diff {
+ font-family: monospace;
+ font-size: 12px;
+ white-space: pre;
+}
+
+td.mode {
+ font-family: monospace;
+}
+
+/* progress of blame_interactive */
+div#progress_bar {
+ height: 2px;
+ margin-bottom: -2px;
+ background-color: #d8d9d0;
+}
+div#progress_info {
+ float: right;
+ text-align: right;
+}
+
+/* format of (optional) objects size in 'tree' view */
+td.size {
+ font-family: monospace;
+ text-align: right;
+}
+
+/* styling of diffs (patchsets): commitdiff and blobdiff views */
+div.diff.header,
+div.diff.extended_header {
+ white-space: normal;
+}
+
+div.diff.header {
+ font-weight: bold;
+
+ background-color: #edece6;
+
+ margin-top: 4px;
+ padding: 4px 0px 2px 0px;
+ border: solid #d9d8d1;
+ border-width: 1px 0px 1px 0px;
+}
+
+div.diff.header a.path {
+ text-decoration: underline;
+}
+
+div.diff.extended_header,
+div.diff.extended_header a.path,
+div.diff.extended_header a.hash {
+ color: #777777;
+}
+
+div.diff.extended_header .info {
+ color: #b0b0b0;
+}
+
+div.diff.extended_header {
+ background-color: #f6f5ee;
+ padding: 2px 0px 2px 0px;
+}
+
+div.diff a.list,
+div.diff a.path,
+div.diff a.hash {
+ text-decoration: none;
+}
+
+div.diff a.list:hover,
+div.diff a.path:hover,
+div.diff a.hash:hover {
+ text-decoration: underline;
+}
+
+div.diff.to_file a.path,
+div.diff.to_file {
+ color: #007000;
+}
+
+div.diff.add {
+ color: #008800;
+}
+
+div.diff.from_file a.path,
+div.diff.from_file {
+ color: #aa0000;
+}
+
+div.diff.rem {
+ color: #cc0000;
+}
+
+div.diff.chunk_header a,
+div.diff.chunk_header {
+ color: #990099;
+}
+
+div.diff.chunk_header {
+ border: dotted #ffe0ff;
+ border-width: 1px 0px 0px 0px;
+ margin-top: 2px;
+}
+
+div.diff.chunk_header span.chunk_info {
+ background-color: #ffeeff;
+}
+
+div.diff.chunk_header span.section {
+ color: #aa22aa;
+}
+
+div.diff.incomplete {
+ color: #cccccc;
+}
+
+div.diff.nodifferences {
+ font-weight: bold;
+ color: #600000;
+}
+
+div.index_include {
+ border: solid #d9d8d1;
+ border-width: 0px 0px 1px;
+ padding: 12px 8px;
+}
+
+div.search {
+ font-size: 100%;
+ font-weight: normal;
+ margin: 4px 8px;
+ float: right;
+ top: 56px;
+ right: 12px
+}
+
+p.projsearch {
+ text-align: center;
+}
+
+td.linenr {
+ text-align: right;
+}
+
+a.linenr {
+ color: #999999;
+ text-decoration: none
+}
+
+a.rss_logo {
+ float: right;
+ padding: 3px 0px;
+ width: 35px;
+ line-height: 10px;
+ border: 1px solid;
+ border-color: #fcc7a5 #7d3302 #3e1a01 #ff954e;
+ color: #ffffff;
+ background-color: #ff6600;
+ font-weight: bold;
+ font-family: sans-serif;
+ font-size: 70%;
+ text-align: center;
+ text-decoration: none;
+}
+
+a.rss_logo:hover {
+ background-color: #ee5500;
+}
+
+a.rss_logo.generic {
+ background-color: #ff8800;
+}
+
+a.rss_logo.generic:hover {
+ background-color: #ee7700;
+}
+
+span.refs span {
+ padding: 0px 4px;
+ font-size: 70%;
+ font-weight: normal;
+ border: 1px solid;
+ background-color: #ffaaff;
+ border-color: #ffccff #ff00ee #ff00ee #ffccff;
+}
+
+span.refs span a {
+ text-decoration: none;
+ color: inherit;
+}
+
+span.refs span a:hover {
+ text-decoration: underline;
+}
+
+span.refs span.indirect {
+ font-style: italic;
+}
+
+span.refs span.ref {
+ background-color: #aaaaff;
+ border-color: #ccccff #0033cc #0033cc #ccccff;
+}
+
+span.refs span.tag {
+ background-color: #ffffaa;
+ border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
+}
+
+span.refs span.head {
+ background-color: #aaffaa;
+ border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
+}
+
+span.atnight {
+ color: #cc0000;
+}
+
+span.match {
+ color: #e00000;
+}
+
+div.binary {
+ font-style: italic;
+}
+
+/* Style definition generated by highlight 2.4.5, http://www.andre-simon.de/ */
+
+/* Highlighting theme definition: */
+
+.num { color:#2928ff; }
+.esc { color:#ff00ff; }
+.str { color:#ff0000; }
+.dstr { color:#818100; }
+.slc { color:#838183; font-style:italic; }
+.com { color:#838183; font-style:italic; }
+.dir { color:#008200; }
+.sym { color:#000000; }
+.line { color:#555555; }
+.kwa { color:#000000; font-weight:bold; }
+.kwb { color:#830000; }
+.kwc { color:#000000; font-weight:bold; }
+.kwd { color:#010181; }
--- /dev/null
+<?php
+
+require_once("config.php");
+require_once("lib/PHPGitWeb.class.php");
+
+$page = new PHPGitWeb();
+
+if(array_key_exists('e', $_GET)) {
+ $page->load_extension($_GET['e']);
+} else {
+ if(array_key_exists('p', $_GET))
+ $page->load_project($_GET['p']);
+ if(array_key_exists('a', $_GET))
+ $page->load_content($_GET['a']);
+ else
+ $page->load_content('projects');
+
+ echo $page->get_content();
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/* ContentProvider.class.php - phpgitweb
+ * Copyright (C) 2011-2012 Philipp Kreil (pk910)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class ContentProvider {
+ private static $template_cache = array();
+ private static $overall_content = array();
+
+ private $template, $subtemplate;
+ private $content = array();
+
+ public function __construct($template, $subtemplate, $content = null) {
+ $this->template = $template;
+ $this->subtemplate = $subtemplate;
+
+ if(is_array($content)) {
+ $this->content = $content;
+ }
+ }
+
+ public function set($name, $value) {
+ $this->content[strtolower($name)] = $value;
+ }
+
+ public function append($name, $value) {
+ if(!$value)
+ return;
+ if(!array_key_exists(strtolower($name), $this->content))
+ $this->content[strtolower($name)] = array();
+ $this->content[strtolower($name)][] = $value;
+ }
+
+ public static function overall_set($name, $value) {
+ self::$overall_content[strtolower($name)] = $value;
+ }
+
+ private function load_template($template, $subtemplate) {
+ $tpl_name = 'templates/'.(GitConfig::TEMPLATE_NAME ? GitConfig::TEMPLATE_NAME : 'default').'/'.$template.'.tpl';
+ if(!file_exists($tpl_name))
+ return null;
+ $tpl = file($tpl_name);
+ self::$template_cache[$template] = array();
+
+ $cname = null;
+ foreach ($tpl as $line) {
+ if(preg_match('/^# \[(.*)\]/i', $line, $result)) {
+ $cname = $result[1];
+ self::$template_cache[$template][$cname] = '';
+ } else if($cname)
+ self::$template_cache[$template][$cname] .= $line;
+ }
+
+ return (array_key_exists($subtemplate, self::$template_cache[$template]) ? self::$template_cache[$template][$subtemplate] : null);
+ }
+
+ private function replace_placeholder($result) {
+ $var = strtolower($result[1]);
+ switch($var) {
+ case "version":
+ $rep = PHPGITWEB_VERSION;
+ break;
+ case "year":
+ $rep = date("Y");
+ break;
+ case "title":
+ $rep = GitConfig::GITWEB_TITLE;
+ break;
+ case "rendertime":
+ $rep = "%rendertime%"; //gets replaced later
+ break;
+ default:
+ if(array_key_exists($var, $this->content)) {
+ $rep = $this->resolve_content($this->content[$var]);
+ } else if(array_key_exists($var, self::$overall_content)) {
+ $rep = $this->resolve_content(self::$overall_content[$var]);
+ } else
+ $rep = $var;
+ }
+ return $rep;
+ }
+
+ private function resolve_content($content) {
+ $output = "";
+ if(is_array($content)) {
+ foreach($content as $part) {
+ $output .= $this->resolve_content($part);
+ }
+ } elseif(is_a($content, "ContentProvider"))
+ $output = $content->output();
+ else
+ $output = $content;
+ return $output;
+ }
+
+ public function output() {
+ $subtemplate = strtolower($this->subtemplate);
+ if(array_key_exists($this->template, self::$template_cache))
+ $template_html = (array_key_exists($subtemplate, self::$template_cache[$this->template]) ? self::$template_cache[$this->template][$subtemplate] : null);
+ else {
+ $template_html = $this->load_template($this->template, $subtemplate);
+ }
+ $template_html = preg_replace_callback('/%([^%]*)%/', array($this, "replace_placeholder"), $template_html);
+
+ return $template_html;
+ }
+
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/* GitCommand.class.php - phpgitweb
+ * Copyright (C) 2011-2012 Philipp Kreil (pk910)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class GitCommand {
+ private static $git = null;
+ private static $counter = 0;
+
+ private static function command_header($git_path = null) {
+ if(!self::$git) {
+ //find git executable
+ if(GitConfig::GIT_EXEC)
+ self::$git = GitConfig::GIT_EXEC;
+ else
+ self::$git = str_replace(array("\n", "\r"), array("", ""), `which git`);
+ if(!self::$git) {
+ trigger_error("Can not find git executable.", E_USER_ERROR);
+ self::$git = "git";
+ }
+ }
+ $command = self::$git;
+ if($git_path)
+ $command .= " --git-dir=".$git_path;
+ return $command;
+ }
+
+ private static function execute($command) {
+ $result = array();
+ exec($command, $result);
+ self::$counter++;
+ return implode("\n", $result);
+ }
+
+ public static function core_version() {
+ $command = self::command_header();
+ if(!$command)
+ return null;
+ $command .= " --version";
+ $version = self::execute($command);
+ preg_match("/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/i", $version, $result);
+ return $result[0];
+ }
+
+ public static function last_activity($git_path, $ref = null) {
+ $command = self::command_header($git_path);
+ if(!$command)
+ return null;
+ $command .= " for-each-ref --format=%\(committer\) --sort=-committerdate --count=1";
+ if($ref)
+ $command .= " ".$ref;
+ $age = self::execute($command);
+ preg_match("/[0-9]{9,}/i", $age, $result);
+ return $result[0];
+ }
+
+ private static function parse_commit($commit_data) {
+ $commit = array();
+ $rev_lines = explode("\n", str_replace("\r", "", $commit_data));
+ $commit['id'] = $rev_lines[0];
+ foreach($rev_lines as $rev_line) {
+ if(substr($rev_line, 0, 4) == " ") {
+ if(array_key_exists('text', $commit))
+ $commit['text'] .= "\n";
+ else
+ $commit['text'] = '';
+ $commit['text'] .= substr($rev_line, 4);
+ } else {
+ $opt = explode(" ", $rev_line, 2);
+ if($opt[0] == "tree")
+ $commit['tree'] = $opt[1];
+ else if($opt[0] == "parent")
+ $commit['parent'][] = $opt[1];
+ else if($opt[0] == "author") {
+ preg_match('/(.*) <([^>]*)> ([0-9]*) [+-0-9]*/i', $opt[1], $matches);
+ $commit['author'] = $matches[1];
+ $commit['author_mail'] = $matches[2];
+ $commit['author_time'] = $matches[3];
+ } else if($opt[0] == "committer") {
+ preg_match('/(.*) <([^>]*)> ([0-9]*) [+-0-9]*/i', $opt[1], $matches);
+ $commit['committer'] = $matches[1];
+ $commit['committer_mail'] = $matches[2];
+ $commit['committer_time'] = $matches[3];
+ }
+ }
+ }
+ return $commit;
+ }
+
+ public static function get_commits($git_path, $head, $maxcount, $skip, $file = null) {
+ $command = self::command_header($git_path);
+ if(!$command)
+ return null;
+ $command .= " rev-list --header --max-count=".$maxcount." --skip=".$skip." ".($head ? $head : "--all")." --";
+ if($file)
+ $command .= " ".$file;
+ $commit_list = self::execute($command);
+ $commits = array();
+ foreach(explode("\000", $commit_list) as $commit) {
+ if($commit)
+ $commits[] = self::parse_commit($commit);
+ }
+ return $commits;
+ }
+
+}
--- /dev/null
+<?php
+/* GITCore.class.php - phpgitweb
+ * Copyright (C) 2011-2012 Philipp Kreil (pk910)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+define("PHPGITWEB_VERSION", "0.0.1");
+require_once("lib/ContentProvider.class.php");
+require_once("lib/ProjectLoader.class.php");
+require_once("lib/GitCommand.class.php");
+require_once("lib/Tools.class.php");
+require_once("lib/graph.class.php");
+
+class PHPGitWeb {
+ private $page, $rendertime;
+ private $project, $project_loader, $project_header;
+
+ public function __construct() {
+ $this->rendertime = microtime(true);
+ $this->page = new ContentProvider('main', 'main');
+ set_error_handler(array($this, "error_handler"));
+ $this->append_header_nav('projects', '?a=projects', false);
+ $this->page->set('git_version', GitCommand::core_version());
+
+ $this->project_loader = new ProjectLoader();
+ }
+
+ public function get_project_loader() {
+ return $this->project_loader;
+ }
+
+ public function load_project($project) {
+ $this->project = $this->project_loader->getProject($project);
+
+ if(!$this->project)
+ $this->page->append('content', new ContentProvider('main', 'project_error'));
+ else {
+ ContentProvider::overall_set('project', $this->project['name']);
+ ContentProvider::overall_set('project_head', "HEAD");
+ $this->project_header = new ContentProvider('main', 'project_header');
+ $this->project_header->set('sub_nav', "");
+ $this->page->append('content', $this->project_header);
+ }
+ }
+
+ public function get_project_header() {
+ return $this->project_header;
+ }
+
+ public function load_content($page) {
+ $class_name = 'page_'.basename($page);
+ $class_file = 'pages/'.basename($page).'.class.php';
+ $static_file = 'pages/'.basename($page).'.html';
+ if(file_exists($class_file)) {
+ require_once($class_file);
+ $pageobj = new $class_name;
+ $this->page->append('content', $pageobj->main($this, $this->project));
+ } else if(file_exists($static_file)) {
+ $this->page->append('content', file_get_contents($static_file));
+ } else
+ $this->page->append('content', file_get_contents('pages/404.html'));
+ if($this->project_header) {
+ $this->project_header->set('nav_summary', new ContentProvider('main', ($page == 'summary' ? 'project_header_nav_active' : 'project_header_nav_link'), array('name' => "summary", 'link' => "summary")));
+ $this->project_header->set('nav_shortlog', new ContentProvider('main', ($page == 'shortlog' ? 'project_header_nav_active' : 'project_header_nav_link'), array('name' => "shortlog", 'link' => "shortlog")));
+ $this->project_header->set('nav_log', new ContentProvider('main', ($page == 'log' ? 'project_header_nav_active' : 'project_header_nav_link'), array('name' => "log", 'link' => "log")));
+ $this->project_header->set('nav_commit', new ContentProvider('main', ($page == 'commit' ? 'project_header_nav_active' : 'project_header_nav_link'), array('name' => "commit", 'link' => "commit")));
+ $this->project_header->set('nav_commitdiff', new ContentProvider('main', ($page == 'commitdiff' ? 'project_header_nav_active' : 'project_header_nav_link'), array('name' => "commitdiff", 'link' => "commitdiff")));
+ $this->project_header->set('nav_tree', new ContentProvider('main', ($page == 'tree' ? 'project_header_nav_active' : 'project_header_nav_link'), array('name' => "tree", 'link' => "tree")));
+ }
+ }
+
+ public function append_header_nav($name, $link, $prepend_slash = true) {
+ if($prepend_slash)
+ $this->page->append('header_nav', ' / ');
+ if($link)
+ $this->page->append('header_nav', '<a href="'.$link.'">'.$name.'</a>');
+ else
+ $this->page->append('header_nav', $name);
+ }
+
+ public function load_extension($extension) {
+ switch($extension) {
+ case "graph":
+ $graph = new graph_image_generator();
+ $graph->generate($_GET['gd']);
+ break;
+ }
+ }
+
+ public function get_content() {
+ $html = $this->page->output();
+ $rendertime = round(microtime(true) - $this->rendertime,3);
+ $html = str_replace(array("%rendertime%"), array($rendertime), $html);
+ return $html;
+ }
+
+ public function error_handler($errno, $errstr, $errfile, $errline) {
+ $error = new ContentProvider('main', 'error');
+ $etype = "";
+ switch($errno) {
+ case E_ERROR: $etype = "E_ERROR"; break;
+ case E_WARNING: $etype = "E_WARNING"; break;
+ case E_PARSE: $etype = "E_PARSE"; break;
+ case E_NOTICE: $etype = "E_NOTICE"; break;
+ case E_CORE_ERROR: $etype = "E_CORE_ERROR"; break;
+ case E_CORE_WARNING: $etype = "E_CORE_WARNING"; break;
+ case E_COMPILE_ERROR: $etype = "E_COMPILE_ERROR"; break;
+ case E_COMPILE_WARNING: $etype = "E_COMPILE_WARNING"; break;
+ case E_USER_ERROR: $etype = "E_USER_ERROR"; break;
+ case E_USER_WARNING: $etype = "E_USER_WARNING"; break;
+ case E_USER_NOTICE: $etype = "E_USER_NOTICE"; break;
+ case E_STRICT: $etype = "E_STRICT"; break;
+ case E_RECOVERABLE_ERROR: $etype = "E_RECOVERABLE_ERROR"; break;
+ case E_DEPRECATED: $etype = "E_DEPRECATED"; break;
+ case E_USER_DEPRECATED: $etype = "E_USER_DEPRECATED"; break;
+ }
+ $error->set('errno', $errno);
+ $error->set('errtype', $etype);
+ $error->set('errstr', $errstr);
+ $error->set('errfile', $errfile);
+ $error->set('errline', $errline);
+ $this->page->append('content', $error);
+ }
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/* ProjectLoader.class.php - phpgitweb
+ * Copyright (C) 2011-2012 Philipp Kreil (pk910)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class ProjectLoader {
+ private $projects = NULL;
+
+ private function loadProjects($load_details = true) {
+ $this->projects = array();
+ if(GitConfig::PROJECT_LIST) {
+ foreach(file(GitConfig::PROJECT_LIST) as $entry) {
+ $entry = explode(" ", $entry);
+
+ if($load_details) {
+ //check if project really exists
+ $project = $this->loadProject($entry[0], false);
+ if($project === NULL)
+ continue;
+ } else
+ $project['name'] = $entry[0];
+
+ $project['owner'] = $entry[1];
+ $this->projects[] = $project;
+ }
+ } else {
+ //walk through PROJECT_ROOT
+
+ }
+ }
+
+ private function loadProject($name, $check_strict_export = true) {
+ $project = array();
+ $project['name'] = $name;
+
+ $dir_seperator = (substr(GitConfig::PROJECT_ROOT, -1) == '/' ? '' : '/');
+ if(is_dir(GitConfig::PROJECT_ROOT.$dir_seperator.$name))
+ $project['path'] = GitConfig::PROJECT_ROOT.$dir_seperator.$name;
+ else if(is_dir(GitConfig::PROJECT_ROOT.$dir_seperator.$name.".git"))
+ $project['path'] = GitConfig::PROJECT_ROOT.$dir_seperator.$name.".git";
+ else
+ return NULL;
+ if(is_dir($project['path'].'/.git'))
+ $project['path'] .= '/.git';
+
+ if(GitConfig::EXPORT_FILE && !file_exists($project['path'].'/'.GitConfig::EXPORT_FILE))
+ return NULL;
+
+ if(GitConfig::STRICT_EXPORT && $check_strict_export) {
+ if($this->projects === NULL)
+ $this->loadProjects(false);
+ $found = false;
+ foreach($this->projects as $p) {
+ if(strtolower($p['name']) == strtolower($name)) {
+ $found = true;
+ $project['name'] = $p['name'];
+ break;
+ }
+ }
+ if(!$found)
+ return NULL;
+ }
+
+ if(file_exists($project['path'].'/description'))
+ $project['description'] = file_get_contents($project['path'].'/description');
+ else
+ $project['description'] = "";
+
+ if(GitConfig::PROJECT_OWNER)
+ $project['owner'] = GitConfig::PROJECT_OWNER;
+ else {
+ $project['owner'] = fileowner($project['path']);
+ $owner = posix_getpwuid($project['owner']);
+ if($owner && $owner['name'])
+ $project['owner'] = $owner['name'];
+ }
+
+ return $project;
+ }
+
+ public function getProjectList() {
+ $this->loadProjects();
+ return $this->projects;
+ }
+
+ public function getProject($name) {
+ return $this->loadProject($name);
+ }
+
+ public function getLastChange($project) {
+ if(!array_key_exists('last_activity', $project)) {
+ $project['last_activity'] = GitCommand::last_activity($project['path']);
+ if(!$project['last_activity'])
+ $project['last_activity'] = 0;
+ }
+ return $project['last_activity'];
+ }
+
+ private function getProjectRefsRecursive(&$project, $cref) {
+ if ($dh = opendir($project['path'].'/'.$cref)) {
+ while (($file = readdir($dh)) !== false) {
+ if($file == '.' || $file == '..')
+ continue;
+ if(is_dir($project['path'].'/'.$cref.'/'.$file))
+ $this->getProjectRefsRecursive($project, $cref.'/'.$file);
+ else {
+ $refval = file($project['path'].'/'.$cref.'/'.$file);
+ $project['refs'][$cref.($cref == '' ? '' : '/').$file] = str_replace(array("\r", "\n"), array("", ""), $refval[0]);
+ }
+ }
+ }
+ }
+
+ public function getProjectRefs($project) {
+ if(!array_key_exists('refs', $project)) {
+ if(is_dir($project['path'].'/refs')) {
+ $project['refs'] = array();
+ $this->getProjectRefsRecursive($project, 'refs');
+ }
+ }
+ return $project['refs'];
+ }
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/* Tools.class.php - phpgitweb
+ * Copyright (C) 2011-2012 Philipp Kreil (pk910)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class Tools {
+
+ public static function age_calculate($last_change) {
+ $result = array();
+ $now = time();
+ $age = ($last_change > 0 ? ($now - $last_change) : 0);
+
+ if ($age > 60*60*24*365*2) {
+ $age_str = floor($age/60/60/24/365);
+ $age_str .= " years ago";
+ } else if ($age > 60*60*24*(365/12)*2) {
+ $age_str = floor($age/60/60/24/(365/12));
+ $age_str .= " months ago";
+ $max_cache = min((60*60*24*(365/12)) - ($age % (60*60*24*(365/12))), (60*60*24*365*2) - $age);
+ } else if ($age > 60*60*24*7*2) {
+ $age_str = floor($age/60/60/24/7);
+ $age_str .= " weeks ago";
+ $max_cache = min((60*60*24*7) - ($age % (60*60*24*7)), (60*60*24*(365/12)*2) - $age);
+ } else if ($age > 60*60*24*2) {
+ $age_str = floor($age/60/60/24);
+ $age_str .= " days ago";
+ $max_cache = min((60*60*24) - ($age % (60*60*24)), (60*60*24*7*2) - $age);
+ } else if ($age > 60*60*2) {
+ $age_str = floor($age/60/60);
+ $age_str .= " hours ago";
+ $max_cache = min((60*60) - ($age % (60*60)), (60*60*24*2) - $age);
+ } else if ($age > 60*2) {
+ $age_str = floor($age/60);
+ $age_str .= " min ago";
+ $max_cache = min(60 - ($age % 60), (60*60*2) - $age);
+ } else if ($age > 2) {
+ $age_str = $age;
+ $age_str .= " sec ago";
+ $max_cache = 1;
+ } else if ($age >= 0) {
+ $age_str = "right now";
+ $max_cache = 1;
+ } else {
+ $max_cache = -1;
+ }
+ if($age == 0) {
+ $age_class = "noage";
+ } else if ($age < 60*60*2) {
+ $age_class = "age0";
+ } else if ($age < 60*60*24*2) {
+ $age_class = "age1";
+ } else {
+ $age_class = "age2";
+ }
+
+ return array("age_str" => $age_str, "age_class" => $age_class, "max_cache" => $max_cache);
+ }
+
+ public static function chop_text($text, $len, $add_len) {
+ if(strlen($text) <= $len + $add_len)
+ return $text;
+ $ctext = substr($text, 0, $len);
+ for($i = 0; $i < $add_len; $i++) {
+ if($text[$len+$i] == ' ')
+ break;
+ $ctext .= $text[$len+$i];
+ }
+ $ctext .= "...";
+ return $ctext;
+ }
+
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/* graph.class.php - phpgitweb
+ * Copyright (C) 2011-2012 Philipp Kreil (pk910)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class graph_data_generator {
+ const DOT_TYPE_NORMAL = 'a';
+ const DOT_TYPE_MERGE = 'b';
+ const DOT_TYPE_INIT = 'c';
+
+ private $data = array();
+ private $graph = array();
+ private $max_branches, $brach_id = 1, $branch_id = 1;
+
+ public function __construct() {
+ $this->max_branches = 10;
+ $this->data['branches'] = array();
+ $this->data['ubranches'] = array();
+ }
+
+ public function add_branch($first_id, $name) {
+ $existing = false;
+ foreach($this->data['branches'] as &$branch) {
+ if($branch['next'] == $first_id) {
+ $existing = true;
+ $branch['name'][] = $name;
+ break;
+ }
+ }
+ unset($branch);
+ if($existing)
+ continue;
+ $this->data['branches'][count($this->data['branches'])] = array(
+ "id" => $this->brach_id++,
+ "uid" => $this->branch_uid++,
+ "active" => true,
+ "sticky" => true,
+ "name" => array($name),
+ "next" => $first_id,
+ "pre_merge" => false
+ );
+ }
+
+ public function parse($commits) {
+ $brach_id = $this->brach_id;
+ $branch_uid = $this->branch_id;
+ $first_commit = (count($this->data['branches']) == 0 ? true : false);
+ foreach($commits as $commit) {
+ //get current branch
+ $commit['merge'] = array();
+ $commit['dot_type'] = self::DOT_TYPE_NORMAL;
+ if($first_commit) {
+ $first_commit = false;
+ $this->data['branches'][0] = array();
+ $branch = &$this->data['branches'][0];
+ $branch['id'] = $brach_id++;
+ $branch['uid'] = $branch_uid++;
+ $branch['active'] = true;
+ } else {
+ $first = true;
+ foreach($this->data['branches'] as $id => &$cbranch) {
+ if($cbranch['next'] == $commit['id']) {
+ if($first && !$cbranch['pre_merge']) {
+ $branch = &$this->data['branches'][$id];
+ $first = false;
+ }
+ }
+ }
+ foreach($this->data['branches'] as $id => &$cbranch) {
+ if($cbranch['next'] == $commit['id']) {
+ if($first) {
+ $branch = &$this->data['branches'][$id];
+ $first = false;
+ } else if($cbranch['id'] == $branch['id']) {
+ } else {
+ $commit['merge'][] = array("point" => $cbranch['id'], "start" => true, "end" => false);
+ $cbranch['active'] = false;
+ if($cbranch['pre_merge']) {
+ $cbranch['pre_merge_start'] = true;
+ $cbranch['pre_merge_id'] = $branch['id'];
+ $this->data['ubranches'][$cbranch['uid']] = $this->data['branches'][$cbranch['id']-1];
+ }
+ }
+ }
+ }
+ unset($cbranch);
+ if($first) {
+ $this->data['branches'][count($this->data['branches'])] = array();
+ $branch = &$this->data['branches'][count($this->data['branches'])-1];
+ $branch['id'] = $brach_id++;
+ $branch['uid'] = $branch_uid++;
+ $branch['active'] = true;
+ $branch['pre_merge'] = false;
+
+ }
+ }
+
+ if(array_key_exists('parent', $commit) && count($commit['parent']) > 1) {
+ //merge(s)
+ for($j = 1; $j < count($commit['parent']); $j++) {
+ $add = true;
+ foreach($this->data['branches'] as $cbranch) {
+ if($cbranch['next'] == $commit['parent'][$j]) {
+ $add = false;
+ break;
+ }
+ }
+ if($add) {
+ $cadd = true;
+ foreach($this->data['branches'] as $bid => &$cbranch) {
+ if(!$cbranch['active']) {
+ $cadd = false;
+ break;
+ }
+ }
+ if($cadd) {
+ $this->data['branches'][count($this->data['branches'])] = array();
+ $cbranch = &$this->data['branches'][count($this->data['branches'])-1];
+ $cbranch['id'] = $brach_id++;
+ }
+ $cbranch['uid'] = $branch_uid++;
+ $cbranch['active'] = true;
+ $cbranch['pre_merge'] = true;
+ $cbranch['next'] = $commit['parent'][$j];
+ }
+ $commit['merge'][] = array("point" => $cbranch['id'], "start" => false, "end" => $add);
+ $commit['dot_type'] = self::DOT_TYPE_MERGE;
+ $this->data['ubranches'][$cbranch['uid']] = $this->data['branches'][$cbranch['id']-1];
+ unset($cbranch);
+ }
+ } else if(!array_key_exists('parent', $commit) || count($commit['parent']) == 0) {
+ $branch['active'] = false;
+ $commit['dot_type'] = self::DOT_TYPE_INIT;
+ }
+ $branch['next'] = (array_key_exists('parent', $commit) ? $commit['parent'][0] : null);
+ $branch['pre_merge'] = false;
+ $this->data['ubranches'][$branch['uid']] = $this->data['branches'][$branch['id']-1];
+
+ $commit['dot'] = $branch['id'];
+
+ foreach($this->data['branches'] as $id => $cbranch) {
+ $commit['branches'][$id] = $cbranch;
+ }
+
+ $this->graph[$commit['id']] = $this->graph_data($commit);
+ //echo$commit['id']." ".$this->get_graph($commit['id'])."\n";
+ }
+ }
+
+ private function graph_data($commit) {
+ $data = array();
+ $data['d'] = array();
+ $data['d']['p'] = $commit['dot']; //dot position
+ $data['d']['type'] = 'a';
+ $data['l'] = array(); //lines
+ $data['br'] = array(); //branches for color check
+ foreach($commit['branches'] as $branch) {
+ if($branch['pre_merge'] || $commit['merge']) {
+ $data['br'][] = $branch['uid'];
+ }
+ if($branch['active']) {
+ if($commit['dot'] == $branch['id']) continue;
+ $show = true;
+ if($commit['merge']) {
+ foreach($commit['merge'] as $merge) {
+ if($merge['point'] == $branch['id']) {
+ $show = false;
+ break;
+ }
+ }
+ }
+ if(!$show) continue;
+ if($branch['id'] > $this->max_branches) continue;
+ $data['l'][] = $branch['id'];
+ }
+ }
+ $data['m'] = array(); //merges
+ if($commit['merge']) {
+ foreach($commit['merge'] as $merge) {
+ $mergepoint = array();
+ $mergepoint['hl'] = array();
+ $mergepoint['p'] = $merge['point'];
+
+ if($commit['dot'] <= $this->max_branches)
+ $mergepoint['dd'] = ($commit['dot'] < $merge['point'] ? 'r' : 'l');
+ else
+ $mergepoint['dd'] = 'n';
+
+ $mergepoint['ml'] = ($merge['start'] ? 1 : 0) + ($merge['end'] ? 2 : 0);
+ if($merge['point'] <= $this->max_branches)
+ $mergepoint['md']=($commit['dot'] < $merge['point'] ? 'l' : 'r');
+ else
+ $mergepoint['md'] = 'n';
+ $min = ($commit['dot'] < $merge['point'] ? $commit['dot'] : $merge['point']) + 1;
+ $max = ($commit['dot'] < $merge['point'] ? $merge['point'] : $commit['dot']);
+ for($i = $min; $i < $max; $i++) {
+ if($i > $this->max_branches) continue;
+ $mergepoint['hl'][] = $i;
+ }
+ $data['m'][] = $mergepoint;
+ }
+ }
+ $data['d']['type'] = $commit['dot_type'];
+ return $data;
+ }
+
+ public function get_graph($id) {
+ $graph = $this->graph[$id];
+ $data = $graph['d']['p'].$graph['d']['type'].count($this->data['branches']).'('.implode(',', $graph['l']).')';
+ $first_merge = true;
+ foreach($graph['m'] as $merge) {
+ if(!$first_merge)
+ $data .= '|';
+ $first_merge = false;
+ $data.=$merge['p'].$merge['dd'].$merge['md'].$merge['ml'];
+ foreach($merge['hl'] as $hline)
+ $data.=','.$hline;
+ }
+ $graph['cs'] = array();
+ foreach($graph['br'] as $buid) {
+ $branch = $this->data['ubranches'][$buid];
+ if($branch['pre_merge'] && $branch['pre_merge_start'])
+ $graph['cs'][] = $branch['id']."=".$branch['pre_merge_id'];
+ }
+ if(count($graph['cs'])) {
+ $data .= '('.implode(',', $graph['cs']).')';
+ }
+ return $data;
+ }
+
+}
+
+class graph_image_generator {
+ private $max_branches = 10;
+ private $image;
+ private $size = 20;
+ private $tile_size = 20;
+ private $colors, $color_swap = array();
+
+ public function generate($data) {
+ $data = $this->parse_data($data);
+ if(!$data)
+ return;
+
+ $this->colors = array(
+ NULL,
+ array(255, 0, 0),
+ array(array(0, 255, 0), array(0, 192, 0)),
+ array(0, 0, 255),
+ array(128, 128, 128),
+ array(128, 128, 0),
+ array(0, 128, 128),
+ array(128, 0, 128)
+ );
+
+ $count = $data['count'];
+ if($count > $this->max_branches)
+ $count = $this->max_branches;
+ $this->image = imagecreatetruecolor($count * $this->size, $this->size);
+ $transparentIndex = imagecolorallocate($this->image, 0xFF, 0xFF, 0xFF);
+ imagefill($this->image, 0, 0, $transparentIndex);
+
+ $this->apply_data($data);
+
+ imagecolortransparent($this->image, $transparentIndex);
+
+ header('Content-Type: image/png');
+ imagepng($this->image);
+ imagedestroy($this->image);
+ }
+
+ private function parse_data($data) {
+ //$data = array();
+ if(!preg_match("/^([0-9]+)([abc]{1})([0-9]+)\(([^\)]*)\)([a-z0-9,\|]*)(\(([^\)]*)\)|)/i", $data, $matches))
+ return null;
+
+ $data = array();
+ $data['dot'] = array();
+ $data['dot']['pos'] = $matches[1];
+ $data['dot']['type'] = $matches[2];
+ $data['count'] = $matches[3];
+
+ if($matches[4] != '')
+ $data['l'] = explode(',', $matches[4]);
+ else
+ $data['l'] = array();
+
+ $data['m'] = array();
+ if($matches[5] != '') {
+ foreach(explode('|', $matches[5]) as $m) {
+ $merge = array();
+
+ if(!preg_match("/^([0-9]+)([rln]{1})([rln]{1})([0123]{1})(.*)/i", $m, $sm))
+ return null;
+ $merge['hl'] = array();
+ $merge['pos'] = $sm[1];
+ $merge['dd'] = $sm[2];
+ $merge['md'] = $sm[3];
+ $merge['ml'] = $sm[4];
+ if($sm[5] != '') {
+ $merge['hl'] = explode(',', $sm[5]);
+ }
+ $data['m'][] = $merge;
+ }
+ }
+ if($matches[6] != '' && $matches[7] != '') {
+ foreach(explode(',', $matches[7]) as $cswap) {
+ $cswap = explode("=", $cswap);
+ $this->color_swap[$cswap[0]] = $cswap[1];
+ }
+ }
+ return $data;
+ }
+
+ function image_set_color($src, $color) {
+ imagesavealpha($src, true);
+ imagealphablending($src, false);
+ // scan image pixels
+ for ($x = 0; $x < $this->size; $x++) {
+ for ($y = 0; $y < $this->size; $y++) {
+ $src_pix = imagecolorat($src,$x,$y);
+ $src_pix_array = imagecolorsforindex($src, $src_pix);
+
+ imagesetpixel($src, $x, $y, imagecolorallocatealpha($src, $color[0], $color[1], $color[2], $src_pix_array['alpha']));
+ }
+ }
+ }
+
+ function overlay_image($name, $left, $color = false) {
+ $image2 = imagecreatefrompng($name);
+
+ if($color) {
+ $this->image_set_color($image2, $color);
+ }
+ imagecopyresampled($this->image, $image2, $left, 0, 0, 0, $this->size, $this->size, $this->tile_size, $this->tile_size);
+ }
+
+ function get_color($id, $text = false) {
+ if(array_key_exists($id, $this->color_swap))
+ $id = $this->color_swap[$id];
+ $color_array = $this->colors[($id - 1) % count($this->colors)];
+ if($text && is_array($color_array[0]) && $color_array[1])
+ return $color_array[1];
+ return (is_array($color_array[0]) ? $color_array[0] : $color_array);
+ }
+
+ private function apply_data($data) {
+ foreach($data['l'] as $l)
+ $this->overlay_image("img/line.png", ($l-1) * $this->size, $this->get_color($l));
+ foreach($data['m'] as $m) {
+ if($m['dd'] == 'r')
+ $this->overlay_image("img/dot_merge_right.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($m['pos']));
+ else if($m['dd'] == 'l')
+ $this->overlay_image("img/dot_merge_left.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($m['pos']));
+ if($m['md'] == 'r')
+ $this->overlay_image("img/".(($m['ml'] & 1) ? "branch" : "merge")."_right.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos']));
+ else if($m['md'] == 'l')
+ $this->overlay_image("img/".(($m['ml'] & 1) ? "branch" : "merge")."_left.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos']));
+ if($m['ml'] == 0)
+ $this->overlay_image("img/line.png", ($m['pos'] - 1) * $this->size, $this->get_color($m['pos']));
+ foreach($m['hl'] as $hl) {
+ $this->overlay_image("img/line_h.png", ($hl - 1) * $this->size, $this->get_color($m['pos']));
+ }
+ }
+ if($data['dot']['type'] == 'a')
+ $this->overlay_image("img/dot.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos']));
+ else if($data['dot']['type'] == 'b')
+ $this->overlay_image("img/dot_merge.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos']));
+ else if($data['dot']['type'] == 'c')
+ $this->overlay_image("img/dot_init.png", ($data['dot']['pos'] - 1) * $this->size, $this->get_color($data['dot']['pos']));
+
+ }
+
+}
+
+?>
\ No newline at end of file
--- /dev/null
+order deny, allow
+deny from all
\ No newline at end of file
--- /dev/null
+<div class="page_body">
+404 - Page not found
+<br /><br />
+</div>
\ No newline at end of file
--- /dev/null
+<?php
+/* projects.class.php - phpgitweb
+ * Copyright (C) 2011-2012 Philipp Kreil (pk910)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class page_projects {
+ private $page, $phpgitweb;
+
+ public function main($phpgitweb, $project) {
+ $this->phpgitweb = $phpgitweb;
+ $this->page = new ContentProvider('projects', 'main');
+
+ $project_loader = $phpgitweb->get_project_loader();
+ $project_counter = 0;
+ $project_list = $project_loader->getProjectList();
+
+ foreach($project_list as $projectid => &$project) {
+ //load some additional information
+ $project['last_change'] = $project_loader->getLastChange($project);
+ }
+ unset($project);
+
+ if(array_key_exists('o', $_GET))
+ $order = $_GET['o'];
+ else
+ $order = 'project';
+
+ $this->page->set('header_project', new ContentProvider('projects', ($order == 'project' ? 'head_order_active' : 'head_order_link'), array('name' => "Project", 'tag' => "project")));
+ $this->page->set('header_description', new ContentProvider('projects', ($order == 'descr' ? 'head_order_active' : 'head_order_link'), array('name' => "Description", 'tag' => "descr")));
+ $this->page->set('header_owner', new ContentProvider('projects', ($order == 'owner' ? 'head_order_active' : 'head_order_link'), array('name' => "Owner", 'tag' => "owner")));
+ $this->page->set('header_age', new ContentProvider('projects', ($order == 'age' ? 'head_order_active' : 'head_order_link'), array('name' => "Last Change", 'tag' => "age")));
+
+ switch($order) {
+ case 'project':
+ usort($project_list, array($this, "sort_by_project"));
+ break;
+ case 'descr':
+ usort($project_list, array($this, "sort_by_description"));
+ break;
+ case 'owner':
+ usort($project_list, array($this, "sort_by_owner"));
+ break;
+ case 'age':
+ usort($project_list, array($this, "sort_by_age"));
+ break;
+ }
+
+ foreach($project_list as $projectid => $project) {
+ $project_counter++;
+ $project_entry = $this->project(($project_counter % 2 ? 'dark' : 'light'), $project);
+ $this->page->append('projects', $project_entry);
+ }
+
+ return $this->page;
+ }
+
+ private function sort_by_project($a, $b) {
+ return strcmp($a['name'], $b['name']);
+ }
+ private function sort_by_description($a, $b) {
+ $ret = strcmp($a['description'], $b['description']);
+ if($ret == 0)
+ $ret = strcmp($a['name'], $b['name']);
+ return $ret;
+ }
+ private function sort_by_owner($a, $b) {
+ $ret = strcmp($a['owner'], $b['owner']);
+ if($ret == 0)
+ $ret = strcmp($a['name'], $b['name']);
+ return $ret;
+ }
+ private function sort_by_age($a, $b) {
+ $ret = $a['last_change'] - $b['last_change'];
+ if($ret == 0)
+ $ret = strcmp($a['name'], $b['name']);
+ return $ret;
+ }
+
+ private function project($class, $project) {
+ $entry = new ContentProvider('projects', 'project');
+ $entry->set('class', $class);
+ $entry->set('project', $project['name']);
+ $entry->set('name', htmlentities($project['name']));
+ $entry->set('description', htmlentities($project['description']));
+ $entry->set('owner', htmlentities($project['owner']));
+
+ $age = Tools::age_calculate($project['last_change']);
+
+ $entry->set('ageclass', $age['age_class']);
+ $entry->set('age', $age['age_str']);
+ return $entry;
+ }
+
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/* shortlog.class.php - phpgitweb
+ * Copyright (C) 2011-2012 Philipp Kreil (pk910)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+require_once('lib/graph.class.php');
+
+class shortlog {
+ private $project;
+ private $graph_data;
+
+ public function generate_shortlog($project, $head, $max, $skip, $file = null, $pages = true, $next_page = 0) {
+ $this->project = $project;
+ $content = new ContentProvider('shortlog', 'shortlog');
+ $commits = GitCommand::get_commits($project['path'], $head, $max+$skip+1, 0, $file);
+
+ $this->graph_data = new graph_data_generator();
+ if($head == null) {
+ //add all refs to the graph
+ foreach($this->project['refs'] as $ref => $rhash) {
+ if(preg_match('#^refs/heads/#i', $ref) && preg_match('/^[a-f0-9]*$/i', $rhash)) {
+ $this->graph_data->add_branch($rhash, $ref);
+ }
+ }
+ foreach($this->project['refs'] as $ref => $rhash) {
+ if(preg_match('#^refs/remotes/#i', $ref) && preg_match('/^[a-f0-9]*$/i', $rhash)) {
+ $this->graph_data->add_branch($rhash, $ref);
+ }
+ }
+ }
+ $this->graph_data->parse($commits);
+
+ $commit_counter = 0;
+ foreach($commits as $commit) {
+ $commit_counter++;
+ if($commit_counter < $skip)
+ continue;
+ if($commit_counter > $max+$skip) {
+ if($pages) {
+ $content->append('entries', new ContentProvider('shortlog', 'shortlog_page', array("page" => $next_page)));
+ } else
+ $content->append('entries', new ContentProvider('shortlog', 'shortlog_more'));
+ } else
+ $content->append('entries', $this->shortlog_entry(($commit_counter % 2 ? 'dark' : 'light'), $commit));
+ }
+
+ return $content;
+ }
+
+ private function shortlog_entry($class, $commit) {
+ $entry = new ContentProvider('shortlog', 'shortlog_entry');
+ $entry->set('class', $class);
+ $entry->set('hash', $commit['id']);
+ $entry->set('author', htmlentities($commit['author']));
+ $entry->set('message', htmlentities(Tools::chop_text($commit['text'], 50, 5)));
+ $age = time() - $commit['committer_time'];
+ $date_str = date("Y-m-d", ($commit['committer_time'] ? $commit['committer_time'] : $commit['author_time']));
+ $age_calc = Tools::age_calculate($commit['committer_time']);
+ $age_str = $age_calc['age_str'];
+ if($age > 60*60*24*7*2) {
+ $entry->set('date', $age_str);
+ $entry->set('age', $date_str);
+ } else {
+ $entry->set('date', $date_str);
+ $entry->set('age', $age_str);
+ }
+ $entry->set('graph_data', $this->graph_data->get_graph($commit['id']));
+
+ $entry->set('refs', $this->shortlog_commit_refs($commit['id']));
+
+ return $entry;
+ }
+
+ private function shortlog_commit_refs($hash) {
+ if(!is_array($this->project['refs']))
+ return "";
+ $refs = new ContentProvider('shortlog', 'shortlog_refs');
+ $found = false;
+ foreach($this->project['refs'] as $ref => $rhash) {
+ if(strtolower($rhash) == strtolower($hash)) {
+ $refexp = explode('/', $ref, 3);
+ $reftype = $refexp[1];
+ if($reftype == 'heads')
+ $reftype = 'head';
+ else if($reftype == 'remotes')
+ $reftype = 'remote';
+ else if($reftype == 'tags')
+ $reftype = 'tag';
+ $refs->append('refs', new ContentProvider('shortlog', 'shortlog_ref_'.$reftype, array("name" => $refexp[2], "ref_link" => $ref)));
+ $found = true;
+ }
+ }
+ return ($found ? $refs : "");
+ }
+
+}
+
+
+class page_shortlog {
+ private $page, $phpgitweb;
+
+ public function main($phpgitweb, $project) {
+ $this->phpgitweb = $phpgitweb;
+ $this->project = $project;
+ if(!$this->project)
+ return;
+ $project['refs'] = $phpgitweb->get_project_loader()->getProjectRefs($project);
+ $this->page = new ContentProvider('shortlog', 'main');
+
+ //pages
+ if(array_key_exists('pg', $_GET)) {
+ $skip = $_GET['pg'] * 100;
+ $next_page = $_GET['pg'] + 1;
+ } else {
+ $skip = 0;
+ $next_page = 1;
+ }
+
+ $shortlog = new shortlog();
+ $this->page->set('shortlog', $shortlog->generate_shortlog($project, null, 100, $skip, null, true, $next_page));
+
+ return $this->page;
+ }
+
+}
+
+?>
\ No newline at end of file
--- /dev/null
+<?php
+/* summary.class.php - phpgitweb
+ * Copyright (C) 2011-2012 Philipp Kreil (pk910)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+require_once('pages/shortlog.class.php');
+
+class page_summary {
+ private $page, $phpgitweb;
+
+ public function main($phpgitweb, $project) {
+ $this->phpgitweb = $phpgitweb;
+ if(!$project)
+ return;
+ $project['refs'] = $phpgitweb->get_project_loader()->getProjectRefs($project);
+
+ $this->page = new ContentProvider('summary', 'main');
+
+ $this->page->set('description', htmlentities($project['description']));
+ $this->page->set('owner', htmlentities($project['owner']));
+
+ $shortlog = new shortlog();
+ $this->page->set('shortlog', $shortlog->generate_shortlog($project, null, 16, 0, null, false));
+
+ return $this->page;
+ }
+
+}
+
+?>
\ No newline at end of file
--- /dev/null
+# [main]
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
+<!-- PHPGitWeb interface version %version%, (C) 2012-%year%, Philipp Kreil (pk910) -->
+<!-- git core binaries version %git_version% -->
+<head>
+<meta http-equiv="content-type" content="application/xhtml+xml; charset=utf-8"/>
+<meta name="generator" content="phpgitweb/%version% git/%git_version%"/>
+<meta name="robots" content="index, nofollow"/>
+<title>%title%</title>
+<link rel="stylesheet" type="text/css" href="gitweb.css"/>
+<link rel="alternate" title="%title% projects list" href="?a=project_index" type="text/plain; charset=utf-8" />
+<link rel="alternate" title="%title% projects feeds" href="?a=opml" type="text/x-opml" />
+<link rel="shortcut icon" href="git-favicon.png" type="image/png" />
+</head>
+<body>
+<div class="page_header">
+<a title="git homepage" href="http://dev.pk910.de/phpgitweb/"><img src="img/git-logo.png" width="72" height="27" alt="git" class="logo"/></a>
+%header_nav%
+</div>
+%content%
+<div class="page_footer">
+<div class="page_footer_text">
+Rendertime: %rendertime% sec
+</div>
+<a class="rss_logo" href="?a=opml">OPML</a> <a class="rss_logo" href="?a=project_index">TXT</a>
+</div>
+</body>
+</html>
+
+# [error]
+<div class="page_body">
+<pre>
+<b>%errtype%</b>: %errstr% in <b>%errfile%</b> on line <b>%errline%</b>
+</pre>
+</div>
+
+# [project_error]
+<div class="page_body">
+404 - Project not found
+<br /><br />
+</div>
+
+# [project_header]
+<form method="get" enctype="application/x-www-form-urlencoded">
+<div class="search">
+<input name="p" type="hidden" value="%project%" />
+<input name="a" type="hidden" value="search" />
+<input name="h" type="hidden" value="%project_head%" />
+<select name="st" >
+<option selected="selected" value="commit">commit</option>
+<option value="grep">grep</option>
+<option value="author">author</option>
+<option value="committer">committer</option>
+<option value="pickaxe">pickaxe</option>
+</select><sup><a href="?p=%project%&a=search_help">?</a></sup> search:
+<input type="text" name="s" />
+<span title="Extended regular expression"><label><input type="checkbox" name="sr" value="1" />re</label></span></div>
+</form>
+<div class="page_nav">
+%nav_summary% | %nav_shortlog% | %nav_log% | %nav_commit% | %nav_commitdiff% | %nav_tree%<br/>
+%sub_nav%<br/>
+</div>
+
+# [project_header_nav_link]
+<a href="/?p=%project%&a=%link%">%name%</a>
+
+# [project_header_nav_active]
+%name%
+
--- /dev/null
+# [main]
+<table class="project_list">
+ <tr>
+ <th>%header_project%</th>
+ <th>%header_description%</th>
+ <th>%header_owner%</th>
+ <th>%header_age%</th>
+ <th></th>
+ </tr>
+ %projects%
+</table>
+
+# [head_order_link]
+<a class="header" href="?o=%tag%">%name%</a>
+
+# [head_order_active]
+%name%
+
+# [project]
+ <tr class="%class%">
+ <td><a class="list" href="?p=%project%&a=summary">%name%</a></td>
+ <td><a class="list" title="%description%" href="?p=%project%&a=summary">%description%</a></td>
+ <td><i>%owner%</i></td>
+ <td class="%age_class%">%age%</td>
+ <td class="link"><a href="?p=%project%&a=summary">summary</a> | <a href="?p=%project%&a=shortlog">shortlog</a> | <a href="?p=%project%&a=log">log</a> | <a href="?p=%project%&a=tree">tree</a></td>
+ </tr>
\ No newline at end of file
--- /dev/null
+# [main]
+<div class="header">
+ <a class="title" href="?p=%project%&a=summary">%project%</a>
+</div>
+%shortlog%
+
+# [shortlog]
+<table class="shortlog" cellspacing="0" cellpadding="0">
+ <tr class="header">
+ <td colspan="2"><img class="graph" src="?e=graph&gd=%graph_data%" /></td>
+ <td valign="bottom"><b>Author</b></td>
+ <td valign="bottom"><b>Commit</b></td>
+ <td></td>
+ </tr>
+ %entries%
+</table>
+
+# [shortlog_entry]
+<tr class="%class%">
+ <td><img class="graph" src="?e=graph&gd=%graph_data%" /></td>
+ <td title="%date%"><i>%age%</i></td>
+ <td class="author"><a title="Search for commits authored by %author%" class="list" href="?p=%project%&a=search&h=%project_head%&s=%author%&st=author">%author%</a></td>
+ <td><a class="list subject" href="?p=%project%&a=commit&h=%hash%">%message%</a> %refs%</td>
+ <td class="link"><a href="?p=%project%&a=commit&h=%hash%">commit</a> | <a href="?p=%project%&a=commitdiff&h=%hash%">commitdiff</a> | <a href="?p=%project%&a=tree&h=%hash%">tree</a> | <a title="in format: tar.gz" href="?p=%project%&e=snapshot&h=%hash%&sf=tgz">snapshot</a></td>
+</tr>
+
+# [shortlog_more]
+<tr>
+<td colspan="4"><a href="?p=%project%&a=shortlog">...</a></td>
+</tr>
+
+# [shortlog_page]
+<td colspan="4"><a title="Alt-n" accesskey="n" href="?p=%project%&a=shortlog&pg=%page%">next</a></td>
+
+# [shortlog_refs]
+<span class="refs"> %refs%</span>
+
+# [shortlog_ref_head]
+<span class="head" title="heads/%name%"><a href="?p=%project%&a=shortlog&h=%ref_link%">%name%</a></span>
+
+# [shortlog_ref_remote]
+<span class="remote" title="remotes/%name%"><a href="?p=%project%&a=shortlog&h=%ref_link%">%name%</a></span>
+
+# [shortlog_ref_tag]
+<span class="tag" title="tags/%name%"><a href="?p=%project%&a=shortlog&h=%ref_link%">%name%</a></span>
\ No newline at end of file
--- /dev/null
+# [main]
+<div class="title"> </div>
+<table class="projects_list">
+ <tr id="metadata_desc"><td>description</td><td>%description%</td></tr>
+ <tr id="metadata_owner"><td>owner</td><td>%owner%</td></tr>
+ <tr id="metadata_lchange"><td>last change</td><td>%last_change%</td></tr>
+ <tr class="metadata_url"><td>URL</td><td>%git_link%</td></tr>
+</table>
+<div class="header">
+ <a class="title" href="?p=%project%&a=shortlog">shortlog</a>
+</div>
+%shortlog%
+<div class="header">
+<a class="title" href="?p=%project%&a=heads">heads</a>
+</div>
+<table class="heads">
+ %heads%
+</table>
+
+# [head]
+<tr class="%class%">
+<td><i>%age%</i></td>
+<td class="%head_class%"><a class="list name" href="?p=%project%&a=shortlog&h=%head_link%">%name%</a></td>
+<td class="link"><a href="?p=%project%&a=shortlog&h=%head_link%">shortlog</a> | <a href="?p=%project%&a=log&h=%head_link%">log</a> | <a href="?p=%project%&a=tree&h=%head_link%">tree</a></td>
+</tr>