*/ 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(); ?> <?php echo $meta["title"]; ?> 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 ?> "; $log = parse_log($project); if (count($log) > 0) { $date = new DateTime($log[0]["date"]); echo ""; } ?>
Name Description Last Activity
" . $date->format("Y-m-d") . "
Mode File
">">
buffer($contents); $memory_limit = (int)ini_get("memory_limit") * 1024 ** ["k" => 1, "m" => 2, "g" => 3][strtolower(ini_get("memory_limit")[-1])]; ?>
Mime Type: