swarm: get changedFiles by a range of commits

Change-Id: I3710414acdff1eef447e308d6ae3827e27add04b
This commit is contained in:
Sébastien Blin
2020-02-06 13:36:30 -05:00
parent 0c6540ba2e
commit 3ef9cabae7
3 changed files with 281 additions and 10 deletions

View File

@ -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

View File

@ -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;

View File

@ -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