LCOV - code coverage report
Current view: top level - foo/src/jamidht - gitserver.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 216 276 78.3 %
Date: 2025-08-24 09:11:10 Functions: 29 71 40.8 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2025 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  This program is free software: you can redistribute it and/or modify
       5             :  *  it under the terms of the GNU General Public License as published by
       6             :  *  the Free Software Foundation, either version 3 of the License, or
       7             :  *  (at your option) any later version.
       8             :  *
       9             :  *  This program is distributed in the hope that it will be useful,
      10             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12             :  *  GNU General Public License for more details.
      13             :  *
      14             :  *  You should have received a copy of the GNU General Public License
      15             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16             :  */
      17             : #include "gitserver.h"
      18             : 
      19             : #include "fileutils.h"
      20             : #include "logger.h"
      21             : #include "gittransport.h"
      22             : #include "manager.h"
      23             : #include <opendht/thread_pool.h>
      24             : #include <dhtnet/multiplexed_socket.h>
      25             : #include <fmt/compile.h>
      26             : 
      27             : #include <charconv>
      28             : #include <ctime>
      29             : #include <fstream>
      30             : #include <git2.h>
      31             : #include <iomanip>
      32             : 
      33             : using namespace std::string_view_literals;
      34             : constexpr auto FLUSH_PKT = "0000"sv;
      35             : constexpr auto NAK_PKT = "0008NAK\n"sv;
      36             : constexpr auto DONE_CMD = "done\n"sv;
      37             : constexpr auto WANT_CMD = "want"sv;
      38             : constexpr auto HAVE_CMD = "have"sv;
      39             : constexpr auto SERVER_CAPABILITIES
      40             :     = " HEAD\0side-band side-band-64k shallow no-progress include-tag"sv;
      41             : 
      42             : namespace jami {
      43             : 
      44             : class GitServer::Impl
      45             : {
      46             : public:
      47         762 :     Impl(const std::string& accountId,
      48             :          const std::string& repositoryId,
      49             :          const std::string& repository,
      50             :          const std::shared_ptr<dhtnet::ChannelSocket>& socket)
      51         762 :         : accountId_(accountId)
      52         762 :         , repositoryId_(repositoryId)
      53         762 :         , repository_(repository)
      54         762 :         , socket_(socket)
      55             :     {
      56        2286 :         JAMI_DEBUG("[Account {}] [Conversation {}] [GitServer {}] created",
      57             :             accountId_,
      58             :             repositoryId_,
      59             :             fmt::ptr(this));
      60             :         // Check at least if repository is correct
      61             :         git_repository* repo;
      62         762 :         if (git_repository_open(&repo, repository_.c_str()) != 0) {
      63           0 :             socket_->shutdown();
      64           0 :             return;
      65             :         }
      66         762 :         git_repository_free(repo);
      67             : 
      68         762 :         socket_->setOnRecv([this](const uint8_t* buf, std::size_t len) {
      69        5539 :             std::lock_guard lk(destroyMtx_);
      70        5539 :             if (isDestroying_)
      71           0 :                 return len;
      72        5539 :             if (parseOrder(std::string_view((const char*)buf, len)))
      73       15095 :                 while(parseOrder());
      74        5539 :             return len;
      75        5539 :         });
      76           0 :     }
      77         762 :     ~Impl() {
      78         762 :         stop();
      79        2286 :         JAMI_DEBUG("[Account {}] [Conversation {}] [GitServer {}] destroyed",
      80             :             accountId_,
      81             :             repositoryId_,
      82             :             fmt::ptr(this));
      83         762 :     }
      84        1886 :     void stop()
      85             :     {
      86        1886 :         std::lock_guard lk(destroyMtx_);
      87        1886 :         if (isDestroying_.exchange(true)) {
      88        1124 :             socket_->setOnRecv({});
      89        1124 :             socket_->shutdown();
      90             :         }
      91        1886 :     }
      92             :     bool parseOrder(std::string_view buf = {});
      93             : 
      94             :     void sendReferenceCapabilities(bool sendVersion = false);
      95             :     bool NAK();
      96             :     void ACKCommon();
      97             :     bool ACKFirst();
      98             :     void sendPackData();
      99             :     std::map<std::string, std::string> getParameters(std::string_view pkt_line);
     100             : 
     101             :     std::string accountId_ {};
     102             :     std::string repositoryId_ {};
     103             :     std::string repository_ {};
     104             :     std::shared_ptr<dhtnet::ChannelSocket> socket_ {};
     105             :     std::string wantedReference_ {};
     106             :     std::string common_ {};
     107             :     std::vector<std::string> haveRefs_ {};
     108             :     std::string cachedPkt_ {};
     109             :     std::mutex destroyMtx_ {};
     110             :     std::atomic_bool isDestroying_ {false};
     111             :     onFetchedCb onFetchedCb_ {};
     112             : };
     113             : 
     114             : bool
     115       20634 : GitServer::Impl::parseOrder(std::string_view buf)
     116             : {
     117       20634 :     std::string pkt = std::move(cachedPkt_);
     118       20634 :     if (!buf.empty())
     119        5539 :         pkt += buf;
     120             : 
     121             :     // Parse pkt len
     122             :     // Reference: https://github.com/git/git/blob/master/Documentation/technical/protocol-common.txt#L51
     123             :     // The first four bytes define the length of the packet and 0000 is a FLUSH pkt
     124             : 
     125       20634 :     unsigned int pkt_len = 0;
     126       20634 :     auto [p, ec] = std::from_chars(pkt.data(), pkt.data() + 4, pkt_len, 16);
     127       20634 :     if (ec != std::errc()) {
     128           0 :         JAMI_ERROR("[Account {}] [Conversation {}] [GitServer {}] Unable to parse packet size", accountId_, repositoryId_, fmt::ptr(this));
     129             :     }
     130       20634 :     if (pkt_len != pkt.size()) {
     131             :         // Store next packet part
     132       17642 :         if (pkt_len == 0) {
     133             :             // FLUSH_PKT
     134        3551 :             pkt_len = 4;
     135             :         }
     136       17642 :         cachedPkt_ = pkt.substr(pkt_len);
     137             :     }
     138             : 
     139       20634 :     auto pack = std::string_view(pkt).substr(4, pkt_len - 4);
     140       20634 :     if (pack == DONE_CMD) {
     141             :         // Reference:
     142             :         // https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L390 Do
     143             :         // not do multi-ack, just send ACK + pack file
     144             :         // In case of no common base, send NAK
     145        3012 :         JAMI_LOG("[Account {}] [Conversation {}] [GitServer {}] Peer negotiation is done. Answering to want order", accountId_, repositoryId_, fmt::ptr(this));
     146             :         bool sendData;
     147        1004 :         if (common_.empty())
     148         144 :             sendData = NAK();
     149             :         else
     150         860 :             sendData = ACKFirst();
     151        1004 :         if (sendData)
     152        1004 :             sendPackData();
     153        1004 :         return !cachedPkt_.empty();
     154       19630 :     } else if (pack.empty()) {
     155        3551 :         if (!haveRefs_.empty()) {
     156             :             // Reference:
     157             :             // https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L390
     158             :             // Do not do multi-ack, just send ACK + pack file In case of no common base ACK
     159         560 :             ACKCommon();
     160         560 :             NAK();
     161             :         }
     162        3551 :         return !cachedPkt_.empty();
     163             :     }
     164             : 
     165       16079 :     auto lim = pack.find(' ');
     166       16079 :     auto cmd = pack.substr(0, lim);
     167       16079 :     auto dat = (lim < pack.size()) ? pack.substr(lim+1) : std::string_view{};
     168       16079 :     if (cmd == UPLOAD_PACK_CMD) {
     169             :         // Cf: https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L166
     170             :         // References discovery
     171        5964 :         JAMI_LOG("[Account {}] [Conversation {}] [GitServer {}] Upload pack command detected.", accountId_, repositoryId_, fmt::ptr(this));
     172        1988 :         auto version = 1;
     173        1988 :         auto parameters = getParameters(dat);
     174        1988 :         auto versionIt = parameters.find("version");
     175        1988 :         bool sendVersion = false;
     176        1988 :         if (versionIt != parameters.end()) {
     177           0 :             auto [p, ec] = std::from_chars(versionIt->second.data(),
     178           0 :                                            versionIt->second.data() + versionIt->second.size(),
     179           0 :                                            version);
     180           0 :             if (ec == std::errc()) {
     181           0 :                 sendVersion = true;
     182             :             } else {
     183           0 :                 JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Invalid version detected: {}", accountId_, repositoryId_, fmt::ptr(this), versionIt->second);
     184             :             }
     185             :         }
     186        1988 :         if (version == 1) {
     187        1988 :             sendReferenceCapabilities(sendVersion);
     188             :         } else {
     189           0 :             JAMI_ERROR("[Account {}] [Conversation {}] [GitServer {}] That protocol version is not yet supported (version: {:d})", accountId_, repositoryId_, fmt::ptr(this), version);
     190             :         }
     191       16079 :     } else if (cmd == WANT_CMD) {
     192             :         // Reference:
     193             :         // https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L229
     194             :         // TODO can have more want
     195        1004 :         wantedReference_ = dat.substr(0, 40);
     196        3012 :         JAMI_LOG("[Account {}] [Conversation {}] [GitServer {}] Peer want ref: {}", accountId_, repositoryId_, fmt::ptr(this), wantedReference_);
     197       13087 :     } else if (cmd == HAVE_CMD) {
     198       13087 :         const auto& commit = haveRefs_.emplace_back(dat.substr(0, 40));
     199       13087 :         if (common_.empty()) {
     200             :             // Detect first common commit
     201             :             // Reference:
     202             :             // https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L390
     203             :             // TODO do not open repository every time
     204             :             git_repository* repo;
     205         860 :             if (git_repository_open(&repo, repository_.c_str()) != 0) {
     206           0 :                 JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to open {}", accountId_, repositoryId_, fmt::ptr(this), repository_);
     207           0 :                 return !cachedPkt_.empty();
     208             :             }
     209         860 :             GitRepository rep {repo, git_repository_free};
     210             :             git_oid commit_id;
     211         860 :             if (git_oid_fromstr(&commit_id, commit.c_str()) == 0) {
     212             :                 // Reference found
     213         860 :                 common_ = commit;
     214             :             }
     215         860 :         }
     216             :     } else {
     217           0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unwanted packet received: {}", accountId_, repositoryId_, fmt::ptr(this), pkt);
     218             :     }
     219       16079 :     return !cachedPkt_.empty();
     220       20634 : }
     221             : 
     222             : std::string
     223       28224 : toGitHex(size_t value) {
     224       84672 :     return fmt::format(FMT_COMPILE("{:04x}"), value & 0x0FFFF);
     225             : }
     226             : 
     227             : void
     228        1988 : GitServer::Impl::sendReferenceCapabilities(bool sendVersion)
     229             : {
     230             :     // Get references
     231             :     // First, get the HEAD reference
     232             :     // https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L166
     233             :     git_repository* repo;
     234        1988 :     if (git_repository_open(&repo, repository_.c_str()) != 0) {
     235           0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to open {}", accountId_, repositoryId_, fmt::ptr(this), repository_);
     236           0 :         socket_->shutdown();
     237           0 :         return;
     238             :     }
     239        1988 :     GitRepository rep {repo, git_repository_free};
     240             : 
     241             :     // Answer with the version number
     242             :     // **** When the client initially connects the server will immediately respond
     243             :     // **** with a version number (if "version=1" is sent as an Extra Parameter),
     244        1988 :     std::error_code ec;
     245        1988 :     if (sendVersion) {
     246           0 :         constexpr auto toSend = "000eversion 1\0"sv;
     247           0 :         socket_->write(reinterpret_cast<const unsigned char*>(toSend.data()),
     248             :                        toSend.size(),
     249             :                        ec);
     250           0 :         if (ec) {
     251           0 :             JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}", accountId_, repositoryId_, fmt::ptr(this), repository_, ec.message());
     252           0 :             socket_->shutdown();
     253           0 :             return;
     254             :         }
     255             :     }
     256             : 
     257             :     git_oid commit_id;
     258        1988 :     if (git_reference_name_to_id(&commit_id, rep.get(), "HEAD") < 0) {
     259           0 :         JAMI_ERROR("[Account {}] [Conversation {}] [GitServer {}] Unable to get reference for HEAD", accountId_, repositoryId_, fmt::ptr(this));
     260           0 :         socket_->shutdown();
     261           0 :         return;
     262             :     }
     263        1988 :     std::string_view currentHead = git_oid_tostr_s(&commit_id);
     264             : 
     265             :     // Send references
     266        1988 :     std::ostringstream packet;
     267        1988 :     packet << toGitHex(5 + currentHead.size() + SERVER_CAPABILITIES.size());
     268        1988 :     packet << currentHead << SERVER_CAPABILITIES << "\n";
     269             : 
     270             :     // Now, add other references
     271             :     git_strarray refs;
     272        1988 :     if (git_reference_list(&refs, rep.get()) == 0) {
     273       26761 :         for (std::size_t i = 0; i < refs.count; ++i) {
     274       24773 :             std::string_view ref = refs.strings[i];
     275       24773 :             if (git_reference_name_to_id(&commit_id, rep.get(), ref.data()) < 0) {
     276           0 :                 JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to get reference for {}", accountId_, repositoryId_, fmt::ptr(this), ref);
     277           0 :                 continue;
     278           0 :             }
     279       24773 :             currentHead = git_oid_tostr_s(&commit_id);
     280             : 
     281       24773 :             packet << toGitHex(6 /* size + space + \n */ + currentHead.size() + ref.size());
     282       24773 :             packet << currentHead << " " << ref << "\n";
     283             :         }
     284             :     }
     285        1988 :     git_strarray_dispose(&refs);
     286             : 
     287             :     // And add FLUSH
     288        1988 :     packet << FLUSH_PKT;
     289        1988 :     auto toSend = packet.str();
     290        1988 :     socket_->write(reinterpret_cast<const unsigned char*>(toSend.data()), toSend.size(), ec);
     291        1988 :     if (ec) {
     292           0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}", accountId_, repositoryId_, fmt::ptr(this), repository_, ec.message());
     293           0 :         socket_->shutdown();
     294             :     }
     295        1988 : }
     296             : 
     297             : void
     298         560 : GitServer::Impl::ACKCommon()
     299             : {
     300         560 :     std::error_code ec;
     301             :     // Ack common base
     302         560 :     if (!common_.empty()) {
     303         560 :         auto toSend = fmt::format(FMT_COMPILE("{:04x}ACK {} continue\n"),
     304         560 :             18 + common_.size() /* size + ACK + space * 2 + continue + \n */, common_);
     305         560 :         socket_->write(reinterpret_cast<const unsigned char*>(toSend.c_str()), toSend.size(), ec);
     306         560 :         if (ec) {
     307           0 :             JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}", accountId_, repositoryId_, fmt::ptr(this), repository_, ec.message());
     308           0 :             socket_->shutdown();
     309             :         }
     310         560 :     }
     311         560 : }
     312             : 
     313             : bool
     314         860 : GitServer::Impl::ACKFirst()
     315             : {
     316         860 :     std::error_code ec;
     317             :     // Ack common base
     318         860 :     if (!common_.empty()) {
     319         860 :         auto toSend = fmt::format(FMT_COMPILE("{:04x}ACK {}\n"),
     320         860 :             9 + common_.size() /* size + ACK + space + \n */, common_);
     321         860 :         socket_->write(reinterpret_cast<const unsigned char*>(toSend.c_str()), toSend.size(), ec);
     322         860 :         if (ec) {
     323           0 :             JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}", accountId_, repositoryId_, fmt::ptr(this), repository_, ec.message());
     324           0 :             socket_->shutdown();
     325           0 :             return false;
     326             :         }
     327         860 :     }
     328         860 :     return true;
     329             : }
     330             : 
     331             : bool
     332         704 : GitServer::Impl::NAK()
     333             : {
     334         704 :     std::error_code ec;
     335             :     // NAK
     336         704 :     socket_->write(reinterpret_cast<const unsigned char*>(NAK_PKT.data()), NAK_PKT.size(), ec);
     337         704 :     if (ec) {
     338           0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}", accountId_, repositoryId_, fmt::ptr(this), repository_, ec.message());
     339           0 :         socket_->shutdown();
     340           0 :         return false;
     341             :     }
     342         704 :     return true;
     343             : }
     344             : 
     345             : void
     346        1004 : GitServer::Impl::sendPackData()
     347             : {
     348             :     git_repository* repo_ptr;
     349        1004 :     if (git_repository_open(&repo_ptr, repository_.c_str()) != 0) {
     350           0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to open {}", accountId_, repositoryId_, fmt::ptr(this), repository_);
     351           0 :         return;
     352             :     }
     353        1004 :     GitRepository repo {repo_ptr, git_repository_free};
     354             : 
     355             :     git_packbuilder* pb_ptr;
     356        1004 :     if (git_packbuilder_new(&pb_ptr, repo.get()) != 0) {
     357           0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to open packbuilder for {}", accountId_, repositoryId_, fmt::ptr(this), repository_);
     358           0 :         return;
     359             :     }
     360        1004 :     GitPackBuilder pb {pb_ptr, git_packbuilder_free};
     361             : 
     362        1004 :     std::string fetched = wantedReference_;
     363             :     git_oid oid;
     364        1004 :     if (git_oid_fromstr(&oid, fetched.c_str()) < 0) {
     365           0 :         JAMI_ERROR("[Account {}] [Conversation {}] [GitServer {}] Unable to get reference for commit {}", accountId_, repositoryId_, fmt::ptr(this), fetched);
     366           0 :         return;
     367             :     }
     368             : 
     369        1004 :     git_revwalk* walker_ptr = nullptr;
     370        1004 :     if (git_revwalk_new(&walker_ptr, repo.get()) < 0 || git_revwalk_push(walker_ptr, &oid) < 0) {
     371           0 :         if (walker_ptr)
     372           0 :             git_revwalk_free(walker_ptr);
     373           0 :         return;
     374             :     }
     375        1004 :     GitRevWalker walker {walker_ptr, git_revwalk_free};
     376        1004 :     git_revwalk_sorting(walker.get(), GIT_SORT_TOPOLOGICAL);
     377             :     // Add first commit
     378        1004 :     std::set<std::string> parents;
     379        1004 :     auto haveCommit = false;
     380             : 
     381        2784 :     while (!git_revwalk_next(&oid, walker.get())) {
     382             :         // log until have refs
     383        2640 :         std::string id = git_oid_tostr_s(&oid);
     384        2640 :         haveCommit |= std::find(haveRefs_.begin(), haveRefs_.end(), id) != haveRefs_.end();
     385        2640 :         auto itParents = std::find(parents.begin(), parents.end(), id);
     386        2640 :         if (itParents != parents.end())
     387        1636 :             parents.erase(itParents);
     388        2640 :         if (haveCommit && parents.size() == 0 /* We are sure that all commits are there */)
     389         860 :             break;
     390        1780 :         if (git_packbuilder_insert_commit(pb.get(), &oid) != 0) {
     391           0 :             JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to open insert commit {} for {}", accountId_, repositoryId_, fmt::ptr(this), git_oid_tostr_s(&oid), repository_);
     392           0 :             return;
     393             :         }
     394             : 
     395             :         // Get next commit to pack
     396             :         git_commit* commit_ptr;
     397        1780 :         if (git_commit_lookup(&commit_ptr, repo.get(), &oid) < 0) {
     398           0 :             JAMI_ERROR("[Account {}] [Conversation {}] [GitServer {}] Unable to look up current commit", accountId_, repositoryId_, fmt::ptr(this));
     399           0 :             return;
     400             :         }
     401        1780 :         GitCommit commit {commit_ptr, git_commit_free};
     402        1780 :         auto parentsCount = git_commit_parentcount(commit.get());
     403        3419 :         for (unsigned int p = 0; p < parentsCount; ++p) {
     404             :             // make sure to explore all branches
     405        1639 :             const git_oid* pid = git_commit_parent_id(commit.get(), p);
     406        1639 :             if (pid)
     407        1639 :                 parents.emplace(git_oid_tostr_s(pid));
     408             :         }
     409        2640 :     }
     410             : 
     411        1004 :     git_buf data = {};
     412        1004 :     if (git_packbuilder_write_buf(&data, pb.get()) != 0) {
     413           0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to write pack data for {}", accountId_, repositoryId_, fmt::ptr(this), repository_);
     414           0 :         return;
     415             :     }
     416             : 
     417        1004 :     std::size_t sent = 0;
     418        1004 :     std::size_t len = data.size;
     419        1004 :     std::error_code ec;
     420        1004 :     std::vector<uint8_t> toSendData;
     421             :     do {
     422             :         // cf https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L166
     423             :         // In 'side-band-64k' mode it will send up to 65519 data bytes plus 1 control code, for a
     424             :         // total of up to 65520 bytes in a pkt-line.
     425        1463 :         std::size_t pkt_size = std::min(static_cast<std::size_t>(65515), len - sent);
     426        1463 :         std::string toSendHeader = toGitHex(pkt_size + 5);
     427        1463 :         toSendData.clear();
     428        1463 :         toSendData.reserve(pkt_size + 5);
     429        1463 :         toSendData.insert(toSendData.end(), toSendHeader.begin(), toSendHeader.end());
     430        1463 :         toSendData.push_back(0x1);
     431        1463 :         toSendData.insert(toSendData.end(), data.ptr + sent, data.ptr + sent + pkt_size);
     432             : 
     433        1463 :         socket_->write(reinterpret_cast<const unsigned char*>(toSendData.data()),
     434             :                        toSendData.size(),
     435             :                        ec);
     436        1463 :         if (ec) {
     437           0 :             JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}", accountId_, repositoryId_, fmt::ptr(this), repository_, ec.message());
     438           0 :             git_buf_dispose(&data);
     439           0 :             return;
     440             :         }
     441        1463 :         sent += pkt_size;
     442        2926 :     } while (sent < len);
     443        1004 :     git_buf_dispose(&data);
     444        1004 :     toSendData = {};
     445             : 
     446             :     // And finish by a little FLUSH
     447        1004 :     socket_->write(reinterpret_cast<const uint8_t*>(FLUSH_PKT.data()), FLUSH_PKT.size(), ec);
     448        1004 :     if (ec) {
     449           0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}", accountId_, repositoryId_, fmt::ptr(this), repository_, ec.message());
     450             :     }
     451             : 
     452             :     // Clear sent data
     453        1004 :     haveRefs_.clear();
     454        1004 :     wantedReference_.clear();
     455        1004 :     common_.clear();
     456        1004 :     if (onFetchedCb_)
     457        1004 :         onFetchedCb_(fetched);
     458        1004 : }
     459             : 
     460             : std::map<std::string, std::string>
     461        1988 : GitServer::Impl::getParameters(std::string_view pkt_line)
     462             : {
     463        1988 :     std::map<std::string, std::string> parameters;
     464        1988 :     std::string key, value;
     465        1988 :     auto isKey = true;
     466        1988 :     auto nullChar = 0;
     467      224644 :     for (auto letter: pkt_line) {
     468      222656 :         if (letter == '\0') {
     469             :             // parameters such as host or version are after the first \0
     470        3976 :             if (nullChar != 0 && !key.empty()) {
     471        1988 :                 parameters.try_emplace(std::move(key), std::move(value));
     472             :             }
     473        3976 :             nullChar += 1;
     474        3976 :             isKey = true;
     475        3976 :             key.clear();
     476        3976 :             value.clear();
     477      218680 :         } else if (letter == '=') {
     478        1988 :             isKey = false;
     479      216692 :         } else if (nullChar != 0) {
     480      135184 :             if (isKey) {
     481        7952 :                 key += letter;
     482             :             } else {
     483      127232 :                 value += letter;
     484             :             }
     485             :         }
     486             :     }
     487        3976 :     return parameters;
     488        1988 : }
     489             : 
     490         762 : GitServer::GitServer(const std::string& accountId,
     491             :                      const std::string& conversationId,
     492         762 :                      const std::shared_ptr<dhtnet::ChannelSocket>& client)
     493             : {
     494        1524 :     auto path = (fileutils::get_data_dir() / accountId / "conversations" / conversationId).string();
     495         762 :     pimpl_ = std::make_unique<GitServer::Impl>(accountId, conversationId, path, client);
     496         762 : }
     497             : 
     498         762 : GitServer::~GitServer()
     499             : {
     500         762 :     stop();
     501         762 :     pimpl_.reset();
     502         762 : }
     503             : 
     504             : void
     505         762 : GitServer::setOnFetched(const onFetchedCb& cb)
     506             : {
     507         762 :     if (!pimpl_)
     508           0 :         return;
     509         762 :     pimpl_->onFetchedCb_ = cb;
     510             : }
     511             : 
     512             : void
     513        1124 : GitServer::stop()
     514             : {
     515        1124 :     pimpl_->stop();
     516        1124 : }
     517             : 
     518             : } // namespace jami

Generated by: LCOV version 1.14