From 34e1ff8730e116694886509a8633f08e84852193 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Blin?=
 <sebastien.blin@savoirfairelinux.com>
Date: Fri, 12 Feb 2021 14:54:33 -0500
Subject: [PATCH] swarm: add "linearizedParent" into commit infos

Previously clients needed to store a graph to linearize and do
complex non-ui work with graph reconstruction and ordering.
So, this commit introduces linearizedParent that gives for each
commit what is the parent message. So clients just needs to
store a list<interactions> and insert new messages after the
announced parent.
This parent is calculated with the equivalent of
`git log --topo-order --date-order`

Change-Id: I10d0809496a9de5b9110fe8164d3d23be1c023d3
---
 src/jamidht/conversation.cpp           |  2 +
 src/jamidht/conversationrepository.cpp | 60 +++++++++++++++++++++++---
 src/jamidht/conversationrepository.h   |  6 +++
 3 files changed, 63 insertions(+), 5 deletions(-)

diff --git a/src/jamidht/conversation.cpp b/src/jamidht/conversation.cpp
index 8a2120b044..83a98283df 100644
--- a/src/jamidht/conversation.cpp
+++ b/src/jamidht/conversation.cpp
@@ -244,8 +244,10 @@ Conversation::Impl::convCommitToMap(const std::vector<ConversationCommit>& commi
                 JAMI_WARN("%s", err.c_str());
             }
         }
+        auto linearizedParent = repository_->linearizedParent(commit.id);
         message["id"] = commit.id;
         message["parents"] = parents;
+        message["linearizedParent"] = linearizedParent ? *linearizedParent : "";
         message["author"] = authorId;
         message["type"] = type;
         message["timestamp"] = std::to_string(commit.timestamp);
diff --git a/src/jamidht/conversationrepository.cpp b/src/jamidht/conversationrepository.cpp
index e86e549d45..5607c9a047 100644
--- a/src/jamidht/conversationrepository.cpp
+++ b/src/jamidht/conversationrepository.cpp
@@ -108,6 +108,7 @@ public:
     std::vector<ConversationCommit> log(const std::string& from,
                                         const std::string& to,
                                         unsigned n) const;
+    std::optional<std::string> linearizedParent(const std::string& commitId) const;
 
     GitObject fileAtTree(const std::string& path, const GitTree& tree) const;
     // NOTE! GitDiff needs to be deteleted before repo
@@ -715,7 +716,8 @@ ConversationRepository::Impl::checkVote(const std::string& userDevice,
         return false;
     }
     auto* blob = reinterpret_cast<git_blob*>(vote.get());
-    auto voteContent = std::string_view(static_cast<const char*>(git_blob_rawcontent(blob)), git_blob_rawsize(blob));
+    auto voteContent = std::string_view(static_cast<const char*>(git_blob_rawcontent(blob)),
+                                        git_blob_rawsize(blob));
     if (!voteContent.empty()) {
         JAMI_ERR("Vote file not empty: %s", votedFile.c_str());
         return false;
@@ -823,7 +825,8 @@ ConversationRepository::Impl::checkValidAdd(const std::string& userDevice,
     }
 
     auto* blob = reinterpret_cast<git_blob*>(blob_invite.get());
-    auto invitation = std::string_view(static_cast<const char*>(git_blob_rawcontent(blob)), git_blob_rawsize(blob));
+    auto invitation = std::string_view(static_cast<const char*>(git_blob_rawcontent(blob)),
+                                       git_blob_rawsize(blob));
     if (!invitation.empty()) {
         JAMI_ERR("Invitation not empty for commit %s", commitId.c_str());
         return false;
@@ -1100,9 +1103,11 @@ ConversationRepository::Impl::isValidUserAtCommit(const std::string& userDevice,
 
     // Check that certificate matches
     auto* blob = reinterpret_cast<git_blob*>(blob_device.get());
-    auto deviceCert = std::string_view(static_cast<const char*>(git_blob_rawcontent(blob)), git_blob_rawsize(blob));
+    auto deviceCert = std::string_view(static_cast<const char*>(git_blob_rawcontent(blob)),
+                                       git_blob_rawsize(blob));
     blob = reinterpret_cast<git_blob*>(blob_parent.get());
-    auto parentCert = std::string_view(static_cast<const char*>(git_blob_rawcontent(blob)), git_blob_rawsize(blob));
+    auto parentCert = std::string_view(static_cast<const char*>(git_blob_rawcontent(blob)),
+                                       git_blob_rawsize(blob));
     auto deviceCertStr = cert->toString(false);
     auto parentCertStr = cert->issuer->toString(true);
 
@@ -1454,7 +1459,7 @@ ConversationRepository::Impl::log(const std::string& from, const std::string& to
         return commits;
     }
     GitRevWalker walker {walker_ptr, git_revwalk_free};
-    git_revwalk_sorting(walker.get(), GIT_SORT_TOPOLOGICAL);
+    git_revwalk_sorting(walker.get(), GIT_SORT_TOPOLOGICAL | GIT_SORT_TIME);
 
     for (auto idx = 0u; !git_revwalk_next(&oid, walker.get()); ++idx) {
         if (n != 0 && idx == n) {
@@ -1507,6 +1512,45 @@ ConversationRepository::Impl::log(const std::string& from, const std::string& to
     return commits;
 }
 
+std::optional<std::string>
+ConversationRepository::Impl::linearizedParent(const std::string& commitId) const
+{
+    git_oid oid;
+    auto repo = repository();
+    if (!repo or git_reference_name_to_id(&oid, repo.get(), "HEAD") < 0) {
+        JAMI_ERR("Cannot get reference for HEAD");
+        return std::nullopt;
+    }
+
+    git_revwalk* walker_ptr = nullptr;
+    if (git_revwalk_new(&walker_ptr, repo.get()) < 0 || git_revwalk_push(walker_ptr, &oid) < 0) {
+        if (walker_ptr)
+            git_revwalk_free(walker_ptr);
+        // This fail can be ok in the case we check if a commit exists before pulling (so can fail
+        // there). only log if the fail is unwanted.
+        return std::nullopt;
+    }
+    GitRevWalker walker {walker_ptr, git_revwalk_free};
+    git_revwalk_sorting(walker.get(), GIT_SORT_TOPOLOGICAL | GIT_SORT_TIME);
+
+    auto ret = false;
+    for (auto idx = 0u; !git_revwalk_next(&oid, walker.get()); ++idx) {
+        git_commit* commit_ptr = nullptr;
+        std::string id = git_oid_tostr_s(&oid);
+        if (git_commit_lookup(&commit_ptr, repo.get(), &oid) < 0) {
+            JAMI_WARN("Failed to look up commit %s", id.c_str());
+            break;
+        }
+
+        if (ret)
+            return id;
+        if (id == commitId)
+            ret = true;
+    }
+
+    return std::nullopt;
+}
+
 GitObject
 ConversationRepository::Impl::fileAtTree(const std::string& path, const GitTree& tree) const
 {
@@ -2734,6 +2778,12 @@ ConversationRepository::validClone() const
     return pimpl_->validCommits(logN("", 0));
 }
 
+std::optional<std::string>
+ConversationRepository::linearizedParent(const std::string& commitId) const
+{
+    return pimpl_->linearizedParent(commitId);
+}
+
 void
 ConversationRepository::removeBranchWith(const std::string& remoteDevice)
 {
diff --git a/src/jamidht/conversationrepository.h b/src/jamidht/conversationrepository.h
index 71457148a6..2d368db6f1 100644
--- a/src/jamidht/conversationrepository.h
+++ b/src/jamidht/conversationrepository.h
@@ -191,6 +191,12 @@ public:
                                         const std::string& to = "") const;
     std::optional<ConversationCommit> getCommit(const std::string& commitId) const;
 
+    /**
+     * Get parent via topological + date sort in branch main of a commit
+     * @param commitId      id to choice
+     */
+    std::optional<std::string> linearizedParent(const std::string& commitId) const;
+
     /**
      * Merge another branch into the main branch
      * @param merge_id      The reference to merge
-- 
GitLab