--- /dev/null
+Gitweb plugin for YOURLS
+========================
+<http://octo.it/yourls-gitweb/>
+
+This plugin can be used to automatically redirect to a Gitweb installation, if
+the provided keywors is not found in the database.
+
+For example, imagine a user opening this URL:
+
+ http://sho.rt/a06c1a52
+
+And further imagine that “a06c1a52” is not a short URL stored in the database.
+This plugin will intercept the “redirect_keyword_not_found” action and check if
+the keyword is a hexadecimal number with at least six (hexadecimal) digits.
+
+If so, the plugin will iterate over all Git repositories in a base directory
+and check if there's an object in one of them which matches the given id. If
+such an object is found, the user will be redirected to the appropriate Gitweb
+page. Otherwise, YOURLS will proceed as usual.
+
+Installation
+------------
+First, you need at least YOURLS, version 1.5. Versions before that do not
+provide a plugin infrastructure.
+
+ * Copy the plugin to “$yourls_dir/user/plugins/”.
+ * Open the administrative interface in the web-browser of your choice.
+ * Open the “Plugins” page and search for the “Gitweb” plugin in the table.
+ * Click “Activate” in the rightmost “Action” column.
+ * A new bulletin “Gitweb” should appear below the link to the “Plugins” page.
+ * Go to the “Gitweb” page.
+ * Insert the base directory and the Gitweb URL. The base directory should be
+ the same directory you specified using “$projectroot” in your Gitweb
+ configuration. You can pass any valid Gitweb URL into the second box –
+ superfluous arguments are automatically stripped.
+ * Click “Update” to store your changes.
+
+Caveats
+-------
+The plugin executes the “git” command line utility to check for objects in the
+configured Git repositories. Great care has been taken that every argument
+passed to the shell has been sanitized, but you may experience problems if
+PHP's “safe mode” is activated. The commands used are:
+
+ * git rev-parse
+ Check existence of an object and determine it's complete object id.
+ * git cat-file
+ Determine an object's type.
+
+License
+-------
+The “Gitweb” plugin is distributed under the terms of the MIT license. The
+complete licensing terms can be found at the beginning of the file
+“plugin.php”.
+
+Author
+------
+Florian “octo” Forster <ff at octo.it>
--- /dev/null
+<?php\r
+/*\r
+Plugin Name: Gitweb\r
+Plugin URI: http://octo.it/yourls-gitweb/\r
+Description: Automatically redirect to a Gitweb installation if an appropriate Git object exists.\r
+Version: 1.0\r
+Author: Florian "octo" Forster\r
+Author URI: http://octo.it/\r
+*/\r
+\r
+/**\r
+ * yourls -- Gitweb plugin\r
+ * Copyright (C) 2011 Florian Forster\r
+ *\r
+ * Permission is hereby granted, free of charge, to any person obtaining a\r
+ * copy of this software and associated documentation files (the "Software"),\r
+ * to deal in the Software without restriction, including without limitation\r
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,\r
+ * and/or sell copies of the Software, and to permit persons to whom the\r
+ * Software is furnished to do so, subject to the following conditions:\r
+ *\r
+ * The above copyright notice and this permission notice shall be included in\r
+ * all copies or substantial portions of the Software.\r
+ *\r
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\r
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\r
+ * DEALINGS IN THE SOFTWARE.\r
+ *\r
+ * Authors:\r
+ * Florian Forster <ff at octo.it>\r
+ **/\r
+\r
+function gitweb_check_repository ($obj, $repo, $dir, $url) /* {{{ */\r
+{\r
+ $output = array ();\r
+ $retval = 0;\r
+\r
+ $obj_name = shell_exec ('git --git-dir=' . escapeshellarg ($dir)\r
+ . ' rev-parse ' . escapeshellarg ($obj)\r
+ . ' 2>/dev/null');\r
+ if (!$obj_type)\r
+ return (false);\r
+\r
+ $obj_type = shell_exec ('git --git-dir=' . escapeshellarg ($dir)\r
+ . ' cat-file -t ' . escapeshellarg ($obj_name)\r
+ . ' 2>/dev/null');\r
+ if (!$obj_type)\r
+ return (false);\r
+\r
+ if ($obj_type == 'commit')\r
+ {\r
+ $to_url = "$url?p=" . urlencode ($repo) . ';a=commitdiff;h=' . urlencode ($obj_name);\r
+ yourls_redirect ($to_url, /* status = */ 301);\r
+ return (true);\r
+ }\r
+ elseif ($obj_type == 'tag')\r
+ {\r
+ $to_url = "$url?p=" . urlencode ($repo) . ';a=tag;h=' . urlencode ($obj_name);\r
+ yourls_redirect ($to_url, /* status = */ 301);\r
+ return (true);\r
+\r
+ }\r
+ elseif ($obj_type == 'tree')\r
+ {\r
+ $to_url = "$url?p=" . urlencode ($repo) . ";a=tree;h=" . urlencode ($obj_name);\r
+ yourls_redirect ($to_url, /* status = */ 301);\r
+ return (true);\r
+ }\r
+ elseif ($obj_type == 'blob')\r
+ {\r
+ $to_url = "$url?p=" . urlencode ($repo) . ";a=blob;h=" . urlencode ($obj_name);\r
+ yourls_redirect ($to_url, /* status = */ 301);\r
+ return (true);\r
+ }\r
+ else\r
+ {\r
+ error_log ("Gitweb plugin: Object \"$obj_name\" in repository \"$repo\" has unknown type \"$obj_type\".");\r
+ return (false);\r
+ }\r
+} /* }}} function gitweb_check_repository */\r
+\r
+/* This callback function is called when the given keyword was not found in the \r
+ * database. I'll see if this looks like an object identifier in a Git \r
+ * repository and, if so, try to locate the object using the local \r
+ * repositories. */\r
+function gitweb_redirect_keyword_not_found ($args) /* {{{ */\r
+{\r
+ $keyword = $args[0];\r
+\r
+ if (!preg_match ('/^[0-9a-fA-F]{6,40}$/', $keyword))\r
+ return;\r
+\r
+ $base_directory = yourls_get_option ('gitweb_base_directory');\r
+ if (!$base_directory)\r
+ return;\r
+\r
+ $base_url = yourls_get_option ('gitweb_base_url');\r
+ if (!$base_url)\r
+ return;\r
+\r
+ $dh = opendir ($base_directory);\r
+ if (!$dh)\r
+ return;\r
+\r
+ while (($subdir = readdir ($dh)) !== false)\r
+ {\r
+ /* Ignore all files and directories starting with a dot, including the \r
+ * special directories "." and "..". */\r
+ if (substr ($subdir, 0, 1) == '.')\r
+ continue;\r
+\r
+ $absdir = "$base_directory/$subdir";\r
+ if (!is_dir ($absdir))\r
+ continue;\r
+\r
+ /* Ignore repositories which are private (i.e. not exported by the \r
+ * git-daemon(1). We might leak information if we don't. */\r
+ if (!file_exists ("$absdir/git-daemon-export-ok"))\r
+ continue;\r
+\r
+ if (gitweb_check_repository ($keyword, $subdir, $absdir, $base_url))\r
+ break;\r
+ }\r
+\r
+ closedir ($dh);\r
+} /* }}} function gitweb_redirect_keyword_not_found */\r
+\r
+function gitweb_set_base_directory ($dir) /* {{{ */\r
+{\r
+ /* Remove trailing slashes. */\r
+ $dir = preg_replace ('/\/+$/', '', $dir);\r
+\r
+ if (!preg_match ('/^\//', $dir))\r
+ {\r
+ print ("<p class=\"error\">Not an absolute path: "\r
+ . htmlspecialchars ($dir)\r
+ . "</p>\n");\r
+ return (false);\r
+ }\r
+\r
+ if (!is_dir ($dir))\r
+ {\r
+ print ("<p class=\"error\">Not a directory: "\r
+ . htmlspecialchars ($dir)\r
+ . "</p>\n");\r
+ return (false);\r
+ }\r
+\r
+ /* Open the directory only to check its permissions. */\r
+ $dh = opendir ($dir);\r
+ if (!$dh)\r
+ {\r
+ print ("<p class=\"error\">Unable to open directory.</p>\n");\r
+ return (false);\r
+ }\r
+ closedir ($dh);\r
+\r
+ yourls_update_option ('gitweb_base_directory', $dir);\r
+ return (true);\r
+} /* }}} function gitweb_set_base_directory */\r
+\r
+function gitweb_set_base_url ($url) /* {{{ */\r
+{\r
+ if (!preg_match ('/https?:\/\//i', $url))\r
+ {\r
+ print ("<p class=\"error\">This does not look like a valid URL: "\r
+ . htmlspecialchars ($url)\r
+ . "</p>\n");\r
+ return (false);\r
+ }\r
+\r
+ $url = preg_replace ('/\?.*/', '', $url);\r
+\r
+ yourls_update_option ('gitweb_base_url', $url);\r
+ return (true);\r
+} /* }}} function gitweb_set_base_directory */\r
+\r
+function gitweb_show_plugin_page () /* {{{ */\r
+{\r
+ echo <<<HTML\r
+ <h2>Gitweb Plugin Administration Page</h2>\r
+ <p>This plugin redirects to a Gitweb installation if a keyword wasn't\r
+ found in the database, looks like a Git object ID and is found in a\r
+ local Git repository.</p>\r
+HTML;\r
+\r
+ if (isset ($_POST['base_directory']))\r
+ gitweb_set_base_directory ($_POST['base_directory']);\r
+\r
+ if (isset ($_POST['base_url']))\r
+ gitweb_set_base_url ($_POST['base_url']);\r
+\r
+ $base_directory = yourls_get_option ('gitweb_base_directory');\r
+ if ($base_directory)\r
+ $base_directory = htmlspecialchars ($base_directory);\r
+\r
+ $base_url = yourls_get_option ('gitweb_base_url');\r
+ if ($base_url)\r
+ $base_url = htmlspecialchars ($base_url);\r
+\r
+ echo <<<HTML\r
+ <form method="post">\r
+ <table style="background-color: #cdcdcd; border-spacing: 1px;">\r
+ <tr>\r
+ <th style="border: 1px solid white; background: #C7E7FF; padding: 4px;"><label for="base_directory">Base directory</label></th>\r
+ <td style="background-color: white; padding: 4px;"><input type="text" id="base_directory" name="base_directory" value="$base_directory" /></td>\r
+ </tr>\r
+ <tr>\r
+ <th style="border: 1px solid white; background: #C7E7FF; padding: 4px;"><label for="base_url">Gitweb URL</label></th>\r
+ <td style="background-color: white; padding: 4px;"><input type="text" id="base_url" name="base_url" value="$base_url" /></td>\r
+ </tr>\r
+ <tr>\r
+ <td colspan="2" style="border: 1px solid white; background-color: #E3F3FF; text-align: right; padding: 4px;"><input type="submit" value="Update" class="button primary" /></td>\r
+ </tr>\r
+ </table>\r
+ </form>\r
+HTML;\r
+} /* }}} function gitweb_show_plugin_page */\r
+\r
+function gitweb_register_plugin_page () /* {{{ */\r
+{\r
+ yourls_register_plugin_page ('gitweb_page', 'Gitweb',\r
+ 'gitweb_show_plugin_page');\r
+} /* }}} function gitweb_register_plugin_page */\r
+\r
+yourls_add_action ('plugins_loaded', 'gitweb_register_plugin_page');\r
+yourls_add_action ('redirect_keyword_not_found', 'gitweb_redirect_keyword_not_found');\r
+\r
+/* vim: set sw=2 sts=2 et fdm=marker : */\r
+?>\r