mirror of
https://git.jami.net/savoirfairelinux/jami-daemon.git
synced 2025-08-12 22:09:25 +08:00
swarm: get changedFiles by a range of commits
Change-Id: I3710414acdff1eef447e308d6ae3827e27add04b
This commit is contained in:
@ -22,12 +22,17 @@
|
||||
#include "jamiaccount.h"
|
||||
#include "fileutils.h"
|
||||
#include "gittransport.h"
|
||||
#include "string_utils.h"
|
||||
|
||||
#include <opendht/rng.h>
|
||||
using random_device = dht::crypto::random_device;
|
||||
|
||||
#include <ctime>
|
||||
#include <fstream>
|
||||
#include <json/json.h>
|
||||
#include <regex>
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
constexpr auto DIFF_REGEX = " +\\| +[0-9]+.*"sv;
|
||||
|
||||
namespace jami {
|
||||
|
||||
@ -57,6 +62,12 @@ public:
|
||||
bool mergeFastforward(const git_oid* target_oid, int is_unborn);
|
||||
bool createMergeCommit(git_index* index, const std::string& wanted_ref);
|
||||
|
||||
bool add(const std::string& path);
|
||||
std::string commit(const std::string& msg);
|
||||
|
||||
GitDiff diff(const std::string& idNew, const std::string& idOld) const;
|
||||
std::string diffStats(const GitDiff& diff) const;
|
||||
|
||||
std::weak_ptr<JamiAccount> account_;
|
||||
const std::string id_;
|
||||
GitRepository repository_ {nullptr, git_repository_free};
|
||||
@ -76,8 +87,7 @@ create_empty_repository(const std::string& path)
|
||||
git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
|
||||
opts.flags |= GIT_REPOSITORY_INIT_MKPATH;
|
||||
opts.initial_head = "main";
|
||||
if (git_repository_init_ext(&repo, path.c_str(), &opts)
|
||||
< 0) {
|
||||
if (git_repository_init_ext(&repo, path.c_str(), &opts) < 0) {
|
||||
JAMI_ERR("Couldn't create a git repository in %s", path.c_str());
|
||||
}
|
||||
return {std::move(repo), git_repository_free};
|
||||
@ -286,8 +296,7 @@ ConversationRepository::Impl::signature()
|
||||
JAMI_ERR("Unable to create a commit signature.");
|
||||
return {nullptr, git_signature_free};
|
||||
}
|
||||
GitSignature sig {sig_ptr, git_signature_free};
|
||||
return std::move(sig);
|
||||
return {sig_ptr, git_signature_free};
|
||||
}
|
||||
|
||||
bool
|
||||
@ -478,6 +487,203 @@ ConversationRepository::Impl::mergeFastforward(const git_oid* target_oid, int is
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
ConversationRepository::Impl::add(const std::string& path)
|
||||
{
|
||||
if (!repository_)
|
||||
return false;
|
||||
git_index* index_ptr = nullptr;
|
||||
if (git_repository_index(&index_ptr, repository_.get()) < 0) {
|
||||
JAMI_ERR("Could not open repository index");
|
||||
return false;
|
||||
}
|
||||
GitIndex index {index_ptr, git_index_free};
|
||||
if (git_index_add_bypath(index.get(), path.c_str()) != 0) {
|
||||
const git_error* err = giterr_last();
|
||||
if (err)
|
||||
JAMI_ERR("Error when adding file: %s", err->message);
|
||||
return false;
|
||||
}
|
||||
return git_index_write(index.get()) == 0;
|
||||
}
|
||||
|
||||
std::string
|
||||
ConversationRepository::Impl::commit(const std::string& msg)
|
||||
{
|
||||
if (!repository_)
|
||||
return {};
|
||||
|
||||
auto account = account_.lock();
|
||||
if (!account)
|
||||
return {};
|
||||
auto deviceId = std::string(account->currentDeviceId());
|
||||
auto name = account->getDisplayName();
|
||||
if (name.empty())
|
||||
name = deviceId;
|
||||
|
||||
git_signature* sig_ptr = nullptr;
|
||||
// Sign commit's buffer
|
||||
if (git_signature_new(&sig_ptr, name.c_str(), deviceId.c_str(), std::time(nullptr), 0) < 0) {
|
||||
JAMI_ERR("Unable to create a commit signature.");
|
||||
return {};
|
||||
}
|
||||
GitSignature sig {sig_ptr, git_signature_free};
|
||||
|
||||
// Retrieve current HEAD
|
||||
git_oid commit_id;
|
||||
if (git_reference_name_to_id(&commit_id, repository_.get(), "HEAD") < 0) {
|
||||
JAMI_ERR("Cannot get reference for HEAD");
|
||||
return {};
|
||||
}
|
||||
|
||||
git_commit* head_ptr = nullptr;
|
||||
if (git_commit_lookup(&head_ptr, repository_.get(), &commit_id) < 0) {
|
||||
JAMI_ERR("Could not look up HEAD commit");
|
||||
return {};
|
||||
}
|
||||
GitCommit head_commit {head_ptr, git_commit_free};
|
||||
|
||||
git_tree* tree_ptr = nullptr;
|
||||
if (git_commit_tree(&tree_ptr, head_commit.get()) < 0) {
|
||||
JAMI_ERR("Could not look up initial tree");
|
||||
return {};
|
||||
}
|
||||
GitTree tree {tree_ptr, git_tree_free};
|
||||
|
||||
git_buf to_sign = {};
|
||||
const git_commit* head_ref[1] = {head_commit.get()};
|
||||
if (git_commit_create_buffer(&to_sign,
|
||||
repository_.get(),
|
||||
sig.get(),
|
||||
sig.get(),
|
||||
nullptr,
|
||||
msg.c_str(),
|
||||
tree.get(),
|
||||
1,
|
||||
&head_ref[0])
|
||||
< 0) {
|
||||
JAMI_ERR("Could not create commit buffer");
|
||||
return {};
|
||||
}
|
||||
|
||||
// git commit -S
|
||||
auto to_sign_vec = std::vector<uint8_t>(to_sign.ptr, to_sign.ptr + to_sign.size);
|
||||
auto signed_buf = account->identity().first->sign(to_sign_vec);
|
||||
std::string signed_str = base64::encode(signed_buf);
|
||||
if (git_commit_create_with_signature(&commit_id,
|
||||
repository_.get(),
|
||||
to_sign.ptr,
|
||||
signed_str.c_str(),
|
||||
"signature")
|
||||
< 0) {
|
||||
JAMI_ERR("Could not sign commit");
|
||||
return {};
|
||||
}
|
||||
|
||||
// Move commit to main branch
|
||||
git_reference* ref_ptr = nullptr;
|
||||
if (git_reference_create(&ref_ptr, repository_.get(), "refs/heads/main", &commit_id, true, nullptr)
|
||||
< 0) {
|
||||
JAMI_WARN("Could not move commit to main");
|
||||
}
|
||||
git_reference_free(ref_ptr);
|
||||
|
||||
auto commit_str = git_oid_tostr_s(&commit_id);
|
||||
if (commit_str) {
|
||||
JAMI_INFO("New message added with id: %s", commit_str);
|
||||
}
|
||||
return commit_str ? commit_str : "";
|
||||
}
|
||||
|
||||
GitDiff
|
||||
ConversationRepository::Impl::diff(const std::string& idNew, const std::string& idOld) const
|
||||
{
|
||||
if (!repository_)
|
||||
return {nullptr, git_diff_free};
|
||||
|
||||
// Retrieve tree for commit new
|
||||
git_oid oid;
|
||||
git_commit* commitNew = nullptr;
|
||||
if (idNew == "HEAD") {
|
||||
JAMI_ERR("@@@ HEAD");
|
||||
if (git_reference_name_to_id(&oid, repository_.get(), "HEAD") < 0) {
|
||||
JAMI_ERR("Cannot get reference for HEAD");
|
||||
return {nullptr, git_diff_free};
|
||||
}
|
||||
|
||||
if (git_commit_lookup(&commitNew, repository_.get(), &oid) < 0) {
|
||||
JAMI_ERR("Could not look up HEAD commit");
|
||||
return {nullptr, git_diff_free};
|
||||
}
|
||||
} else {
|
||||
if (git_oid_fromstr(&oid, idNew.c_str()) < 0
|
||||
|| git_commit_lookup(&commitNew, repository_.get(), &oid) < 0) {
|
||||
JAMI_WARN("Failed to look up commit %s", idNew.c_str());
|
||||
return {nullptr, git_diff_free};
|
||||
}
|
||||
}
|
||||
GitCommit new_commit = {commitNew, git_commit_free};
|
||||
|
||||
git_tree* tNew = nullptr;
|
||||
if (git_commit_tree(&tNew, new_commit.get()) < 0) {
|
||||
JAMI_ERR("Could not look up initial tree");
|
||||
return {nullptr, git_diff_free};
|
||||
}
|
||||
GitTree treeNew = {tNew, git_tree_free};
|
||||
|
||||
git_diff* diff_ptr = nullptr;
|
||||
if (idOld.empty()) {
|
||||
if (git_diff_tree_to_tree(&diff_ptr, repository_.get(), nullptr, treeNew.get(), {}) < 0) {
|
||||
JAMI_ERR("Could not get diff to empty repository");
|
||||
return {nullptr, git_diff_free};
|
||||
}
|
||||
return {diff_ptr, git_diff_free};
|
||||
}
|
||||
|
||||
// Retrieve tree for commit old
|
||||
git_commit* commitOld = nullptr;
|
||||
if (git_oid_fromstr(&oid, idOld.c_str()) < 0
|
||||
|| git_commit_lookup(&commitOld, repository_.get(), &oid) < 0) {
|
||||
JAMI_WARN("Failed to look up commit %s", idOld.c_str());
|
||||
return {nullptr, git_diff_free};
|
||||
}
|
||||
GitCommit old_commit {commitOld, git_commit_free};
|
||||
|
||||
git_tree* tOld = nullptr;
|
||||
if (git_commit_tree(&tOld, old_commit.get()) < 0) {
|
||||
JAMI_ERR("Could not look up initial tree");
|
||||
return {nullptr, git_diff_free};
|
||||
}
|
||||
GitTree treeOld = {tOld, git_tree_free};
|
||||
|
||||
// Calc diff
|
||||
if (git_diff_tree_to_tree(&diff_ptr, repository_.get(), treeOld.get(), treeNew.get(), {}) < 0) {
|
||||
JAMI_ERR("Could not get diff between %s and %s", idOld.c_str(), idNew.c_str());
|
||||
return {nullptr, git_diff_free};
|
||||
}
|
||||
return {diff_ptr, git_diff_free};
|
||||
}
|
||||
|
||||
std::string
|
||||
ConversationRepository::Impl::diffStats(const GitDiff& diff) const
|
||||
{
|
||||
git_diff_stats* stats_ptr = nullptr;
|
||||
if (git_diff_get_stats(&stats_ptr, diff.get()) < 0) {
|
||||
JAMI_ERR("Could not get diff stats");
|
||||
return {};
|
||||
}
|
||||
GitDiffStats stats = {stats_ptr, git_diff_stats_free};
|
||||
|
||||
git_diff_stats_format_t format = GIT_DIFF_STATS_FULL;
|
||||
git_buf statsBuf = {};
|
||||
if (git_diff_stats_to_buf(&statsBuf, stats.get(), format, 80) < 0) {
|
||||
JAMI_ERR("Could not format diff stats");
|
||||
return {};
|
||||
}
|
||||
|
||||
return std::string(statsBuf.ptr, statsBuf.ptr + statsBuf.size);
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
|
||||
std::unique_ptr<ConversationRepository>
|
||||
@ -923,4 +1129,28 @@ ConversationRepository::merge(const std::string& merge_id)
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string
|
||||
ConversationRepository::diffStats(const std::string& newId, const std::string& oldId) const
|
||||
{
|
||||
auto diff = pimpl_->diff(newId, oldId);
|
||||
if (!diff)
|
||||
return {};
|
||||
return pimpl_->diffStats(diff);
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
ConversationRepository::changedFiles(const std::string_view& diffStats)
|
||||
{
|
||||
std::string line;
|
||||
std::vector<std::string> changedFiles;
|
||||
for (auto line : split_string(diffStats, '\n')) {
|
||||
std::regex re(" +\\| +[0-9]+.*");
|
||||
std::svmatch match;
|
||||
if (!std::regex_search(line, match, re) && match.size() == 0)
|
||||
continue;
|
||||
changedFiles.emplace_back(std::regex_replace(std::string {line}, re, "").substr(1));
|
||||
}
|
||||
return changedFiles;
|
||||
}
|
||||
|
||||
} // namespace jami
|
||||
|
@ -143,6 +143,22 @@ public:
|
||||
*/
|
||||
bool merge(const std::string& merge_id);
|
||||
|
||||
/**
|
||||
* Get current diff stats between two commits
|
||||
* @param oldId Old commit
|
||||
* @param newId Recent commit (empty value will compare to the empty repository)
|
||||
* @note "HEAD" is also accepted as parameter for newId
|
||||
* @return diff stats
|
||||
*/
|
||||
std::string diffStats(const std::string& newId, const std::string& oldId = "") const;
|
||||
|
||||
/**
|
||||
* Get changed files from a git diff
|
||||
* @param diffStats The stats to analyze
|
||||
* @return get the changed files from a git diff
|
||||
*/
|
||||
static std::vector<std::string> changedFiles(const std::string_view& diffStats);
|
||||
|
||||
private:
|
||||
ConversationRepository() = delete;
|
||||
class Impl;
|
||||
|
@ -47,6 +47,13 @@ namespace test {
|
||||
class ConversationRepositoryTest : public CppUnit::TestFixture
|
||||
{
|
||||
public:
|
||||
ConversationRepositoryTest()
|
||||
{
|
||||
// Init daemon
|
||||
DRing::init(DRing::InitFlag(DRing::DRING_FLAG_DEBUG | DRing::DRING_FLAG_CONSOLE_LOG));
|
||||
if (not Manager::instance().initialized)
|
||||
CPPUNIT_ASSERT(DRing::start("dring-sample.yml"));
|
||||
}
|
||||
~ConversationRepositoryTest() { DRing::fini(); }
|
||||
static std::string name() { return "ConversationRepository"; }
|
||||
void setUp();
|
||||
@ -63,6 +70,7 @@ private:
|
||||
void testFetch();
|
||||
void testMerge();
|
||||
void testFFMerge();
|
||||
void testDiff();
|
||||
|
||||
std::string addCommit(git_repository* repo,
|
||||
const std::shared_ptr<JamiAccount> account,
|
||||
@ -80,6 +88,7 @@ private:
|
||||
CPPUNIT_TEST(testFetch);
|
||||
CPPUNIT_TEST(testMerge);
|
||||
CPPUNIT_TEST(testFFMerge);
|
||||
CPPUNIT_TEST(testDiff);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
};
|
||||
|
||||
@ -89,11 +98,6 @@ CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(ConversationRepositoryTest,
|
||||
void
|
||||
ConversationRepositoryTest::setUp()
|
||||
{
|
||||
// Init daemon
|
||||
DRing::init(DRing::InitFlag(DRing::DRING_FLAG_DEBUG | DRing::DRING_FLAG_CONSOLE_LOG));
|
||||
if (not Manager::instance().initialized)
|
||||
CPPUNIT_ASSERT(DRing::start("dring-sample.yml"));
|
||||
|
||||
std::map<std::string, std::string> details = DRing::getAccountTemplate("RING");
|
||||
details[ConfProperties::TYPE] = "RING";
|
||||
details[ConfProperties::DISPLAYNAME] = "ALICE";
|
||||
@ -673,6 +677,27 @@ ConversationRepositoryTest::testFFMerge()
|
||||
CPPUNIT_ASSERT(repository->log().size() == 3 /* Initial, commit 1, 2 */);
|
||||
}
|
||||
|
||||
void
|
||||
ConversationRepositoryTest::testDiff()
|
||||
{
|
||||
auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
|
||||
auto aliceDeviceId = aliceAccount->currentDeviceId();
|
||||
auto uri = aliceAccount->getUsername();
|
||||
auto repository = ConversationRepository::createConversation(aliceAccount->weak());
|
||||
|
||||
auto id1 = repository->sendMessage("Commit 1");
|
||||
auto id2 = repository->sendMessage("Commit 2");
|
||||
auto id3 = repository->sendMessage("Commit 3");
|
||||
|
||||
auto diff = repository->diffStats(id2, id1);
|
||||
CPPUNIT_ASSERT(ConversationRepository::changedFiles(diff).empty());
|
||||
diff = repository->diffStats(id1);
|
||||
auto changedFiles = ConversationRepository::changedFiles(diff);
|
||||
CPPUNIT_ASSERT(!changedFiles.empty());
|
||||
CPPUNIT_ASSERT(changedFiles[0] == "admins/" + uri + ".crt");
|
||||
CPPUNIT_ASSERT(changedFiles[1] == "devices/" + aliceDeviceId + ".crt");
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace jami
|
||||
|
||||
|
Reference in New Issue
Block a user