*/
function find_projects(string $path, array $projects = []): array {
$directories = glob($path . "/{.[!.],}*", GLOB_ONLYDIR | GLOB_BRACE);
if (array_search("$path/.git", $directories) !== false) {
$projects[] = $path;
}
else {
if (count(array_intersect(scandir($path), ["HEAD", "objects", "refs"])) == 3) {
$projects[] = $path;
}
else {
foreach ($directories as $directory) {
$projects += find_projects($directory, $projects);
}
}
}
return $projects;
}
global $cache_parse_log;
$cache_parse_log = [];
/**
* parse git log at $path
*/
function parse_log(string $path): array {
global $cache_parse_log;
if (isset($cache_parse_log[$path])) {
return $cache_parse_log[$path];
}
$proxy = md5(strval(microtime(true)));
$process = proc_open(
"git -C \"{$path}\" log " . "--pretty=format:'{%n {$proxy}commit{$proxy}: {$proxy}%H{$proxy},%n {$proxy}author{$proxy}: {$proxy}%aN <%aE>{$proxy},%n {$proxy}date{$proxy}: {$proxy}%ad{$proxy},%n {$proxy}message{$proxy}: {$proxy}%s{$proxy}%n},'",
[
["pipe", "r"],
["pipe", "w"],
["pipe", "w"],
],
$pipes
);
$output = stream_get_contents($pipes[1]);
$string = str_replace(['"', $proxy], ['\\"', '"'], $output);
$string = "[" . rtrim($string, ",") . "]";
$json = json_decode($string, true);
return $cache_parse_log[$path] = $json ?? [];
}
/**
* parse git status at $path
*/
function parse_status(string $path): array {
$process = proc_open(
"git -C \"{$path}\" status --porcelain=v2 --branch",
[
["pipe", "r"],
["pipe", "w"],
["pipe", "w"],
],
$pipes
);
$output = stream_get_contents($pipes[1]);
$status = ["files" => []];
foreach (explode("\n", $output) as $line) {
if (empty($line)) {
continue;
}
$start = substr($line, 0, 1);
if ($start == "#") {
if (str_contains($line, "branch.oid")) {
$status["oid"] = substr($line, strlen("# branch.oid "));
}
else if (str_contains($line, "branch.head")) {
$status["head"] = substr($line, strlen("# branch.head "));
}
else if (str_contains($line, "branch.upstream")) {
$status["upstream"] = substr($line, strlen("# branch.upstream "));
}
else if (str_contains($line, "branch.ab")) {
$status["ab"] = substr($line, strlen("# branch.ab "));
}
}
else {
$parts = explode(" ", $line);
$file = [
"filename" => $parts[count($parts) - 1],
"staged" => false,
"unstaged" => false,
"untracked" => false,
"new_file" => false,
"modified" => false,
];
if ($start == "1") {
$track_status = $parts[1];
if (substr($track_status, 0, 1) == "A") {
$file["staged"] = true;
$file["new_file"] = true;
}
else if (substr($track_status, 0, 1) == "M") {
$file["staged"] = true;
$file["modified"] = true;
}
if (substr($track_status, 1, 1) == "M") {
$file["unstaged"] = true;
$file["modified"] = true;
}
$file = array_merge($file, [
"previous_mode" => $parts[3],
"new_mode" => $parts[4],
"previous_id" => $parts[6],
"new_id" => $parts[7],
]);
}
else if ($start == "?") {
$file["untracked"] = true;
}
$status["files"][] = $file;
}
}
return $status;
}
/**
* get git file tree at $path for $revision under $tree_base_path
*/
function get_file_tree(string $path, string $revision, string $tree_base_path): array
{
$proxy = md5(strval(microtime(true)));
$process = proc_open(
"git -C \"{$path}\" ls-tree {$revision} \"{$tree_base_path}\"" . " --format='{%n {$proxy}mode{$proxy}: {$proxy}%(objectmode){$proxy},%n {$proxy}type{$proxy}: {$proxy}%(objecttype){$proxy},%n {$proxy}name{$proxy}: {$proxy}%(objectname){$proxy},%n {$proxy}size{$proxy}: {$proxy}%(objectsize){$proxy},%n {$proxy}path{$proxy}: {$proxy}%(path){$proxy}%n},'",
[
["pipe", "r"],
["pipe", "w"],
["pipe", "w"],
],
$pipes
);
$output = stream_get_contents($pipes[1]);
$string = str_replace(['\\', '"', $proxy], ['\\\\', '\\"', '"'], $output);
$string = "[" . rtrim(trim($string), ",") . "]";
$tree = json_decode($string, true);
return $tree;
}
/**
* get git file info for single file at $file_path in repository at $path
*/
function get_file_info(string $path, string $file_path): array
{
$tree = get_file_tree($path, "HEAD", $file_path);
$info = [];
foreach ($tree as $file) {
if ($file["path"] == $file_path) {
$info = $file;
break;
}
}
return $info;
}
/**
* get file contents based on $name (git object id) in repository at $path
*/
function read_object_file(string $path, string $name): string
{
$process = proc_open(
"git -C \"{$path}\" cat-file -p \"{$name}\"",
[
["pipe", "r"],
["pipe", "w"],
["pipe", "w"],
],
$pipes
);
return stream_get_contents($pipes[1]);
}
##
# Utility
##
/**
* urlencode whole $url
*/
function uri_encode(string $url): string
{
return implode(
"/",
array_map(
fn ($value) => urlencode($value),
explode("/", $url)
)
);
}
##
# Template
##
/**
* Template root
*/
function template_root(string $content, array $meta = []): string
{
$meta = array_replace_recursive([
"title" => "phpgit - single file git web frontend",
], $meta);
ob_start();
?>
root";
$crumb_parts = "";
foreach ($breadcrumbs as $idx => $crumb) {
$crumb_parts .= "/$crumb";
if ($idx < count($breadcrumbs) - 1) {
echo "/$crumb";
} else {
echo "/$crumb";
}
}
return ob_get_clean();
}
##
# Output
##
$projects_path = realpath($_ENV["PHPGIT_PROJECTS_PATH"]);
$url = parse_url("http://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]");
ob_start();
// Route: /
if ($url["path"] == "/") {
$projects = find_projects($projects_path);
// sort by date
usort($projects, function ($a, $b) {
$a_log = parse_log($a);
if (file_exists(".git/info/web/last-modified")) {
$a_date = new DateTime(file_get_contents(".git/info/web/last-modified"));
} else if (count($a_log) > 0) {
$a_date = new DateTime($a_log[0]["date"]);
} else {
$a_date = date_create()->setTimestamp(0);
}
$b_log = parse_log($b);
if (file_exists(".git/info/web/last-modified")) {
$b_date = new DateTime(file_get_contents(".git/info/web/last-modified"));
} else if (count($b_log) > 0) {
$b_date = new DateTime($b_log[0]["date"]);
} else {
$b_date = date_create()->setTimestamp(0);
}
return $b_date <=> $a_date;
});
// build list
?>
Name |
Description |
Last Activity |
| ";
$log = parse_log($project);
if (count($log) > 0) {
$date = new DateTime($log[0]["date"]);
echo "" . $date->format("Y-m-d") . " | ";
}
?>
buffer($contents);
$memory_limit = (int)ini_get("memory_limit") * 1024 ** ["k" => 1, "m" => 2, "g" => 3][strtolower(ini_get("memory_limit")[-1])];
?>
Mime Type:
Can't process. Memory limit of would be exhausted.