diff --git a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
index a978d063915faccefbf77ef92022bb8e7c81524e..8b39558b88bc12806bce7bc9bb753c6802fc7b61 100644
--- a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
+++ b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
@@ -1707,6 +1707,28 @@
            <arg type="s" name="accountId" direction="in"/>
        </method>
 
+       <method name="updateConversationInfos" tp:name-for-bindings="updateConversationInfos">
+           <tp:added version="10.0.0"/>
+           <tp:docstring>
+               Update conversation's infos (supported keys: title, description, avatar)
+           </tp:docstring>
+           <arg type="s" name="accountId" direction="in"/>
+           <arg type="s" name="conversationId" direction="in"/>
+           <annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="MapStringString"/>
+           <arg type="a{ss}" name="infos" direction="in"/>
+       </method>
+
+       <method name="conversationInfos" tp:name-for-bindings="conversationInfos">
+           <tp:added version="10.0.0"/>
+           <tp:docstring>
+               Get conversation's infos (mode, title, description, avatar)
+           </tp:docstring>
+           <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MapStringString"/>
+           <arg type="a{ss}" name="infos" direction="out"/>
+           <arg type="s" name="accountId" direction="in"/>
+           <arg type="s" name="conversationId" direction="in"/>
+       </method>
+
        <method name="addConversationMember" tp:name-for-bindings="addConversationMember">
            <tp:added version="10.0.0"/>
            <tp:docstring>
diff --git a/bin/dbus/dbusconfigurationmanager.cpp b/bin/dbus/dbusconfigurationmanager.cpp
index 1dd4e1c1d4100e8d079b7bbf9310831e93c3991d..aea2a4d1d1f826213880dfe15192edfe837fff36 100644
--- a/bin/dbus/dbusconfigurationmanager.cpp
+++ b/bin/dbus/dbusconfigurationmanager.cpp
@@ -538,7 +538,9 @@ DBusConfigurationManager::setAccountsOrder(const std::string& order)
 }
 
 auto
-DBusConfigurationManager::validateCertificate(const std::string& accountId, const std::string& certificate) -> decltype(DRing::validateCertificate(accountId, certificate))
+DBusConfigurationManager::validateCertificate(const std::string& accountId,
+                                              const std::string& certificate)
+    -> decltype(DRing::validateCertificate(accountId, certificate))
 {
     return DRing::validateCertificate(accountId, certificate);
 }
@@ -856,6 +858,21 @@ DBusConfigurationManager::getConversationRequests(const std::string& accountId)
     return DRing::getConversationRequests(accountId);
 }
 
+void
+DBusConfigurationManager::updateConversationInfos(const std::string& accountId,
+                                                  const std::string& conversationId,
+                                                  const std::map<std::string, std::string>& infos)
+{
+    DRing::updateConversationInfos(accountId, conversationId, infos);
+}
+
+std::map<std::string, std::string>
+DBusConfigurationManager::conversationInfos(const std::string& accountId,
+                                            const std::string& conversationId)
+{
+    return DRing::conversationInfos(accountId, conversationId);
+}
+
 bool
 DBusConfigurationManager::addConversationMember(const std::string& accountId,
                                                 const std::string& conversationId,
diff --git a/bin/dbus/dbusconfigurationmanager.h b/bin/dbus/dbusconfigurationmanager.h
index e40620fa606f0853bd56c3c0dc1939cc7e6b177f..c471fd08b88ec9c60c221a2f08931994b1187b6b 100644
--- a/bin/dbus/dbusconfigurationmanager.h
+++ b/bin/dbus/dbusconfigurationmanager.h
@@ -195,6 +195,8 @@ public:
         bool removeConversation(const std::string& accountId, const std::string& conversationId);
         std::vector<std::string> getConversations(const std::string& accountId);
         std::vector<std::map<std::string, std::string>> getConversationRequests(const std::string& accountId);
+        void updateConversationInfos(const std::string& accountId, const std::string& conversationId, const std::map<std::string, std::string>& infos);
+        std::map<std::string, std::string> conversationInfos(const std::string& accountId, const std::string& conversationId);
         bool addConversationMember(const std::string& accountId,
                                 const std::string& conversationId,
                                 const std::string& contactUri);
diff --git a/bin/jni/conversation.i b/bin/jni/conversation.i
index 1427bd08ad8f5be8def108299ab046f6873da1d3..82a36b6e74f97162bdc50b3820f4a291c1a3578a 100644
--- a/bin/jni/conversation.i
+++ b/bin/jni/conversation.i
@@ -46,6 +46,8 @@ namespace DRing {
   bool removeConversation(const std::string& accountId, const std::string& conversationId);
   std::vector<std::string> getConversations(const std::string& accountId);
   std::vector<std::map<std::string, std::string>> getConversationRequests(const std::string& accountId);
+  void updateConversationInfos(const std::string& accountId, const std::string& conversationId, const std::map<std::string, std::string>& infos);
+  std::map<std::string, std::string> conversationInfos(const std::string& accountId, const std::string& conversationId);
 
   // Member management
   bool addConversationMember(const std::string& accountId, const std::string& conversationId, const std::string& contactUri);
diff --git a/bin/nodejs/conversation.i b/bin/nodejs/conversation.i
index af662e05ec08345d53971cf08e7f2a5666e5e66f..cd522022b6b07662fa749644ca01005fbe6ef328 100644
--- a/bin/nodejs/conversation.i
+++ b/bin/nodejs/conversation.i
@@ -59,6 +59,8 @@ namespace DRing {
   bool removeConversation(const std::string& accountId, const std::string& conversationId);
   std::vector<std::string> getConversations(const std::string& accountId);
   std::vector<std::map<std::string, std::string>> getConversationRequests(const std::string& accountId);
+  void updateConversationInfos(const std::string& accountId, const std::string& conversationId, const std::map<std::string, std::string>& infos);
+  std::map<std::string, std::string> conversationInfos(const std::string& accountId, const std::string& conversationId);
 
   // Member management
   bool addConversationMember(const std::string& accountId, const std::string& conversationId, const std::string& contactUri);
diff --git a/src/Makefile.am b/src/Makefile.am
index 346d88b2108eef39c39e24023e331ed4523e0cde..dab1cfc8beca06e3d0e21ebb5261f356c681d76b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -183,7 +183,8 @@ libring_la_SOURCES = \
 		generic_io.h \
 		scheduled_executor.h \
 		scheduled_executor.cpp \
-		transport/peer_channel.h
+		transport/peer_channel.h \
+		vcard.h
 
 if HAVE_WIN32
 libring_la_SOURCES += \
diff --git a/src/client/conversation_interface.cpp b/src/client/conversation_interface.cpp
index 4f0c3cf98de0280977cce44ca5e3320e43b08f48..2f7379174e5ae520526ac4a3a95b561875a25bd2 100644
--- a/src/client/conversation_interface.cpp
+++ b/src/client/conversation_interface.cpp
@@ -80,6 +80,23 @@ getConversationRequests(const std::string& accountId)
     return {};
 }
 
+void
+updateConversationInfos(const std::string& accountId,
+                        const std::string& conversationId,
+                        const std::map<std::string, std::string>& infos)
+{
+    if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
+        acc->updateConversationInfos(conversationId, infos);
+}
+
+std::map<std::string, std::string>
+conversationInfos(const std::string& accountId, const std::string& conversationId)
+{
+    if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
+        return acc->conversationInfos(conversationId);
+    return {};
+}
+
 // Member management
 bool
 addConversationMember(const std::string& accountId,
diff --git a/src/dring/conversation_interface.h b/src/dring/conversation_interface.h
index 9e33a649c2f8570cf421c2d8ac1a805e9815eb32..15ea61a6caa4181e93ecd6c98f1e2f63999bb228 100644
--- a/src/dring/conversation_interface.h
+++ b/src/dring/conversation_interface.h
@@ -43,6 +43,13 @@ DRING_PUBLIC std::vector<std::string> getConversations(const std::string& accoun
 DRING_PUBLIC std::vector<std::map<std::string, std::string>> getConversationRequests(
     const std::string& accountId);
 
+// Conversation's infos management
+DRING_PUBLIC void updateConversationInfos(const std::string& accountId,
+                                          const std::string& conversationId,
+                                          const std::map<std::string, std::string>& infos);
+DRING_PUBLIC std::map<std::string, std::string> conversationInfos(const std::string& accountId,
+                                                                  const std::string& conversationId);
+
 // Member management
 DRING_PUBLIC bool addConversationMember(const std::string& accountId,
                                         const std::string& conversationId,
diff --git a/src/jamidht/conversation.cpp b/src/jamidht/conversation.cpp
index 2acce647948195c49b6b37175ebff7cb4b658e87..6d4a6cc53b374c9578b4f7a64dd93fe64a72cda1 100644
--- a/src/jamidht/conversation.cpp
+++ b/src/jamidht/conversation.cpp
@@ -534,8 +534,10 @@ Conversation::generateInvitation() const
     // Invite the new member to the conversation
     std::map<std::string, std::string> invite;
     Json::Value root;
+    for (const auto& [k, v] : infos()) {
+        root["metadatas"][k] = v;
+    }
     root["conversationId"] = id();
-    // TODO metadatas
     Json::StreamWriterBuilder wbuilder;
     wbuilder["commentStyle"] = "None";
     wbuilder["indentation"] = "";
@@ -587,4 +589,26 @@ Conversation::isInitialMember(const std::string& uri) const
     return std::find(members.begin(), members.end(), uri) != members.end();
 }
 
+std::string
+Conversation::updateInfos(const std::map<std::string, std::string>& map)
+{
+    return pimpl_->repository_->updateInfos(map);
+}
+
+std::map<std::string, std::string>
+Conversation::infos() const
+{
+    return pimpl_->repository_->infos();
+}
+
+std::vector<uint8_t>
+Conversation::vCard() const
+{
+    try {
+        return fileutils::loadFile(pimpl_->repoPath() + DIR_SEPARATOR_STR + "profile.vcf");
+    } catch (...) {
+    }
+    return {};
+}
+
 } // namespace jami
\ No newline at end of file
diff --git a/src/jamidht/conversation.h b/src/jamidht/conversation.h
index eb7e5365b6a7b9ce03656dab571554497a5d5969..52bd889e21f981dd347271fa9af55fe49a0bf4c6 100644
--- a/src/jamidht/conversation.h
+++ b/src/jamidht/conversation.h
@@ -32,14 +32,17 @@ class JamiAccount;
 class ConversationRepository;
 enum class ConversationMode;
 
-using OnPullCb = std::function<void(bool fetchOk,std::vector<std::map<std::string, std::string>>&& newMessages)>;
-using OnLoadMessages = std::function<void(std::vector<std::map<std::string, std::string>>&& messages)>;
-
+using OnPullCb = std::function<void(bool fetchOk,
+                                    std::vector<std::map<std::string, std::string>>&& newMessages)>;
+using OnLoadMessages
+    = std::function<void(std::vector<std::map<std::string, std::string>>&& messages)>;
 
 class Conversation : public std::enable_shared_from_this<Conversation>
 {
 public:
-    Conversation(const std::weak_ptr<JamiAccount>& account, ConversationMode mode, const std::string& otherMember = "");
+    Conversation(const std::weak_ptr<JamiAccount>& account,
+                 ConversationMode mode,
+                 const std::string& otherMember = "");
     Conversation(const std::weak_ptr<JamiAccount>& account, const std::string& conversationId = "");
     Conversation(const std::weak_ptr<JamiAccount>& account,
                  const std::string& remoteDevice,
@@ -100,7 +103,9 @@ public:
      * @param fromMessage      The most recent message ("" = last (default))
      * @param toMessage        The oldest message ("" = last (default)), no limit
      */
-    void loadMessages(const OnLoadMessages& cb, const std::string& fromMessage = "", const std::string& toMessage = "");
+    void loadMessages(const OnLoadMessages& cb,
+                      const std::string& fromMessage = "",
+                      const std::string& toMessage = "");
     /**
      * Get last commit id
      * @return last commit id
@@ -170,8 +175,22 @@ public:
      */
     std::vector<std::string> getInitialMembers() const;
     bool isInitialMember(const std::string& uri) const;
-private:
 
+    /**
+     * Change repository's infos
+     * @param map       New infos (supported keys: title, description, avatar)
+     * @return the commit id
+     */
+    std::string updateInfos(const std::map<std::string, std::string>& map);
+
+    /**
+     * Retrieve current infos (title, description, avatar, mode)
+     * @return infos
+     */
+    std::map<std::string, std::string> infos() const;
+    std::vector<uint8_t> vCard() const;
+
+private:
     std::shared_ptr<Conversation> shared()
     {
         return std::static_pointer_cast<Conversation>(shared_from_this());
diff --git a/src/jamidht/conversationrepository.cpp b/src/jamidht/conversationrepository.cpp
index 693dd27aa06b07e4abd74aff9b11dd8a0d7a9b7c..69844babf043732f1db1c57bd24c42a77c04b9e7 100644
--- a/src/jamidht/conversationrepository.cpp
+++ b/src/jamidht/conversationrepository.cpp
@@ -24,6 +24,7 @@
 #include "gittransport.h"
 #include "string_utils.h"
 #include "client/ring_signal.h"
+#include "vcard.h"
 
 using random_device = dht::crypto::random_device;
 
@@ -47,23 +48,19 @@ public:
         : account_(account)
         , id_(id)
     {
-        repository_ = repository();
-        if (!repository_)
-            throw std::logic_error("Couldn't initialize repo");
-
         initMembers();
     }
 
+    // NOTE! We use temporary GitRepository to avoid to keep file opened (TODO check why
+    // git_remote_fetch() leaves pack-data opened)
     GitRepository repository() const
     {
-        // TODO use only one object
         auto shared = account_.lock();
         if (!shared)
             return {nullptr, git_repository_free};
         auto path = fileutils::get_data_dir() + DIR_SEPARATOR_STR + shared->getAccountID()
                     + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + id_;
         git_repository* repo = nullptr;
-        // TODO share this repo with GitServer
         if (git_repository_open(&repo, path.c_str()) != 0)
             return {nullptr, git_repository_free};
         return {std::move(repo), git_repository_free};
@@ -94,12 +91,16 @@ public:
                           const std::string& uriMember,
                           const std::string& commitid,
                           const std::string& parentId) const;
+    bool checkValidProfileUpdate(const std::string& userDevice,
+                                 const std::string& commitid,
+                                 const std::string& parentId) const;
 
     bool add(const std::string& path);
     std::string commit(const std::string& msg);
     ConversationMode mode() const;
 
-    GitDiff diff(const std::string& idNew, const std::string& idOld) const;
+    // NOTE! GitDiff needs to be deteleted before repo
+    GitDiff diff(git_repository* repo, const std::string& idNew, const std::string& idOld) const;
     std::string diffStats(const std::string& newId, const std::string& oldId) const;
     std::string diffStats(const GitDiff& diff) const;
 
@@ -109,13 +110,12 @@ public:
                                         unsigned n) const;
 
     GitObject fileAtTree(const std::string& path, const GitTree& tree) const;
-    GitTree treeAtCommit(const std::string& commitId) const;
+    // NOTE! GitDiff needs to be deteleted before repo
+    GitTree treeAtCommit(git_repository* repo, const std::string& commitId) const;
     std::string getCommitType(const std::string& commitMsg) const;
 
     std::vector<std::string> getInitialMembers() const;
 
-    GitRepository repository_ {nullptr, git_repository_free};
-
     std::weak_ptr<JamiAccount> account_;
     const std::string id_;
     mutable std::optional<ConversationMode> mode_ {};
@@ -130,7 +130,12 @@ public:
         return members_;
     }
 
+    bool resolveConflicts(git_index* index, const std::string& other_id);
+
     void initMembers();
+
+    // Permissions
+    MemberRole updateProfilePermLvl_ {MemberRole::ADMIN};
 };
 
 /////////////////////////////////////////////////////////////////////////////////
@@ -387,7 +392,8 @@ ConversationRepository::Impl::createMergeCommit(git_index* index, const std::str
 {
     // The merge will occur between current HEAD and wanted_ref
     git_reference* head_ref_ptr = nullptr;
-    if (git_repository_head(&head_ref_ptr, repository_.get()) < 0) {
+    auto repo = repository();
+    if (git_repository_head(&head_ref_ptr, repo.get()) < 0) {
         JAMI_ERR("Could not get HEAD reference");
         return false;
     }
@@ -395,7 +401,7 @@ ConversationRepository::Impl::createMergeCommit(git_index* index, const std::str
 
     // Maybe that's a ref, so DWIM it
     git_reference* merge_ref_ptr = nullptr;
-    git_reference_dwim(&merge_ref_ptr, repository_.get(), wanted_ref.c_str());
+    git_reference_dwim(&merge_ref_ptr, repo.get(), wanted_ref.c_str());
     GitReference merge_ref {merge_ref_ptr, git_reference_free};
 
     GitSignature sig {signature()};
@@ -424,13 +430,12 @@ ConversationRepository::Impl::createMergeCommit(git_index* index, const std::str
         return false;
     }
     git_annotated_commit* annotated_ptr = nullptr;
-    if (git_annotated_commit_lookup(&annotated_ptr, repository_.get(), &commit_id) < 0) {
+    if (git_annotated_commit_lookup(&annotated_ptr, repo.get(), &commit_id) < 0) {
         JAMI_ERR("Couldn't lookup commit %s", wanted_ref.c_str());
         return false;
     }
     GitAnnotatedCommit annotated {annotated_ptr, git_annotated_commit_free};
-    if (git_commit_lookup(&parent, repository_.get(), git_annotated_commit_id(annotated.get()))
-        < 0) {
+    if (git_commit_lookup(&parent, repo.get(), git_annotated_commit_id(annotated.get())) < 0) {
         JAMI_ERR("Couldn't lookup commit %s", wanted_ref.c_str());
         return false;
     }
@@ -439,11 +444,14 @@ ConversationRepository::Impl::createMergeCommit(git_index* index, const std::str
     // Prepare our commit tree
     git_oid tree_oid;
     git_tree* tree = nullptr;
-    if (git_index_write_tree(&tree_oid, index) < 0) {
+    if (git_index_write_tree_to(&tree_oid, index, repo.get()) < 0) {
+        const git_error* err = giterr_last();
+        if (err)
+            JAMI_ERR("XXX checkout index: %s", err->message);
         JAMI_ERR("Couldn't write index");
         return false;
     }
-    if (git_tree_lookup(&tree, repository_.get(), &tree_oid) < 0) {
+    if (git_tree_lookup(&tree, repo.get(), &tree_oid) < 0) {
         JAMI_ERR("Couldn't lookup tree");
         return false;
     }
@@ -452,7 +460,7 @@ ConversationRepository::Impl::createMergeCommit(git_index* index, const std::str
     git_buf to_sign = {};
     const git_commit* parents_ptr[2] {parents[0].get(), parents[1].get()};
     if (git_commit_create_buffer(&to_sign,
-                                 repository_.get(),
+                                 repo.get(),
                                  sig.get(),
                                  sig.get(),
                                  nullptr,
@@ -474,7 +482,7 @@ ConversationRepository::Impl::createMergeCommit(git_index* index, const std::str
     std::string signed_str = base64::encode(signed_buf);
     git_oid commit_oid;
     if (git_commit_create_with_signature(&commit_oid,
-                                         repository_.get(),
+                                         repo.get(),
                                          to_sign.ptr,
                                          signed_str.c_str(),
                                          "signature")
@@ -488,20 +496,24 @@ ConversationRepository::Impl::createMergeCommit(git_index* index, const std::str
         JAMI_INFO("New merge commit added with id: %s", commit_str);
         // Move commit to main branch
         git_reference* ref_ptr = nullptr;
-        if (git_reference_create(&ref_ptr,
-                                 repository_.get(),
-                                 "refs/heads/main",
-                                 &commit_oid,
-                                 true,
-                                 nullptr)
+        if (git_reference_create(&ref_ptr, repo.get(), "refs/heads/main", &commit_oid, true, nullptr)
             < 0) {
             JAMI_WARN("Could not move commit to main");
         }
         git_reference_free(ref_ptr);
     }
 
-    // We're done merging, cleanup the repository state
-    git_repository_state_cleanup(repository_.get());
+    // We're done merging, cleanup the repository state & index
+    git_repository_state_cleanup(repo.get());
+
+    git_object* target_ptr = nullptr;
+    if (git_object_lookup(&target_ptr, repo.get(), &commit_oid, GIT_OBJ_COMMIT) != 0) {
+        JAMI_ERR("failed to lookup OID %s", git_oid_tostr_s(&commit_oid));
+        return false;
+    }
+    GitObject target {target_ptr, git_object_free};
+
+    git_reset(repo.get(), target.get(), GIT_RESET_HARD, nullptr);
 
     return true;
 }
@@ -515,7 +527,7 @@ ConversationRepository::Impl::mergeFastforward(const git_oid* target_oid, int is
     if (is_unborn) {
         git_reference* head_ref_ptr = nullptr;
         // HEAD reference is unborn, lookup manually so we don't try to resolve it
-        if (git_reference_lookup(&head_ref_ptr, repository_.get(), "HEAD") < 0) {
+        if (git_reference_lookup(&head_ref_ptr, repo.get(), "HEAD") < 0) {
             JAMI_ERR("failed to lookup HEAD ref");
             return false;
         }
@@ -525,18 +537,13 @@ ConversationRepository::Impl::mergeFastforward(const git_oid* target_oid, int is
         const auto* symbolic_ref = git_reference_symbolic_target(head_ref.get());
 
         // Create our main reference on the target OID
-        if (git_reference_create(&target_ref_ptr,
-                                 repository_.get(),
-                                 symbolic_ref,
-                                 target_oid,
-                                 0,
-                                 nullptr)
+        if (git_reference_create(&target_ref_ptr, repo.get(), symbolic_ref, target_oid, 0, nullptr)
             < 0) {
             JAMI_ERR("failed to create main reference");
             return false;
         }
 
-    } else if (git_repository_head(&target_ref_ptr, repository_.get()) < 0) {
+    } else if (git_repository_head(&target_ref_ptr, repo.get()) < 0) {
         // HEAD exists, just lookup and resolve
         JAMI_ERR("failed to get HEAD reference");
         return false;
@@ -545,7 +552,7 @@ ConversationRepository::Impl::mergeFastforward(const git_oid* target_oid, int is
 
     // Lookup the target object
     git_object* target_ptr = nullptr;
-    if (git_object_lookup(&target_ptr, repository_.get(), target_oid, GIT_OBJ_COMMIT) != 0) {
+    if (git_object_lookup(&target_ptr, repo.get(), target_oid, GIT_OBJ_COMMIT) != 0) {
         JAMI_ERR("failed to lookup OID %s", git_oid_tostr_s(target_oid));
         return false;
     }
@@ -555,7 +562,7 @@ ConversationRepository::Impl::mergeFastforward(const git_oid* target_oid, int is
     git_checkout_options ff_checkout_options;
     git_checkout_init_options(&ff_checkout_options, GIT_CHECKOUT_OPTIONS_VERSION);
     ff_checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE;
-    if (git_checkout_tree(repository_.get(), target.get(), &ff_checkout_options) != 0) {
+    if (git_checkout_tree(repo.get(), target.get(), &ff_checkout_options) != 0) {
         JAMI_ERR("failed to checkout HEAD reference");
         return false;
     }
@@ -578,7 +585,7 @@ ConversationRepository::Impl::add(const std::string& path)
     if (!repo)
         return false;
     git_index* index_ptr = nullptr;
-    if (git_repository_index(&index_ptr, repository_.get()) < 0) {
+    if (git_repository_index(&index_ptr, repo.get()) < 0) {
         JAMI_ERR("Could not open repository index");
         return false;
     }
@@ -611,8 +618,9 @@ ConversationRepository::Impl::checkOnlyDeviceCertificate(const std::string& user
     }
 
     // Retrieve tree for recent commit
-    auto treeNew = treeAtCommit(commitId);
-    auto treeOld = treeAtCommit(parentId);
+    auto repo = repository();
+    auto treeNew = treeAtCommit(repo.get(), commitId);
+    auto treeOld = treeAtCommit(repo.get(), parentId);
     if (not treeNew or not treeOld)
         return false;
     if (!fileAtTree(deviceFile, treeNew)) {
@@ -655,8 +663,9 @@ ConversationRepository::Impl::checkVote(const std::string& userDevice,
         return false;
     }
 
-    auto treeNew = treeAtCommit(commitId);
-    auto treeOld = treeAtCommit(parentId);
+    auto repo = repository();
+    auto treeNew = treeAtCommit(repo.get(), commitId);
+    auto treeOld = treeAtCommit(repo.get(), parentId);
     if (not treeNew or not treeOld)
         return false;
 
@@ -758,7 +767,7 @@ ConversationRepository::Impl::checkValidAdd(const std::string& userDevice,
     auto userUri = cert->getIssuerUID();
 
     auto repo = repository();
-    std::string repoPath = git_repository_workdir(repository_.get());
+    std::string repoPath = git_repository_workdir(repo.get());
     if (mode() == ConversationMode::ONE_TO_ONE) {
         auto initialMembers = getInitialMembers();
         auto it = std::find(initialMembers.begin(), initialMembers.end(), uriMember);
@@ -801,10 +810,10 @@ ConversationRepository::Impl::checkValidAdd(const std::string& userDevice,
         }
     }
 
-    auto treeOld = treeAtCommit(parentId);
+    auto treeOld = treeAtCommit(repo.get(), parentId);
     if (not treeOld)
         return false;
-    auto treeNew = treeAtCommit(commitId);
+    auto treeNew = treeAtCommit(repo.get(), commitId);
     if (not treeNew)
         return false;
     auto blob_invite = fileAtTree(invitedFile, treeNew);
@@ -854,8 +863,9 @@ ConversationRepository::Impl::checkValidJoins(const std::string& userDevice,
     auto deviceFile = std::string("devices") + DIR_SEPARATOR_STR + userDevice + ".crt";
 
     // Retrieve tree for commits
-    auto treeNew = treeAtCommit(commitId);
-    auto treeOld = treeAtCommit(parentId);
+    auto repo = repository();
+    auto treeNew = treeAtCommit(repo.get(), commitId);
+    auto treeOld = treeAtCommit(repo.get(), parentId);
     if (not treeNew or not treeOld)
         return false;
 
@@ -907,8 +917,9 @@ ConversationRepository::Impl::checkValidRemove(const std::string& userDevice,
     auto removeSelf = userUri == uriMember;
 
     // Retrieve tree for recent commit
-    auto treeNew = treeAtCommit(commitId);
-    auto treeOld = treeAtCommit(parentId);
+    auto repo = repository();
+    auto treeNew = treeAtCommit(repo.get(), commitId);
+    auto treeOld = treeAtCommit(repo.get(), parentId);
     if (not treeNew or not treeOld)
         return false;
 
@@ -986,8 +997,7 @@ ConversationRepository::Impl::checkValidRemove(const std::string& userDevice,
     // If not for self check that vote is valid and not added
     auto nbAdmins = 0;
     auto nbVotes = 0;
-    auto repo = repository();
-    std::string repoPath = git_repository_workdir(repository_.get());
+    std::string repoPath = git_repository_workdir(repo.get());
     for (const auto& certificate : fileutils::readDirectory(repoPath + "admins")) {
         if (certificate.find(".crt") == std::string::npos) {
             JAMI_WARN("Incorrect file found: %s", certificate.c_str());
@@ -1009,6 +1019,51 @@ ConversationRepository::Impl::checkValidRemove(const std::string& userDevice,
     return !bannedFiles.empty();
 }
 
+bool
+ConversationRepository::Impl::checkValidProfileUpdate(const std::string& userDevice,
+                                                      const std::string& commitId,
+                                                      const std::string& parentId) const
+{
+    auto cert = tls::CertificateStore::instance().getCertificate(userDevice);
+    if (!cert && cert->issuer)
+        return false;
+    auto userUri = cert->issuer->getId().toString();
+    auto valid = false;
+    {
+        std::lock_guard<std::mutex> lk(membersMtx_);
+        for (const auto& member : members_) {
+            if (member.uri == userUri) {
+                valid = member.role <= updateProfilePermLvl_;
+                break;
+            }
+        }
+    }
+    if (!valid) {
+        JAMI_ERR("Profile changed from unauthorized user: %s", userDevice.c_str());
+        return false;
+    }
+
+    // Retrieve tree for recent commit
+    auto repo = repository();
+    auto treeNew = treeAtCommit(repo.get(), commitId);
+    auto treeOld = treeAtCommit(repo.get(), parentId);
+    if (not treeNew or not treeOld)
+        return false;
+
+    auto changedFiles = ConversationRepository::changedFiles(diffStats(commitId, parentId));
+    // Check that no weird file is added nor removed
+    for (const auto& f : changedFiles) {
+        if (f == "profile.vcf") {
+            // Ignore
+        } else {
+            JAMI_ERR("Unwanted changed file detected: %s", f.c_str());
+            return false;
+        }
+    }
+
+    return true;
+}
+
 bool
 ConversationRepository::Impl::isValidUserAtCommit(const std::string& userDevice,
                                                   const std::string& commitId) const
@@ -1019,7 +1074,8 @@ ConversationRepository::Impl::isValidUserAtCommit(const std::string& userDevice,
     auto userUri = cert->getIssuerUID();
 
     // Retrieve tree for commit
-    auto tree = treeAtCommit(commitId);
+    auto repo = repository();
+    auto tree = treeAtCommit(repo.get(), commitId);
     if (not tree)
         return false;
 
@@ -1119,7 +1175,7 @@ ConversationRepository::Impl::commit(const std::string& msg)
     // Retrieve current index
     git_index* index_ptr = nullptr;
     auto repo = repository();
-    if (git_repository_index(&index_ptr, repository_.get()) < 0) {
+    if (git_repository_index(&index_ptr, repo.get()) < 0) {
         JAMI_ERR("Could not open repository index");
         return {};
     }
@@ -1132,20 +1188,20 @@ ConversationRepository::Impl::commit(const std::string& msg)
     }
 
     git_tree* tree_ptr = nullptr;
-    if (git_tree_lookup(&tree_ptr, repository_.get(), &tree_id) < 0) {
+    if (git_tree_lookup(&tree_ptr, repo.get(), &tree_id) < 0) {
         JAMI_ERR("Could not look up initial tree");
         return {};
     }
     GitTree tree = {tree_ptr, git_tree_free};
 
     git_oid commit_id;
-    if (git_reference_name_to_id(&commit_id, repository_.get(), "HEAD") < 0) {
+    if (git_reference_name_to_id(&commit_id, repo.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) {
+    if (git_commit_lookup(&head_ptr, repo.get(), &commit_id) < 0) {
         JAMI_ERR("Could not look up HEAD commit");
         return {};
     }
@@ -1154,7 +1210,7 @@ ConversationRepository::Impl::commit(const std::string& msg)
     git_buf to_sign = {};
     const git_commit* head_ref[1] = {head_commit.get()};
     if (git_commit_create_buffer(&to_sign,
-                                 repository_.get(),
+                                 repo.get(),
                                  sig.get(),
                                  sig.get(),
                                  nullptr,
@@ -1172,7 +1228,7 @@ ConversationRepository::Impl::commit(const std::string& msg)
     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(),
+                                         repo.get(),
                                          to_sign.ptr,
                                          signed_str.c_str(),
                                          "signature")
@@ -1183,7 +1239,7 @@ ConversationRepository::Impl::commit(const std::string& msg)
 
     // 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)
+    if (git_reference_create(&ref_ptr, repo.get(), "refs/heads/main", &commit_id, true, nullptr)
         < 0) {
         JAMI_WARN("Could not move commit to main");
     }
@@ -1268,15 +1324,18 @@ ConversationRepository::Impl::mode() const
 std::string
 ConversationRepository::Impl::diffStats(const std::string& newId, const std::string& oldId) const
 {
-    if (auto d = diff(newId, oldId))
+    auto repo = repository();
+    if (auto d = diff(repo.get(), newId, oldId))
         return diffStats(d);
     return {};
 }
 
 GitDiff
-ConversationRepository::Impl::diff(const std::string& idNew, const std::string& idOld) const
+ConversationRepository::Impl::diff(git_repository* repo,
+                                   const std::string& idNew,
+                                   const std::string& idOld) const
 {
-    if (!repository_) {
+    if (!repo) {
         JAMI_ERR("Cannot get reference for HEAD");
         return {nullptr, git_diff_free};
     }
@@ -1285,18 +1344,18 @@ ConversationRepository::Impl::diff(const std::string& idNew, const std::string&
     git_oid oid;
     git_commit* commitNew = nullptr;
     if (idNew == "HEAD") {
-        if (git_reference_name_to_id(&oid, repository_.get(), "HEAD") < 0) {
+        if (git_reference_name_to_id(&oid, repo, "HEAD") < 0) {
             JAMI_ERR("Cannot get reference for HEAD");
             return {nullptr, git_diff_free};
         }
 
-        if (git_commit_lookup(&commitNew, repository_.get(), &oid) < 0) {
+        if (git_commit_lookup(&commitNew, repo, &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) {
+            || git_commit_lookup(&commitNew, repo, &oid) < 0) {
             JAMI_WARN("Failed to look up commit %s", idNew.c_str());
             return {nullptr, git_diff_free};
         }
@@ -1312,7 +1371,7 @@ ConversationRepository::Impl::diff(const std::string& idNew, const std::string&
 
     git_diff* diff_ptr = nullptr;
     if (idOld.empty()) {
-        if (git_diff_tree_to_tree(&diff_ptr, repository_.get(), nullptr, treeNew.get(), {}) < 0) {
+        if (git_diff_tree_to_tree(&diff_ptr, repo, nullptr, treeNew.get(), {}) < 0) {
             JAMI_ERR("Could not get diff to empty repository");
             return {nullptr, git_diff_free};
         }
@@ -1321,8 +1380,7 @@ ConversationRepository::Impl::diff(const std::string& idNew, const std::string&
 
     // 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) {
+    if (git_oid_fromstr(&oid, idOld.c_str()) < 0 || git_commit_lookup(&commitOld, repo, &oid) < 0) {
         JAMI_WARN("Failed to look up commit %s", idOld.c_str());
         return {nullptr, git_diff_free};
     }
@@ -1336,7 +1394,7 @@ ConversationRepository::Impl::diff(const std::string& idNew, const std::string&
     GitTree treeOld = {tOld, git_tree_free};
 
     // Calc diff
-    if (git_diff_tree_to_tree(&diff_ptr, repository_.get(), treeOld.get(), treeNew.get(), {}) < 0) {
+    if (git_diff_tree_to_tree(&diff_ptr, repo, 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};
     }
@@ -1348,7 +1406,7 @@ ConversationRepository::Impl::behind(const std::string& from) const
 {
     git_oid oid_local, oid_remote;
     auto repo = repository();
-    if (git_reference_name_to_id(&oid_local, repository_.get(), "HEAD") < 0) {
+    if (git_reference_name_to_id(&oid_local, repo.get(), "HEAD") < 0) {
         JAMI_ERR("Cannot get reference for HEAD");
         return {};
     }
@@ -1357,7 +1415,7 @@ ConversationRepository::Impl::behind(const std::string& from) const
         return {};
     }
     size_t ahead = 0, behind = 0;
-    if (git_graph_ahead_behind(&ahead, &behind, repository_.get(), &oid_local, &oid_remote) != 0) {
+    if (git_graph_ahead_behind(&ahead, &behind, repo.get(), &oid_local, &oid_remote) != 0) {
         JAMI_ERR("Cannot get commits ahead for commit %s", from.c_str());
         return {};
     }
@@ -1377,7 +1435,7 @@ ConversationRepository::Impl::log(const std::string& from, const std::string& to
     git_oid oid;
     auto repo = repository();
     if (from.empty()) {
-        if (git_reference_name_to_id(&oid, repository_.get(), "HEAD") < 0) {
+        if (git_reference_name_to_id(&oid, repo.get(), "HEAD") < 0) {
             JAMI_ERR("Cannot get reference for HEAD");
             return commits;
         }
@@ -1389,8 +1447,9 @@ ConversationRepository::Impl::log(const std::string& from, const std::string& to
     }
 
     git_revwalk* walker_ptr = nullptr;
-    if (git_revwalk_new(&walker_ptr, repository_.get()) < 0
-        || git_revwalk_push(walker_ptr, &oid) < 0) {
+    if (git_revwalk_new(&walker_ptr, repo.get()) < 0 || git_revwalk_push(walker_ptr, &oid) < 0) {
+        if (walker_ptr)
+            git_revwalk_free(walker_ptr);
         JAMI_DBG("Couldn't init revwalker for conversation %s", id_.c_str());
         return commits;
     }
@@ -1403,7 +1462,7 @@ ConversationRepository::Impl::log(const std::string& from, const std::string& to
         }
         git_commit* commit_ptr = nullptr;
         std::string id = git_oid_tostr_s(&oid);
-        if (git_commit_lookup(&commit_ptr, repository_.get(), &oid) < 0) {
+        if (git_commit_lookup(&commit_ptr, repo.get(), &oid) < 0) {
             JAMI_WARN("Failed to look up commit %s", id.c_str());
             break;
         }
@@ -1433,11 +1492,7 @@ ConversationRepository::Impl::log(const std::string& from, const std::string& to
         cc->author = std::move(author);
         cc->parents = std::move(parents);
         git_buf signature = {}, signed_data = {};
-        if (git_commit_extract_signature(&signature,
-                                         &signed_data,
-                                         repository_.get(),
-                                         &oid,
-                                         "signature")
+        if (git_commit_extract_signature(&signature, &signed_data, repo.get(), &oid, "signature")
             < 0) {
             JAMI_WARN("Could not extract signature for commit %s", id.c_str());
         } else {
@@ -1467,13 +1522,11 @@ ConversationRepository::Impl::fileAtTree(const std::string& path, const GitTree&
 }
 
 GitTree
-ConversationRepository::Impl::treeAtCommit(const std::string& commitId) const
+ConversationRepository::Impl::treeAtCommit(git_repository* repo, const std::string& commitId) const
 {
     git_oid oid;
     git_commit* commit = nullptr;
-    auto repo = repository();
-    if (git_oid_fromstr(&oid, commitId.c_str()) < 0
-        || git_commit_lookup(&commit, repository_.get(), &oid) < 0) {
+    if (git_oid_fromstr(&oid, commitId.c_str()) < 0 || git_commit_lookup(&commit, repo, &oid) < 0) {
         JAMI_WARN("Failed to look up commit %s", commitId.c_str());
         return GitTree {nullptr, git_tree_free};
     }
@@ -1533,16 +1586,85 @@ ConversationRepository::Impl::getInitialMembers() const
     return {authorId};
 }
 
+bool
+ConversationRepository::Impl::resolveConflicts(git_index* index, const std::string& other_id)
+{
+    git_index_conflict_iterator* conflict_iterator = nullptr;
+    const git_index_entry* ancestor_out = nullptr;
+    const git_index_entry* our_out = nullptr;
+    const git_index_entry* their_out = nullptr;
+
+    git_index_conflict_iterator_new(&conflict_iterator, index);
+
+    git_oid head_commit_id;
+    auto repo = repository();
+    if (git_reference_name_to_id(&head_commit_id, repo.get(), "HEAD") < 0) {
+        JAMI_ERR("Cannot get reference for HEAD");
+        return false;
+    }
+    auto commit_str = git_oid_tostr_s(&head_commit_id);
+    if (!commit_str)
+        return false;
+    auto useRemote = (other_id > commit_str); // Choose by commit version
+
+    // NOTE: for now, only authorize conflicts on "profile.vcf"
+    std::vector<git_index_entry> new_entries;
+    while (git_index_conflict_next(&ancestor_out, &our_out, &their_out, conflict_iterator)
+           != GIT_ITEROVER) {
+        if (ancestor_out && ancestor_out->path && our_out && our_out->path && their_out
+            && their_out->path) {
+            if (std::string(ancestor_out->path) == "profile.vcf") {
+                // Checkout wanted version. copy the index_entry
+                git_index_entry resolution = useRemote ? *their_out : *our_out;
+                resolution.flags &= GIT_INDEX_STAGE_NORMAL;
+                if (!(resolution.flags & GIT_IDXENTRY_VALID))
+                    resolution.flags |= GIT_IDXENTRY_VALID;
+                // NOTE: do no git_index_add yet, wait for after full conflict checks
+                new_entries.push_back(resolution);
+                continue;
+            }
+            JAMI_ERR("Conflict detected on a file that is not authorized: %s", ancestor_out->path);
+            return false;
+        }
+        return false;
+    }
+
+    for (auto& entry : new_entries)
+        git_index_add(index, &entry);
+    git_index_conflict_cleanup(index);
+    git_index_conflict_iterator_free(conflict_iterator);
+
+    // Checkout and cleanup
+    git_checkout_options opt;
+    git_checkout_options_init(&opt, GIT_CHECKOUT_OPTIONS_VERSION);
+    opt.checkout_strategy |= GIT_CHECKOUT_FORCE;
+    opt.checkout_strategy |= GIT_CHECKOUT_ALLOW_CONFLICTS;
+    if (other_id > commit_str)
+        opt.checkout_strategy |= GIT_CHECKOUT_USE_THEIRS;
+    else
+        opt.checkout_strategy |= GIT_CHECKOUT_USE_OURS;
+
+    if (git_checkout_index(repo.get(), index, &opt) < 0) {
+        const git_error* err = giterr_last();
+        if (err)
+            JAMI_ERR("Cannot checkout index: %s", err->message);
+        return false;
+    }
+
+    return true;
+}
+
 void
 ConversationRepository::Impl::initMembers()
 {
-    if (!repository_)
+    auto repo = repository();
+    if (!repo)
         return;
 
     std::vector<std::string> uris;
     std::lock_guard<std::mutex> lk(membersMtx_);
     members_.clear();
-    std::string repoPath = git_repository_workdir(repository_.get());
+    std::string repoPath = git_repository_workdir(repo.get());
     std::vector<std::string> paths = {repoPath + DIR_SEPARATOR_STR + "invited",
                                       repoPath + DIR_SEPARATOR_STR + "admins",
                                       repoPath + DIR_SEPARATOR_STR + "members",
@@ -1687,7 +1809,7 @@ ConversationRepository::cloneConversation(const std::weak_ptr<JamiAccount>& acco
     if (git_clone(&rep, url.str().c_str(), path.c_str(), nullptr) < 0) {
         const git_error* err = giterr_last();
         if (err)
-            JAMI_ERR("Error when retrieving remote conversation: %s", err->message);
+            JAMI_ERR("Error when retrieving remote conversation: %s %s", err->message, path.c_str());
         return nullptr;
     }
     git_repository_free(rep);
@@ -1827,6 +1949,21 @@ ConversationRepository::Impl::validCommits(
                     }
                     return false;
                 }
+            } else if (type == "application/update-profile") {
+                if (!checkValidProfileUpdate(userDevice, commit.id, commit.parents[0])) {
+                    JAMI_WARN("Malformed profile updates commit %s. Please check you use the "
+                              "latest version "
+                              "of Jami, or that your contact is not doing unwanted stuff.",
+                              commit.id.c_str());
+                    if (auto shared = account_.lock()) {
+                        emitSignal<DRing::ConversationSignal::OnConversationError>(
+                            shared->getAccountID(),
+                            id_,
+                            EVALIDFETCH,
+                            "Malformed profile updates commit");
+                    }
+                    return false;
+                }
             } else {
                 // Note: accept all mimetype here, as we can have new mimetypes
                 // Just avoid to add weird files
@@ -1896,7 +2033,8 @@ ConversationRepository::addMember(const std::string& uri)
         name = deviceId;
 
     // First, we need to add the member file to the repository if not present
-    std::string repoPath = git_repository_workdir(pimpl_->repository_.get());
+    auto repo = pimpl_->repository();
+    std::string repoPath = git_repository_workdir(repo.get());
 
     std::string invitedPath = repoPath + "invited";
     if (!fileutils::recursive_mkdir(invitedPath, 0700)) {
@@ -1955,8 +2093,9 @@ ConversationRepository::amend(const std::string& id, const std::string& msg)
     GitSignature sig {sig_ptr, git_signature_free};
 
     git_commit* commit_ptr = nullptr;
+    auto repo = pimpl_->repository();
     if (git_oid_fromstr(&tree_id, id.c_str()) < 0
-        || git_commit_lookup(&commit_ptr, pimpl_->repository_.get(), &tree_id) < 0) {
+        || git_commit_lookup(&commit_ptr, repo.get(), &tree_id) < 0) {
         JAMI_WARN("Failed to look up commit %s", id.c_str());
         return {};
     }
@@ -1971,12 +2110,7 @@ ConversationRepository::amend(const std::string& id, const std::string& msg)
 
     // Move commit to main branch
     git_reference* ref_ptr = nullptr;
-    if (git_reference_create(&ref_ptr,
-                             pimpl_->repository_.get(),
-                             "refs/heads/main",
-                             &commit_id,
-                             true,
-                             nullptr)
+    if (git_reference_create(&ref_ptr, repo.get(), "refs/heads/main", &commit_id, true, nullptr)
         < 0) {
         JAMI_WARN("Could not move commit to main");
     }
@@ -2005,17 +2139,15 @@ ConversationRepository::fetch(const std::string& remoteDeviceId)
     auto lastCommit = lastMsg[0].id;
 
     // Assert that repository exists
+    auto repo = pimpl_->repository();
     std::string channelName = "git://" + remoteDeviceId + '/' + pimpl_->id_;
-    auto res = git_remote_lookup(&remote_ptr, pimpl_->repository_.get(), remoteDeviceId.c_str());
+    auto res = git_remote_lookup(&remote_ptr, repo.get(), remoteDeviceId.c_str());
     if (res != 0) {
         if (res != GIT_ENOTFOUND) {
             JAMI_ERR("Couldn't lookup for remote %s", remoteDeviceId.c_str());
             return false;
         }
-        if (git_remote_create(&remote_ptr,
-                              pimpl_->repository_.get(),
-                              remoteDeviceId.c_str(),
-                              channelName.c_str())
+        if (git_remote_create(&remote_ptr, repo.get(), remoteDeviceId.c_str(), channelName.c_str())
             < 0) {
             JAMI_ERR("Could not create remote for repository for conversation %s",
                      pimpl_->id_.c_str());
@@ -2060,7 +2192,8 @@ ConversationRepository::remoteHead(const std::string& remoteDeviceId,
                                    const std::string& branch) const
 {
     git_remote* remote_ptr = nullptr;
-    if (git_remote_lookup(&remote_ptr, pimpl_->repository_.get(), remoteDeviceId.c_str()) < 0) {
+    auto repo = pimpl_->repository();
+    if (git_remote_lookup(&remote_ptr, repo.get(), remoteDeviceId.c_str()) < 0) {
         JAMI_WARN("No remote found with id: %s", remoteDeviceId.c_str());
         return {};
     }
@@ -2069,7 +2202,7 @@ ConversationRepository::remoteHead(const std::string& remoteDeviceId,
     git_reference* head_ref_ptr = nullptr;
     std::string remoteHead = "refs/remotes/" + remoteDeviceId + "/" + branch;
     git_oid commit_id;
-    if (git_reference_name_to_id(&commit_id, pimpl_->repository_.get(), remoteHead.c_str()) < 0) {
+    if (git_reference_name_to_id(&commit_id, repo.get(), remoteHead.c_str()) < 0) {
         const git_error* err = giterr_last();
         if (err)
             JAMI_ERR("failed to lookup %s ref: %s", remoteHead.c_str(), err->message);
@@ -2092,7 +2225,8 @@ ConversationRepository::commitMessage(const std::string& msg)
     auto deviceId = std::string(account->currentDeviceId());
 
     // First, we need to add device file to the repository if not present
-    std::string repoPath = git_repository_workdir(pimpl_->repository_.get());
+    auto repo = pimpl_->repository();
+    std::string repoPath = git_repository_workdir(repo.get());
     std::string path = std::string("devices") + DIR_SEPARATOR_STR + deviceId + ".crt";
     std::string devicePath = repoPath + path;
     if (!fileutils::isFile(devicePath)) {
@@ -2138,13 +2272,14 @@ bool
 ConversationRepository::merge(const std::string& merge_id)
 {
     // First, the repository must be in a clean state
-    int state = git_repository_state(pimpl_->repository_.get());
+    auto repo = pimpl_->repository();
+    int state = git_repository_state(repo.get());
     if (state != GIT_REPOSITORY_STATE_NONE) {
         JAMI_ERR("Merge operation aborted: repository is in unexpected state %d", state);
         return false;
     }
     // Checkout main (to do a `git_merge branch`)
-    if (git_repository_set_head(pimpl_->repository_.get(), "refs/heads/main") < 0) {
+    if (git_repository_set_head(repo.get(), "refs/heads/main") < 0) {
         JAMI_ERR("Merge operation aborted: couldn't checkout main branch");
         return false;
     }
@@ -2156,7 +2291,7 @@ ConversationRepository::merge(const std::string& merge_id)
         return false;
     }
     git_annotated_commit* annotated_ptr = nullptr;
-    if (git_annotated_commit_lookup(&annotated_ptr, pimpl_->repository_.get(), &commit_id) < 0) {
+    if (git_annotated_commit_lookup(&annotated_ptr, repo.get(), &commit_id) < 0) {
         JAMI_ERR("Merge operation aborted: couldn't lookup commit %s", merge_id.c_str());
         return false;
     }
@@ -2166,12 +2301,12 @@ ConversationRepository::merge(const std::string& merge_id)
     git_merge_analysis_t analysis;
     git_merge_preference_t preference;
     const git_annotated_commit* const_annotated = annotated.get();
-    if (git_merge_analysis(&analysis, &preference, pimpl_->repository_.get(), &const_annotated, 1)
-        < 0) {
+    if (git_merge_analysis(&analysis, &preference, repo.get(), &const_annotated, 1) < 0) {
         JAMI_ERR("Merge operation aborted: repository analysis failed");
         return false;
     }
 
+    // Handle easy merges
     if (analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) {
         JAMI_INFO("Already up-to-date");
         return true;
@@ -2191,40 +2326,51 @@ ConversationRepository::merge(const std::string& merge_id)
             return false;
         }
         return true;
-    } else if (analysis & GIT_MERGE_ANALYSIS_NORMAL) {
-        git_merge_options merge_opts;
-        git_merge_options_init(&merge_opts, GIT_MERGE_OPTIONS_VERSION);
-        merge_opts.file_flags = GIT_MERGE_FILE_STYLE_DIFF3;
-        git_checkout_options checkout_opts;
-        git_checkout_options_init(&checkout_opts, GIT_CHECKOUT_OPTIONS_VERSION);
-        checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_ALLOW_CONFLICTS;
-
-        if (preference & GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY) {
-            JAMI_ERR("Fast-forward is preferred, but only a merge is possible");
-            return false;
-        }
+    }
 
-        if (git_merge(pimpl_->repository_.get(), &const_annotated, 1, &merge_opts, &checkout_opts)
-            < 0) {
-            const git_error* err = giterr_last();
-            if (err)
-                JAMI_ERR("Git merge failed: %s", err->message);
-            return false;
-        }
+    // Else we want to check for conflicts
+    git_oid head_commit_id;
+    if (git_reference_name_to_id(&head_commit_id, repo.get(), "HEAD") < 0) {
+        JAMI_ERR("Cannot get reference for HEAD");
+        return false;
+    }
+
+    git_commit* head_ptr = nullptr;
+    if (git_commit_lookup(&head_ptr, repo.get(), &head_commit_id) < 0) {
+        JAMI_ERR("Could not look up HEAD commit");
+        return false;
     }
+    GitCommit head_commit {head_ptr, git_commit_free};
 
+    git_commit* other__ptr = nullptr;
+    if (git_commit_lookup(&other__ptr, repo.get(), &commit_id) < 0) {
+        JAMI_ERR("Could not look up HEAD commit");
+        return false;
+    }
+    GitCommit other_commit {other__ptr, git_commit_free};
+
+    git_merge_options merge_opts;
+    git_merge_options_init(&merge_opts, GIT_MERGE_OPTIONS_VERSION);
     git_index* index_ptr = nullptr;
-    if (git_repository_index(&index_ptr, pimpl_->repository_.get()) < 0) {
-        JAMI_ERR("Merge operation aborted: could not open repository index");
+    if (git_merge_commits(&index_ptr, repo.get(), head_commit.get(), other_commit.get(), &merge_opts)
+        < 0) {
+        const git_error* err = giterr_last();
+        if (err)
+            JAMI_ERR("Git merge failed: %s", err->message);
         return false;
     }
     GitIndex index {index_ptr, git_index_free};
     if (git_index_has_conflicts(index.get())) {
-        JAMI_WARN("Merge operation aborted: the merge operation resulted in some conflicts");
-        return false;
+        JAMI_INFO("Some conflicts were detected during the merge operations. Resolution phase.");
+        if (!pimpl_->resolveConflicts(index.get(), merge_id) or !git_add_all(repo.get())) {
+            JAMI_ERR("Merge operation aborted; Can't automatically resolve conflicts");
+            return false;
+        }
     }
+
     auto result = pimpl_->createMergeCommit(index.get(), merge_id);
     JAMI_INFO("Merge done between %s and main", merge_id.c_str());
+
     return result;
 }
 
@@ -2253,7 +2399,8 @@ std::string
 ConversationRepository::join()
 {
     // Check that not already member
-    std::string repoPath = git_repository_workdir(pimpl_->repository_.get());
+    auto repo = pimpl_->repository();
+    std::string repoPath = git_repository_workdir(repo.get());
     auto account = pimpl_->account_.lock();
     if (!account)
         return {};
@@ -2287,7 +2434,7 @@ ConversationRepository::join()
     file << parentCert->toString(true);
     file.close();
     // git add -A
-    if (!git_add_all(pimpl_->repository_.get())) {
+    if (!git_add_all(repo.get())) {
         return {};
     }
     Json::Value json;
@@ -2334,7 +2481,8 @@ ConversationRepository::leave()
         name = deviceId;
 
     // Remove related files
-    std::string repoPath = git_repository_workdir(pimpl_->repository_.get());
+    auto repo = pimpl_->repository();
+    std::string repoPath = git_repository_workdir(repo.get());
 
     std::string adminFile = repoPath + "admins" + DIR_SEPARATOR_STR + uri + ".crt";
     std::string memberFile = repoPath + "members" + DIR_SEPARATOR_STR + uri + ".crt";
@@ -2373,7 +2521,7 @@ ConversationRepository::leave()
         }
     }
 
-    if (!git_add_all(pimpl_->repository_.get())) {
+    if (!git_add_all(repo.get())) {
         return {};
     }
 
@@ -2399,7 +2547,8 @@ void
 ConversationRepository::erase()
 {
     // First, we need to add the member file to the repository if not present
-    std::string repoPath = git_repository_workdir(pimpl_->repository_.get());
+    auto repo = pimpl_->repository();
+    std::string repoPath = git_repository_workdir(repo.get());
 
     JAMI_DBG() << "Erasing " << repoPath;
     fileutils::removeAll(repoPath, true);
@@ -2416,7 +2565,8 @@ ConversationRepository::voteKick(const std::string& uri, bool isDevice)
 {
     // Add vote + commit
     // TODO simplify
-    std::string repoPath = git_repository_workdir(pimpl_->repository_.get());
+    auto repo = pimpl_->repository();
+    std::string repoPath = git_repository_workdir(repo.get());
     auto account = pimpl_->account_.lock();
     if (!account)
         return {};
@@ -2462,7 +2612,8 @@ ConversationRepository::resolveVote(const std::string& uri, bool isDevice)
     // Count ratio admin/votes
     auto nbAdmins = 0, nbVotes = 0;
     // For each admin, check if voted
-    std::string repoPath = git_repository_workdir(pimpl_->repository_.get());
+    auto repo = pimpl_->repository();
+    std::string repoPath = git_repository_workdir(repo.get());
     std::string adminsPath = repoPath + "admins";
     std::string membersPath = repoPath + "members";
     std::string devicesPath = repoPath + "devices";
@@ -2531,7 +2682,7 @@ ConversationRepository::resolveVote(const std::string& uri, bool isDevice)
         }
 
         // Commit
-        if (!git_add_all(pimpl_->repository_.get()))
+        if (!git_add_all(repo.get()))
             return {};
 
         Json::Value json;
@@ -2621,4 +2772,121 @@ ConversationRepository::pinCertificates()
     }
 }
 
+std::string
+ConversationRepository::updateInfos(const std::map<std::string, std::string>& profile)
+{
+    auto account = pimpl_->account_.lock();
+    if (!account)
+        return {};
+    auto uri = std::string(account->getUsername());
+    auto valid = false;
+    {
+        std::lock_guard<std::mutex> lk(pimpl_->membersMtx_);
+        for (const auto& member : pimpl_->members_) {
+            if (member.uri == uri) {
+                valid = member.role <= pimpl_->updateProfilePermLvl_;
+                break;
+            }
+        }
+    }
+    if (!valid) {
+        JAMI_ERR("Not enough authorization for updating infos");
+        emitSignal<DRing::ConversationSignal::OnConversationError>(
+            account->getAccountID(),
+            pimpl_->id_,
+            EUNAUTHORIZED,
+            "Not enough authorization for updating infos");
+        return {};
+    }
+
+    auto infosMap = infos();
+    for (const auto& [k, v] : profile) {
+        infosMap[k] = v;
+    }
+    auto repo = pimpl_->repository();
+    std::string repoPath = git_repository_workdir(repo.get());
+    auto profilePath = repoPath + "profile.vcf";
+    auto file = fileutils::ofstream(profilePath, std::ios::trunc | std::ios::binary);
+    if (!file.is_open()) {
+        JAMI_ERR("Could not write data to %s", profilePath.c_str());
+        return {};
+    }
+    file << vCard::Delimiter::BEGIN_TOKEN;
+    file << vCard::Delimiter::END_LINE_TOKEN;
+    file << vCard::Property::VCARD_VERSION;
+    file << ":2.1";
+    file << vCard::Delimiter::END_LINE_TOKEN;
+    auto titleIt = infosMap.find("title");
+    if (titleIt != infosMap.end()) {
+        file << vCard::Property::FORMATTED_NAME;
+        file << ":";
+        file << titleIt->second;
+        file << vCard::Delimiter::END_LINE_TOKEN;
+    }
+    auto descriptionIt = infosMap.find("description");
+    if (descriptionIt != infosMap.end()) {
+        file << vCard::Property::DESCRIPTION;
+        file << ":";
+        file << descriptionIt->second;
+        file << vCard::Delimiter::END_LINE_TOKEN;
+    }
+    file << vCard::Property::PHOTO;
+    file << vCard::Delimiter::SEPARATOR_TOKEN;
+    file << vCard::Property::BASE64;
+    auto avatarIt = infosMap.find("avatar");
+    if (avatarIt != infosMap.end()) {
+        // TODO type=png? store another way?
+        file << ":";
+        file << avatarIt->second;
+    }
+    file << vCard::Delimiter::END_LINE_TOKEN;
+    file << vCard::Delimiter::END_TOKEN;
+    file.close();
+
+    if (!pimpl_->add("profile.vcf"))
+        return {};
+    Json::Value json;
+    json["type"] = "application/update-profile";
+    Json::StreamWriterBuilder wbuilder;
+    wbuilder["commentStyle"] = "None";
+    wbuilder["indentation"] = "";
+
+    return pimpl_->commit(Json::writeString(wbuilder, json));
+}
+
+std::map<std::string, std::string>
+ConversationRepository::infos() const
+{
+    try {
+        auto repo = pimpl_->repository();
+        std::string repoPath = git_repository_workdir(repo.get());
+        auto profilePath = repoPath + "profile.vcf";
+        std::map<std::string, std::string> result;
+        if (fileutils::isFile(profilePath)) {
+            auto content = fileutils::loadTextFile(profilePath);
+            result = ConversationRepository::infosFromVCard(vCard::utils::toMap(content));
+        }
+        result["mode"] = std::to_string(static_cast<int>(mode()));
+        return result;
+    } catch (...) {
+    }
+    return {};
+}
+
+std::map<std::string, std::string>
+ConversationRepository::infosFromVCard(const std::map<std::string, std::string>& details)
+{
+    std::map<std::string, std::string> result;
+    for (const auto& [k, v] : details) {
+        if (k == vCard::Property::FORMATTED_NAME) {
+            result["title"] = v;
+        } else if (k == vCard::Property::DESCRIPTION) {
+            result["description"] = v;
+        } else if (k.find(vCard::Property::PHOTO) == 0) {
+            result["avatar"] = v;
+        }
+    }
+    return result;
+}
+
 } // namespace jami
diff --git a/src/jamidht/conversationrepository.h b/src/jamidht/conversationrepository.h
index f6c2752c5a60d9f8f80e36d5ce308ab485e22f67..1e8449a2d7b46dea12d3817d82fa7e2d9c36efa8 100644
--- a/src/jamidht/conversationrepository.h
+++ b/src/jamidht/conversationrepository.h
@@ -48,6 +48,7 @@ namespace jami {
 constexpr auto EFETCH = 1;
 constexpr auto EINVALIDMODE = 2;
 constexpr auto EVALIDFETCH = 3;
+constexpr auto EUNAUTHORIZED = 4;
 
 class JamiAccount;
 class ChannelSocket;
@@ -266,6 +267,21 @@ public:
      */
     void pinCertificates();
 
+    /**
+     * Change repository's infos
+     * @param map       New infos (supported keys: title, description, avatar)
+     * @return the commit id
+     */
+    std::string updateInfos(const std::map<std::string, std::string>& map);
+
+    /**
+     * Retrieve current infos (title, description, avatar, mode)
+     * @return infos
+     */
+    std::map<std::string, std::string> infos() const;
+    static std::map<std::string, std::string> infosFromVCard(
+        const std::map<std::string, std::string>& details);
+
 private:
     ConversationRepository() = delete;
     class Impl;
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index abf36690e9349cc25764804c5019a3a582b2f378..5d5c850e28b6ac5886d8723964a54a67553ba4c1 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -80,6 +80,7 @@
 #include "security/certstore.h"
 #include "libdevcrypto/Common.h"
 #include "base64.h"
+#include "vcard.h"
 #include "im/instant_messaging.h"
 
 #include <opendht/thread_pool.h>
@@ -1232,6 +1233,9 @@ JamiAccount::loadAccount(const std::string& archive_password,
                             req.from = uri;
                             req.conversationId = conversationId;
                             req.received = std::time(nullptr);
+                            auto details = vCard::utils::toMap(
+                                std::string_view(reinterpret_cast<const char*>(payload.data()), payload.size()));
+                            req.metadatas = ConversationRepository::infosFromVCard(details);
                             acc->conversationsRequests_[conversationId] = std::move(req);
                         }
                         emitSignal<DRing::ConfigurationSignal::IncomingTrustRequest>(
@@ -2208,11 +2212,7 @@ JamiAccount::onTrackedBuddyOnline(const dht::InfoHash& contactId)
         // offline maybe) To avoid the contact to never receive the conv request, retry there
         std::lock_guard<std::mutex> lock(configurationMutex_);
         if (accountManager_)
-            accountManager_
-                ->sendTrustRequest(id,
-                                   convId,
-                                   {}); /* TODO payload?, MessageEngine not generic and will be able
-                                           to move to conversation's requests */
+            accountManager_->sendTrustRequest(id, convId, conversationVCard(convId));
     }
 }
 
@@ -3602,7 +3602,7 @@ JamiAccount::sendTextMessage(const std::string& to,
                                    JAMI_DBG()
                                        << "[Account " << getAccountID() << "] [message " << token
                                        << "] Put encrypted " << (ok ? "ok" : "failed");
-                                   if (not ok) {
+                                   if (not ok && dhtPeerConnector_ /* Check if not joining */) {
                                        std::unique_lock<std::mutex> l(confirm->lock);
                                        auto lt = confirm->listenTokens.find(h);
                                        if (lt != confirm->listenTokens.end()) {
@@ -4121,6 +4121,73 @@ JamiAccount::getConversationRequests()
     return requests;
 }
 
+void
+JamiAccount::updateConversationInfos(const std::string& conversationId,
+                                     const std::map<std::string, std::string>& infos,
+                                     bool sync)
+{
+    std::lock_guard<std::mutex> lk(conversationsMtx_);
+    // Add a new member in the conversation
+    auto it = conversations_.find(conversationId);
+    if (it == conversations_.end()) {
+        JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str());
+        return;
+    }
+
+    auto commitId = it->second->updateInfos(infos);
+    if (commitId.empty()) {
+        JAMI_WARN("Couldn't update infos on %s", conversationId.c_str());
+        return;
+    }
+    if (!sync)
+        return;
+    // Announce new message
+    it->second->loadMessages(
+        [w = weak(), conversationId, commitId](auto&& messages) {
+            auto shared = w.lock();
+            if (not shared or messages.empty())
+                return; // should not happen
+            std::lock_guard<std::mutex> lk(shared->conversationsMtx_);
+            auto it = shared->conversations_.find(conversationId);
+            if (it == shared->conversations_.end())
+                return;
+            emitSignal<DRing::ConversationSignal::MessageReceived>(shared->getAccountID(),
+                                                                   conversationId,
+                                                                   messages.front());
+            shared->sendMessageNotification(*it->second, commitId, true);
+        },
+        commitId,
+        1);
+}
+
+std::map<std::string, std::string>
+JamiAccount::conversationInfos(const std::string& conversationId) const
+{
+    std::lock_guard<std::mutex> lk(conversationsMtx_);
+    // Add a new member in the conversation
+    auto it = conversations_.find(conversationId);
+    if (it == conversations_.end()) {
+        JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str());
+        return {};
+    }
+
+    return it->second->infos();
+}
+
+std::vector<uint8_t>
+JamiAccount::conversationVCard(const std::string& conversationId) const
+{
+    std::lock_guard<std::mutex> lk(conversationsMtx_);
+    // Add a new member in the conversation
+    auto it = conversations_.find(conversationId);
+    if (it == conversations_.end()) {
+        JAMI_ERR("Conversation %s doesn't exist", conversationId.c_str());
+        return {};
+    }
+
+    return it->second->vCard();
+}
+
 // Member management
 bool
 JamiAccount::addConversationMember(const std::string& conversationId,
diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h
index 7d425839b5111eef8fca90c3329900cbcf70e765..6a801e8d2c3b730b4eeb902c4d524202cd047a23 100644
--- a/src/jamidht/jamiaccount.h
+++ b/src/jamidht/jamiaccount.h
@@ -504,13 +504,21 @@ public:
 
     std::string_view currentDeviceId() const;
     // Conversation management
-    std::string startConversation(ConversationMode mode = ConversationMode::INVITES_ONLY, const std::string& otherMember = "");
+    std::string startConversation(ConversationMode mode = ConversationMode::INVITES_ONLY,
+                                  const std::string& otherMember = "");
     void acceptConversationRequest(const std::string& conversationId);
     void declineConversationRequest(const std::string& conversationId);
     std::vector<std::string> getConversations();
     bool removeConversation(const std::string& conversationId);
     std::vector<std::map<std::string, std::string>> getConversationRequests();
 
+    // Conversation's infos management
+    void updateConversationInfos(const std::string& conversationId,
+                                 const std::map<std::string, std::string>& infos,
+                                 bool sync = true);
+    std::map<std::string, std::string> conversationInfos(const std::string& conversationId) const;
+    std::vector<uint8_t> conversationVCard(const std::string& conversationId) const;
+
     // Member management
     bool addConversationMember(const std::string& conversationId,
                                const std::string& contactUri,
diff --git a/src/vcard.h b/src/vcard.h
new file mode 100644
index 0000000000000000000000000000000000000000..9e9f1d33e4c8d34959afbd4e89a2d9fe43ac86ec
--- /dev/null
+++ b/src/vcard.h
@@ -0,0 +1,103 @@
+/****************************************************************************
+ *    Copyright (C) 2017-2020 Savoir-faire Linux Inc.                             *
+ *   Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>           *
+ *   Author : Alexandre Lision <alexandre.lision@savoirfairelinux.com>      *
+ *                                                                          *
+ *   This library is free software; you can redistribute it and/or          *
+ *   modify it under the terms of the GNU Lesser General Public             *
+ *   License as published by the Free Software Foundation; either           *
+ *   version 2.1 of the License, or (at your option) any later version.     *
+ *                                                                          *
+ *   This library 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      *
+ *   Lesser General Public License for more details.                        *
+ *                                                                          *
+ *   You should have received a copy of the GNU General Public License      *
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.  *
+ ***************************************************************************/
+#pragma once
+
+#include <string>
+#include <string_view>
+#include <map>
+
+#include "string_utils.h"
+
+namespace vCard {
+
+constexpr static const char* PROFILE_VCF = "x-jami/jami.profile.vcard";
+
+struct Delimiter
+{
+    constexpr static const char* SEPARATOR_TOKEN = ";";
+    constexpr static const char* END_LINE_TOKEN = "\n";
+    constexpr static const char* BEGIN_TOKEN = "BEGIN:VCARD";
+    constexpr static const char* END_TOKEN = "END:VCARD";
+};
+
+struct Property
+{
+    constexpr static const char* UID = "UID";
+    constexpr static const char* VCARD_VERSION = "VERSION";
+    constexpr static const char* ADDRESS = "ADR";
+    constexpr static const char* AGENT = "AGENT";
+    constexpr static const char* BIRTHDAY = "BDAY";
+    constexpr static const char* CATEGORIES = "CATEGORIES";
+    constexpr static const char* CLASS = "CLASS";
+    constexpr static const char* DELIVERY_LABEL = "LABEL";
+    constexpr static const char* EMAIL = "EMAIL";
+    constexpr static const char* FORMATTED_NAME = "FN";
+    constexpr static const char* GEOGRAPHIC_POSITION = "GEO";
+    constexpr static const char* KEY = "KEY";
+    constexpr static const char* LOGO = "LOGO";
+    constexpr static const char* MAILER = "MAILER";
+    constexpr static const char* NAME = "N";
+    constexpr static const char* NICKNAME = "NICKNAME";
+    constexpr static const char* DESCRIPTION = "DESCRIPTION";
+    constexpr static const char* NOTE = "NOTE";
+    constexpr static const char* ORGANIZATION = "ORG";
+    constexpr static const char* PHOTO = "PHOTO";
+    constexpr static const char* PRODUCT_IDENTIFIER = "PRODID";
+    constexpr static const char* REVISION = "REV";
+    constexpr static const char* ROLE = "ROLE";
+    constexpr static const char* SORT_STRING = "SORT-STRING";
+    constexpr static const char* SOUND = "SOUND";
+    constexpr static const char* TELEPHONE = "TEL";
+    constexpr static const char* TIME_ZONE = "TZ";
+    constexpr static const char* TITLE = "TITLE";
+    constexpr static const char* URL = "URL";
+    constexpr static const char* BASE64 = "ENCODING=BASE64";
+    constexpr static const char* TYPE_PNG = "TYPE=PNG";
+    constexpr static const char* TYPE_JPEG = "TYPE=JPEG";
+    constexpr static const char* PHOTO_PNG = "PHOTO;ENCODING=BASE64;TYPE=PNG";
+    constexpr static const char* PHOTO_JPEG = "PHOTO;ENCODING=BASE64;TYPE=JPEG";
+
+    constexpr static const char* X_RINGACCOUNT = "X-RINGACCOUNTID";
+};
+
+namespace utils {
+/**
+ * Payload to vCard
+ * @param content payload
+ * @return the vCard representation
+ */
+static std::map<std::string, std::string>
+toMap(std::string_view content)
+{
+    std::map<std::string, std::string> vCard;
+
+    std::string_view line;
+    while (jami::getline(content, line)) {
+        if (line.size()) {
+            const auto dblptPos = line.find(':');
+            if (dblptPos == std::string::npos)
+                continue;
+            vCard.emplace(line.substr(0, dblptPos), line.substr(dblptPos + 1));
+        }
+    }
+    return vCard;
+}
+} // namespace utils
+
+} // namespace vCard
diff --git a/test/unitTest/conversation/conversation.cpp b/test/unitTest/conversation/conversation.cpp
index 36d7b77f9e0afb7ee6f3d8fa05200d727d533391..ddc452576a6cff20493eb777754531833328a150 100644
--- a/test/unitTest/conversation/conversation.cpp
+++ b/test/unitTest/conversation/conversation.cpp
@@ -143,6 +143,12 @@ private:
     void testAddOfflineContactThenConnect();
     void testDeclineTrustRequestDoNotGenerateAnother();
     void testConversationMemberEvent();
+    void testUpdateProfile();
+    void testCheckProfileInConversationRequest();
+    void testCheckProfileInTrustRequest();
+    void testMemberCannotUpdateProfile();
+    void testUpdateProfileWithBadFile();
+    void testFetchProfileUnauthorized();
 
     CPPUNIT_TEST_SUITE(ConversationTest);
     CPPUNIT_TEST(testCreateConversation);
@@ -189,6 +195,12 @@ private:
     CPPUNIT_TEST(testAddOfflineContactThenConnect);
     CPPUNIT_TEST(testDeclineTrustRequestDoNotGenerateAnother);
     CPPUNIT_TEST(testConversationMemberEvent);
+    CPPUNIT_TEST(testUpdateProfile);
+    CPPUNIT_TEST(testCheckProfileInConversationRequest);
+    CPPUNIT_TEST(testCheckProfileInTrustRequest);
+    CPPUNIT_TEST(testMemberCannotUpdateProfile);
+    CPPUNIT_TEST(testUpdateProfileWithBadFile);
+    CPPUNIT_TEST(testFetchProfileUnauthorized);
     CPPUNIT_TEST_SUITE_END();
 };
 
@@ -3686,6 +3698,409 @@ ConversationTest::testDeclineTrustRequestDoNotGenerateAnother()
     CPPUNIT_ASSERT(!cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 }
 
+void
+ConversationTest::testUpdateProfile()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+    std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
+    auto messageBobReceived = 0, messageAliceReceived = 0;
+    bool requestReceived = false;
+    bool conversationReady = false;
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
+        [&](const std::string& accountId,
+            const std::string& /* conversationId */,
+            std::map<std::string, std::string> /*message*/) {
+            if (accountId == bobId) {
+                messageBobReceived += 1;
+            } else {
+                messageAliceReceived += 1;
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(
+        DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
+            [&](const std::string& /*accountId*/,
+                const std::string& /* conversationId */,
+                std::map<std::string, std::string> /*metadatas*/) {
+                requestReceived = true;
+                cv.notify_one();
+            }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& /* conversationId */) {
+            if (accountId == bobId) {
+                conversationReady = true;
+                cv.notify_one();
+            }
+        }));
+    DRing::registerSignalHandlers(confHandlers);
+
+    auto convId = aliceAccount->startConversation();
+
+    CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
+
+    messageAliceReceived = 0;
+    bobAccount->acceptConversationRequest(convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
+        return conversationReady && messageAliceReceived == 1;
+    }));
+
+    messageBobReceived = 0;
+    aliceAccount->updateConversationInfos(convId, {{"title", "My awesome swarm"}});
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBobReceived == 1; }));
+
+    auto infos = bobAccount->conversationInfos(convId);
+    CPPUNIT_ASSERT(infos["title"] == "My awesome swarm");
+    CPPUNIT_ASSERT(infos["description"].empty());
+
+    DRing::unregisterSignalHandlers();
+}
+
+void
+ConversationTest::testCheckProfileInConversationRequest()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+    std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
+    auto messageBobReceived = 0, messageAliceReceived = 0;
+    bool requestReceived = false;
+    bool conversationReady = false;
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
+        [&](const std::string& accountId,
+            const std::string& /* conversationId */,
+            std::map<std::string, std::string> /*message*/) {
+            if (accountId == bobId) {
+                messageBobReceived += 1;
+            } else {
+                messageAliceReceived += 1;
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(
+        DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
+            [&](const std::string& /*accountId*/,
+                const std::string& /* conversationId */,
+                std::map<std::string, std::string> /*metadatas*/) {
+                requestReceived = true;
+                cv.notify_one();
+            }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& /* conversationId */) {
+            if (accountId == bobId) {
+                conversationReady = true;
+                cv.notify_one();
+            }
+        }));
+    DRing::registerSignalHandlers(confHandlers);
+
+    auto convId = aliceAccount->startConversation();
+    aliceAccount->updateConversationInfos(convId, {{"title", "My awesome swarm"}});
+
+    CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
+    auto requests = bobAccount->getConversationRequests();
+    CPPUNIT_ASSERT(requests.size() == 1);
+    CPPUNIT_ASSERT(requests.front()["title"] == "My awesome swarm");
+
+    DRing::unregisterSignalHandlers();
+}
+
+void
+ConversationTest::testCheckProfileInTrustRequest()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    auto aliceUri = aliceAccount->getUsername();
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+    std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
+    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false;
+    std::string convId = "";
+    std::string vcard = "BEGIN:VCARD\n\
+VERSION:2.1\n\
+FN:TITLE\n\
+DESCRIPTION:DESC\n\
+END:VCARD";
+    confHandlers.insert(DRing::exportable_callback<DRing::ConfigurationSignal::IncomingTrustRequest>(
+        [&](const std::string& account_id,
+            const std::string& /*from*/,
+            const std::vector<uint8_t>& payload,
+            time_t /*received*/) {
+            if (account_id == bobId
+                && std::string(payload.data(), payload.data() + payload.size()) == vcard)
+                requestReceived = true;
+            cv.notify_one();
+        }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& conversationId) {
+            if (accountId == aliceId) {
+                convId = conversationId;
+            } else if (accountId == bobId) {
+                conversationReady = true;
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
+        [&](const std::string& accountId,
+            const std::string& conversationId,
+            std::map<std::string, std::string> message) {
+            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
+                memberMessageGenerated = true;
+                cv.notify_one();
+            }
+        }));
+    DRing::registerSignalHandlers(confHandlers);
+    aliceAccount->addContact(bobUri);
+    std::vector<uint8_t> payload(vcard.begin(), vcard.end());
+    aliceAccount->sendTrustRequest(bobUri, payload);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(5), [&]() {
+        return !convId.empty() && requestReceived;
+    }));
+}
+
+void
+ConversationTest::testMemberCannotUpdateProfile()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+
+    auto convId = aliceAccount->startConversation();
+
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+    std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
+    auto messageBobReceived = 0, messageAliceReceived = 0;
+    bool requestReceived = false;
+    bool conversationReady = false;
+    bool errorDetected = false;
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
+        [&](const std::string& accountId,
+            const std::string& /* conversationId */,
+            std::map<std::string, std::string> /*message*/) {
+            if (accountId == bobId) {
+                messageBobReceived += 1;
+            } else {
+                messageAliceReceived += 1;
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(
+        DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
+            [&](const std::string& /*accountId*/,
+                const std::string& /* conversationId */,
+                std::map<std::string, std::string> /*metadatas*/) {
+                requestReceived = true;
+                cv.notify_one();
+            }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& /* conversationId */) {
+            if (accountId == bobId) {
+                conversationReady = true;
+                cv.notify_one();
+            }
+        }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::OnConversationError>(
+        [&](const std::string& accountId,
+            const std::string& conversationId,
+            int code,
+            const std::string& /* what */) {
+            if (accountId == bobId && conversationId == convId && code == 4)
+                errorDetected = true;
+            cv.notify_one();
+        }));
+    DRing::registerSignalHandlers(confHandlers);
+
+    CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
+
+    messageAliceReceived = 0;
+    bobAccount->acceptConversationRequest(convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
+        return conversationReady && messageAliceReceived == 1;
+    }));
+
+    messageBobReceived = 0;
+    bobAccount->updateConversationInfos(convId, {{"title", "My awesome swarm"}});
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(5), [&]() { return errorDetected; }));
+
+    DRing::unregisterSignalHandlers();
+}
+
+void
+ConversationTest::testUpdateProfileWithBadFile()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    auto convId = aliceAccount->startConversation();
+
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+    std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
+    auto messageBobReceived = 0, messageAliceReceived = 0;
+    bool requestReceived = false;
+    bool conversationReady = false;
+    bool errorDetected = false;
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
+        [&](const std::string& accountId,
+            const std::string& /* conversationId */,
+            std::map<std::string, std::string> /*message*/) {
+            if (accountId == bobId) {
+                messageBobReceived += 1;
+            } else {
+                messageAliceReceived += 1;
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(
+        DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
+            [&](const std::string& /*accountId*/,
+                const std::string& /* conversationId */,
+                std::map<std::string, std::string> /*metadatas*/) {
+                requestReceived = true;
+                cv.notify_one();
+            }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& /* conversationId */) {
+            if (accountId == bobId) {
+                conversationReady = true;
+                cv.notify_one();
+            }
+        }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::OnConversationError>(
+        [&](const std::string& accountId,
+            const std::string& conversationId,
+            int code,
+            const std::string& /* what */) {
+            if (accountId == bobId && conversationId == convId && code == 3)
+                errorDetected = true;
+            cv.notify_one();
+        }));
+    DRing::registerSignalHandlers(confHandlers);
+
+    CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
+
+    messageAliceReceived = 0;
+    bobAccount->acceptConversationRequest(convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
+        return conversationReady && messageAliceReceived == 1;
+    }));
+
+    // Update profile but with bad file
+    addFile(aliceAccount, convId, "BADFILE");
+    std::string vcard = "BEGIN:VCARD\n\
+VERSION:2.1\n\
+FN:TITLE\n\
+DESCRIPTION:DESC\n\
+END:VCARD";
+    addFile(aliceAccount, convId, "profile.vcf", vcard);
+    Json::Value root;
+    root["type"] = "application/update-profile";
+    commit(aliceAccount, convId, root);
+    errorDetected = false;
+    aliceAccount->sendMessage(convId, "hi"s);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
+
+    DRing::unregisterSignalHandlers();
+}
+
+void
+ConversationTest::testFetchProfileUnauthorized()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    auto convId = aliceAccount->startConversation();
+
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+    std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
+    auto messageBobReceived = 0, messageAliceReceived = 0;
+    bool requestReceived = false;
+    bool conversationReady = false;
+    bool errorDetected = false;
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
+        [&](const std::string& accountId,
+            const std::string& /* conversationId */,
+            std::map<std::string, std::string> /*message*/) {
+            if (accountId == bobId) {
+                messageBobReceived += 1;
+            } else {
+                messageAliceReceived += 1;
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(
+        DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
+            [&](const std::string& /*accountId*/,
+                const std::string& /* conversationId */,
+                std::map<std::string, std::string> /*metadatas*/) {
+                requestReceived = true;
+                cv.notify_one();
+            }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& /* conversationId */) {
+            if (accountId == bobId) {
+                conversationReady = true;
+                cv.notify_one();
+            }
+        }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::OnConversationError>(
+        [&](const std::string& accountId,
+            const std::string& conversationId,
+            int code,
+            const std::string& /* what */) {
+            if (accountId == aliceId && conversationId == convId && code == 3)
+                errorDetected = true;
+            cv.notify_one();
+        }));
+    DRing::registerSignalHandlers(confHandlers);
+
+    CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
+
+    messageAliceReceived = 0;
+    bobAccount->acceptConversationRequest(convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
+        return conversationReady && messageAliceReceived == 1;
+    }));
+
+    // Fake realist profile update
+    std::string vcard = "BEGIN:VCARD\n\
+VERSION:2.1\n\
+FN:TITLE\n\
+DESCRIPTION:DESC\n\
+END:VCARD";
+    addFile(bobAccount, convId, "profile.vcf", vcard);
+    Json::Value root;
+    root["type"] = "application/update-profile";
+    commit(bobAccount, convId, root);
+    errorDetected = false;
+    bobAccount->sendMessage(convId, "hi"s);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
+
+    DRing::unregisterSignalHandlers();
+}
+
 } // namespace test
 } // namespace jami
 
diff --git a/test/unitTest/conversationRepository/conversationRepository.cpp b/test/unitTest/conversationRepository/conversationRepository.cpp
index f10834d0823271f7bbaa01fbe4ed2503c21c0170..86865e93e75cb4f2f683bfe3b458d6a0c2c0fc08 100644
--- a/test/unitTest/conversationRepository/conversationRepository.cpp
+++ b/test/unitTest/conversationRepository/conversationRepository.cpp
@@ -37,6 +37,7 @@
 #include "account_const.h"
 
 #include <git2.h>
+#include <filesystem>
 
 using namespace std::string_literals;
 using namespace DRing::Account;
@@ -71,14 +72,20 @@ private:
     void testMerge();
     void testFFMerge();
     void testDiff();
+    // NOTE: Just for debug. the test is a bit complex to write
+    // due to the clone/fetch verifications (initial commits, size).
+    // void testCloneHugeRepo();
+
+    void testMergeProfileWithConflict();
 
     std::string addCommit(git_repository* repo,
                           const std::shared_ptr<JamiAccount> account,
                           const std::string& branch,
                           const std::string& commit_msg);
+    void addAll(git_repository* repo);
     bool merge_in_main(const std::shared_ptr<JamiAccount> account,
-                         git_repository* repo,
-                         const std::string& commit_ref);
+                       git_repository* repo,
+                       const std::string& commit_ref);
 
     CPPUNIT_TEST_SUITE(ConversationRepositoryTest);
     CPPUNIT_TEST(testCreateRepository);
@@ -89,6 +96,9 @@ private:
     CPPUNIT_TEST(testMerge);
     CPPUNIT_TEST(testFFMerge);
     CPPUNIT_TEST(testDiff);
+    CPPUNIT_TEST(testMergeProfileWithConflict);
+    // CPPUNIT_TEST(testCloneHugeRepo);
+
     CPPUNIT_TEST_SUITE_END();
 };
 
@@ -257,7 +267,8 @@ ConversationRepositoryTest::testCloneViaChannelSocket()
 
     aliceAccount->connectionManager().connectDevice(DeviceId(bobDeviceId),
                                                     "git://*",
-                                                    [&](std::shared_ptr<ChannelSocket> socket, const DeviceId&) {
+                                                    [&](std::shared_ptr<ChannelSocket> socket,
+                                                        const DeviceId&) {
                                                         if (socket) {
                                                             successfullyConnected = true;
                                                             sendSocket = socket;
@@ -419,12 +430,11 @@ ConversationRepositoryTest::testFetch()
     std::shared_ptr<ChannelSocket> channelSocket = nullptr;
     std::shared_ptr<ChannelSocket> sendSocket = nullptr;
 
-    bobAccount->connectionManager().onChannelRequest(
-        [&](const DeviceId&, const std::string& name) {
-            successfullyReceive = name == "git://*";
-            ccv.notify_one();
-            return true;
-        });
+    bobAccount->connectionManager().onChannelRequest([&](const DeviceId&, const std::string& name) {
+        successfullyReceive = name == "git://*";
+        ccv.notify_one();
+        return true;
+    });
 
     aliceAccount->connectionManager().onChannelRequest(
         [&](const DeviceId&, const std::string&) { return true; });
@@ -438,7 +448,8 @@ ConversationRepositoryTest::testFetch()
 
     aliceAccount->connectionManager().connectDevice(DeviceId(bobDeviceId),
                                                     "git://*",
-                                                    [&](std::shared_ptr<ChannelSocket> socket, const DeviceId&) {
+                                                    [&](std::shared_ptr<ChannelSocket> socket,
+                                                        const DeviceId&) {
                                                         if (socket) {
                                                             successfullyConnected = true;
                                                             sendSocket = socket;
@@ -471,7 +482,8 @@ ConversationRepositoryTest::testFetch()
     // Open a new channel to simulate the fact that we are later
     aliceAccount->connectionManager().connectDevice(DeviceId(bobDeviceId),
                                                     "git://*",
-                                                    [&](std::shared_ptr<ChannelSocket> socket, const DeviceId&) {
+                                                    [&](std::shared_ptr<ChannelSocket> socket,
+                                                        const DeviceId&) {
                                                         if (socket) {
                                                             successfullyConnected = true;
                                                             sendSocket = socket;
@@ -553,12 +565,26 @@ ConversationRepositoryTest::addCommit(git_repository* repo,
     }
     GitCommit head_commit {head_ptr, git_commit_free};
 
+    // Retrieve current index
+    git_index* index_ptr = nullptr;
+    if (git_repository_index(&index_ptr, repo) < 0) {
+        JAMI_ERR("Could not open repository index");
+        return {};
+    }
+    GitIndex index {index_ptr, git_index_free};
+
+    git_oid tree_id;
+    if (git_index_write_tree(&tree_id, index.get()) < 0) {
+        JAMI_ERR("Unable to write initial tree from index");
+        return {};
+    }
+
     git_tree* tree_ptr = nullptr;
-    if (git_commit_tree(&tree_ptr, head_commit.get()) < 0) {
+    if (git_tree_lookup(&tree_ptr, repo, &tree_id) < 0) {
         JAMI_ERR("Could not look up initial tree");
         return {};
     }
-    GitTree tree {tree_ptr, git_tree_free};
+    GitTree tree = {tree_ptr, git_tree_free};
 
     git_buf to_sign = {};
     const git_commit* head_ref[1] = {head_commit.get()};
@@ -605,6 +631,19 @@ ConversationRepositoryTest::addCommit(git_repository* repo,
     return commit_str ? commit_str : "";
 }
 
+void
+ConversationRepositoryTest::addAll(git_repository* repo)
+{
+    // git add -A
+    git_index* index_ptr = nullptr;
+    git_strarray array = {nullptr, 0};
+    if (git_repository_index(&index_ptr, repo) < 0)
+        return;
+    GitIndex index {index_ptr, git_index_free};
+    git_index_add_all(index.get(), &array, 0, nullptr, nullptr);
+    git_index_write(index.get());
+}
+
 void
 ConversationRepositoryTest::testMerge()
 {
@@ -696,6 +735,148 @@ ConversationRepositoryTest::testDiff()
     CPPUNIT_ASSERT(changedFiles[1] == "devices/" + aliceDeviceId + ".crt");
 }
 
+void
+ConversationRepositoryTest::testMergeProfileWithConflict()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto repository = ConversationRepository::createConversation(aliceAccount->weak());
+
+    // Assert that repository exists
+    CPPUNIT_ASSERT(repository != nullptr);
+    auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID()
+                    + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + repository->id();
+    CPPUNIT_ASSERT(fileutils::isDirectory(repoPath));
+
+    // Assert that first commit is signed by alice
+    git_repository* repo;
+    CPPUNIT_ASSERT(git_repository_open(&repo, repoPath.c_str()) == 0);
+
+    auto profile = std::ofstream(repoPath + DIR_SEPARATOR_STR + "profile.vcf");
+    if (profile.is_open()) {
+        profile << "TITLE: SWARM\n";
+        profile << "SUBTITLE: Some description\n";
+        profile << "AVATAR: BASE64\n";
+        profile.close();
+    }
+    addAll(repo);
+    auto id1 = addCommit(repo, aliceAccount, "main", "add profile");
+    profile = std::ofstream(repoPath + DIR_SEPARATOR_STR + "profile.vcf");
+    if (profile.is_open()) {
+        profile << "TITLE: SWARM\n";
+        profile << "SUBTITLE: New description\n";
+        profile << "AVATAR: BASE64\n";
+        profile.close();
+    }
+    addAll(repo);
+    auto id2 = addCommit(repo, aliceAccount, "main", "modify profile");
+
+    git_reference* ref = nullptr;
+    git_commit* commit = nullptr;
+    git_oid commit_id;
+    git_oid_fromstr(&commit_id, id1.c_str());
+    git_commit_lookup(&commit, repo, &commit_id);
+    git_branch_create(&ref, repo, "to_merge", commit, false);
+    git_reference_free(ref);
+    git_repository_set_head(repo, "refs/heads/to_merge");
+
+    profile = std::ofstream(repoPath + DIR_SEPARATOR_STR + "profile.vcf");
+    if (profile.is_open()) {
+        profile << "TITLE: SWARM\n";
+        profile << "SUBTITLE: Another description\n";
+        profile << "AVATAR: BASE64\n";
+        profile.close();
+    }
+    addAll(repo);
+    auto id3 = addCommit(repo, aliceAccount, "to_merge", "modify profile merge");
+
+    // This will create a merge commit
+    repository->merge(id3);
+    CPPUNIT_ASSERT(repository->log().size() == 5 /* Initial, add, modify 1, modify 2, merge */);
+}
+
+/*
+void
+ConversationRepositoryTest::testCloneHugeRepo()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto aliceDeviceId = std::string(aliceAccount->currentDeviceId());
+    auto uri = aliceAccount->getUsername();
+    auto bobDeviceId = std::string(bobAccount->currentDeviceId());
+
+    bobAccount->connectionManager().onICERequest([](const DeviceId&) { return true; });
+    aliceAccount->connectionManager().onICERequest([](const DeviceId&) { return true; });
+
+    auto convPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID()
+                    + DIR_SEPARATOR_STR + "conversations";
+    fileutils::recursive_mkdir(convPath);
+    const auto copyOptions = std::filesystem::copy_options::overwrite_existing |
+std::filesystem::copy_options::recursive; auto repoPath = convPath + DIR_SEPARATOR_STR +
+"8d3be095ebff73be1c43f193d02407b946d7895d"; std::filesystem::copy("/home/amarok/daemon/", repoPath,
+copyOptions);
+
+    auto repository = ConversationRepository(aliceAccount->weak(),
+"8d3be095ebff73be1c43f193d02407b946d7895d"); auto clonedPath = fileutils::get_data_dir() +
+DIR_SEPARATOR_STR + bobAccount->getAccountID()
+                      + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + repository.id();
+
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable rcv, scv;
+    bool successfullyConnected = false;
+    bool successfullyReceive = false;
+    bool receiverConnected = false;
+    std::shared_ptr<ChannelSocket> channelSocket = nullptr;
+    std::shared_ptr<ChannelSocket> sendSocket = nullptr;
+
+    bobAccount->connectionManager().onChannelRequest(
+        [&successfullyReceive](const DeviceId&, const std::string& name) {
+            successfullyReceive = name == "git://*";
+            return true;
+        });
+
+    aliceAccount->connectionManager().onChannelRequest(
+        [&successfullyReceive](const DeviceId&, const std::string& name) { return true; });
+
+    bobAccount->connectionManager().onConnectionReady(
+        [&](const DeviceId&, const std::string& name, std::shared_ptr<ChannelSocket> socket) {
+            receiverConnected = socket && (name == "git://*");
+            channelSocket = socket;
+            rcv.notify_one();
+        });
+
+    aliceAccount->connectionManager().connectDevice(DeviceId(bobDeviceId),
+                                                    "git://*",
+                                                    [&](std::shared_ptr<ChannelSocket> socket,
+                                                        const DeviceId&) {
+                                                        if (socket) {
+                                                            successfullyConnected = true;
+                                                            sendSocket = socket;
+                                                        }
+                                                        scv.notify_one();
+                                                    });
+
+    ;
+    scv.wait_for(lk, std::chrono::seconds(10));
+    CPPUNIT_ASSERT(rcv.wait_for(lk, std::chrono::seconds(10), [&] { return receiverConnected; }));
+    CPPUNIT_ASSERT(scv.wait_for(lk, std::chrono::seconds(10), [&] { return successfullyConnected;
+})); CPPUNIT_ASSERT(successfullyReceive);
+
+    bobAccount->addGitSocket(aliceDeviceId, repository.id(), channelSocket);
+    GitServer gs(aliceId, repository.id(), sendSocket);
+
+    JAMI_ERR("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+    auto cloned = ConversationRepository::cloneConversation(bobAccount->weak(),
+                                                            aliceDeviceId,
+                                                            repository.id());
+    gs.stop();
+    JAMI_ERR("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ END CLONE");
+
+    CPPUNIT_ASSERT(cloned != nullptr);
+    CPPUNIT_ASSERT(fileutils::isDirectory(clonedPath));
+}
+*/
+
 } // namespace test
 } // namespace jami