continued :)
authorpk910 <philipp@zoelle1.de>
Thu, 14 Feb 2013 05:20:05 +0000 (06:20 +0100)
committerpk910 <philipp@zoelle1.de>
Thu, 14 Feb 2013 05:20:05 +0000 (06:20 +0100)
30 files changed:
htdocs/config.example.php
htdocs/gitweb.css [new file with mode: 0644]
htdocs/img/branch_left.png [new file with mode: 0644]
htdocs/img/branch_right.png [new file with mode: 0644]
htdocs/img/dot.png [new file with mode: 0644]
htdocs/img/dot_init.png [new file with mode: 0644]
htdocs/img/dot_merge.png [new file with mode: 0644]
htdocs/img/dot_merge_left.png [new file with mode: 0644]
htdocs/img/dot_merge_right.png [new file with mode: 0644]
htdocs/img/git-logo.png [new file with mode: 0644]
htdocs/img/line.png [new file with mode: 0644]
htdocs/img/line_h.png [new file with mode: 0644]
htdocs/img/merge_left.png [new file with mode: 0644]
htdocs/img/merge_right.png [new file with mode: 0644]
htdocs/index.php [new file with mode: 0644]
htdocs/lib/ContentProvider.class.php [new file with mode: 0644]
htdocs/lib/GitCommand.class.php [new file with mode: 0644]
htdocs/lib/PHPGitWeb.class.php [new file with mode: 0644]
htdocs/lib/ProjectLoader.class.php [new file with mode: 0644]
htdocs/lib/Tools.class.php [new file with mode: 0644]
htdocs/lib/graph.class.php [new file with mode: 0644]
htdocs/pages/.htaccess [new file with mode: 0644]
htdocs/pages/404.html [new file with mode: 0644]
htdocs/pages/projects.class.php [new file with mode: 0644]
htdocs/pages/shortlog.class.php [new file with mode: 0644]
htdocs/pages/summary.class.php [new file with mode: 0644]
htdocs/templates/default/main.tpl [new file with mode: 0644]
htdocs/templates/default/projects.tpl [new file with mode: 0644]
htdocs/templates/default/shortlog.tpl [new file with mode: 0644]
htdocs/templates/default/summary.tpl [new file with mode: 0644]

index b8441e06ce3ebb8ec8606be264a2743b3bb19fde..f33327883a91b8ff8da28f73c3704a0bd05fec86 100644 (file)
@@ -1,5 +1,5 @@
 <?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
diff --git a/htdocs/gitweb.css b/htdocs/gitweb.css
new file mode 100644 (file)
index 0000000..a2b59f1
--- /dev/null
@@ -0,0 +1,612 @@
+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; }
diff --git a/htdocs/img/branch_left.png b/htdocs/img/branch_left.png
new file mode 100644 (file)
index 0000000..06016a1
Binary files /dev/null and b/htdocs/img/branch_left.png differ
diff --git a/htdocs/img/branch_right.png b/htdocs/img/branch_right.png
new file mode 100644 (file)
index 0000000..1db9a66
Binary files /dev/null and b/htdocs/img/branch_right.png differ
diff --git a/htdocs/img/dot.png b/htdocs/img/dot.png
new file mode 100644 (file)
index 0000000..433e75a
Binary files /dev/null and b/htdocs/img/dot.png differ
diff --git a/htdocs/img/dot_init.png b/htdocs/img/dot_init.png
new file mode 100644 (file)
index 0000000..5fbde45
Binary files /dev/null and b/htdocs/img/dot_init.png differ
diff --git a/htdocs/img/dot_merge.png b/htdocs/img/dot_merge.png
new file mode 100644 (file)
index 0000000..70691d0
Binary files /dev/null and b/htdocs/img/dot_merge.png differ
diff --git a/htdocs/img/dot_merge_left.png b/htdocs/img/dot_merge_left.png
new file mode 100644 (file)
index 0000000..0fa7c87
Binary files /dev/null and b/htdocs/img/dot_merge_left.png differ
diff --git a/htdocs/img/dot_merge_right.png b/htdocs/img/dot_merge_right.png
new file mode 100644 (file)
index 0000000..8ccef25
Binary files /dev/null and b/htdocs/img/dot_merge_right.png differ
diff --git a/htdocs/img/git-logo.png b/htdocs/img/git-logo.png
new file mode 100644 (file)
index 0000000..f4ede2e
Binary files /dev/null and b/htdocs/img/git-logo.png differ
diff --git a/htdocs/img/line.png b/htdocs/img/line.png
new file mode 100644 (file)
index 0000000..1358887
Binary files /dev/null and b/htdocs/img/line.png differ
diff --git a/htdocs/img/line_h.png b/htdocs/img/line_h.png
new file mode 100644 (file)
index 0000000..0585711
Binary files /dev/null and b/htdocs/img/line_h.png differ
diff --git a/htdocs/img/merge_left.png b/htdocs/img/merge_left.png
new file mode 100644 (file)
index 0000000..cfb950d
Binary files /dev/null and b/htdocs/img/merge_left.png differ
diff --git a/htdocs/img/merge_right.png b/htdocs/img/merge_right.png
new file mode 100644 (file)
index 0000000..67e9d32
Binary files /dev/null and b/htdocs/img/merge_right.png differ
diff --git a/htdocs/index.php b/htdocs/index.php
new file mode 100644 (file)
index 0000000..0834ad7
--- /dev/null
@@ -0,0 +1,21 @@
+<?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
diff --git a/htdocs/lib/ContentProvider.class.php b/htdocs/lib/ContentProvider.class.php
new file mode 100644 (file)
index 0000000..cd88b91
--- /dev/null
@@ -0,0 +1,123 @@
+<?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
diff --git a/htdocs/lib/GitCommand.class.php b/htdocs/lib/GitCommand.class.php
new file mode 100644 (file)
index 0000000..4e20281
--- /dev/null
@@ -0,0 +1,119 @@
+<?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;
+       }
+       
+}
diff --git a/htdocs/lib/PHPGitWeb.class.php b/htdocs/lib/PHPGitWeb.class.php
new file mode 100644 (file)
index 0000000..06ae9b0
--- /dev/null
@@ -0,0 +1,138 @@
+<?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
diff --git a/htdocs/lib/ProjectLoader.class.php b/htdocs/lib/ProjectLoader.class.php
new file mode 100644 (file)
index 0000000..eb1c789
--- /dev/null
@@ -0,0 +1,138 @@
+<?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
diff --git a/htdocs/lib/Tools.class.php b/htdocs/lib/Tools.class.php
new file mode 100644 (file)
index 0000000..8ae0717
--- /dev/null
@@ -0,0 +1,87 @@
+<?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
diff --git a/htdocs/lib/graph.class.php b/htdocs/lib/graph.class.php
new file mode 100644 (file)
index 0000000..5cf843e
--- /dev/null
@@ -0,0 +1,389 @@
+<?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
diff --git a/htdocs/pages/.htaccess b/htdocs/pages/.htaccess
new file mode 100644 (file)
index 0000000..43c4479
--- /dev/null
@@ -0,0 +1,2 @@
+order deny, allow
+deny from all
\ No newline at end of file
diff --git a/htdocs/pages/404.html b/htdocs/pages/404.html
new file mode 100644 (file)
index 0000000..c708bdd
--- /dev/null
@@ -0,0 +1,4 @@
+<div class="page_body">
+404 - Page not found
+<br /><br />
+</div>
\ No newline at end of file
diff --git a/htdocs/pages/projects.class.php b/htdocs/pages/projects.class.php
new file mode 100644 (file)
index 0000000..ade8eb6
--- /dev/null
@@ -0,0 +1,109 @@
+<?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
diff --git a/htdocs/pages/shortlog.class.php b/htdocs/pages/shortlog.class.php
new file mode 100644 (file)
index 0000000..a7293c8
--- /dev/null
@@ -0,0 +1,140 @@
+<?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
diff --git a/htdocs/pages/summary.class.php b/htdocs/pages/summary.class.php
new file mode 100644 (file)
index 0000000..30838e1
--- /dev/null
@@ -0,0 +1,43 @@
+<?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
diff --git a/htdocs/templates/default/main.tpl b/htdocs/templates/default/main.tpl
new file mode 100644 (file)
index 0000000..52d883d
--- /dev/null
@@ -0,0 +1,71 @@
+# [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%
+
diff --git a/htdocs/templates/default/projects.tpl b/htdocs/templates/default/projects.tpl
new file mode 100644 (file)
index 0000000..8691135
--- /dev/null
@@ -0,0 +1,26 @@
+# [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
diff --git a/htdocs/templates/default/shortlog.tpl b/htdocs/templates/default/shortlog.tpl
new file mode 100644 (file)
index 0000000..6e8ec37
--- /dev/null
@@ -0,0 +1,45 @@
+# [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
diff --git a/htdocs/templates/default/summary.tpl b/htdocs/templates/default/summary.tpl
new file mode 100644 (file)
index 0000000..6ed8a24
--- /dev/null
@@ -0,0 +1,25 @@
+# [main]
+<div class="title">&nbsp;</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>