+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
diff --git a/docs/licenses/CC0 b/docs/licenses/CC0
new file mode 100644
index 0000000..0e259d4
--- /dev/null
+++ b/docs/licenses/CC0
@@ -0,0 +1,121 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator
+and subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for
+the purpose of contributing to a commons of creative, cultural and
+scientific works ("Commons") that the public can reliably and without fear
+of later claims of infringement build upon, modify, incorporate in other
+works, reuse and redistribute as freely as possible in any form whatsoever
+and for any purposes, including without limitation commercial purposes.
+These owners may contribute to the Commons to promote the ideal of a free
+culture and the further production of creative, cultural and scientific
+works, or to gain reputation or greater distribution for their Work in
+part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any
+expectation of additional consideration or compensation, the person
+associating CC0 with a Work (the "Affirmer"), to the extent that he or she
+is an owner of Copyright and Related Rights in the Work, voluntarily
+elects to apply CC0 to the Work and publicly distribute the Work under its
+terms, with knowledge of his or her Copyright and Related Rights in the
+Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not
+limited to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display,
+ communicate, and translate a Work;
+ ii. moral rights retained by the original author(s) and/or performer(s);
+iii. publicity and privacy rights pertaining to a person's image or
+ likeness depicted in a Work;
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+ v. rights protecting the extraction, dissemination, use and reuse of data
+ in a Work;
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation
+ thereof, including any amended or successor version of such
+ directive); and
+vii. other similar, equivalent or corresponding rights throughout the
+ world based on applicable law or treaty, and any national
+ implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention
+of, applicable law, Affirmer hereby overtly, fully, permanently,
+irrevocably and unconditionally waives, abandons, and surrenders all of
+Affirmer's Copyright and Related Rights and associated claims and causes
+of action, whether now known or unknown (including existing as well as
+future claims and causes of action), in the Work (i) in all territories
+worldwide, (ii) for the maximum duration provided by applicable law or
+treaty (including future time extensions), (iii) in any current or future
+medium and for any number of copies, and (iv) for any purpose whatsoever,
+including without limitation commercial, advertising or promotional
+purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
+member of the public at large and to the detriment of Affirmer's heirs and
+successors, fully intending that such Waiver shall not be subject to
+revocation, rescission, cancellation, termination, or any other legal or
+equitable action to disrupt the quiet enjoyment of the Work by the public
+as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason
+be judged legally invalid or ineffective under applicable law, then the
+Waiver shall be preserved to the maximum extent permitted taking into
+account Affirmer's express Statement of Purpose. In addition, to the
+extent the Waiver is so judged Affirmer hereby grants to each affected
+person a royalty-free, non transferable, non sublicensable, non exclusive,
+irrevocable and unconditional license to exercise Affirmer's Copyright and
+Related Rights in the Work (i) in all territories worldwide, (ii) for the
+maximum duration provided by applicable law or treaty (including future
+time extensions), (iii) in any current or future medium and for any number
+of copies, and (iv) for any purpose whatsoever, including without
+limitation commercial, advertising or promotional purposes (the
+"License"). The License shall be deemed effective as of the date CC0 was
+applied by Affirmer to the Work. Should any part of the License for any
+reason be judged legally invalid or ineffective under applicable law, such
+partial invalidity or ineffectiveness shall not invalidate the remainder
+of the License, and in such case Affirmer hereby affirms that he or she
+will not (i) exercise any of his or her remaining Copyright and Related
+Rights in the Work or (ii) assert any associated claims and causes of
+action with respect to the Work, in either case contrary to Affirmer's
+express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+ b. Affirmer offers the Work as-is and makes no representations or
+ warranties of any kind concerning the Work, express, implied,
+ statutory or otherwise, including without limitation warranties of
+ title, merchantability, fitness for a particular purpose, non
+ infringement, or the absence of latent or other defects, accuracy, or
+ the present or absence of errors, whether or not discoverable, all to
+ the greatest extent permissible under applicable law.
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without
+ limitation any person's Copyright and Related Rights in the Work.
+ Further, Affirmer disclaims responsibility for obtaining any necessary
+ consents, permissions or other rights required for any use of the
+ Work.
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to
+ this CC0 or use of the Work.
diff --git a/docs/modifying.md b/docs/modifying.md
new file mode 100644
index 0000000..1f60ab5
--- /dev/null
+++ b/docs/modifying.md
@@ -0,0 +1,45 @@
+# Modifying simple-gitv
+
+See [code-overview.md](code-overview.md) to understand the organization of the codebase and get started.
+
+## License and Providing Source Code
+
+This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
+
+Per the AGPLv3: "Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software."
+
+Simply put, if you use this program on a site which other people will access, you should include a link to the source code of the version running on your site.
+
+(You are not obligated to let me know, but please do! I may want to incorporate your changes into my version.)
+
+### Exceptions
+
+#### Configuration Files
+
+Modified configuration files do not need to be provided to users, as long as the available source is sufficient such that a user should reasonably be able to create their own configuration files, e.g., from templates or directions in the documentation.
+
+If you modify the program sufficiently such that requires a modified or additional configuration file to those provided in the templates of the previous version, please include a usable template.
+
+The goal is to include sufficient instructions for running the software on **a** server, not to include instructions for running the software on **your** server specifically.
+
+#### Themes
+
+If you use a modified theme and have not otherwise modified the program, you are not obligated to publish your own version of the program as a whole, only the modified theme.
+
+As the modified theme in CSS form will be served to clients who connect to your site, it is not necessary to publish the theme separately; however, to ensure that other users may use the themes, you must either declare the theme to be in the public domain or license the theme under a [GPL-compatible free software license](https://www.gnu.org/licenses/license-list.html#GPLCompatibleLicenses). Recommended licenses:
+
+- [Creative Commons CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/legalcode)
+- [Unlicense](https://unlicense.org/)
+- [DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE, version 2](http://www.wtfpl.net/txt/copying/)
+
+If the theme is generated by a program, you must publish instructions for recreating the theme in addition to the theme itself.
+
+### How to Publish the Source Code
+
+If you are running an unmodified version of simple-gitv, you do not have to do anything. src/parts/footer.php already includes a link to the project.
+
+If you are running a modified version of simple-gitv, the best way to publish your changes is to host the code on simple-gitv (or another Git hosting service). The easiest way to do this is to commit changes to the code when you make them, and clone your own repo. For example, if your repositories are in /srv/git/ and your running code is in /var/www/git/ then you can run:
+
+`git clone /srv/git/simple-gitv /var/www/git`
+
+Change the link in src/parts/footer.php to point to your version of the program.
diff --git a/docs/version b/docs/version
new file mode 100644
index 0000000..524cb55
--- /dev/null
+++ b/docs/version
@@ -0,0 +1 @@
+1.1.1
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..586d8a3
--- /dev/null
+++ b/index.php
@@ -0,0 +1,80 @@
+.
+*/
+
+// get version
+$version = trim(file_get_contents("docs/version"));
+
+// include assorted important utilities
+include "src/utils.php";
+
+// read arguments from user config
+$config = parse_ini_file("config.ini");
+$repos = strip_trailing_slash($config["repositories"]);
+$title = $config["title"];
+$theme = $config["theme"];
+
+// get file to serve based on URL
+$url = strip_trailing_slash(substr($_SERVER['REQUEST_URI'], 1));
+$page_type = get_page_type($url);
+
+if ($page_type === "invalid") {
+ include "src/errors/404.php";
+ die();
+}
+
+// homepage does not need to check for Git repo
+if ($page_type === "home") {
+ include "src/pages/home.php";
+ die();
+}
+
+// get Git repo
+$project = before_first_slash($url);
+$git_dir = get_git_dir("$repos/$project");
+
+// 404 if not a git directory
+if (! $git_dir) {
+ include "src/errors/404.php";
+ die();
+}
+
+// use cached version if it exists, otherwise make it
+$last_commit = get_last_commit_time($git_dir);
+if (file_exists("cache/$url.html") && $last_commit < filemtime("cache/$url.html")) {
+ include "cache/$url.html";
+} else {
+ // create directory in cache if it doesn't exist
+ $directory = "cache/" . before_last_slash($url);
+ if (!file_exists($directory)) {
+ mkdir($directory, 0777, true);
+ }
+
+ // create cached file
+ ob_start();
+
+ // get default branch
+ $branch = get_main_git_branch($git_dir);
+
+ include "src/pages/$page_type.php";
+ $cache_file = fopen("cache/$url.html", 'w');
+ fwrite($cache_file, ob_get_contents());
+ fclose($cache_file);
+ ob_end_flush();
+}
+?>
diff --git a/src/errors/404.php b/src/errors/404.php
new file mode 100644
index 0000000..a9d86f9
--- /dev/null
+++ b/src/errors/404.php
@@ -0,0 +1,26 @@
+.
+*/
+
+$title = "404 Not Found";
+include "src/parts/header.php";
+echo "\t\tNot Found
\n";
+echo "\t\tSorry, that does not appear to be a valid page.
\n";
+echo "\t\tCheck that the repository is correct and that the link is to a valid object.
\n";
+include "src/parts/footer.php";
+?>
diff --git a/src/pages/blob.php b/src/pages/blob.php
new file mode 100644
index 0000000..f950598
--- /dev/null
+++ b/src/pages/blob.php
@@ -0,0 +1,46 @@
+.
+*/
+
+// strip repo/blob/
+$path = $url;
+for ($i = 0; $i < 2; $i++) {
+ $path = after_first_slash($path);
+}
+
+$branch = before_first_slash($path);
+// strip branch name
+$path = after_first_slash($path);
+
+$url = strip_trailing_slash($url);
+$name = after_last_slash($url);
+$git_dir = get_git_dir("$repos/$project");
+
+// if no contents, it's not a valid URL
+if (! is_git_file("$git_dir", "$path")) {
+ include "src/errors/404.php";
+ die();
+}
+
+include "src/parts/header.php";
+include "src/parts/description.php";
+include "src/parts/menu.php";
+include "src/parts/preview.php";
+include "src/parts/footer.php";
+
+?>
diff --git a/src/pages/commit.php b/src/pages/commit.php
new file mode 100644
index 0000000..e0096ba
--- /dev/null
+++ b/src/pages/commit.php
@@ -0,0 +1,54 @@
+.
+*/
+
+// get commit ID (strip project/commit), then strip any trailing slashes
+$commit = before_first_slash(after_first_slash(after_first_slash($url)));
+
+include "src/parts/header.php";
+include "src/parts/description.php";
+include "src/parts/menu.php";
+
+// get commit stats
+$commit_stats = explode("\n",trim(shell_exec("cd $git_dir && git show --stat $commit")));
+
+// display commit table
+echo "\t\t\n";
+foreach($commit_stats as $stat) {
+ echo htmlspecialchars($stat)."\n";
+}
+echo "\t\t
\n\n";
+
+// display changed files
+$files_changed = explode("\n",trim(shell_exec("cd $git_dir && git show --format=tformat: --name-only $commit")));
+foreach ($files_changed as $file) {
+ echo "\t\t\n";
+ echo "\t\t\t
$file
\n";
+ echo "\t\t\t
\n";
+ $changes = explode("\n",trim(shell_exec("cd $git_dir && git show $commit -- $file")));
+ echo "\t\t\t
\n";
+ foreach ($changes as $change) {
+ echo htmlspecialchars($change)."\n";
+ }
+ echo "\t\t\t
\n";
+ echo "\t\t
\n";
+ echo "\t\t
\n\n";
+}
+
+include "src/parts/footer.php";
+?>
diff --git a/src/pages/commits.php b/src/pages/commits.php
new file mode 100644
index 0000000..61f8f6e
--- /dev/null
+++ b/src/pages/commits.php
@@ -0,0 +1,38 @@
+.
+*/
+
+include "src/parts/header.php";
+include "src/parts/description.php";
+include "src/parts/menu.php";
+
+// get commits
+$commits = explode("\n",trim(shell_exec("cd $git_dir && TZ=UTC git log --date=format-local:'%Y-%m-%d %H:%M:%S' --pretty=format:\"%h - %an, %ad : %s\"")));
+
+// display commit table
+foreach($commits as $commit) {
+ if (!empty($commit)) {
+ echo "\t\t$commit
\n";
+ }
+}
+
+echo "\t\t
\n\n";
+
+include "src/parts/footer.php";
+
+?>
diff --git a/src/pages/home.php b/src/pages/home.php
new file mode 100644
index 0000000..ba66ce9
--- /dev/null
+++ b/src/pages/home.php
@@ -0,0 +1,61 @@
+.
+*/
+
+// get subdirectories in specified repos directory
+$dirs = scandir($repos);
+
+// remove all dirs that are not git repos
+$dir_length = sizeof($dirs);
+$git_dirs = array();
+for ($i = 0; $i < $dir_length; $i++) {
+ // I could just start indexing from 2 instead
+ if ($dirs[$i] === "." || $dirs[$i] === "..") {
+ unset($dirs[$i]);
+ } else {
+ $git_dir = get_git_dir("$repos/$dirs[$i]");
+ if (! $git_dir) {
+ unset ($dirs[$i]);
+ } else {
+ $git_dirs[$dirs[$i]] = $git_dir;
+ }
+ }
+}
+
+// include header
+include "src/parts/header.php";
+
+// domain link
+echo "\t\t\n";
+
+// list git repos
+echo "\t\t\n";
+echo "\t\t
Repositories
\n";
+echo "\t\t
\n";
+foreach ($dirs as $dir) {
+ echo "\t\t\t
\n";
+ $git_dir = $git_dirs[$dir];
+ $description = get_repo_description($git_dir);
+ echo "\t\t\t
$description
\n";
+ echo "\t\t\t
\n";
+}
+echo "\t\t
\n";
+
+// include footer
+include "src/parts/footer.php";
+?>
diff --git a/src/pages/raw.php b/src/pages/raw.php
new file mode 100644
index 0000000..f451196
--- /dev/null
+++ b/src/pages/raw.php
@@ -0,0 +1,40 @@
+.
+*/
+
+// strip repo/raw/master/
+$path = $url;
+for ($i = 0; $i < 3; $i++) {
+ $path = after_first_slash($path);
+}
+
+// set header by filetype
+// TODO: add other filetypes
+if (ends_with($path,".jpg") || ends_with($path,".jpeg")) {
+ header("Content-type: image/jpeg");
+} elseif (ends_with($path,".png")) {
+ header("Content-type: image/png");
+} elseif (ends_with($path,".gif")) {
+ header("Content-type: image/gif");
+} else {
+ header("Content-type: text/plain");
+}
+
+// output file bytes
+passthru("cd $git_dir && git show --pretty=raw HEAD:$path");
+?>
diff --git a/src/pages/repo.php b/src/pages/repo.php
new file mode 100644
index 0000000..5bbf051
--- /dev/null
+++ b/src/pages/repo.php
@@ -0,0 +1,37 @@
+.
+*/
+
+include "src/parts/header.php";
+include "src/parts/description.php";
+include "src/parts/menu.php";
+
+// no path for the table because it's the repo root
+$path = "";
+include "src/parts/table.php";
+
+// display readme
+$readme = get_readme(list_files($git_dir));
+if (!empty($readme)) {
+ $path = $readme;
+ $name = $readme;
+ include "src/parts/preview.php";
+}
+
+include "src/parts/footer.php";
+?>
diff --git a/src/pages/tags.php b/src/pages/tags.php
new file mode 100644
index 0000000..16aa3ef
--- /dev/null
+++ b/src/pages/tags.php
@@ -0,0 +1,37 @@
+.
+*/
+
+include "src/parts/header.php";
+include "src/parts/description.php";
+include "src/parts/menu.php";
+
+// get tags
+$tags = explode("\n",trim(shell_exec("cd $git_dir && git tag --sort=\"-creatordate\" --format='%(creatordate:short) - %(refname:strip=2)'")));
+
+// display tags
+foreach ($tags as $tag) {
+ if (!empty($tag)) {
+ echo "\t\t\t$tag
\n";
+ }
+}
+
+echo "\t\t
\n\n";
+
+include "src/parts/footer.php";
+?>
diff --git a/src/pages/tree.php b/src/pages/tree.php
new file mode 100644
index 0000000..1b44f9e
--- /dev/null
+++ b/src/pages/tree.php
@@ -0,0 +1,45 @@
+.
+*/
+
+// strip repo/tree/
+$path = $url;
+for ($i = 0; $i < 2; $i++) {
+ $path = after_first_slash($path);
+}
+
+$branch = before_first_slash($path);
+// strip branch name
+$path = after_first_slash($path);
+
+$name = after_last_slash($url);
+
+// if no contents, it's not a valid URL
+$files = list_files("$git_dir", "$path");
+if (! $files) {
+ include "src/errors/404.php";
+ die();
+}
+
+include "src/parts/header.php";
+include "src/parts/description.php";
+include "src/parts/menu.php";
+include "src/parts/table.php";
+include "src/parts/footer.php";
+
+?>
diff --git a/src/parts/description.php b/src/parts/description.php
new file mode 100644
index 0000000..0804457
--- /dev/null
+++ b/src/parts/description.php
@@ -0,0 +1,45 @@
+.
+*/
+
+// page name / repo name / path / to / current
+echo "\t\t\n";
+echo "\t\t\t$title / \n";
+echo "\t\t\t$project";
+if ($page_type === "tree" || $page_type === "blob") {
+ echo " / \n";
+ $path_before = "/";
+ $path_remaining = strip_trailing_slash($path);
+ while ($path_remaining !== $name) {
+ $slash = strpos($path_remaining,"/");
+ $this_tree = substr($path_remaining,0,$slash);
+ echo "\t\t\t$this_tree / \n";
+ $path_before = "$path_before$this_tree/";
+ $path_remaining = substr($path_remaining,$slash+1);
+ }
+ echo "\t\t\t$name\n";
+ echo "\t\t
\n";
+} else { // domain / repo name
+ echo "\n\t\t\n";
+}
+
+// repo description
+$description = get_repo_description($git_dir);
+echo "\t\t$description
\n";
+echo "\t\t
\n\n";
+?>
diff --git a/src/parts/footer.php b/src/parts/footer.php
new file mode 100644
index 0000000..2ad30b9
--- /dev/null
+++ b/src/parts/footer.php
@@ -0,0 +1,22 @@
+.
+*/
+
+echo "\t\t\n";
+echo "\t