git repos / sago_web_git

src/git_repo.h

browsing at commit = 2aba3f2d16b6d85c2eea5ee133677e3b14a0f57b

raw · blame · history

#pragma once

#include <cstdint>
#include <ctime>
#include <stdexcept>
#include <string>
#include <vector>

struct git_repository;

// Thrown for any libgit2 failure or invalid lookup. Handlers translate this
// into an HTTP error response.
class GitError : public std::runtime_error {
public:
    explicit GitError(const std::string &what) : std::runtime_error(what) {}
};

struct TreeEntry {
    std::string name;     // last path component
    std::string path;     // full path relative to repo root
    std::string oid;      // hex object id
    bool is_dir = false;  // tree
    bool is_submodule = false;
    uint64_t size = 0;    // blob size in bytes (0 for non-blobs)
};

struct CommitInfo {
    std::string oid;
    std::string short_oid;
    std::string summary;
    std::string body; // message past the summary, "" when there is none
    std::string author_name;
    std::string author_email;
    std::time_t time = 0;
};

struct BlameLine {
    int number = 0;        // 1-based line number
    std::string short_oid; // commit that last touched the line
    std::string author;
    std::time_t time = 0;
    std::string content;   // the line text (without newline)
};

struct TagInfo {
    std::string name;
    std::string target_oid; // commit the tag resolves to
    std::string short_oid;
    std::time_t time = 0;
    bool annotated = false;
};

struct BranchInfo {
    std::string name;
    std::string target_oid; // commit the branch points at
    std::string short_oid;
    std::time_t time = 0;
    bool is_default = false; // true for the repository's HEAD branch
};

struct TagDetail {
    std::string name;
    std::string target_oid;
    std::string short_oid;
    bool annotated = false;
    std::string tagger_name;
    std::time_t time = 0;
    std::string message;
};

struct FileChange {
    char status = '?'; // A, M, D, R, C, T
    std::string path;
    std::string old_path; // set for renames/copies
};

struct DiffLine {
    std::string css;     // CSS class: d-add, d-del, d-hunk, d-file, d-ctx
    std::string content; // line text (with +/- prefix for code lines)
};

struct CommitDetail {
    std::string oid;
    std::string short_oid;
    std::string author_name;
    std::string author_email;
    std::time_t time = 0;
    std::string message;
    std::vector<std::string> parents;
    std::string first_parent; // "" for a root commit; used for "before" links
    std::vector<FileChange> files;
    std::vector<DiffLine> diff;
};

enum class ObjectKind { Blob, Tree, Commit, Tag, Unknown };

struct ObjectView {
    ObjectKind kind = ObjectKind::Unknown;
    std::string oid;
    std::string type_name;
    std::string body;             // textual summary or blob preview
    bool is_binary = false;
    std::vector<TreeEntry> entries; // populated for trees
};

// libgit2 process-wide init/shutdown. Construct exactly once in main().
class GitLibrary {
public:
    GitLibrary();
    ~GitLibrary();
    GitLibrary(const GitLibrary &) = delete;
    GitLibrary &operator=(const GitLibrary &) = delete;
};

// Wraps a single opened repository. Not thread safe: open one per request.
class GitRepo {
public:
    explicit GitRepo(const std::string &path);
    ~GitRepo();
    GitRepo(const GitRepo &) = delete;
    GitRepo &operator=(const GitRepo &) = delete;

    // Short name of the default branch (HEAD), e.g. "main".
    std::string default_branch() const;

    // URL of the "origin" remote, or "" when there is no such remote.
    std::string remote_url() const;

    // Resolve a revision to a full commit oid. Empty id means HEAD.
    std::string resolve_commit(const std::string &id) const;

    // Directory listing of `path` ("" = root) at the given commit.
    std::vector<TreeEntry> list_tree(const std::string &commit_oid,
                                     const std::string &path) const;

    // Read a blob located at `path` within the commit tree.
    // Returns false if no such path exists.
    bool read_file(const std::string &commit_oid, const std::string &path,
                   std::string &out, bool &is_binary) const;

    // First README* file at the root of the commit tree, or "" if none.
    std::string find_readme(const std::string &commit_oid) const;

    std::vector<BlameLine> blame(const std::string &commit_oid,
                                 const std::string &path) const;

    std::vector<CommitInfo> log(const std::string &start_oid, int limit) const;

    // All tags, sorted by name.
    std::vector<TagInfo> tags() const;

    // Names of tags whose target commit is `commit_oid`, sorted by name.
    std::vector<std::string> tags_for_commit(const std::string &commit_oid) const;

    // Details of a single tag by short name (e.g. "v1.0").
    TagDetail tag_detail(const std::string &name) const;

    // All local branches, sorted by name.
    std::vector<BranchInfo> branches() const;

    // Local branches whose history contains `commit_oid`, sorted by name.
    std::vector<BranchInfo> branches_for_commit(const std::string &commit_oid) const;

    // Commit metadata plus the diff against its first parent.
    CommitDetail commit_detail(const std::string &oid) const;

    // The commit's diff as a raw unified patch.
    std::string commit_patch(const std::string &oid) const;

    // Inspect an arbitrary object by (possibly abbreviated) oid.
    ObjectView inspect(const std::string &oid) const;

    // Writes a gzip-compressed tar archive of the commit's tree to `out_path`,
    // recursing into initialized submodules. `top_dir` is the directory placed
    // at the archive root (e.g. "repo_v1.0"). Throws GitError when a submodule
    // cannot be resolved or any other libgit2 lookup fails; the archive writer
    // throws ArchiveError on I/O failure.
    void write_archive(const std::string &commit_oid, const std::string &top_dir,
                       const std::string &out_path) const;

private:
    git_repository *repo_ = nullptr;
};