/* This file is part of FediBlock-backend. * Copyright © 2020 tastytea * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include "git.hpp" #include "files.hpp" #include "fs-compat.hpp" #include "json.hpp" #include #include #include #include #include #include #if LIBGIT2_VER_MAJOR == 0 # if LIBGIT2_VER_MINOR < 28 # define git_error_last giterr_last # endif # if LIBGIT2_VER_MINOR < 99 # define git_credential_ssh_key_new git_cred_ssh_key_new # define git_push_options_init git_push_init_options # endif #endif namespace FediBlock::git { using std::ofstream; using std::runtime_error; using std::stoull; using std::string; using std::string_view; using std::to_string; using std::uint64_t; git_repository *_repo{nullptr}; void check(int error) { if (error != 0) { const git_error *e = git_error_last(); throw runtime_error{e->message}; } } int cred_acquire(git_cred **cred, const char * /*url*/, const char *username_from_url, unsigned int /*allowed_types*/, void * /*payload*/) { return git_credential_ssh_key_new( cred, username_from_url, (files::get_datadir() / "ssh_id.pub").c_str(), (files::get_datadir() / "ssh_id").c_str(), nullptr); } void clone() { git_clone_options options = GIT_CLONE_OPTIONS_INIT; options.fetch_opts.callbacks.credentials = cred_acquire; check(git_clone(&_repo, "git@schlomp.space:FediBlock/data.git", (files::get_tmpdir() / "repo").c_str(), &options)); } uint64_t get_last_id() { constexpr string_view branch_prefix{"web-"}; uint64_t id{0}; git_branch_iterator *it; if (git_branch_iterator_new(&it, _repo, GIT_BRANCH_ALL) == 0) { git_reference *ref; git_branch_t type; while (git_branch_next(&ref, &type, it) == 0) { const char *out{nullptr}; git_branch_name(&out, ref); if (type == GIT_BRANCH_REMOTE) { const string branch_name{out}; size_t pos{branch_name.find(branch_prefix)}; if (pos != string::npos) { pos += branch_prefix.size(); uint64_t newid{stoull(branch_name.substr(pos))}; if (newid > id) { id = newid; } } } git_reference_free(ref); } git_branch_iterator_free(it); } return id; } void create_branch() { const auto id{get_last_id() + 1}; const string branch_name = "web-" + std::to_string(id); const string ref_name{"refs/heads/" + branch_name}; git_oid oid_parent; git_commit *commit; // Get SHA1 of HEAD. check(git_reference_name_to_id(&oid_parent, _repo, "HEAD")); // Translate SHA-1 to git_commit. check(git_commit_lookup(&commit, _repo, &oid_parent)); git_reference *branch; // Create new branch. check(git_branch_create(&branch, _repo, branch_name.c_str(), commit, 0)); check(git_repository_set_head(_repo, ref_name.c_str())); } void commit(const cgi::entry_type &entry) { // Write files. const auto id{get_last_id() + 1}; const string basename{files::get_tmpdir() / "repo" / ("web-" + to_string(id))}; ofstream file(basename + ".json"); if (!file.good()) { throw; // FIXME } file << json::to_json(entry); file.close(); if (!entry.screenshot_filepath.empty()) { const string extension{fs::path(entry.screenshot_filepath).extension()}; fs::copy(entry.screenshot_filepath, basename + extension); } // Add files. git_index *index{nullptr}; check(git_repository_index(&index, _repo)); check(git_index_add_all(index, nullptr, 0, nullptr, nullptr)); check(git_index_write(index)); // Create commit. git_signature *sig; git_oid oid_commit; git_oid oid_tree; git_oid oid_parent; git_tree *tree; git_object *parent{nullptr}; git_reference *ref{nullptr}; check(git_signature_now(&sig, "Web", "Don't @ me")); check(git_revparse_ext(&parent, &ref, _repo, "HEAD")); check(git_repository_index(&index, _repo)); check(git_index_write_tree(&oid_tree, index)); check(git_index_write(index)); check(git_tree_lookup(&tree, _repo, &oid_tree)); check(git_reference_name_to_id(&oid_parent, _repo, "HEAD")); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) check(git_commit_create_v(&oid_commit, _repo, "HEAD", sig, sig, nullptr, ("New entry: " + entry.instance).c_str(), tree, parent != nullptr ? 1 : 0, parent)); git_index_free(index); git_signature_free(sig); git_tree_free(tree); } void push() { git_push_options options; git_remote *remote{nullptr}; const string branch_name{"web-" + std::to_string(get_last_id() + 1)}; string refspec_str{("refs/heads/" + branch_name)}; char *refspec = &refspec_str[0]; const git_strarray refspecs = {&refspec, 1}; check(git_remote_lookup(&remote, _repo, "origin")); check(git_push_options_init(&options, GIT_PUSH_OPTIONS_VERSION)); options.callbacks.credentials = cred_acquire; check(git_remote_push(remote, &refspecs, &options)); } } // namespace FediBlock::git