src/application.cpp
browsing at commit = 4fe276c400f51581a3858e55f7b9a48a1f90f419
#include "application.h"
#include "content.h"
#include "git_repo.h"
#include "repo_scanner.h"
#include "util.h"
#include <cppcms/http_request.h>
#include <cppcms/http_response.h>
#include <cppcms/service.h>
#include <cppcms/url_dispatcher.h>
#include <fstream>
namespace {
constexpr int LOG_LIMIT = 200;
const std::string SKIN = "git";
// Minimal content-type table for the assets we ship.
std::string mime_for(const std::string &path) {
const size_t dot = path.find_last_of('.');
const std::string ext = dot == std::string::npos ? "" : path.substr(dot + 1);
if (ext == "css") return "text/css";
if (ext == "js") return "application/javascript";
if (ext == "svg") return "image/svg+xml";
if (ext == "png") return "image/png";
if (ext == "ico") return "image/x-icon";
return "application/octet-stream";
}
} // namespace
GitApp::GitApp(cppcms::service &srv, const RepoScanner *scanner)
: cppcms::application(srv), scanner_(scanner) {
static_root_ = settings().get<std::string>("git.static_root", "./static");
// Root arrives as "" because script_name "/" is stripped; accept both.
dispatcher().assign("^/?$", &GitApp::frontpage, this);
dispatcher().assign("^/static/(.+)$", &GitApp::static_file, this, 1);
dispatcher().assign("^/repos/?$", &GitApp::repos_list, this);
dispatcher().assign("^/repos/([^/]+)/tree/?(.*)$", &GitApp::tree, this, 1, 2);
dispatcher().assign("^/repos/([^/]+)/raw/(.+)$", &GitApp::raw, this, 1, 2);
dispatcher().assign("^/repos/([^/]+)/blame/(.+)$", &GitApp::blame, this, 1, 2);
dispatcher().assign("^/repos/([^/]+)/object/([0-9a-f]+)$", &GitApp::object, this, 1, 2);
dispatcher().assign("^/repos/([^/]+)/log/?$", &GitApp::log, this, 1);
dispatcher().assign("^/repos/([^/]+)/commit/([0-9a-f]+)\\.patch$", &GitApp::commit_patch, this, 1, 2);
dispatcher().assign("^/repos/([^/]+)/commit/([0-9a-f]+)$", &GitApp::commit, this, 1, 2);
dispatcher().assign("^/repos/([^/]+)/tags/?$", &GitApp::tags, this, 1);
dispatcher().assign("^/repos/([^/]+)/tags/(.+)$", &GitApp::tag, this, 1, 2);
dispatcher().assign("^/repos/([^/]+)/?$", &GitApp::repo_home, this, 1);
}
void GitApp::main(std::string url) {
response().content_type("text/html; charset=utf-8");
cppcms::application::main(url);
}
void GitApp::static_file(std::string rel) {
if (rel.find("..") != std::string::npos) {
show_error(403, "Forbidden");
return;
}
const std::string full = static_root_ + "/" + rel;
std::ifstream file(full, std::ios::binary);
if (!file) {
show_error(404, "Not found: " + rel);
return;
}
response().content_type(mime_for(rel));
response().out() << file.rdbuf();
}
std::unique_ptr<GitRepo> GitApp::open(const std::string &name) {
const std::string path = scanner_->path_for(name);
if (path.empty()) {
show_error(404, "No such repository: " + name);
return nullptr;
}
try {
return std::make_unique<GitRepo>(path);
} catch (const GitError &e) {
show_error(500, e.what());
return nullptr;
}
}
std::string GitApp::commit_from_request(const GitRepo &repo) {
const std::string id = request().get("id");
if (id.empty())
return repo.resolve_commit("");
if (!util::is_hex_oid(id))
throw GitError("invalid id parameter");
return repo.resolve_commit(id);
}
void GitApp::show_error(int code, const std::string &message) {
response().status(code);
response().content_type("text/plain; charset=utf-8");
response().out() << message << "\n";
}
void GitApp::frontpage() {
content::frontpage c;
c.title = "Multi Git Frontend";
c.repo_count = scanner_->names().size();
render(SKIN, "frontpage", c);
}
void GitApp::repos_list() {
content::repo_list c;
c.title = "Repositories";
c.repos = scanner_->names();
render(SKIN, "repo_list", c);
}
void GitApp::repo_home(std::string name) {
std::unique_ptr<GitRepo> repo = open(name);
if (!repo)
return;
try {
content::repo_home c;
c.title = name;
c.repo = name;
c.commit_oid = commit_from_request(*repo);
c.query = request().get("id").empty() ? "" : "?id=" + c.commit_oid;
c.default_branch = repo->default_branch();
c.readme_name = repo->find_readme(c.commit_oid);
if (!c.readme_name.empty()) {
bool is_binary = false;
repo->read_file(c.commit_oid, c.readme_name, c.readme_text, is_binary);
if (is_binary)
c.readme_text.clear();
}
render(SKIN, "repo_home", c);
} catch (const GitError &e) {
show_error(404, e.what());
}
}
void GitApp::tree(std::string name, std::string path) {
std::unique_ptr<GitRepo> repo = open(name);
if (!repo)
return;
try {
const std::string commit = commit_from_request(*repo);
const std::string query = request().get("id").empty() ? "" : "?id=" + commit;
const std::string clean = util::trim_slashes(path);
// A path pointing at a file shows the file; otherwise it is a directory.
std::string text;
bool is_binary = false;
if (!clean.empty() && repo->read_file(commit, clean, text, is_binary)) {
content::file_view c;
c.title = name + "/" + clean;
c.repo = name;
c.query = query;
c.commit_oid = commit;
c.path = clean;
c.filename = util::basename(clean);
c.is_binary = is_binary;
c.is_markdown = !is_binary && util::is_markdown_path(clean);
c.text = is_binary ? "" : text;
c.size = text.size();
render(SKIN, "file_view", c);
return;
}
content::tree_view c;
c.title = name + (clean.empty() ? "" : "/" + clean);
c.repo = name;
c.query = query;
c.commit_oid = commit;
c.path = clean;
c.is_root = clean.empty();
c.parent_path = util::parent_path(clean);
c.entries = repo->list_tree(commit, clean);
render(SKIN, "tree_view", c);
} catch (const GitError &e) {
show_error(404, e.what());
}
}
void GitApp::raw(std::string name, std::string path) {
std::unique_ptr<GitRepo> repo = open(name);
if (!repo)
return;
try {
const std::string commit = commit_from_request(*repo);
std::string content;
bool is_binary = false;
if (!repo->read_file(commit, util::trim_slashes(path), content, is_binary)) {
show_error(404, "No such file: " + path);
return;
}
response().content_type(is_binary ? "application/octet-stream"
: "text/plain; charset=utf-8");
response().out().write(content.data(), static_cast<std::streamsize>(content.size()));
} catch (const GitError &e) {
show_error(404, e.what());
}
}
void GitApp::blame(std::string name, std::string path) {
std::unique_ptr<GitRepo> repo = open(name);
if (!repo)
return;
try {
const std::string commit = commit_from_request(*repo);
content::blame_view c;
c.repo = name;
c.query = request().get("id").empty() ? "" : "?id=" + commit;
c.commit_oid = commit;
c.path = util::trim_slashes(path);
c.filename = util::basename(c.path);
c.title = "blame " + name + "/" + c.path;
c.lines = repo->blame(commit, c.path);
render(SKIN, "blame_view", c);
} catch (const GitError &e) {
show_error(404, e.what());
}
}
void GitApp::log(std::string name) {
std::unique_ptr<GitRepo> repo = open(name);
if (!repo)
return;
try {
const std::string commit = commit_from_request(*repo);
content::log_view c;
c.repo = name;
c.query = request().get("id").empty() ? "" : "?id=" + commit;
c.commit_oid = commit;
c.title = "log " + name;
c.commits = repo->log(commit, LOG_LIMIT);
render(SKIN, "log_view", c);
} catch (const GitError &e) {
show_error(404, e.what());
}
}
void GitApp::object(std::string name, std::string oid) {
std::unique_ptr<GitRepo> repo = open(name);
if (!repo)
return;
try {
content::object_view c;
c.repo = name;
c.title = "object " + oid;
c.object = repo->inspect(oid);
render(SKIN, "object_view", c);
} catch (const GitError &e) {
show_error(404, e.what());
}
}
void GitApp::tags(std::string name) {
std::unique_ptr<GitRepo> repo = open(name);
if (!repo)
return;
try {
content::tags_view c;
c.repo = name;
c.title = "tags " + name;
c.tags = repo->tags();
render(SKIN, "tags_view", c);
} catch (const GitError &e) {
show_error(404, e.what());
}
}
void GitApp::tag(std::string name, std::string tag_name) {
std::unique_ptr<GitRepo> repo = open(name);
if (!repo)
return;
try {
content::tag_view c;
c.repo = name;
c.title = "tag " + tag_name;
c.tag = repo->tag_detail(tag_name);
render(SKIN, "tag_view", c);
} catch (const GitError &e) {
show_error(404, e.what());
}
}
void GitApp::commit(std::string name, std::string oid) {
std::unique_ptr<GitRepo> repo = open(name);
if (!repo)
return;
try {
content::commit_view c;
c.repo = name;
c.title = "commit " + oid;
c.commit = repo->commit_detail(oid);
render(SKIN, "commit_view", c);
} catch (const GitError &e) {
show_error(404, e.what());
}
}
void GitApp::commit_patch(std::string name, std::string oid) {
std::unique_ptr<GitRepo> repo = open(name);
if (!repo)
return;
try {
const std::string patch = repo->commit_patch(oid);
response().content_type("text/plain; charset=utf-8");
response().out().write(patch.data(), static_cast<std::streamsize>(patch.size()));
} catch (const GitError &e) {
show_error(404, e.what());
}
}