startgitStatic page generator for git repositories |
git clone git://git.dimitrijedobrota.com/startgit.git |
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING | |
commit | 90953851fe6e33f57f948c23e9cd77f912acee66 |
parent | bcedaa95fc80b3ace4d07bfb38caea5048344335 |
author | Dimitrije Dobrota <mail@dimitrijedobrota.com> |
date | Tue, 14 Jan 2025 19:16:01 +0100 |
Refactor into multiple files
Diffstat:M | CMakeLists.txt | | | ++++++- |
M | source/branch.cpp | | | +++++++++++++++++++++++++++++++++++++++- |
M | source/branch.hpp | | | +++++++++++++++++++++- |
A | source/commit.cpp | | | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | source/commit.hpp | | | +++++++++++++++++++++++++++++++++++++ |
A | source/diff.cpp | | | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | source/diff.hpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | source/file.cpp | | | ++++++++++++++ |
A | source/file.hpp | | | +++++++++++++++++++++++ |
M | source/main.cpp | | | +++++++++++++++++++++++++--------------------------------------------------------- |
M | source/repository.cpp | | | ++++++++++++++- |
M | source/repository.hpp | | | ++++++++ |
A | source/tag.cpp | | | +++++++++++++++ |
A | source/tag.hpp | | | +++++++++++++++++++++++++ |
A | source/utils.cpp | | | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | source/utils.hpp | | | ++++++++++++++++ |
16 files changed, 670 insertions(+), 324 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
@@ -4,7 +4,7 @@ include(cmake/prelude.cmake)
project(
startgit
VERSION 0.1.16
VERSION 0.1.17
DESCRIPTION "Static page generator for git repositories"
HOMEPAGE_URL "https://git.dimitrijedobrota.com/stargit.git"
LANGUAGES CXX
@@ -24,7 +24,12 @@ find_package(poafloc 1 CONFIG REQUIRED)
add_library(
startgit_lib OBJECT
source/branch.cpp
source/diff.cpp
source/tag.cpp
source/commit.cpp
source/file.cpp
source/repository.cpp
source/utils.cpp
)
target_link_libraries(startgit_lib PUBLIC git2wrap::git2wrap hemplate::hemplate poafloc::poafloc)
diff --git a/source/branch.cpp b/source/branch.cpp
@@ -1,12 +1,50 @@
#include <functional>
#include "branch.hpp"
#include <git2wrap/revwalk.hpp>
#include "repository.hpp"
namespace startgit
{
branch::branch(git2wrap::branch brnch)
branch::branch(git2wrap::branch brnch, repository& repo)
: m_branch(std::move(brnch))
, m_name(m_branch.get_name())
{
git2wrap::revwalk rwalk(repo.get());
const git2wrap::object obj = repo.get().revparse(m_name.c_str());
rwalk.push(obj.get_id());
while (auto commit = rwalk.next()) {
m_commits.emplace_back(std::move(commit));
}
std::function<void(const git2wrap::tree&, const std::string& path)> traverse =
[&](const auto& l_tree, const auto& path)
{
for (size_t i = 0; i < l_tree.get_entrycount(); i++) {
const auto entry = l_tree.get_entry(i);
const auto full_path =
(!path.empty() ? path + "/" : "") + entry.get_name();
switch (entry.get_type()) {
case GIT_OBJ_BLOB:
break;
case GIT_OBJ_TREE:
traverse(entry.to_tree(), full_path);
continue;
default:
continue;
}
m_files.emplace_back(entry, full_path);
}
};
traverse(get_last_commit().get_tree(), "");
}
} // namespace startgit
diff --git a/source/branch.hpp b/source/branch.hpp
@@ -1,23 +1,43 @@
#pragma once
#include <string>
#include <vector>
#include <git2wrap/branch.hpp>
#include "commit.hpp"
#include "file.hpp"
namespace startgit
{
class repository;
class branch
{
public:
explicit branch(git2wrap::branch brnch);
explicit branch(git2wrap::branch brnch, repository& repo);
branch(const branch&) = delete;
branch& operator=(const branch&) = delete;
branch(branch&&) = default;
branch& operator=(branch&&) = default;
~branch() = default;
const auto& get() const { return m_branch; }
const std::string& get_name() const { return m_name; }
const commit& get_last_commit() const { return m_commits[0]; }
const auto& get_commits() const { return m_commits; }
const auto& get_files() const { return m_files; }
private:
git2wrap::branch m_branch;
std::string m_name;
std::vector<commit> m_commits;
std::vector<file> m_files;
};
} // namespace startgit
diff --git a/source/commit.cpp b/source/commit.cpp
@@ -0,0 +1,78 @@
#include "commit.hpp"
#include <git2wrap/diff.hpp>
#include <git2wrap/signature.hpp>
#include "utils.hpp"
namespace startgit
{
commit::commit(git2wrap::commit cmmt)
: m_commit(std::move(cmmt))
, m_diff(m_commit)
{
}
std::string commit::get_id() const
{
return m_commit.get_id().get_hex_string(shasize);
}
std::string commit::get_parent_id() const
{
return m_commit.get_parent().get_id().get_hex_string(shasize);
}
size_t commit::get_parentcount() const
{
return m_commit.get_parentcount();
}
std::string commit::get_summary() const
{
std::string summary = m_commit.get_summary();
static const int summary_limit = 50;
if (summary.size() > summary_limit) {
summary.resize(summary_limit);
for (size_t i = summary.size() - 1; i >= summary.size() - 4; i--) {
summary[i] = '.';
}
}
return summary;
}
std::string commit::get_time() const
{
return long_to_string(m_commit.get_time());
}
std::string commit::get_author_name() const
{
return m_commit.get_author().get_name();
}
std::string commit::get_author_email() const
{
return m_commit.get_author().get_name();
}
std::string commit::get_author_time() const
{
return long_to_string(m_commit.get_author().get_time());
}
git2wrap::tree commit::get_tree() const
{
return m_commit.get_tree();
}
std::string commit::get_message() const
{
return m_commit.get_message();
}
} // namespace startgit
diff --git a/source/commit.hpp b/source/commit.hpp
@@ -0,0 +1,37 @@
#pragma once
#include <git2wrap/commit.hpp>
#include <git2wrap/tree.hpp>
#include "diff.hpp"
namespace startgit
{
class commit
{
public:
explicit commit(git2wrap::commit cmmt);
const auto& get() const { return m_commit; }
const diff& get_diff() const { return m_diff; }
std::string get_id() const;
std::string get_parent_id() const;
size_t get_parentcount() const;
std::string get_summary() const;
std::string get_time() const;
std::string get_author_name() const;
std::string get_author_email() const;
std::string get_author_time() const;
git2wrap::tree get_tree() const;
std::string get_message() const;
private:
static const int shasize = 40;
git2wrap::commit m_commit;
diff m_diff;
};
} // namespace startgit
diff --git a/source/diff.cpp b/source/diff.cpp
@@ -0,0 +1,68 @@
#include "diff.hpp"
namespace startgit
{
diff::diff(const git2wrap::commit& cmmt)
: m_diff(nullptr, nullptr)
, m_stats(nullptr)
{
const auto ptree = cmmt.get_parentcount() > 0
? cmmt.get_parent().get_tree()
: git2wrap::tree(nullptr, nullptr);
git2wrap::diff_options opts;
git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION);
opts.flags = GIT_DIFF_DISABLE_PATHSPEC_MATCH | GIT_DIFF_IGNORE_SUBMODULES
| GIT_DIFF_INCLUDE_TYPECHANGE;
m_diff = git2wrap::diff::tree_to_tree(cmmt.get_tree(), ptree, &opts);
m_stats = m_diff.get_stats();
m_diff.foreach(file_cb, nullptr, hunk_cb, line_cb, this);
}
int diff::file_cb(const git_diff_delta* delta,
float /* progress */,
void* payload)
{
diff& crnt = *reinterpret_cast<diff*>(payload); // NOLINT
crnt.m_deltas.emplace_back(delta);
return 0;
}
int diff::hunk_cb(const git_diff_delta* /* delta */,
const git_diff_hunk* hunk,
void* payload)
{
diff& crnt = *reinterpret_cast<diff*>(payload); // NOLINT
crnt.m_deltas.back().m_hunks.emplace_back(hunk);
return 0;
}
int diff::line_cb(const git_diff_delta* /* delta */,
const git_diff_hunk* /* hunk */,
const git_diff_line* line,
void* payload)
{
diff& crnt = *reinterpret_cast<diff*>(payload); // NOLINT
crnt.m_deltas.back().m_hunks.back().m_lines.emplace_back(line);
return 0;
}
std::string diff::get_files_changed() const
{
return std::to_string(m_stats.get_files_changed());
}
std::string diff::get_insertions() const
{
return std::to_string(m_stats.get_insertions());
}
std::string diff::get_deletions() const
{
return std::to_string(m_stats.get_deletions());
}
} // namespace startgit
diff --git a/source/diff.hpp b/source/diff.hpp
@@ -0,0 +1,90 @@
#pragma once
#include <string>
#include <git2wrap/commit.hpp>
#include <git2wrap/diff.hpp>
namespace startgit
{
struct line
{
friend class diff;
explicit line(const git_diff_line* lin)
: m_content(lin->content, lin->content_len)
, m_origin(lin->origin)
{
}
const std::string& get_content() const { return m_content; }
char get_origin() const { return m_origin; }
private:
std::string m_content;
char m_origin;
};
struct hunk
{
friend class diff;
explicit hunk(const git_diff_hunk* dlt)
: m_ptr(*dlt)
{
}
const auto* operator->() const { return &m_ptr; }
const auto& get_lines() const { return m_lines; }
private:
git_diff_hunk m_ptr;
std::vector<line> m_lines;
};
struct delta
{
friend class diff;
explicit delta(const git_diff_delta* dlt)
: m_ptr(*dlt)
{
}
const auto* operator->() const { return &m_ptr; }
const auto& get_hunks() const { return m_hunks; }
private:
git_diff_delta m_ptr;
std::vector<hunk> m_hunks;
};
class diff
{
public:
explicit diff(const git2wrap::commit& cmmt);
std::string get_files_changed() const;
std::string get_insertions() const;
std::string get_deletions() const;
const auto& get_deltas() const { return m_deltas; }
private:
static int file_cb(const git_diff_delta* delta,
float progress,
void* payload);
static int hunk_cb(const git_diff_delta* delta,
const git_diff_hunk* hunk,
void* payload);
static int line_cb(const git_diff_delta* delta,
const git_diff_hunk* hunk,
const git_diff_line* line,
void* payload);
git2wrap::diff m_diff;
git2wrap::diff_stats m_stats;
std::vector<delta> m_deltas;
};
} // namespace startgit
diff --git a/source/file.cpp b/source/file.cpp
@@ -0,0 +1,14 @@
#include "file.hpp"
#include "utils.hpp"
namespace startgit
{
file::file(const git2wrap::tree_entry& entry, std::string path)
: m_filemode(filemode(entry.get_filemode()))
, m_path(std::move(path))
{
}
} // namespace startgit
diff --git a/source/file.hpp b/source/file.hpp
@@ -0,0 +1,23 @@
#pragma once
#include <string>
#include <git2wrap/tree.hpp>
namespace startgit
{
class file
{
public:
file(const git2wrap::tree_entry& entry, std::string path);
std::string get_filemode() const { return m_filemode; }
std::string get_path() const { return m_path; }
private:
std::string m_filemode;
std::string m_path;
};
} // namespace startgit
diff --git a/source/main.cpp b/source/main.cpp
@@ -4,85 +4,13 @@
#include <iostream>
#include <string>
#include <git2.h>
#include <git2wrap/diff.hpp>
#include <git2wrap/error.hpp>
#include <git2wrap/libgit2.hpp>
#include <git2wrap/object.hpp>
#include <git2wrap/repository.hpp>
#include <git2wrap/revwalk.hpp>
#include <git2wrap/signature.hpp>
#include <git2wrap/tag.hpp>
#include <hemplate/classes.hpp>
#include <poafloc/poafloc.hpp>
#include <sys/stat.h>
#include "repository.hpp"
std::string long_to_string(int64_t date)
{
std::stringstream strs;
strs << std::put_time(std::gmtime(&date), "%Y-%m-%d %H:%M"); // NOLINT
return strs.str();
}
std::string long_to_string(const git2wrap::time& time)
{
std::stringstream strs;
strs << std::put_time(std::gmtime(&time.time), // NOLINT
"%a, %e %b %Y %H:%M:%S ");
strs << (time.offset < 0 ? '-' : '+');
strs << std::format("{:02}{:02}", time.offset / 60, time.offset % 60);
return strs.str();
}
// NOLINTBEGIN
// clang-format off
void xmlencode(std::ostream& ost, const std::string& s)
{
for (const char c: s) {
switch(c) {
case '<': ost << "<"; break;
case '>': ost << ">"; break;
case '\'': ost << "'"; break;
case '&': ost << "&"; break;
case '"': ost << """; break;
default: ost << c;
}
}
}
std::string filemode(git2wrap::filemode_t filemode)
{
std::string mode(10, '-');
if (S_ISREG(filemode)) mode[0] = '-';
else if (S_ISBLK(filemode)) mode[0] = 'b';
else if (S_ISCHR(filemode)) mode[0] = 'c';
else if (S_ISDIR(filemode)) mode[0] = 'd';
else if (S_ISFIFO(filemode)) mode[0] = 'p';
else if (S_ISLNK(filemode)) mode[0] = 'l';
else if (S_ISSOCK(filemode)) mode[0] = 's';
else mode[0] = '?';
if (filemode & S_IRUSR) mode[1] = 'r';
if (filemode & S_IWUSR) mode[2] = 'w';
if (filemode & S_IXUSR) mode[3] = 'x';
if (filemode & S_IRGRP) mode[4] = 'r';
if (filemode & S_IWGRP) mode[5] = 'w';
if (filemode & S_IXGRP) mode[6] = 'x';
if (filemode & S_IROTH) mode[7] = 'r';
if (filemode & S_IWOTH) mode[8] = 'w';
if (filemode & S_IXOTH) mode[9] = 'x';
if (filemode & S_ISUID) mode[3] = (mode[3] == 'x') ? 's' : 'S';
if (filemode & S_ISGID) mode[6] = (mode[6] == 'x') ? 's' : 'S';
if (filemode & S_ISVTX) mode[9] = (mode[9] == 'x') ? 't' : 'T';
return mode;
}
// clang-format on
// NOLINTEND
#include "utils.hpp"
void write_header(std::ostream& ost,
const std::string& repo,
@@ -192,7 +120,7 @@ void write_title(std::ostream& ost,
ost << html::hr();
}
void write_commit_table(std::ostream& ost, git2wrap::revwalk& rwalk)
void write_commit_table(std::ostream& ost, const startgit::branch& branch)
{
using namespace hemplate; // NOLINT
@@ -208,47 +136,44 @@ void write_commit_table(std::ostream& ost, git2wrap::revwalk& rwalk)
ost << html::thead();
ost << html::tbody();
while (const auto commit = rwalk.next()) {
const auto url =
std::format("./commit/{}.html", commit.get_id().get_hex_string(22));
const auto ptree = commit.get_parentcount() > 0
? commit.get_parent().get_tree()
: git2wrap::tree(nullptr, nullptr);
git2wrap::diff_options opts;
git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION);
opts.flags = GIT_DIFF_DISABLE_PATHSPEC_MATCH | GIT_DIFF_IGNORE_SUBMODULES
| GIT_DIFF_INCLUDE_TYPECHANGE;
const auto stats =
git2wrap::diff::tree_to_tree(commit.get_tree(), ptree, &opts)
.get_stats();
static const int summary_limit = 50;
std::string summary = commit.get_summary();
if (summary.size() > summary_limit) {
summary.resize(summary_limit);
for (size_t i = summary.size() - 1; i >= summary.size() - 4; i--) {
summary[i] = '.';
}
}
for (const auto& commit : branch.get_commits()) {
const auto url = std::format("./commit/{}.html", commit.get_id());
ost << html::tr()
.add(html::td(long_to_string(commit.get_time())))
.add(html::td().add(html::a(summary).set("href", url)))
.add(html::td(commit.get_author().get_name()))
.add(html::td(std::to_string(stats.files_changed())))
.add(html::td(std::to_string(stats.insertions())))
.add(html::td(std::to_string(stats.deletions())));
.add(html::td(commit.get_time()))
.add(html::td().add(
html::a(commit.get_summary()).set("href", url)))
.add(html::td(commit.get_author_name()))
.add(html::td(commit.get_diff().get_files_changed()))
.add(html::td(commit.get_diff().get_insertions()))
.add(html::td(commit.get_diff().get_deletions()));
}
ost << html::tbody();
ost << html::table();
}
void write_repo_table(std::ostream& ost,
const std::vector<startgit::repository>& repos)
void write_repo_table_entry(std::ostream& ost, const startgit::repository& repo)
{
using namespace hemplate; // NOLINT
for (const auto& branch : repo.get_branches()) {
if (branch.get_name() != "master") {
continue;
}
const auto url = repo.get_name() + "/master/log.html";
ost << html::tr()
.add(html::td().add(html::a(repo.get_name()).set("href", url)))
.add(html::td(repo.get_description()))
.add(html::td(repo.get_owner()))
.add(html::td(branch.get_commits()[0].get_author_time()));
break;
}
}
void write_repo_table(std::ostream& ost, const std::stringstream& index)
{
using namespace hemplate; // NOLINT
@@ -262,30 +187,13 @@ void write_repo_table(std::ostream& ost,
ost << html::thead();
ost << html::tbody();
for (const auto& repo : repos) {
try {
const git2wrap::object obj = repo.get().revparse("master");
const git2wrap::commit commit = repo.get().commit_lookup(obj.get_id());
ost << html::tr()
.add(html::td().add(
html::a(repo.get_name())
.set("href", repo.get_name() + "/master/log.html")))
.add(html::td(repo.get_description()))
.add(html::td(repo.get_owner()))
.add(html::td(
long_to_string(commit.get_author().get_time().time)));
} catch (const git2wrap::error& error) {
std::cerr << std::format("Warning: {} no master branch\n",
repo.get_path().string());
}
}
ost << index.str();
ost << html::tbody();
ost << html::table();
}
void write_files_table(std::ostream& ost, const git2wrap::tree& tree)
void write_files_table(std::ostream& ost, const startgit::branch& branch)
{
using namespace hemplate; // NOLINT
@@ -298,32 +206,14 @@ void write_files_table(std::ostream& ost, const git2wrap::tree& tree)
ost << html::thead();
ost << html::tbody();
std::function<void(
std::ostream&, const git2wrap::tree&, const std::string& path)>
traverse = [&traverse](auto& l_ost, const auto& l_tree, const auto& path)
{
for (size_t i = 0; i < l_tree.get_entrycount(); i++) {
const auto entry = l_tree.get_entry(i);
const auto full_path =
(!path.empty() ? path + "/" : "") + entry.get_name();
switch (entry.get_type()) {
case GIT_OBJ_BLOB:
break;
case GIT_OBJ_TREE:
traverse(l_ost, entry.to_tree(), full_path);
continue;
default:
continue;
}
for (const auto& file : branch.get_files()) {
const auto url = std::format("./file/{}.html", file.get_path());
l_ost << html::tr()
.add(html::td(filemode((entry.get_filemode()))))
.add(html::td().add(html::a(full_path).set("href", "./")))
.add(html::td("0"));
}
};
traverse(ost, tree, "");
ost << html::tr()
.add(html::td(file.get_filemode()))
.add(html::td().add(html::a(file.get_path()).set("href", url)))
.add(html::td("0"));
}
ost << html::tbody();
ost << html::table();
@@ -347,14 +237,16 @@ void write_branch_table(std::ostream& ost,
ost << html::tbody();
for (const auto& branch : repo.get_branches()) {
const git2wrap::object obj = repo.get().revparse(branch.get_name().c_str());
const git2wrap::commit commit = repo.get().commit_lookup(obj.get_id());
const auto& last = branch.get_last_commit();
const auto url = branch.get_name() != branch_name
? std::format("../{}/refs.html", branch.get_name())
: "";
ost << html::tr()
.add(html::td(branch.get_name() == branch_name ? "*" : " "))
.add(html::td(branch.get_name()))
.add(html::td(long_to_string(commit.get_time())))
.add(html::td(commit.get_author().get_name()));
.add(html::td().add(html::a(branch.get_name()).set("href", url)))
.add(html::td(last.get_time()))
.add(html::td(last.get_author_name()));
}
ost << html::tbody();
@@ -376,182 +268,130 @@ void write_tag_table(std::ostream& ost, const startgit::repository& repo)
ost << html::thead();
ost << html::tbody();
struct payload_t
{
std::ostream& ost; // NOLINT
const startgit::repository& repo; // NOLINT
} payload(ost, repo);
auto callback = +[](const char*, git_oid* objid, void* payload_p)
{
auto l_payload = *reinterpret_cast<payload_t*>(payload_p); // NOLINT
const auto tag = l_payload.repo.get().tag_lookup(git2wrap::oid(objid));
l_payload.ost << html::tr()
.add(html::td(" "))
.add(html::td(tag.get_name()))
.add(html::td(
long_to_string(tag.get_tagger().get_time().time)))
.add(html::td(tag.get_tagger().get_name()));
return 0;
};
repo.get().tag_foreach(callback, &payload);
for (const auto& tag : repo.get_tags()) {
ost << html::tr()
.add(html::td(" "))
.add(html::td(tag.get_name()))
.add(html::td(tag.get_time()))
.add(html::td(tag.get_name()));
}
ost << html::tbody();
ost << html::table();
}
void write_file_changes(std::ostream& ost, const git2wrap::diff& diff)
void write_file_changes(std::ostream& ost, const startgit::diff& diff)
{
using namespace hemplate; // NOLINT
const auto file_cb =
+[](const git_diff_delta* delta, float /* progress */, void* payload)
{
auto& l_ost = *reinterpret_cast<std::ostream*>(payload); // NOLINT
ost << html::b("Diffstat:");
ost << html::table() << html::tbody();
for (const auto& delta : diff.get_deltas()) {
static const char* marker = " ADMRC T ";
const std::string filename = delta->new_file.path;
l_ost << html::tr()
.add(html::td(std::string(1, marker[delta->status])))
.add(html::td().add(
html::a(filename).set("href", "#" + filename)))
.add(html::td("|"))
.add(html::td("..."));
const std::string link = std::format("#{}", delta->new_file.path);
return 0;
};
ost << html::tr()
.add(html::td(std::string(1, marker[delta->status])))
.add(html::td().add(
html::a(delta->new_file.path).set("href", link)))
.add(html::td("|"))
.add(html::td("..."));
}
ost << html::b("Diffstat:");
ost << html::table() << html::tbody();
diff.foreach(file_cb, nullptr, nullptr, nullptr, &ost);
ost << html::tbody() << html::table();
/*
ost << html::pre(
std::format("{} files changed, {} insertions(+), {} deletions(-)"));
*/
ost << html::span(
std::format("{} files changed, {} insertions(+), {} deletions(-)",
diff.get_files_changed(),
diff.get_insertions(),
diff.get_deletions()));
}
void write_file_diffs(std::ostream& ost, const git2wrap::diff& diff)
void write_file_diffs(std::ostream& ost, const startgit::diff& diff)
{
using namespace hemplate; // NOLINT
const auto file_cb =
+[](const git_diff_delta* delta, float /* progress */, void* payload)
{
auto& l_ost = *reinterpret_cast<std::ostream*>(payload); // NOLINT
for (const auto& delta : diff.get_deltas()) {
const auto new_link = std::format("../file/{}.html", delta->new_file.path);
const auto old_link = std::format("../file/{}.html", delta->old_file.path);
l_ost << html::h3().set("id", delta->new_file.path);
l_ost << "diff --git";
l_ost << " a/" << html::a(delta->new_file.path).set("href", new_link);
l_ost << " b/" << html::a(delta->old_file.path).set("href", old_link);
l_ost << html::h3();
return 0;
};
ost << html::h3().set("id", delta->new_file.path);
ost << "diff --git";
ost << " a/" << html::a(delta->new_file.path).set("href", new_link);
ost << " b/" << html::a(delta->old_file.path).set("href", old_link);
ost << html::h3();
const auto hunk_cb = +[](const git_diff_delta* /* delta */,
const git_diff_hunk* hunk,
void* payload)
{
auto& l_ost = *reinterpret_cast<std::ostream*>(payload); // NOLINT
const std::string header(hunk->header); // NOLINT
for (const auto& hunk : delta.get_hunks()) {
const std::string header(hunk->header); // NOLINT
l_ost << html::h4();
l_ost << std::format("@@ -{},{} +{},{} @@ ",
ost << html::h4();
ost << std::format("@@ -{},{} +{},{} @@ ",
hunk->new_start,
hunk->new_lines,
hunk->old_start,
hunk->old_lines);
xmlencode(l_ost, header.substr(header.rfind('@') + 2));
l_ost << html::h4();
return 0;
};
startgit::xmlencode(ost, header.substr(header.rfind('@') + 2));
ost << html::h4();
for (const auto& line : hunk.get_lines()) {
if (line.get_origin() == '-') {
ost << html::span().set(
"style", "color: var(--theme_green); white-space: pre;");
} else if (line.get_origin() == '+') {
ost << html::span().set("style",
"color: var(--theme_red); white-space: pre;");
} else {
ost << html::span().set("style", "white-space: pre;");
}
const auto line_cb = +[](const git_diff_delta* /* delta */,
const git_diff_hunk* /* hunk */,
const git_diff_line* line,
void* payload)
{
auto& l_ost = *reinterpret_cast<std::ostream*>(payload); // NOLINT
if (line->origin == '-') {
l_ost << html::span().set("style",
"color: var(--theme_green); white-space: pre;");
} else if (line->origin == '+') {
l_ost << html::span().set("style",
"color: var(--theme_red); white-space: pre;");
} else {
l_ost << html::span().set("style", "white-space: pre;");
startgit::xmlencode(ost, line.get_content());
ost << html::span();
}
}
xmlencode(l_ost, std::string(line->content, line->content_len));
l_ost << html::span();
return 0;
};
diff.foreach(file_cb, nullptr, hunk_cb, line_cb, &ost);
}
}
void write_commit_diff(std::ostream& ost, const git2wrap::commit& commit)
void write_commit_diff(std::ostream& ost, const startgit::commit& commit)
{
using namespace hemplate; // NOLINT
const auto ptree = commit.get_parentcount() > 0
? commit.get_parent().get_tree()
: git2wrap::tree(nullptr, nullptr);
git2wrap::diff_options opts;
git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION);
opts.flags = GIT_DIFF_DISABLE_PATHSPEC_MATCH | GIT_DIFF_IGNORE_SUBMODULES
| GIT_DIFF_INCLUDE_TYPECHANGE;
const auto diff =
git2wrap::diff::tree_to_tree(commit.get_tree(), ptree, &opts);
ost << html::table() << html::tbody();
const std::string cid = commit.get_id().get_hex_string(22);
const auto url = std::format("../commit/{}.html", commit.get_id());
ost << html::tr()
.add(html::td().add(html::b("commit")))
.add(html::td().add(html::a(cid).set(
"href", std::format("../commit/{}.html", cid))));
.add(html::td().add(html::a(commit.get_id()).set("href", url)));
if (commit.get_parentcount() > 0) {
const std::string pid = commit.get_parent().get_id().get_hex_string(22);
const auto purl = std::format("../commit/{}.html", commit.get_parent_id());
ost << html::tr()
.add(html::td().add(html::b("parent")))
.add(html::td().add(html::a(pid).set(
"href", std::format("../commit/{}.html", pid))));
.add(html::td().add(
html::a(commit.get_parent_id()).set("href", purl)));
}
const auto mailto = std::string("mailto:") + commit.get_author_email();
ost << html::tr();
ost << html::td().add(html::b("author"));
ost << html::td() << commit.get_author().get_name() << " <";
ost << html::a(commit.get_author().get_email())
.set("href",
std::string("mailto:") + commit.get_author().get_email());
ost << html::td() << commit.get_author_name() << " <";
ost << html::a(commit.get_author_email()).set("href", mailto);
ost << ">" << html::td();
ost << html::tr();
ost << html::tr()
.add(html::td().add(html::b("date")))
.add(html::td(long_to_string(commit.get_author().get_time())));
.add(html::td(commit.get_author_time()));
ost << html::tbody() << html::table();
ost << html::br() << html::p().set("style", "white-space: pre;");
xmlencode(ost, commit.get_message());
startgit::xmlencode(ost, commit.get_message());
ost << html::p();
write_file_changes(ost, diff);
write_file_changes(ost, commit.get_diff());
ost << html::hr();
write_file_diffs(ost, diff);
write_file_diffs(ost, commit.get_diff());
}
void write_footer(std::ostream& ost)
@@ -599,18 +439,13 @@ void write_log(const std::filesystem::path& base,
{
std::ofstream ofs(base / "log.html");
git2wrap::revwalk rwalk(repo.get());
const git2wrap::object obj = repo.get().revparse(branch.get_name().c_str());
rwalk.push(obj.get_id());
write_header(ofs,
repo.get_name(),
branch.get_name(),
repo.get_owner(),
repo.get_description());
write_title(ofs, repo, branch.get_name());
write_commit_table(ofs, rwalk);
write_commit_table(ofs, branch);
write_footer(ofs);
}
@@ -620,16 +455,13 @@ void write_files(const std::filesystem::path& base,
{
std::ofstream ofs(base / "files.html");
const git2wrap::object obj = repo.get().revparse(branch.get_name().c_str());
const git2wrap::commit commit = repo.get().commit_lookup(obj.get_id());
write_header(ofs,
repo.get_name(),
branch.get_name(),
repo.get_owner(),
repo.get_description());
write_title(ofs, repo, branch.get_name());
write_files_table(ofs, commit.get_tree());
write_files_table(ofs, branch);
write_footer(ofs);
}
@@ -639,9 +471,6 @@ void write_refs(const std::filesystem::path& base,
{
std::ofstream ofs(base / "refs.html");
const git2wrap::object obj = repo.get().revparse(branch.get_name().c_str());
const git2wrap::commit commit = repo.get().commit_lookup(obj.get_id());
write_header(ofs,
repo.get_name(),
branch.get_name(),
@@ -657,14 +486,8 @@ void write_commits(const std::filesystem::path& base,
const startgit::repository& repo,
const startgit::branch& branch)
{
const git2wrap::object obj = repo.get().revparse(branch.get_name().c_str());
git2wrap::revwalk rwalk(repo.get());
rwalk.push(obj.get_id());
while (const auto commit = rwalk.next()) {
const auto hash = commit.get_id().get_hex_string(22);
const std::string filename = hash + ".html";
for (const auto& commit : branch.get_commits()) {
const std::string filename = commit.get_id() + ".html";
std::ofstream ofs(base / filename);
write_header(ofs,
@@ -726,21 +549,12 @@ int main(int argc, char* argv[])
return 1;
}
try {
const git2wrap::libgit2 libgit;
std::vector<startgit::repository> repos;
// open all repositories
for (const auto& repo_path : args.repos) {
try {
repos.emplace_back(repo_path);
} catch (const git2wrap::error& err) {
std::cerr << std::format("Warning: {} is not a repository\n",
repo_path.string());
}
}
const git2wrap::libgit2 libgit;
std::stringstream index;
for (const auto& repo : repos) {
for (const auto& repo_path : args.repos) {
try {
const startgit::repository repo(repo_path);
const std::filesystem::path base = args.output_dir / repo.get_name();
std::filesystem::create_directory(base);
@@ -757,20 +571,24 @@ int main(int argc, char* argv[])
write_commits(commit, repo, branch);
}
}
// Build repo index
std::ofstream ofs(args.output_dir / "index.html");
write_header(ofs,
"Git repository",
"~",
"Dimitrije Dobrota",
"Collection of all public git repositories");
write_repo_table(ofs, repos);
write_footer(ofs);
write_repo_table_entry(index, repo);
} catch (const git2wrap::error& err) {
std::cerr << std::format("Warning: {} is not a repository\n",
repo_path.string());
}
}
} catch (const git2wrap::error& err) {
std::ofstream ofs(args.output_dir / "index.html");
write_header(ofs,
"Git repository",
"~",
"Dimitrije Dobrota",
"Collection of all public git repositories");
write_repo_table(ofs, index);
write_footer(ofs);
/*
catch (const git2wrap::error& err) {
std::cerr << std::format("({}:{}) Error {}/{}: {}\n",
err.get_file(),
err.get_line(),
@@ -778,6 +596,7 @@ int main(int argc, char* argv[])
err.get_klass(),
err.get_message());
}
*/
return 0;
}
diff --git a/source/repository.cpp b/source/repository.cpp
@@ -14,12 +14,25 @@ repository::repository(const std::filesystem::path& path)
, m_owner(read_file(path, "owner"))
, m_description(read_file(path, "description"))
{
// Get branches
for (auto it = m_repo.branch_begin(GIT_BRANCH_LOCAL);
it != m_repo.branch_end();
++it)
{
m_branches.emplace_back(it->dup());
// const branch brnch(it->dup(), *this);
// m_branches.push_front(std::move(brnch));
m_branches.emplace_back(it->dup(), *this);
}
// Get tags
auto callback = +[](const char*, git_oid* objid, void* payload_p)
{
auto& repo = *reinterpret_cast<repository*>(payload_p); // NOLINT
repo.m_tags.emplace_back(repo.m_repo.tag_lookup(git2wrap::oid(objid)));
return 0;
};
m_repo.tag_foreach(callback, this);
}
std::string repository::read_file(const std::filesystem::path& base,
diff --git a/source/repository.hpp b/source/repository.hpp
@@ -7,6 +7,7 @@
#include <git2wrap/repository.hpp>
#include "branch.hpp"
#include "tag.hpp"
namespace startgit
{
@@ -15,6 +16,11 @@ class repository
{
public:
explicit repository(const std::filesystem::path& path);
repository(const repository&) = delete;
repository& operator=(const repository&) = delete;
repository(repository&&) = default;
repository& operator=(repository&&) = default;
~repository() = default;
const auto& get() const { return m_repo; }
const auto& get_path() const { return m_path; }
@@ -25,6 +31,7 @@ public:
const std::string& get_description() const { return m_description; }
const auto& get_branches() const { return m_branches; }
const auto& get_tags() const { return m_tags; }
private:
static std::string read_file(const std::filesystem::path& base,
@@ -40,6 +47,7 @@ private:
std::string m_description;
std::vector<branch> m_branches;
std::vector<tag> m_tags;
};
} // namespace startgit
diff --git a/source/tag.cpp b/source/tag.cpp
@@ -0,0 +1,15 @@
#include "tag.hpp"
#include "utils.hpp"
namespace startgit
{
tag::tag(const git2wrap::tag& tagg)
: m_name(tagg.get_name())
, m_author(tagg.get_tagger().get_name())
, m_time(long_to_string(tagg.get_tagger().get_time().time))
{
}
} // namespace startgit
diff --git a/source/tag.hpp b/source/tag.hpp
@@ -0,0 +1,25 @@
#pragma once
#include <string>
#include <git2wrap/tag.hpp>
namespace startgit
{
class tag
{
public:
explicit tag(const git2wrap::tag& tagg);
std::string get_name() const { return m_name; }
std::string get_author() const { return m_author; }
std::string get_time() const { return m_time; }
private:
std::string m_name;
std::string m_author;
std::string m_time;
};
} // namespace startgit
diff --git a/source/utils.cpp b/source/utils.cpp
@@ -0,0 +1,77 @@
#include <format>
#include <iomanip>
#include <sstream>
#include "utils.hpp"
#include <sys/stat.h>
namespace startgit
{
std::string long_to_string(int64_t date)
{
std::stringstream strs;
strs << std::put_time(std::gmtime(&date), "%Y-%m-%d %H:%M"); // NOLINT
return strs.str();
}
std::string long_to_string(const git2wrap::time& time)
{
std::stringstream strs;
strs << std::put_time(std::gmtime(&time.time), // NOLINT
"%a, %e %b %Y %H:%M:%S ");
strs << (time.offset < 0 ? '-' : '+');
strs << std::format("{:02}{:02}", time.offset / 60, time.offset % 60);
return strs.str();
}
// NOLINTBEGIN
// clang-format off
void xmlencode(std::ostream& ost, const std::string& str)
{
for (const char c: str) {
switch(c) {
case '<': ost << "<"; break;
case '>': ost << ">"; break;
case '\'': ost << "'"; break;
case '&': ost << "&"; break;
case '"': ost << """; break;
default: ost << c;
}
}
}
std::string filemode(git2wrap::filemode_t filemode)
{
std::string mode(10, '-');
if (S_ISREG(filemode)) mode[0] = '-';
else if (S_ISBLK(filemode)) mode[0] = 'b';
else if (S_ISCHR(filemode)) mode[0] = 'c';
else if (S_ISDIR(filemode)) mode[0] = 'd';
else if (S_ISFIFO(filemode)) mode[0] = 'p';
else if (S_ISLNK(filemode)) mode[0] = 'l';
else if (S_ISSOCK(filemode)) mode[0] = 's';
else mode[0] = '?';
if (filemode & S_IRUSR) mode[1] = 'r';
if (filemode & S_IWUSR) mode[2] = 'w';
if (filemode & S_IXUSR) mode[3] = 'x';
if (filemode & S_IRGRP) mode[4] = 'r';
if (filemode & S_IWGRP) mode[5] = 'w';
if (filemode & S_IXGRP) mode[6] = 'x';
if (filemode & S_IROTH) mode[7] = 'r';
if (filemode & S_IWOTH) mode[8] = 'w';
if (filemode & S_IXOTH) mode[9] = 'x';
if (filemode & S_ISUID) mode[3] = (mode[3] == 'x') ? 's' : 'S';
if (filemode & S_ISGID) mode[6] = (mode[6] == 'x') ? 's' : 'S';
if (filemode & S_ISVTX) mode[9] = (mode[9] == 'x') ? 't' : 'T';
return mode;
}
// clang-format on
// NOLINTEND
} // namespace startgit
diff --git a/source/utils.hpp b/source/utils.hpp
@@ -0,0 +1,16 @@
#pragma once
#include <ostream>
#include <string>
#include <git2wrap/types.hpp>
namespace startgit
{
std::string long_to_string(int64_t date);
std::string long_to_string(const git2wrap::time& time);
void xmlencode(std::ostream& ost, const std::string& str);
std::string filemode(git2wrap::filemode_t filemode);
} // namespace startgit