diff --git a/src/jamidht/conversation.cpp b/src/jamidht/conversation.cpp
index ef03874bfae07c3c7711f6033e1a0cd5d87a43ac..f4cc6c8654230b851ebb9d95497efcb25506f1a0 100644
--- a/src/jamidht/conversation.cpp
+++ b/src/jamidht/conversation.cpp
@@ -993,17 +993,35 @@ Conversation::Impl::pull()
             cb(false);
             continue;
         }
-        auto oldId = repo->getHead();
+        auto oldHead = repo->getHead();
+        std::string newHead = oldHead;
         std::unique_lock<std::mutex> lk(writeMtx_);
-        auto newCommits = mergeHistory(deviceId);
-        announce(newCommits);
+        auto commits = mergeHistory(deviceId);
+        if (!commits.empty()) {
+            newHead = commits.rbegin()->at("id");
+            // Note: Because clients needs to linearize the history, they need to know all commits
+            // that can be updated.
+            // In this case, all commits until the common merge base should be announced.
+            // The client ill need to update it's model after this.
+            std::string mergeBase = oldHead; // If fast-forward, the merge base is the previous head
+            auto newHeadCommit = repo->getCommit(newHead);
+            if (newHeadCommit != std::nullopt && newHeadCommit->parents.size() > 1) {
+                mergeBase = repo->mergeBase(newHeadCommit->parents[0], newHeadCommit->parents[1]);
+                auto updatedCommits = loadMessages("", mergeBase);
+                // We announce commits from oldest to update to newest. This generally avoid
+                // to get detached commits until they are all announced.
+                std::reverse(std::begin(updatedCommits), std::end(updatedCommits));
+                announce(updatedCommits);
+            } else {
+                announce(commits);
+            }
+        }
         lk.unlock();
         if (cb)
             cb(true);
         // Announce if profile changed
-        auto newId = repo->getHead();
-        if (oldId != newId) {
-            auto diffStats = repo->diffStats(newId, oldId);
+        if (oldHead != newHead) {
+            auto diffStats = repo->diffStats(newHead, oldHead);
             auto changedFiles = repo->changedFiles(diffStats);
             if (find(changedFiles.begin(), changedFiles.end(), "profile.vcf")
                 != changedFiles.end()) {
@@ -1050,9 +1068,7 @@ Conversation::generateInvitation() const
     Json::StreamWriterBuilder wbuilder;
     wbuilder["commentStyle"] = "None";
     wbuilder["indentation"] = "";
-    return {
-        {"application/invite+json", Json::writeString(wbuilder, root)}
-    };
+    return {{"application/invite+json", Json::writeString(wbuilder, root)}};
 }
 
 std::string
diff --git a/src/jamidht/conversationrepository.cpp b/src/jamidht/conversationrepository.cpp
index 61c9d48e3f3e57595aecbe57114f05a4db4f3fa7..129420ddd2d954ce9fc3b80a135125324df65567 100644
--- a/src/jamidht/conversationrepository.cpp
+++ b/src/jamidht/conversationrepository.cpp
@@ -42,11 +42,15 @@ constexpr size_t MAX_FETCH_SIZE {256 * 1024 * 1024}; // 256Mb
 
 namespace jami {
 
-inline std::string_view as_view(const git_blob* blob) {
+inline std::string_view
+as_view(const git_blob* blob)
+{
     return std::string_view(static_cast<const char*>(git_blob_rawcontent(blob)),
-                                       git_blob_rawsize(blob));
+                            git_blob_rawsize(blob));
 }
-inline std::string_view as_view(const GitObject& blob) {
+inline std::string_view
+as_view(const GitObject& blob)
+{
     return as_view(reinterpret_cast<git_blob*>(blob.get()));
 }
 
@@ -228,7 +232,8 @@ public:
                            const std::string& userUri,
                            std::string_view oldCert = ""sv) const
     {
-        auto cert = dht::crypto::Certificate((const uint8_t*)certContent.data(), certContent.size());
+        auto cert = dht::crypto::Certificate((const uint8_t*) certContent.data(),
+                                             certContent.size());
         auto isDeviceCertificate = cert.getId().toString() != userUri;
         auto issuerUid = cert.getIssuerUID();
         if (isDeviceCertificate && issuerUid.empty()) {
@@ -236,7 +241,8 @@ public:
             JAMI_ERR("Empty issuer for %s", cert.getId().to_c_str());
         }
         if (!oldCert.empty()) {
-            auto deviceCert = dht::crypto::Certificate((const uint8_t*)oldCert.data(), oldCert.size());
+            auto deviceCert = dht::crypto::Certificate((const uint8_t*) oldCert.data(),
+                                                       oldCert.size());
             if (isDeviceCertificate) {
                 if (issuerUid != deviceCert.getIssuerUID()) {
                     // NOTE: Here, because JAMS certificate can be incorrectly formatted, there is
@@ -495,7 +501,8 @@ initial_commit(GitRepository& repo,
         return {};
     }
 
-    std::string signed_str = base64::encode(account->identity().first->sign((const uint8_t*)to_sign.ptr, to_sign.size));
+    std::string signed_str = base64::encode(
+        account->identity().first->sign((const uint8_t*) to_sign.ptr, to_sign.size));
 
     // git commit -S
     if (git_commit_create_with_signature(&commit_id,
@@ -898,7 +905,8 @@ ConversationRepository::Impl::checkVote(const std::string& userDevice,
     }
 
     // Check votedFile path
-    static const std::regex regex_votes("votes.(\\w+).(members|devices|admins|invited).(\\w+).(\\w+)");
+    static const std::regex regex_votes(
+        "votes.(\\w+).(members|devices|admins|invited).(\\w+).(\\w+)");
     std::svmatch base_match;
     if (!std::regex_match(votedFile, base_match, regex_votes) or base_match.size() != 5) {
         JAMI_WARN("Invalid votes path: %s", votedFile.c_str());
@@ -2910,6 +2918,20 @@ ConversationRepository::merge(const std::string& merge_id, bool force)
     return {!result.empty(), result};
 }
 
+std::string
+ConversationRepository::mergeBase(const std::string& from, const std::string& to) const
+{
+    if (auto repo = pimpl_->repository()) {
+        git_oid oid, oidFrom, oidMerge;
+        git_oid_fromstr(&oidFrom, from.c_str());
+        git_oid_fromstr(&oid, to.c_str());
+        git_merge_base(&oidMerge, repo.get(), &oid, &oidFrom);
+        if (auto* commit_str = git_oid_tostr_s(&oidMerge))
+            return commit_str;
+    }
+    return {};
+}
+
 std::string
 ConversationRepository::diffStats(const std::string& newId, const std::string& oldId) const
 {
diff --git a/src/jamidht/conversationrepository.h b/src/jamidht/conversationrepository.h
index ec1420d2150c83d67241279848489df7621f05ac..7d66e9b290a8f17f4f822a81374839a6e1a27545 100644
--- a/src/jamidht/conversationrepository.h
+++ b/src/jamidht/conversationrepository.h
@@ -220,6 +220,14 @@ public:
      */
     std::pair<bool, std::string> merge(const std::string& merge_id, bool force = false);
 
+    /**
+     * Get the common parent between two branches
+     * @param from  The first branch
+     * @param to    The second branch
+     * @return the common parent
+     */
+    std::string mergeBase(const std::string& from, const std::string& to) const;
+
     /**
      * Get current diff stats between two commits
      * @param oldId     Old commit