33#include <opendht/crypto.h>
36#include <git2/buffer.h>
37#include <git2/commit.h>
38#include <git2/deprecated.h>
40#include <git2/object.h>
41#include <git2/indexer.h>
42#include <git2/remote.h>
43#include <git2/merge.h>
59using namespace std::string_view_literals;
66bool ConversationRepository::DISABLE_RESET =
false;
67bool ConversationRepository::FETCH_FROM_LOCAL_REPOS =
false;
72inline std::string_view
77inline std::string_view
86 Impl(
const std::shared_ptr<JamiAccount>&
account,
const std::string&
id)
106 throw std::logic_error(
"Invalid git repository");
113 JAMI_WARNING(
"[Account {}] [Conversation {}] Conversation is locked, removing lock {}",
119 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to remove lock {}: {}",
126 auto refPath = std::filesystem::path(
repoPath /
"refs" /
"heads" /
"main.lock");
127 if (std::filesystem::exists(
refPath)) {
128 JAMI_WARNING(
"[Account {}] [Conversation {}] Conversation is locked, removing lock {}",
134 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to remove lock {}: {}",
144 if (std::filesystem::exists(
refPath,
ec)) {
145 JAMI_WARNING(
"[Account {}] [Conversation {}] Conversation is locked for remote {}, removing lock",
148 fileIt.path().filename());
151 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to remove lock {}: {}",
161 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to clean up the repository: {}",
174 msgpack::object_handle
oh = msgpack::unpack((
const char*)
file.data(),
file.size());
177 }
catch (
const std::exception&
e) {
216 auto name = shared->getDisplayName();
262 bool add(
const std::string& path);
276 std::vector<ConversationCommit>
behind(
const std::string& from)
const;
280 const std::string& from =
"",
281 bool logIfNotFound =
true)
const;
291 bool resolveBan(
const std::string_view type,
const std::string& uri);
292 bool resolveUnban(
const std::string_view type,
const std::string& uri);
299 mutable std::optional<ConversationMode>
mode_ {};
305 std::vector<ConversationMember>
members()
const
331 if (!acc->certStore().getCertificate(
issuerUid)) {
335 auto parentCert = std::make_shared<dht::crypto::Certificate>(dhtnet::fileutils::loadFile(
341 if (!acc->certStore().getCertificate(
cert->getPublicKey().getLongId().toString())) {
342 acc->certStore().pinCertificate(
cert,
347 }
catch (
const std::exception&) {
394 std::set<std::string>
ret;
427 auto cert = acc->certStore().getCertificate(deviceId);
441 / fmt::format(
"{}.crt", deviceId);
442 if (!std::filesystem::is_regular_file(
deviceFile))
446 }
catch (
const std::exception&) {
473 auto deviceFile = fmt::format(
"devices/{}.crt", deviceId);
476 JAMI_ERROR(
"{} announced but not found", deviceId);
510 JAMI_ERROR(
"Device certificate with a bad issuer {}",
cert.getId().toString());
515 JAMI_ERROR(
"Certificate with a bad ID {}",
cert.getId().toString());
519 JAMI_ERROR(
"Certificate with a bad ID {}",
cert.getId().toString());
530 JAMI_ERROR(
"Device certificate with a bad issuer {}",
cert.getId().toString());
534 JAMI_ERROR(
"Certificate with a bad ID {}",
cert.getId().toString());
558 opts.initial_head =
"main";
560 JAMI_ERROR(
"Unable to create a git repository in {}", path);
576 JAMI_ERROR(
"Unable to open repository index");
595 const std::shared_ptr<JamiAccount>&
account,
599 auto deviceId =
account->currentDeviceId();
606 if (!dhtnet::fileutils::recursive_mkdir(
adminsPath, 0700)) {
621 std::ofstream
file(
adminPath, std::ios::trunc | std::ios::binary);
622 if (!
file.is_open()) {
629 if (!dhtnet::fileutils::recursive_mkdir(
devicesPath, 0700)) {
636 file = std::ofstream(
devicePath, std::ios::trunc | std::ios::binary);
637 if (!
file.is_open()) {
644 if (!dhtnet::fileutils::recursive_mkdir(
crlsPath, 0700)) {
650 for (
const auto&
crl :
account->identity().second->getRevocationLists()) {
654 std::ofstream
file(
crlPath, std::ios::trunc | std::ios::binary);
655 if (!
file.is_open()) {
665 if (!dhtnet::fileutils::recursive_mkdir(
invitedPath, 0700)) {
676 if (!
file.is_open()) {
700 const std::shared_ptr<JamiAccount>&
account,
704 auto deviceId = std::string(
account->currentDeviceId());
705 auto name =
account->getDisplayName();
718 JAMI_ERROR(
"Unable to create a commit signature.");
725 JAMI_ERROR(
"Unable to open the repository index");
731 JAMI_ERROR(
"Unable to write initial tree from index");
736 JAMI_ERROR(
"Unable to look up the initial tree");
742 json[
"mode"] =
static_cast<int>(mode);
746 json[
"type"] =
"initial";
752 JAMI_ERROR(
"Unable to create initial buffer");
761 JAMI_ERROR(
"Unable to sign the initial commit");
788 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to create a commit signature: no name set",
accountId_,
id_);
808 if (!validateDevice()) {
809 JAMI_ERROR(
"[Account {}] [Conversation {}] Invalid device. Not migrated?", accountId_, id_);
814 auto repo = repository();
816 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to get HEAD reference", accountId_, id_);
842 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to peel HEAD reference", accountId_, id_);
852 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to look up commit {}", accountId_, id_,
wanted_ref);
857 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to look up commit {}", accountId_, id_,
wanted_ref);
868 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to write index: {}", accountId_, id_,
err->message);
872 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to look up tree", accountId_, id_);
882#if LIBGIT2_VER_MAJOR == 1 && LIBGIT2_VER_MINOR == 8 \
883 && (LIBGIT2_VER_REVISION == 0 || LIBGIT2_VER_REVISION == 1 || LIBGIT2_VER_REVISION == 3)
893 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to create commit buffer: {}",
900 auto account = account_.lock();
910 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to sign commit", accountId_, id_);
917 JAMI_LOG(
"[Account {}] [Conversation {}] New merge commit added with id: {}", accountId_, id_,
commit_str);
923 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to move commit to main: {}",
941 JAMI_ERROR(
"[Account {}] [Conversation {}] failed to look up OID {}: {}",
960 auto repo = repository();
962 JAMI_ERROR(
"[Account {}] [Conversation {}] No repository found", accountId_, id_);
969 JAMI_ERROR(
"[Account {}] [Conversation {}] failed to look up HEAD ref", accountId_, id_);
981 JAMI_ERROR(
"[Account {}] [Conversation {}] failed to create main reference: {}",
990 JAMI_ERROR(
"[Account {}] [Conversation {}] failed to get HEAD reference", accountId_, id_);
998 JAMI_ERROR(
"[Account {}] [Conversation {}] failed to look up OID {}",
1012 JAMI_ERROR(
"[Account {}] [Conversation {}] failed to checkout HEAD reference: {}",
1017 JAMI_ERROR(
"[Account {}] [Conversation {}] failed to checkout HEAD reference: unknown error",
1026 JAMI_ERROR(
"[Account {}] [Conversation {}] failed to move HEAD reference", accountId_, id_);
1037 auto repo = repository();
1042 JAMI_ERROR(
"Unable to open repository index");
1061 auto repo = repository();
1120 auto repo = repository();
1146 if (
editedCommitMap->at(
"type") ==
"application/data-transfer+json") {
1188 auto repo = repository();
1208 static const std::regex
regex_votes(
"votes.(\\w+).(members|devices|admins|invited).(\\w+).(\\w+)");
1221 std::string_view type = svsub_match_view(
base_match[2]);
1245 if (type !=
"devices") {
1281 auto repo = repository();
1351 std::string
bannedFile = fmt::format(
"{}/{}/{}.crt",
1383 auto repo = repository();
1444 auto repo = repository();
1461 static const std::regex
regex_devices(
"devices.(\\w+)\\.crt");
1471 JAMI_ERROR(
"Unwanted changed file detected: {}",
f);
1504 auto repo = repository();
1518 std::vector<std::string>
voters;
1523 const std::regex
regex_votes(
"votes." +
voteType +
".(members|devices|admins|invited).(\\w+).(\\w+)");
1524 static const std::regex
regex_devices(
"devices.(\\w+)\\.crt");
1525 static const std::regex
regex_banned(
"banned.(members|devices|admins).(\\w+)\\.crt");
1550 JAMI_ERROR(
"Invalid banned file detected : {}",
f);
1554 JAMI_ERROR(
"Unwanted changed file detected: {}",
f);
1596 for (
const auto& certificate : dhtnet::fileutils::readDirectory(
repoPath +
"admins")) {
1597 if (certificate.find(
".crt") == std::string::npos) {
1602 auto adminUri = certificate.substr(0, certificate.size() - std::string(
".crt").size());
1623 auto repo = repository();
1657 if (
f ==
"profile.vcf") {
1671 JAMI_ERROR(
"Unwanted changed file detected: {}",
f);
1683std::optional<std::set<std::string_view>>
1690 JAMI_LOG(
"[Account {}] [Conversation {}] Index of delta out of range!", accountId_, id_);
1691 return std::nullopt;
1708 const std::vector<std::string>& parents)
const
1711 auto repo = repository();
1716 if (
static_cast<int>(parents.size()) != 2)
1733 JAMI_ERROR(
"[Account {}] [Conversation {}] Failed to git diff of merge and first parent "
1753 JAMI_ERROR(
"[Account {}] [Conversation {}] Failed to git diff of merge and second parent "
1767 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to get deltas from diffs for merge commit {}",
1796 auto acc = account_.lock();
1801 auto repo = repository();
1874 acc->certStore().pinCertificate(std::move(
deviceCert));
1875 acc->certStore().pinCertificate(std::move(
parentCert));
1885 auto account = account_.lock();
1886 auto repo = repository();
1957 auto repo = repository();
1958 auto account = account_.lock();
1960 JAMI_WARNING(
"[Account {}] [Conversation {}] Invalid repository detected", accountId_, id_);
1963 auto path = fmt::format(
"devices/{}.crt", deviceId_);
1966 if (!std::filesystem::is_regular_file(
devicePath)) {
1975 }
catch (
const std::exception&) {
1980 "[Account {}] [Conversation {}] Device certificate is no longer valid. Attempting to update certificate.",
1986 JAMI_ERROR(
"[Account {}] [Conversation {}] Current device's certificate is invalid. A migration is needed",
1991 std::ofstream
file(
devicePath, std::ios::trunc | std::ios::binary);
1992 if (!
file.is_open()) {
1993 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to write data to {}", accountId_, id_,
devicePath);
1999 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to add file {}", accountId_, id_,
devicePath);
2005 auto adminPath = fmt::format(
"admins/{}.crt", userId_);
2006 auto memberPath = fmt::format(
"members/{}.crt", userId_);
2015 JAMI_ERROR(
"[Account {}] [Conversation {}] Invalid parent path (not in members or admins)", accountId_, id_);
2022 }
catch (
const std::exception&) {
2027 "[Account {}] [Conversation {}] Account certificate is no longer valid. Attempting to update certificate.",
2033 std::ofstream
file(
parentPath, std::ios::trunc | std::ios::binary);
2034 if (!
file.is_open()) {
2035 JAMI_ERROR(
"Unable to write data to {}", path);
2054 JAMI_ERROR(
"[Account {}] [Conversation {}] commit failed: Invalid device", accountId_, id_);
2059 JAMI_ERROR(
"[Account {}] [Conversation {}] commit failed: Unable to generate signature", accountId_, id_);
2062 auto account = account_.lock();
2066 auto repo = repository();
2070 JAMI_ERROR(
"[Account {}] [Conversation {}] commit failed: Unable to open repository index", accountId_, id_);
2077 JAMI_ERROR(
"[Account {}] [Conversation {}] commit failed: Unable to write initial tree from index",
2085 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to look up initial tree", accountId_, id_);
2092 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to get reference for HEAD", accountId_, id_);
2098 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to look up HEAD commit", accountId_, id_);
2107#if LIBGIT2_VER_MAJOR == 1 && LIBGIT2_VER_MINOR == 8 \
2108 && (LIBGIT2_VER_REVISION == 0 || LIBGIT2_VER_REVISION == 1 || LIBGIT2_VER_REVISION == 3)
2116 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to create commit buffer", accountId_, id_);
2125 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to sign commit", accountId_, id_);
2136 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to move commit to main: {}",
2148 JAMI_LOG(
"[Account {}] [Conversation {}] New message added with id: {}", accountId_, id_,
commit_str);
2157 if (mode_ != std::nullopt)
2163 throw std::logic_error(
"Unable to retrieve first commit");
2170 throw std::logic_error(
"Unable to retrieve first commit");
2172 if (!
root.isMember(
"mode")) {
2174 throw std::logic_error(
"No mode detected for initial commit");
2195 "Incorrect mode detected");
2196 throw std::logic_error(
"Incorrect mode detected");
2204 if (
auto repo = repository()) {
2215 JAMI_ERROR(
"Unable to get reference for HEAD");
2222 if (
idNew ==
"HEAD") {
2224 JAMI_ERROR(
"Unable to get reference for HEAD");
2243 JAMI_ERROR(
"Unable to look up initial tree");
2249 if (
idOld.empty()) {
2251 JAMI_ERROR(
"Unable to get diff to empty repository");
2267 JAMI_ERROR(
"Unable to look up initial tree");
2280std::vector<ConversationCommit>
2284 auto repo = repository();
2288 JAMI_ERROR(
"Unable to get reference for HEAD");
2294 JAMI_ERROR(
"Unable to get reference for commit {}", from);
2300 JAMI_ERROR(
"Unable to get any merge base for commit {} and {}", from,
head);
2303 for (std::size_t
i = 0;
i <
bases.count; ++
i) {
2321 const std::string& from,
2322 bool logIfNotFound)
const
2327 auto repo = repository();
2329 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to get reference for HEAD", accountId_, id_);
2348 JAMI_DEBUG(
"[Account {}] [Conversation {}] Unable to init revwalker from {}", accountId_, id_, from);
2359 JAMI_WARNING(
"[Account {}] [Conversation {}] Failed to look up commit {}", accountId_, id_,
id);
2380std::vector<ConversationCommit>
2383 std::vector<ConversationCommit>
commits {};
2387 [&](
const auto&
id,
const auto& author,
const auto& commit) {
2390 commits.rbegin()->linearized_parent = id;
2393 return CallbackResult::Skip;
2412 if (
options.authorUri !=
"") {
2424 [&](
auto&& cc) {
commits.emplace(
commits.end(), std::forward<decltype(cc)>(cc)); },
2425 [](
auto,
auto,
auto) {
return false; },
2457 JAMI_WARNING(
"[Account {}] [Conversation {}] Failed to look up commit {}", accountId_, id_,
commitId);
2463 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to look up initial tree", accountId_, id_);
2469std::vector<std::string>
2472 auto acc = account_.lock();
2509 auto repo = repository();
2511 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to get reference for HEAD", accountId_, id_);
2566 using std::filesystem::path;
2567 auto repo = repository();
2569 throw std::logic_error(
"Invalid Git repository");
2571 std::vector<std::string>
uris;
2572 std::lock_guard
lk(membersMtx_);
2585 for (
const auto& [role, p] :
paths) {
2586 for (
const auto&
f : std::filesystem::directory_iterator(
repoPath / p,
ec)) {
2587 auto uri =
f.path().stem().string();
2588 if (std::find(
uris.begin(),
uris.end(), uri) ==
uris.end()) {
2590 uris.emplace_back(uri);
2606std::optional<std::map<std::string, std::string>>
2611 JAMI_ERROR(
"[Account {}] [Conversation {}] Invalid author ID for commit {}", accountId_, id_, commit.
id);
2612 return std::nullopt;
2614 std::string parents;
2621 std::string type {};
2624 std::string body {};
2625 std::map<std::string, std::string> message;
2629 for (
auto const&
id :
cm.getMemberNames()) {
2631 type =
cm[
id].asString();
2634 message.insert({
id,
cm[
id].asString()});
2639 return std::nullopt;
2640 }
else if (type ==
"application/data-transfer+json") {
2642 auto tid = message[
"tid"];
2644 message[
"fileId"] =
getFileId(commit.
id,
tid, message[
"displayName"]);
2646 message[
"fileId"] =
"";
2649 message[
"id"] = commit.
id;
2650 message[
"parents"] = parents;
2653 message[
"type"] = type;
2654 message[
"timestamp"] = std::to_string(commit.
timestamp);
2664 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to get diff stats", accountId_, id_);
2672 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to format diff stats", accountId_, id_);
2698 std::vector<std::string> parents;
2709 JAMI_WARNING(
"[Account {}] [Conversation {}] Unable to extract signature for commit {}",
2725std::unique_ptr<ConversationRepository>
2731 std::uniform_int_distribution<uint64_t>
dist;
2735 if (std::filesystem::is_directory(
tmpPath)) {
2739 if (!dhtnet::fileutils::recursive_mkdir(
tmpPath, 0700)) {
2740 JAMI_ERROR(
"An error occurred when creating {}. Abort create conversations.",
tmpPath);
2750 JAMI_ERROR(
"An error occurred while adding the initial files.");
2751 dhtnet::fileutils::removeAll(
tmpPath,
true);
2759 dhtnet::fileutils::removeAll(
tmpPath,
true);
2769 dhtnet::fileutils::removeAll(
tmpPath,
true);
2775 return std::make_unique<ConversationRepository>(
account,
id);
2778std::pair<std::unique_ptr<ConversationRepository>, std::vector<ConversationCommit>>
2780 const std::string& deviceId,
2781 const std::string& conversationId)
2784 if (conversationId.empty()) {
2785 JAMI_ERROR(
"[Account {}] Clone conversation with empty conversationId",
account->getAccountID());
2792 auto url = fmt::format(
"git://{}/{}", deviceId, conversationId);
2795 url = fmt::format(
"file://{}",
2810 JAMI_ERROR(
"Abort fetching repository, the fetch is too big: {} bytes ({}/{})",
2811 stats->received_bytes,
2812 stats->received_objects,
2813 stats->total_objects);
2819 if (std::filesystem::is_directory(path)) {
2821 JAMI_WARNING(
"[Account {}] [Conversation {}] Removing non empty directory {}",
2825 if (dhtnet::fileutils::removeAll(path,
true) != 0)
2829 JAMI_DEBUG(
"[Account {}] [Conversation {}] Start clone of {:s} to {}",
2839 JAMI_ERROR(
"[Account {}] [Conversation {}] Error when retrieving remote conversation: {:s} {}",
2845 JAMI_ERROR(
"[Account {}] [Conversation {}] Unknown error {:d} when retrieving remote conversation",
2852 auto repo = std::make_unique<ConversationRepository>(
account, conversationId);
2853 repo->pinCertificates(
true);
2857 JAMI_ERROR(
"[Account {}] [Conversation {}] An error occurred while validating remote conversation.",
2862 JAMI_LOG(
"[Account {}] [Conversation {}] New conversation cloned in {}",
2872 auto repo = repository();
2904 "Malformed commit");
2908 if (commit.parents.size() == 0) {
2909 if (!checkInitialCommit(
userDevice, commit.id, commit.commit_msg)) {
2910 JAMI_WARNING(
"[Account {}] [Conversation {}] Malformed initial commit {}. Please "
2911 "ensure that you are using the latest "
2912 "version of Jami, or that one of your contacts is not performing any "
2913 "unwanted actions.",
2920 "Malformed initial commit");
2923 }
else if (commit.parents.size() == 1) {
2924 std::string type = {},
editId = {};
2927 type =
cm[
"type"].asString();
2933 "Malformed commit");
2937 if (type ==
"vote") {
2939 if (!checkVote(
userDevice, commit.id, commit.parents[0])) {
2940 JAMI_WARNING(
"[Account {}] [Conversation {}] Malformed vote commit {}. Please "
2941 "ensure that you are using the latest "
2942 "version of Jami, or that one of your contacts is not performing "
2943 "any unwanted actions.",
2954 }
else if (type ==
"member") {
2955 std::string
action =
cm[
"action"].asString();
2960 JAMI_WARNING(
"[Account {}] [Conversation {}] Commit {} with invalid member URI {}. Please ensure "
2961 "that you are using the latest version of Jami, or that one of your contacts is not "
2962 "performing any unwanted actions.",
2971 "Invalid member URI");
2976 JAMI_WARNING(
"[Account {}] [Conversation {}] Malformed add commit {}. Please ensure that you "
2977 "are using the latest version of Jami, or that one of your contacts is not "
2978 "performing any unwanted actions.",
2986 "Malformed add member commit");
2989 }
else if (
action ==
"join") {
2991 JAMI_WARNING(
"[Account {}] [Conversation {}] Malformed joins commit {}. "
2992 "Please ensure that you are using the latest "
2993 "version of Jami, or that one of your contacts is not "
2994 "performing any unwanted actions.",
3002 "Malformed join member commit");
3005 }
else if (
action ==
"remove") {
3010 JAMI_WARNING(
"[Account {}] [Conversation {}] Malformed removes commit {}. "
3011 "Please ensure that you are using the latest "
3012 "version of Jami, or that one of your contacts is not "
3013 "performing any unwanted actions.",
3021 "Malformed remove member commit");
3027 JAMI_WARNING(
"[Account {}] [Conversation {}] Malformed removes commit {}. "
3028 "Please ensure that you are using the latest "
3029 "version of Jami, or that one of your contacts is not "
3030 "performing any unwanted actions.",
3038 "Malformed ban member commit");
3042 JAMI_WARNING(
"[Account {}] [Conversation {}] Malformed member commit {} with "
3043 "action {}. Please ensure that you are using the latest "
3044 "version of Jami, or that one of your contacts is not performing "
3045 "any unwanted actions.",
3054 "Malformed member commit");
3057 }
else if (type ==
"application/update-profile") {
3058 if (!checkValidProfileUpdate(
userDevice, commit.id, commit.parents[0])) {
3059 JAMI_WARNING(
"[Account {}] [Conversation {}] Malformed profile updates commit "
3060 "{}. Please ensure that you are using the latest "
3061 "version of Jami, or that one of your contacts is not performing "
3062 "any unwanted actions.",
3070 "Malformed profile updates commit");
3073 }
else if (type ==
"application/edited-message" || !
editId.empty()) {
3075 JAMI_ERROR(
"Commit {:s} malformed", commit.id);
3080 "Malformed edit commit");
3087 if (!checkValidUserDiff(
userDevice, commit.id, commit.parents[0])) {
3088 JAMI_WARNING(
"[Account {}] [Conversation {}] Malformed {} commit {}. Please "
3089 "ensure that you are using the latest "
3090 "version of Jami, or that one of your contacts is not performing "
3091 "any unwanted actions.",
3100 "Malformed commit");
3108 JAMI_WARNING(
"[Account {}] [Conversation {}] Malformed commit {}. Please ensure "
3109 "that you are using the latest "
3110 "version of Jami, or that one of your contacts is not performing any "
3111 "unwanted actions. {}",
3127 JAMI_WARNING(
"[Account {}] [Conversation {}] Malformed commit {}.Please ensure "
3128 "that you are using the latest "
3129 "version of Jami, or that one of your contacts is not performing any "
3130 "unwanted actions. {}",
3138 "Malformed commit");
3142 if (!checkValidMergeCommit(commit.id, commit.parents)) {
3143 JAMI_WARNING(
"[Account {}] [Conversation {}] Malformed merge commit {}. Please "
3144 "ensure that you are using the latest "
3145 "version of Jami, or that one of your contacts is not performing "
3146 "any unwanted actions.",
3154 "Malformed merge commit");
3158 JAMI_DEBUG(
"[Account {}] [Conversation {}] Validate commit {}", accountId_, id_, commit.id);
3165ConversationRepository::ConversationRepository(
const std::shared_ptr<JamiAccount>&
account,
const std::string&
id)
3180 std::lock_guard
lkOp(pimpl_->opMtx_);
3181 pimpl_->resetHard();
3182 auto repo = pimpl_->repository();
3190 if (!dhtnet::fileutils::recursive_mkdir(
invitedPath, 0700)) {
3195 if (std::filesystem::is_regular_file(
devicePath)) {
3200 std::ofstream
file(
devicePath, std::ios::trunc | std::ios::binary);
3201 if (!
file.is_open()) {
3205 std::string path =
"invited/" + uri;
3206 if (!pimpl_->add(path))
3210 json[
"action"] =
"add";
3212 json[
"type"] =
"member";
3215 JAMI_ERROR(
"Unable to commit addition of member {}", uri);
3219 std::lock_guard
lk(pimpl_->membersMtx_);
3221 pimpl_->saveMembers();
3228 pimpl_->onMembersChanged_ = std::move(
cb);
3240 auto repo = pimpl_->repository();
3258 JAMI_ERROR(
"Unable to move commit to main: {}",
err->message);
3279 std::lock_guard
lkOp(pimpl_->opMtx_);
3280 pimpl_->resetHard();
3288 auto repo = pimpl_->repository();
3294 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to look up for remote {}",
3304 = fmt::format(
"file://{}",
3309 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to create remote for repository",
3324 JAMI_ERROR(
"Abort fetching repository, the fetch is too big: {} bytes ({}/{})",
3325 stats->received_bytes,
3326 stats->received_objects,
3327 stats->total_objects);
3335 JAMI_WARNING(
"[Account {}] [Conversation {}] Unable to fetch remote repository: {:s}",
3346std::vector<std::map<std::string, std::string>>
3352 JAMI_WARNING(
"[Account {}] [Conversation {}] Unable to get HEAD of {}", pimpl_->accountId_, pimpl_->id_, uri);
3360 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to validate history with {}",
3371 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to merge history with {}",
3381 if (commit != std::nullopt)
3385 JAMI_LOG(
"[Account {}] [Conversation {}] Successfully merged history with {}", pimpl_->accountId_, pimpl_->id_, uri);
3387 for (
auto& commit : result) {
3388 auto it = commit.find(
"type");
3389 if (
it != commit.end() &&
it->second ==
"member") {
3392 if (commit[
"action"] ==
"ban")
3403 auto repo = pimpl_->repository();
3439 std::string path = fmt::format(
"devices/{}.crt",
deviceId_);
3441 if (!std::filesystem::is_regular_file(
devicePath)) {
3442 std::ofstream
file(
devicePath, std::ios::trunc | std::ios::binary);
3443 if (!
file.is_open()) {
3464 auto repo = repository();
3470 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to get HEAD commit: {}", accountId_, id_, error);
3480 std::lock_guard
lkOp(pimpl_->opMtx_);
3481 pimpl_->resetHard();
3492std::vector<std::string>
3495 pimpl_->addUserDevice();
3496 std::vector<std::string>
ret;
3499 ret.emplace_back(pimpl_->commit(
msg));
3503std::vector<ConversationCommit>
3513 const std::string& from,
3514 bool logIfNotFound)
const
3522 return pimpl_->hasCommit(
commitId);
3525std::optional<ConversationCommit>
3528 return pimpl_->getCommit(
commitId);
3531std::pair<bool, std::string>
3534 std::lock_guard
lkOp(pimpl_->opMtx_);
3535 pimpl_->resetHard();
3537 auto repo = pimpl_->repository();
3539 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to merge without repo", pimpl_->accountId_, pimpl_->id_);
3544 pimpl_->resetHard();
3547 JAMI_ERROR(
"[Account {}] [Conversation {}] Merge operation aborted: repository is in unexpected state {}",
3556 JAMI_ERROR(
"[Account {}] [Conversation {}] Merge operation aborted: unable to checkout main branch",
3565 JAMI_ERROR(
"[Account {}] [Conversation {}] Merge operation aborted: unable to look up commit {}",
3573 JAMI_ERROR(
"[Account {}] [Conversation {}] Merge operation aborted: unable to look up commit {}",
3586 JAMI_ERROR(
"[Account {}] [Conversation {}] Merge operation aborted: repository analysis failed",
3599 JAMI_LOG(
"[Account {}] [Conversation {}] Merge analysis result: Unborn", pimpl_->accountId_, pimpl_->id_);
3601 JAMI_LOG(
"[Account {}] [Conversation {}] Merge analysis result: Fast-forward",
3609 JAMI_ERROR(
"[Account {}] [Conversation {}] Fast forward merge failed: {}",
3618 if (!pimpl_->validateDevice() && !
force) {
3619 JAMI_ERROR(
"[Account {}] [Conversation {}] Invalid device. Not migrated?", pimpl_->accountId_, pimpl_->id_);
3626 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to get reference for HEAD", pimpl_->accountId_, pimpl_->id_);
3632 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to look up HEAD commit", pimpl_->accountId_, pimpl_->id_);
3639 JAMI_ERROR(
"[Account {}] [Conversation {}] Unable to look up HEAD commit", pimpl_->accountId_, pimpl_->id_);
3651 JAMI_ERROR(
"[Account {}] [Conversation {}] Git merge failed: {}",
3659 JAMI_LOG(
"Some conflicts were detected during the merge operations. Resolution phase.");
3661 JAMI_ERROR(
"Merge operation aborted; Unable to automatically resolve conflicts");
3665 auto result = pimpl_->createMergeCommit(index.get(),
merge_id);
3668 return {!result.empty(), result};
3677std::vector<std::string>
3680 static const std::regex
re(
" +\\| +[0-9]+.*");
3682 std::string_view
line;
3695 std::lock_guard
lkOp(pimpl_->opMtx_);
3696 pimpl_->resetHard();
3698 auto repo = pimpl_->repository();
3702 auto account = pimpl_->account_.lock();
3723 if (!dhtnet::fileutils::recursive_mkdir(
membersPath, 0700)) {
3727 std::ofstream
file(
memberFile, std::ios::trunc | std::ios::binary);
3728 if (!
file.is_open()) {
3739 json[
"action"] =
"join";
3741 json[
"type"] =
"member";
3744 std::lock_guard
lk(pimpl_->membersMtx_);
3747 for (
auto&
member : pimpl_->members_) {
3756 pimpl_->saveMembers();
3765 std::lock_guard
lkOp(pimpl_->opMtx_);
3766 pimpl_->resetHard();
3768 auto account = pimpl_->account_.lock();
3769 auto repo = pimpl_->repository();
3775 auto crt = fmt::format(
"{}.crt", pimpl_->userId_);
3781 if (std::filesystem::is_regular_file(
adminFile,
ec)) {
3785 if (std::filesystem::is_regular_file(
memberFile,
ec)) {
3790 for (
const auto&
crl :
account->identity().second->getRevocationLists()) {
3793 auto crlPath =
crlsPath / pimpl_->deviceId_ / fmt::format(
"{}.crl", dht::toHex(
crl->getNumber()));
3794 if (std::filesystem::is_regular_file(
crlPath,
ec)) {
3800 for (
const auto& certificate : std::filesystem::directory_iterator(
repoPath /
"devices",
ec)) {
3801 if (certificate.is_regular_file(
ec)) {
3804 if (
cert.getIssuerUID() == pimpl_->userId_)
3805 std::filesystem::remove(certificate.path(),
ec);
3817 json[
"action"] =
"remove";
3818 json[
"uri"] = pimpl_->userId_;
3819 json[
"type"] =
"member";
3822 std::lock_guard
lk(pimpl_->membersMtx_);
3823 pimpl_->members_.erase(std::remove_if(pimpl_->members_.begin(),
3824 pimpl_->members_.end(),
3825 [&](
auto&
member) { return member.uri == pimpl_->userId_; }),
3826 pimpl_->members_.end());
3827 pimpl_->saveMembers();
3837 if (
auto repo = pimpl_->repository()) {
3840 dhtnet::fileutils::removeAll(
repoPath,
true);
3847 return pimpl_->mode();
3853 std::lock_guard
lkOp(pimpl_->opMtx_);
3854 pimpl_->resetHard();
3855 auto repo = pimpl_->repository();
3856 auto account = pimpl_->account_.lock();
3869 auto oldFile =
repoPath / type / (uri + (type !=
"invited" ?
".crt" :
""));
3870 if (!std::filesystem::is_regular_file(
oldFile)) {
3871 JAMI_WARNING(
"Didn't found file for {} with type {}", uri, type);
3877 if (!dhtnet::fileutils::recursive_mkdir(
voteDirectory, 0700)) {
3890 if (!pimpl_->add(
toAdd))
3895 json[
"type"] =
"vote";
3902 std::lock_guard
lkOp(pimpl_->opMtx_);
3903 pimpl_->resetHard();
3904 auto repo = pimpl_->repository();
3905 auto account = pimpl_->account_.lock();
3916 if (!dhtnet::fileutils::recursive_mkdir(
voteDirectory, 0700)) {
3929 if (!pimpl_->add(
toAdd.c_str()))
3934 json[
"type"] =
"vote";
3941 auto repo = repository();
3946 auto crtStr = uri + (type !=
"invited" ?
".crt" :
"");
3951 if (!dhtnet::fileutils::recursive_mkdir(
destPath, 0700)) {
3952 JAMI_ERROR(
"An error occurred while creating the {} directory. Abort resolving vote.",
destPath);
3959 JAMI_ERROR(
"An error occurred while moving the {} origin file path to the {} destination "
3960 "file path. Abort resolving vote.",
3967 if (type !=
"devices") {
3969 for (
const auto& certificate : std::filesystem::directory_iterator(
devicesPath,
ec)) {
3970 auto certPath = certificate.path();
3974 if (
issuer->getPublicKey().getId().to_view() == uri)
3975 dhtnet::fileutils::remove(
certPath,
true);
3980 std::lock_guard
lk(membersMtx_);
3983 for (
auto&
member : members_) {
4000 auto repo = repository();
4003 auto crtStr = uri + (type !=
"invited" ?
".crt" :
"");
4007 if (!dhtnet::fileutils::recursive_mkdir(
destPath, 0700)) {
4008 JAMI_ERROR(
"An error occurred while creating the {} destination path. Abort resolving vote.",
destPath);
4018 std::lock_guard
lk(membersMtx_);
4022 if (type ==
"invited")
4024 else if (type ==
"admins")
4027 for (
auto&
member : members_) {
4043 std::lock_guard
lkOp(pimpl_->opMtx_);
4044 pimpl_->resetHard();
4048 auto repo = pimpl_->repository();
4054 for (
const auto& certificate : dhtnet::fileutils::readDirectory(
adminsPath)) {
4055 if (certificate.find(
".crt") == std::string::npos) {
4059 auto adminUri = certificate.substr(0, certificate.size() - std::string(
".crt").size());
4066 JAMI_WARNING(
"More than half of the admins voted to ban {}, applying the ban.", uri);
4072 if (!pimpl_->resolveBan(type, uri))
4075 if (!pimpl_->resolveUnban(type, uri))
4086 json[
"type"] =
"member";
4094std::pair<std::vector<ConversationCommit>,
bool>
4108std::pair<std::vector<ConversationCommit>,
bool>
4112 if (!pimpl_->validCommits(
commits))
4114 return {std::move(
commits),
true};
4136 auto repo = pimpl_->repository();
4146std::vector<std::string>
4149 return pimpl_->getInitialMembers();
4152std::vector<ConversationMember>
4155 return pimpl_->members();
4158std::set<std::string>
4164std::map<std::string, std::vector<DeviceId>>
4174 pimpl_->initMembers();
4182 auto acc = pimpl_->account_.lock();
4183 auto repo = pimpl_->repository();
4192 for (
const auto& path :
paths) {
4194 std::promise<bool> p;
4195 std::future<bool>
f = p.get_future();
4196 acc->certStore().pinCertificatePath(path, [&](
auto ) { p.set_value(
true); });
4199 acc->certStore().pinCertificatePath(path, {});
4207 return pimpl_->uriFromDevice(deviceId);
4213 std::lock_guard
lkOp(pimpl_->opMtx_);
4214 pimpl_->resetHard();
4217 std::lock_guard
lk(pimpl_->membersMtx_);
4218 for (
const auto&
member : pimpl_->members_) {
4219 if (
member.uri == pimpl_->userId_) {
4220 valid =
member.role <= pimpl_->updateProfilePermLvl_;
4226 JAMI_ERROR(
"Insufficient permission to update information.");
4230 "Insufficient permission to update information.");
4235 for (
const auto& [
k, v] :
profile) {
4238 auto repo = pimpl_->repository();
4242 auto profilePath =
repoPath /
"profile.vcf";
4243 std::ofstream
file(profilePath, std::ios::trunc | std::ios::binary);
4244 if (!
file.is_open()) {
4245 JAMI_ERROR(
"Unable to write data to {}", profilePath);
4283 if (!pimpl_->add(
"profile.vcf"))
4286 json[
"type"] =
"application/update-profile";
4290std::map<std::string, std::string>
4293 if (
auto repo = pimpl_->repository()) {
4296 auto profilePath =
repoPath /
"profile.vcf";
4297 std::map<std::string, std::string> result;
4299 if (std::filesystem::is_regular_file(profilePath,
ec)) {
4304 result[
"mode"] = std::to_string(
static_cast<int>(
mode()));
4312std::map<std::string, std::string>
4315 std::map<std::string, std::string> result;
4318 result[
"title"] = std::move(v);
4320 result[
"description"] = std::move(v);
4322 result[
"avatar"] = std::move(v);
4324 result[
"rdvAccount"] = std::move(v);
4326 result[
"rdvDevice"] = std::move(v);
4335 if (
auto repo = pimpl_->repository()) {
4338 JAMI_ERROR(
"Unable to get reference for HEAD");
4347std::optional<std::map<std::string, std::string>>
4350 return pimpl_->convCommitToMap(commit);
4353std::vector<std::map<std::string, std::string>>
4356 std::vector<std::map<std::string, std::string>> result = {};
4357 result.reserve(
commits.size());
4358 for (
const auto& commit :
commits) {
4359 if (
auto message = pimpl_->convCommitToMap(commit))
4360 result.emplace_back(*message);
std::weak_ptr< JamiAccount > account_
const std::string userId_
bool resolveUnban(const std::string_view type, const std::string &uri)
GitObject fileAtTree(const std::string &path, const GitTree &tree) const
bool checkVote(const std::string &userDevice, const std::string &commitId, const std::string &parentId) const
bool hasCommit(const std::string &commitId) const
const std::string deviceId_
const std::string accountId_
std::string commit(const std::string &msg, bool verifyDevice=true)
ConversationMode mode() const
OnMembersChanged onMembersChanged_
std::string uriFromDevice(const std::string &deviceId, const std::string &commitId="") const
Retrieve the user related to a device using the account's certificate store.
std::map< std::string, std::vector< DeviceId > > devices(bool ignoreExpired=true) const
std::string diffStats(const std::string &newId, const std::string &oldId) const
std::map< std::string, std::string > deviceToUri_
std::string commitMessage(const std::string &msg, bool verifyDevice=true)
std::optional< std::set< std::string_view > > getDeltaPathsFromDiff(const GitDiff &diff) const
Get the deltas from a git diff.
std::vector< ConversationCommit > behind(const std::string &from) const
GitTree treeAtCommit(git_repository *repo, const std::string &commitId) const
bool validCommits(const std::vector< ConversationCommit > &commits) const
GitObject memberCertificate(std::string_view memberUri, const GitTree &tree) const
bool checkInitialCommit(const std::string &userDevice, const std::string &commitId, const std::string &commitMsg) const
std::set< std::string > memberUris(std::string_view filter, const std::set< MemberRole > &filteredRoles) const
std::filesystem::path conversationDataPath_
void forEachCommit(PreConditionCb &&preCondition, std::function< void(ConversationCommit &&)> &&emplaceCb, PostConditionCb &&postCondition, const std::string &from="", bool logIfNotFound=true) const
bool add(const std::string &path)
bool checkValidVoteResolution(const std::string &userDevice, const std::string &uriMember, const std::string &commitId, const std::string &parentId, const std::string &voteType) const
std::filesystem::path membersCache_
bool checkEdit(const std::string &userDevice, const ConversationCommit &commit) const
bool checkValidProfileUpdate(const std::string &userDevice, const std::string &commitid, const std::string &parentId) const
std::vector< ConversationMember > members_
bool verifyCertificate(std::string_view certContent, const std::string &userUri, std::string_view oldCert=""sv) const
Verify that a certificate modification is correct.
MemberRole updateProfilePermLvl_
std::vector< ConversationCommit > log(const LogOptions &options) const
bool isValidUserAtCommit(const std::string &userDevice, const std::string &commitId, const git_buf &sig, const git_buf &sig_data) const
std::string uriFromDeviceAtCommit(const std::string &deviceId, const std::string &commitId) const
Retrieve the user related to a device using certificate directly from the repository at a specific co...
std::string createMergeCommit(git_index *index, const std::string &wanted_ref)
Impl(const std::shared_ptr< JamiAccount > &account, const std::string &id)
std::string getDisplayName() const
GitDiff diff(git_repository *repo, const std::string &idNew, const std::string &idOld) const
std::optional< std::map< std::string, std::string > > convCommitToMap(const ConversationCommit &commit) const
std::vector< ConversationMember > members() const
bool resolveBan(const std::string_view type, const std::string &uri)
bool checkValidAdd(const std::string &userDevice, const std::string &uriMember, const std::string &commitid, const std::string &parentId) const
bool checkValidJoins(const std::string &userDevice, const std::string &uriMember, const std::string &commitid, const std::string &parentId) const
ConversationCommit parseCommit(git_repository *repo, const git_commit *commit) const
bool resolveConflicts(git_index *index, const std::string &other_id)
bool checkValidMergeCommit(const std::string &mergeId, const std::vector< std::string > &parents) const
Validate the merge commit by ensuring the absence of invalid files.
std::mutex deviceToUriMtx_
std::optional< ConversationCommit > getCommit(const std::string &commitId) const
std::vector< std::string > getInitialMembers() const
bool checkValidRemove(const std::string &userDevice, const std::string &uriMember, const std::string &commitid, const std::string &parentId) const
std::optional< ConversationMode > mode_
GitRepository repository() const
bool mergeFastforward(const git_oid *target_oid, int is_unborn)
bool checkValidUserDiff(const std::string &userDevice, const std::string &commitId, const std::string &parentId) const
std::map< std::string, std::vector< DeviceId > > devices(bool ignoreExpired=true) const
Get conversation's devices.
std::string leave()
Erase self from repository.
static LIBJAMI_TEST_EXPORT std::unique_ptr< ConversationRepository > createConversation(const std::shared_ptr< JamiAccount > &account, ConversationMode mode=ConversationMode::INVITES_ONLY, const std::string &otherMember="")
Creates a new repository, with initial files, where the first commit hash is the conversation id.
std::vector< std::string > commitMessages(const std::vector< std::string > &msgs)
std::string addMember(const std::string &uri)
Write the certificate in /members and commit the change.
void removeBranchWith(const std::string &remoteDevice)
Delete branch with remote.
bool fetch(const std::string &remoteDeviceId)
Fetch a remote repository via the given socket.
void erase()
Erase repository.
std::string getHead() const
Get current HEAD hash.
std::string commitMessage(const std::string &msg, bool verifyDevice=true)
Add a new commit to the conversation.
std::vector< std::map< std::string, std::string > > mergeHistory(const std::string &uri, std::function< void(const std::string &)> &&disconnectFromPeerCb={})
Merge the history of the conversation with another peer.
std::map< std::string, std::string > infos() const
Retrieve current infos (title, description, avatar, mode)
std::set< std::string > memberUris(std::string_view filter, const std::set< MemberRole > &filteredRoles) const
std::optional< ConversationCommit > getCommit(const std::string &commitId) const
static std::vector< std::string > changedFiles(std::string_view diffStats)
Get changed files from a git diff.
ConversationMode mode() const
Get conversation's mode.
bool isValidUserAtCommit(const std::string &userDevice, const std::string &commitId, const git_buf &sig, const git_buf &sig_data) const
Verify the signature against the given commit.
bool validCommits(const std::vector< ConversationCommit > &commitsToValidate) const
Validate that commits are not malformed.
std::string updateInfos(const std::map< std::string, std::string > &map)
Change repository's infos.
std::string amend(const std::string &id, const std::string &msg)
Amend a commit message.
void refreshMembers() const
To use after a merge with member's events, refresh members knowledge.
std::string voteKick(const std::string &uri, const std::string &type)
The voting system is divided in two parts.
static LIBJAMI_TEST_EXPORT std::pair< std::unique_ptr< ConversationRepository >, std::vector< ConversationCommit > > cloneConversation(const std::shared_ptr< JamiAccount > &account, const std::string &deviceId, const std::string &conversationId)
Clones a conversation on a remote device.
std::vector< std::map< std::string, std::string > > convCommitsToMap(const std::vector< ConversationCommit > &commits) const
Convert ConversationCommit to MapStringString for the client.
std::string join()
Join a repository.
~ConversationRepository()
void onMembersChanged(OnMembersChanged &&cb)
const std::string & id() const
Return the conversation id.
std::string remoteHead(const std::string &remoteDeviceId, const std::string &branch="main") const
Retrieve remote head.
std::vector< ConversationCommit > log(const LogOptions &options={}) const
Get commits depending on the options we pass.
std::string resolveVote(const std::string &uri, const std::string_view type, const std::string &voteType)
Validate if a vote is finished.
std::string voteUnban(const std::string &uri, const std::string_view type)
Add a vote to re-add a user.
std::pair< std::vector< ConversationCommit >, bool > validClone() const
Validate a clone.
std::pair< std::vector< ConversationCommit >, bool > validFetch(const std::string &remoteDevice) const
Validate a fetch with remote device.
std::string uriFromDevice(const std::string &deviceId) const
Retrieve the uri from a deviceId.
void pinCertificates(bool blocking=false)
Because conversations can contains non contacts certificates, this methods loads certificates in conv...
bool hasCommit(const std::string &commitId) const
Check if a commit exists in the repository.
static std::map< std::string, std::string > infosFromVCard(vCard::utils::VCardData &&details)
std::vector< ConversationMember > members() const
Get conversation's members.
std::vector< std::string > getInitialMembers() const
One to one util, get initial members.
std::pair< bool, std::string > merge(const std::string &merge_id, bool force=false)
Merge another branch into the main branch.
std::optional< std::map< std::string, std::string > > convCommitToMap(const ConversationCommit &commit) const
std::string diffStats(const std::string &newId, const std::string &oldId="") const
Get current diff stats between two commits.
constexpr auto DIFF_REGEX
constexpr size_t MAX_FETCH_SIZE
#define JAMI_ERROR(formatstr,...)
#define JAMI_DEBUG(formatstr,...)
#define JAMI_WARNING(formatstr,...)
#define JAMI_LOG(formatstr,...)
static const std::filesystem::path DEVICES
static const std::filesystem::path INVITED
static const std::filesystem::path BANNED
static const std::filesystem::path ADMINS
static const std::filesystem::path MEMBERS
std::string encode(std::string_view str)
std::vector< unsigned char > decode(std::string_view str)
const std::filesystem::path & get_data_dir()
std::vector< uint8_t > loadFile(const std::filesystem::path &path, const std::filesystem::path &default_dir)
Read the full content of a file at path.
std::filesystem::path getFullPath(const std::filesystem::path &base, const std::filesystem::path &path)
If path is relative, it is appended to base.
std::string toString(const Json::Value &jsonVal)
bool parse(std::string_view jsonStr, Json::Value &jsonVal)
std::map< std::string, std::string, std::less<> > VCardData
VCardData toMap(std::string_view content)
Payload to vCard.
std::string getFileId(const std::string &commitId, const std::string &tid, const std::string &displayName)
std::unique_ptr< git_remote, GitRemoteDeleter > GitRemote
std::unique_ptr< git_signature, GitSignatureDeleter > GitSignature
std::unique_ptr< git_object, GitObjectDeleter > GitObject
std::unique_ptr< git_repository, GitRepositoryDeleter > GitRepository
GitRepository create_empty_repository(const std::string &path)
Creates an empty repository.
std::string initial_commit(GitRepository &repo, const std::shared_ptr< JamiAccount > &account, ConversationMode mode, const std::string &otherMember="")
Sign and create the initial commit.
void emitSignal(Args... args)
std::function< bool(const std::string &, const GitAuthor &, ConversationCommit &)> PostConditionCb
std::unique_ptr< git_commit, GitCommitDeleter > GitCommit
constexpr auto EINVALIDMODE
bool add_initial_files(GitRepository &repo, const std::shared_ptr< JamiAccount > &account, ConversationMode mode, const std::string &otherMember="")
Adds initial files.
std::unique_ptr< git_reference, GitReferenceDeleter > GitReference
constexpr auto EVALIDFETCH
bool git_add_all(git_repository *repo)
Add all files to index.
constexpr auto EUNAUTHORIZED
bool getline(std::string_view &str, std::string_view &line, char delim='\n')
Similar to @getline_full but skips empty results.
std::unique_ptr< git_diff, GitDiffDeleter > GitDiff
std::unique_ptr< git_tree, GitTreeDeleter > GitTree
std::unique_ptr< git_revwalk, GitRevWalkerDeleter > GitRevWalker
std::unique_ptr< git_index, GitIndexDeleter > GitIndex
static const std::regex regex_display_name("<|>")
std::string_view as_view(const git_blob *blob)
std::unique_ptr< git_diff_stats, GitDiffStatsDeleter > GitDiffStats
std::unique_ptr< git_annotated_commit, GitAnnotatedCommitDeleter > GitAnnotatedCommit
std::unique_ptr< git_buf, GitBufDeleter > GitBuf
std::function< CallbackResult(const std::string &, const GitAuthor &, const GitCommit &)> PreConditionCb
std::unique_ptr< git_index_conflict_iterator, GitIndexConflictIteratorDeleter > GitIndexConflictIterator
std::function< void(const std::set< std::string > &)> OnMembersChanged
bool regex_match(string_view sv, svmatch &m, const regex &e, regex_constants::match_flag_type flags=regex_constants::match_default)
match_results< string_view::const_iterator > svmatch
bool regex_search(string_view sv, svmatch &m, const regex &e, regex_constants::match_flag_type flags=regex_constants::match_default)
std::vector< std::string > parents
std::string linearized_parent
static constexpr std::string_view SEPARATOR_TOKEN
static constexpr std::string_view END_LINE_TOKEN
static constexpr std::string_view BEGIN_TOKEN
static constexpr std::string_view END_TOKEN
static constexpr std::string_view PHOTO
static constexpr std::string_view RDV_ACCOUNT
static constexpr std::string_view BASE64
static constexpr std::string_view VCARD_VERSION
static constexpr std::string_view RDV_DEVICE
static constexpr std::string_view DESCRIPTION
static constexpr std::string_view FORMATTED_NAME
static constexpr std::string_view TITLE
static constexpr std::string_view DESCRIPTION
static constexpr std::string_view RDV_DEVICE
static constexpr std::string_view RDV_ACCOUNT
static constexpr std::string_view AVATAR