From 0a6d29345b57ef71b076003e18d13efd3478764c Mon Sep 17 00:00:00 2001 From: pk910 Date: Thu, 14 Feb 2013 06:20:05 +0100 Subject: [PATCH] continued :) --- htdocs/config.example.php | 32 +- htdocs/gitweb.css | 612 ++++++++++++++++++++++++++ htdocs/img/branch_left.png | Bin 0 -> 263 bytes htdocs/img/branch_right.png | Bin 0 -> 283 bytes htdocs/img/dot.png | Bin 0 -> 230 bytes htdocs/img/dot_init.png | Bin 0 -> 229 bytes htdocs/img/dot_merge.png | Bin 0 -> 152 bytes htdocs/img/dot_merge_left.png | Bin 0 -> 152 bytes htdocs/img/dot_merge_right.png | Bin 0 -> 152 bytes htdocs/img/git-logo.png | Bin 0 -> 207 bytes htdocs/img/line.png | Bin 0 -> 144 bytes htdocs/img/line_h.png | Bin 0 -> 165 bytes htdocs/img/merge_left.png | Bin 0 -> 253 bytes htdocs/img/merge_right.png | Bin 0 -> 247 bytes htdocs/index.php | 21 + htdocs/lib/ContentProvider.class.php | 123 ++++++ htdocs/lib/GitCommand.class.php | 119 +++++ htdocs/lib/PHPGitWeb.class.php | 138 ++++++ htdocs/lib/ProjectLoader.class.php | 138 ++++++ htdocs/lib/Tools.class.php | 87 ++++ htdocs/lib/graph.class.php | 389 ++++++++++++++++ htdocs/pages/.htaccess | 2 + htdocs/pages/404.html | 4 + htdocs/pages/projects.class.php | 109 +++++ htdocs/pages/shortlog.class.php | 140 ++++++ htdocs/pages/summary.class.php | 43 ++ htdocs/templates/default/main.tpl | 71 +++ htdocs/templates/default/projects.tpl | 26 ++ htdocs/templates/default/shortlog.tpl | 45 ++ htdocs/templates/default/summary.tpl | 25 ++ 30 files changed, 2123 insertions(+), 1 deletion(-) create mode 100644 htdocs/gitweb.css create mode 100644 htdocs/img/branch_left.png create mode 100644 htdocs/img/branch_right.png create mode 100644 htdocs/img/dot.png create mode 100644 htdocs/img/dot_init.png create mode 100644 htdocs/img/dot_merge.png create mode 100644 htdocs/img/dot_merge_left.png create mode 100644 htdocs/img/dot_merge_right.png create mode 100644 htdocs/img/git-logo.png create mode 100644 htdocs/img/line.png create mode 100644 htdocs/img/line_h.png create mode 100644 htdocs/img/merge_left.png create mode 100644 htdocs/img/merge_right.png create mode 100644 htdocs/index.php create mode 100644 htdocs/lib/ContentProvider.class.php create mode 100644 htdocs/lib/GitCommand.class.php create mode 100644 htdocs/lib/PHPGitWeb.class.php create mode 100644 htdocs/lib/ProjectLoader.class.php create mode 100644 htdocs/lib/Tools.class.php create mode 100644 htdocs/lib/graph.class.php create mode 100644 htdocs/pages/.htaccess create mode 100644 htdocs/pages/404.html create mode 100644 htdocs/pages/projects.class.php create mode 100644 htdocs/pages/shortlog.class.php create mode 100644 htdocs/pages/summary.class.php create mode 100644 htdocs/templates/default/main.tpl create mode 100644 htdocs/templates/default/projects.tpl create mode 100644 htdocs/templates/default/shortlog.tpl create mode 100644 htdocs/templates/default/summary.tpl diff --git a/htdocs/config.example.php b/htdocs/config.example.php index b8441e0..f333278 100644 --- a/htdocs/config.example.php +++ b/htdocs/config.example.php @@ -1,5 +1,5 @@ . */ +/* Global configuration */ +/* GIT configuration */ +class GitConfig { + /* PHPGitWeb Title / Name */ + const GITWEB_TITLE = "PHPGitWeb"; + + /* path to git projects (.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 index 0000000..a2b59f1 --- /dev/null +++ b/htdocs/gitweb.css @@ -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 index 0000000000000000000000000000000000000000..06016a10a685d56e2cdc6c2741f2df5b12950833 GIT binary patch literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VV{wqX6T`Z5GB1G~&H|6fVg?4j zBOuH;Rhv&5D9B#o>FdgVheL*)Mdw0JRsm3Grl*Tzh{fsTl!SzY6a}7tj+tssTr)ci zd?q!rIP^5`4A|8d#g^s#lKqNN0JGP9g=@EaDmGX$Gas&JV{41&;o)&&xpK%L?qqKO z^K+q0i8$uwg?hiwA2^`ky|VGAN84JZg9dJFE7Vf{NO$g?;&OO{)Q|a%jSm%>nVq>M z+Y&2g&TedUoa@spAt@mtA=!E6bjaDN#cX|29c&C$7i7*PWlYThI+DTD)z4*}Q$iB} D)3jA@ literal 0 HcmV?d00001 diff --git a/htdocs/img/branch_right.png b/htdocs/img/branch_right.png new file mode 100644 index 0000000000000000000000000000000000000000..1db9a66ec020cf1515dc7fae8c43b2bb2ef2239d GIT binary patch literal 283 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VV{wqX6T`Z5GB1G~wj^(N7l!{J zxM1({$v_d#0*}aI1_r((Aj~*bn@<`j$X?><>&kwILq@=W(fmUJJ5Xr0r;B5V#p$<~ z4Y`;MMOq%}ubw-#Osr{c6Z?jH#uWnoDVfQN6%6kaUs{Hhr?#}+f^S1JYco@awC0{>w$(~mU~%@ z;et0F1~5w$crG|Db)ccRFdgVheL)i7?MY1yosFjwt~fLu zXb9qFm5`9wrhN2MJsVrwV;&wJsV7o9^pbhzyZfqiDDK!%?}xZ4qs@P^WjMP z17;>RJB6#KWq>Nb7gl_?W@c_?v7DH6@W6o>n)SQ`BTFdgVheL*)#d7b)zS%&ba!(h>5R22v2?~6F+LM|ZIvYedQct9I$e%Tk*mv&0fdh`K62&ZdnjbO*9lp>o=fjco z2h2=tb_!Qd%K%k=FRb`(&CJ})VmUGC;DG}lWV#atr>M>4S&_*4WLJ&|k2(WGSEOpI U|M|)XKqoMGy85}Sb4q9e03~}#GXMYp literal 0 HcmV?d00001 diff --git a/htdocs/img/dot_merge.png b/htdocs/img/dot_merge.png new file mode 100644 index 0000000000000000000000000000000000000000..70691d065ffb78c080756858252f71e81ce7cc64 GIT binary patch literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VV{wqX6T`Z5GB1G~&H|6fVg?4j zBOuH;Rhv&5D9B#o>FdgVheL*)g^m0A;$=V~El(H65R22v2?~6F+LM|ZIvYFdgVheL*)m9we1zYHj(<>}%WVsSb-L4tL0f&`Nr8(Z7|6HGAy qzocFIQVz7}9cWE?p#hX*;9^jpz$EczX5?j{E(T9mKbLh*2~7ZUwk7xg literal 0 HcmV?d00001 diff --git a/htdocs/img/dot_merge_right.png b/htdocs/img/dot_merge_right.png new file mode 100644 index 0000000000000000000000000000000000000000..8ccef2519da22e12f7cd5304d18864c1e81cfb3b GIT binary patch literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VV{wqX6T`Z5GB1G~&H|6fVg?4j zBOuH;Rhv&5D9B#o>FdgVheL*)Rdia>WlNxtmZytjh{fsT1PRu~2@*_hMW#iD27l5M q5)yv2shx9aY~+bqENH~#2DJI06_a@Q!F{bjT@0SCelF{r5}E*KjU^!f literal 0 HcmV?d00001 diff --git a/htdocs/img/git-logo.png b/htdocs/img/git-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f4ede2e944868b9a08401dafeb2b944c7166fd0a GIT binary patch literal 207 zcmeAS@N?(olHy`uVBq!ia0vp^9zZP3!3-o{XjF!Z>L`5YETeyUSID0|9`*t+IAC;k_XOnrzV4h9cu})*e%Nla>s~FPTory0@{&E6lnV478oW)78&qol`;+ E0HBFb(*OVf literal 0 HcmV?d00001 diff --git a/htdocs/img/line.png b/htdocs/img/line.png new file mode 100644 index 0000000000000000000000000000000000000000..1358887f3f04c2646c18bfc83bf0cde0bbef90ca GIT binary patch literal 144 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc1|)ksWqE-VV{wqX6T`Z5GB1G~&H|6fVg?4j zBOuH;Rhv&5D9B#o>FdgVheL)<$jIEX<1vf&$;4_N1nU&c;&-R}KU+ jyYtGJ7F;>7FiC;o*as%nHYMw$K-CPMu6{1-oD!MFdgVheL)FdgVheL)aOV1e7ph5t7`Xy@VK(Y@f}uXW7ejk!>@rX^dF@R{_4gdeP?C8DN0DL}0pZbl)~ zq}Ys%3=DEwq%5XKvCVSk{R1>Y!G)JuQc^-9&ym+u^_WAF&^KOFdgVheL*)Rpt1a%gjKbPEQxd5R22vDG3P)DGQVmk`@?Am=;`d zIONdV*x1M&J7FTvokIr>e3;PK_|RU=XH%m0M3eu^t{eyr@ZB#VA@Qkjh2kxPNR`7) zA6F>fI^?02Bzp4IaG;|YJYD@<);T3K0RT_^R-ym^ literal 0 HcmV?d00001 diff --git a/htdocs/index.php b/htdocs/index.php new file mode 100644 index 0000000..0834ad7 --- /dev/null +++ b/htdocs/index.php @@ -0,0 +1,21 @@ +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 index 0000000..cd88b91 --- /dev/null +++ b/htdocs/lib/ContentProvider.class.php @@ -0,0 +1,123 @@ +. + */ + +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 index 0000000..4e20281 --- /dev/null +++ b/htdocs/lib/GitCommand.class.php @@ -0,0 +1,119 @@ +. + */ + +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 index 0000000..06ae9b0 --- /dev/null +++ b/htdocs/lib/PHPGitWeb.class.php @@ -0,0 +1,138 @@ +. + */ + +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', ''.$name.''); + 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 index 0000000..eb1c789 --- /dev/null +++ b/htdocs/lib/ProjectLoader.class.php @@ -0,0 +1,138 @@ +. + */ + +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 index 0000000..8ae0717 --- /dev/null +++ b/htdocs/lib/Tools.class.php @@ -0,0 +1,87 @@ +. + */ + +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 index 0000000..5cf843e --- /dev/null +++ b/htdocs/lib/graph.class.php @@ -0,0 +1,389 @@ +. + */ + +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 index 0000000..43c4479 --- /dev/null +++ b/htdocs/pages/.htaccess @@ -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 index 0000000..c708bdd --- /dev/null +++ b/htdocs/pages/404.html @@ -0,0 +1,4 @@ +
+404 - Page not found +

+
\ No newline at end of file diff --git a/htdocs/pages/projects.class.php b/htdocs/pages/projects.class.php new file mode 100644 index 0000000..ade8eb6 --- /dev/null +++ b/htdocs/pages/projects.class.php @@ -0,0 +1,109 @@ +. + */ + +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 index 0000000..a7293c8 --- /dev/null +++ b/htdocs/pages/shortlog.class.php @@ -0,0 +1,140 @@ +. + */ + +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 index 0000000..30838e1 --- /dev/null +++ b/htdocs/pages/summary.class.php @@ -0,0 +1,43 @@ +. + */ + +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 index 0000000..52d883d --- /dev/null +++ b/htdocs/templates/default/main.tpl @@ -0,0 +1,71 @@ +# [main] + + + + + + + + + +%title% + + + + + + + +%content% + + + + +# [error] +
+
+%errtype%: %errstr% in %errfile% on line %errline%
+
+
+ +# [project_error] +
+404 - Project not found +

+
+ +# [project_header] +
+ +
+ + +# [project_header_nav_link] +%name% + +# [project_header_nav_active] +%name% + diff --git a/htdocs/templates/default/projects.tpl b/htdocs/templates/default/projects.tpl new file mode 100644 index 0000000..8691135 --- /dev/null +++ b/htdocs/templates/default/projects.tpl @@ -0,0 +1,26 @@ +# [main] + + + + + + + + + %projects% +
%header_project%%header_description%%header_owner%%header_age%
+ +# [head_order_link] +%name% + +# [head_order_active] +%name% + +# [project] + + %name% + %description% + %owner% + %age% + summary | shortlog | log | tree + \ No newline at end of file diff --git a/htdocs/templates/default/shortlog.tpl b/htdocs/templates/default/shortlog.tpl new file mode 100644 index 0000000..6e8ec37 --- /dev/null +++ b/htdocs/templates/default/shortlog.tpl @@ -0,0 +1,45 @@ +# [main] + +%shortlog% + +# [shortlog] + + + + + + + + %entries% +
AuthorCommit
+ +# [shortlog_entry] + + + %age% + %author% + %message% %refs% + commit | commitdiff | tree | snapshot + + +# [shortlog_more] + +... + + +# [shortlog_page] +next + +# [shortlog_refs] + %refs% + +# [shortlog_ref_head] +%name% + +# [shortlog_ref_remote] +%name% + +# [shortlog_ref_tag] +%name% \ No newline at end of file diff --git a/htdocs/templates/default/summary.tpl b/htdocs/templates/default/summary.tpl new file mode 100644 index 0000000..6ed8a24 --- /dev/null +++ b/htdocs/templates/default/summary.tpl @@ -0,0 +1,25 @@ +# [main] +
 
+ + + + + +
description%description%
owner%owner%
last change%last_change%
+ +%shortlog% +
+heads +
+ + %heads% +
+ +# [head] + +%age% +%name% +shortlog | log | tree + -- 2.20.1