This repository has been archived on 2021-03-22. You can view files and clone it, but cannot push or open issues or pull requests.
backend/src/git.cpp

279 lines
7.4 KiB
C++
Raw Normal View History

2020-07-01 03:51:00 +02:00
/* This file is part of FediBlock-backend.
* Copyright © 2020 tastytea <tastytea@tastytea.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "git.hpp"
#include "config.hpp"
2020-07-01 03:51:00 +02:00
#include "files.hpp"
2020-07-01 08:48:36 +02:00
#include "fs-compat.hpp"
#include "gitea.hpp"
2020-07-01 08:48:36 +02:00
#include "json.hpp"
2020-07-01 03:51:00 +02:00
#include <fmt/format.h>
2020-10-28 11:16:00 +01:00
#include <fmt/ostream.h> // For compatibility with fmt 4.
2020-07-01 08:48:26 +02:00
#include <git2.h>
2020-07-01 03:51:00 +02:00
#include <chrono>
#include <cstdint>
#include <fstream>
2020-07-01 03:51:00 +02:00
#include <stdexcept>
#include <string>
#include <string_view>
#include <thread>
2020-07-01 03:51:00 +02:00
2020-07-01 09:22:36 +02:00
#if LIBGIT2_VER_MAJOR == 0
# if LIBGIT2_VER_MINOR < 28
# define git_error_last giterr_last
# endif
2020-07-01 11:25:43 +02:00
# 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
2020-10-14 04:17:46 +02:00
# define git_fetch_options_init git_fetch_init_options
# define git_checkout_options_init git_checkout_init_options
2020-07-01 11:25:43 +02:00
# endif
2020-07-01 09:22:36 +02:00
#endif
2020-07-01 08:50:16 +02:00
namespace FediBlock::git
2020-07-01 03:51:00 +02:00
{
using fmt::format;
2020-07-01 08:48:36 +02:00
using std::ofstream;
2020-07-01 03:51:00 +02:00
using std::runtime_error;
using std::string;
using std::string_view;
2020-07-01 08:48:36 +02:00
using std::to_string;
using std::uint8_t;
using std::this_thread::sleep_for;
using namespace std::chrono_literals;
2020-07-01 03:51:00 +02:00
2020-07-01 08:48:26 +02:00
git_repository *_repo{nullptr};
const string _clone_url{format("git@{:s}:{:s}/{:s}.git", config::forge_domain,
config::forge_org, config::forge_repo_data)};
fs::path _repo_dir{};
void init(const bool cache)
{
if (cache)
{
_repo_dir = files::get_cachedir() / "repo";
}
else
{
_repo_dir = files::get_tmpdir() / "repo";
}
git_libgit2_init();
}
void cleanup(const bool cache)
{
git_libgit2_shutdown();
if (cache)
{
files::remove_lockfile();
}
else
{
files::remove_tmpdir();
}
}
2020-07-01 03:51:00 +02:00
void check(int error)
2020-07-01 03:51:00 +02:00
{
2020-07-01 04:38:08 +02:00
if (error != 0)
2020-07-01 03:51:00 +02:00
{
const git_error *e = git_error_last();
throw runtime_error{e->message};
}
}
2020-07-01 10:18:53 +02:00
int cred_acquire(git_cred **cred, const char * /*url*/,
const char *username_from_url, unsigned int /*allowed_types*/,
void * /*payload*/)
{
const auto datadir{files::get_datadir()};
return git_credential_ssh_key_new(cred, username_from_url,
(datadir / "ssh_id.pub").c_str(),
(datadir / "ssh_id").c_str(), nullptr);
2020-07-01 10:18:53 +02:00
}
void clone()
2020-07-01 04:38:08 +02:00
{
2020-07-01 10:18:53 +02:00
git_clone_options options = GIT_CLONE_OPTIONS_INIT;
options.fetch_opts.callbacks.credentials = cred_acquire;
check(git_clone(&_repo, _clone_url.data(), _repo_dir.c_str(), &options));
2020-07-01 04:38:08 +02:00
}
void create_branch()
2020-07-01 04:38:08 +02:00
{
const string branch_name{get_branch_name()};
const string ref_name{"refs/heads/" + branch_name};
2020-07-01 04:38:08 +02:00
git_oid oid_parent;
git_commit *commit{nullptr};
2020-07-01 04:38:08 +02:00
// Get SHA1 of HEAD.
check(git_reference_name_to_id(&oid_parent, _repo, "HEAD"));
2020-07-01 04:38:08 +02:00
// Translate SHA-1 to git_commit.
check(git_commit_lookup(&commit, _repo, &oid_parent));
2020-07-01 04:38:08 +02:00
git_reference *branch{nullptr};
2020-07-01 04:38:08 +02:00
// 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()));
2020-07-01 04:38:08 +02:00
}
2020-07-02 07:13:57 +02:00
void commit(const entry_type &entry)
2020-07-01 08:48:36 +02:00
{
// Write files.
const string basename{_repo_dir / get_branch_name()};
2020-07-01 08:48:36 +02:00
ofstream file(basename + ".json");
if (!file.good())
{
throw runtime_error{
format("Could not create file: {:s}.json", basename)};
2020-07-01 08:48:36 +02:00
}
2020-07-01 20:51:35 +02:00
file << json::to_json(entry);
2020-07-01 08:48:36 +02:00
file.close();
if (!entry.screenshot_filepaths.empty())
2020-07-01 08:48:36 +02:00
{
uint8_t counter{0};
for (const auto &screenshot : entry.screenshot_filepaths)
{
++counter;
const string extension{fs::path(screenshot).extension()};
fs::copy(screenshot,
basename + "-" += to_string(counter) += extension);
}
2020-07-01 08:48:36 +02:00
}
// Add files.
git_index *index{nullptr};
check(git_repository_index(&index, _repo));
2020-07-01 08:48:36 +02:00
check(git_index_add_all(index, nullptr, 0, nullptr, nullptr));
2020-07-01 08:48:36 +02:00
check(git_index_write(index));
2020-07-01 08:48:36 +02:00
// Create commit.
git_signature *sig{nullptr};
2020-07-01 08:48:36 +02:00
git_oid oid_commit;
git_oid oid_tree;
git_oid oid_parent;
git_tree *tree{nullptr};
2020-07-01 08:48:36 +02:00
git_object *parent{nullptr};
git_reference *ref{nullptr};
check(git_signature_now(&sig, "Web", "Don't @ me"));
2020-07-01 08:48:36 +02:00
check(git_revparse_ext(&parent, &ref, _repo, "HEAD"));
2020-07-01 08:48:36 +02:00
check(git_repository_index(&index, _repo));
2020-07-01 08:48:36 +02:00
check(git_index_write_tree(&oid_tree, index));
2020-07-01 08:48:36 +02:00
check(git_index_write(index));
2020-07-01 08:48:36 +02:00
check(git_tree_lookup(&tree, _repo, &oid_tree));
2020-07-01 08:48:36 +02:00
check(git_reference_name_to_id(&oid_parent, _repo, "HEAD"));
2020-07-01 08:48:36 +02:00
// 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));
2020-07-01 08:48:36 +02:00
git_index_free(index);
git_signature_free(sig);
git_tree_free(tree);
git_object_free(parent);
git_reference_free(ref);
2020-07-01 08:48:36 +02:00
}
2020-07-01 10:24:38 +02:00
void push()
{
git_push_options options;
git_remote *remote{nullptr};
string refspec_str{("refs/heads/" + get_branch_name())};
2020-07-01 10:24:38 +02:00
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));
2020-07-01 10:48:12 +02:00
options.callbacks.credentials = cred_acquire;
2020-07-01 10:24:38 +02:00
check(git_remote_push(remote, &refspecs, &options));
git_remote_free(remote);
}
string get_branch_name()
{
const auto id{gitea::get_last_pr_number() + 1};
return "web-" + to_string(id);
2020-07-01 10:24:38 +02:00
}
void update_cached_repo(const uint8_t timeout)
{
bool lock_created{false};
for (uint8_t counter{0}; counter < timeout; ++counter)
{
if ((lock_created = files::create_lockfile()))
{
break;
}
sleep_for(1s);
}
if (!lock_created)
{
throw runtime_error{"Repository was locked for too long, giving up."};
}
if (!fs::exists(_repo_dir / ".git"))
{
clone();
return;
}
git_remote *remote{nullptr};
check(git_repository_open(&_repo, _repo_dir.c_str()));
check(git_remote_lookup(&remote, _repo, "origin"));
git_fetch_options fetch_opts;
check(git_fetch_options_init(&fetch_opts, GIT_FETCH_OPTIONS_VERSION));
fetch_opts.callbacks.credentials = cred_acquire;
check(git_remote_fetch(remote, nullptr, &fetch_opts, nullptr));
// FIXME: HEAD is detached.
git_checkout_options checkout_opts;
check(git_checkout_options_init(&checkout_opts,
GIT_CHECKOUT_OPTIONS_VERSION));
checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE;
check(git_repository_set_head(_repo, "refs/remotes/origin/main"));
check(git_checkout_head(_repo, &checkout_opts));
git_remote_free(remote);
}
2020-07-01 08:50:16 +02:00
} // namespace FediBlock::git