LCOV - code coverage report
Current view: top level - src/jamidht - gitserver.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 81.9 % 281 230
Test Date: 2026-08-23 08:52:56 Functions: 78.1 % 32 25

            Line data    Source code
       1              : /*
       2              :  *  Copyright (C) 2004-2026 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 = " HEAD\0side-band side-band-64k shallow no-progress include-tag"sv;
      40              : 
      41              : namespace jami {
      42              : 
      43              : class GitServer::Impl
      44              : {
      45              : public:
      46         1046 :     Impl(const std::string& accountId,
      47              :          const std::string& repositoryId,
      48              :          const std::string& repository,
      49              :          const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket)
      50         1046 :         : accountId_(accountId)
      51         1046 :         , repositoryId_(repositoryId)
      52         1046 :         , repository_(repository)
      53         1046 :         , socket_(socket)
      54              :     {
      55         1046 :         JAMI_DEBUG("[Account {}] [Conversation {}] [GitServer {}] created", accountId_, repositoryId_, fmt::ptr(this));
      56              :         // Check at least if repository is correct
      57              :         git_repository* repo;
      58         1046 :         if (git_repository_open(&repo, repository_.c_str()) != 0) {
      59           72 :             dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
      60           36 :             return;
      61              :         }
      62         1010 :         git_repository_free(repo);
      63              : 
      64         1010 :         socket_->setOnRecv([this](const uint8_t* buf, std::size_t len) {
      65         6174 :             if (parseOrder(std::string_view((const char*) buf, len)))
      66        20490 :                 while (parseOrder())
      67              :                     ;
      68         6176 :             return len;
      69              :         });
      70            0 :     }
      71         1046 :     ~Impl()
      72              :     {
      73         1046 :         stop();
      74         1046 :         JAMI_DEBUG("[Account {}] [Conversation {}] [GitServer {}] destroyed", accountId_, repositoryId_, fmt::ptr(this));
      75         1046 :     }
      76         2573 :     void stop()
      77              :     {
      78         2573 :         if (isDestroying_.exchange(true))
      79         1538 :             return;
      80              :         // Clearing the receive callback waits for an in-flight invocation to
      81              :         // return, so no order can be parsed against a half-destroyed Impl once
      82              :         // this call completes.
      83         1042 :         socket_->setOnRecv({});
      84         2087 :         dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
      85              :     }
      86              :     bool parseOrder(std::string_view buf = {});
      87              : 
      88              :     void sendReferenceCapabilities(bool sendVersion = false);
      89              :     bool NAK();
      90              :     void notifyUpToDate();
      91              :     void ACKCommon();
      92              :     bool ACKFirst();
      93              :     void sendPackData();
      94              :     std::map<std::string, std::string> getParameters(std::string_view pkt_line);
      95              : 
      96              :     std::string accountId_ {};
      97              :     std::string repositoryId_ {};
      98              :     std::string repository_ {};
      99              :     std::shared_ptr<dhtnet::ChannelSocketInterface> socket_ {};
     100              :     std::string wantedReference_ {};
     101              :     // HEAD announced during the reference advertisement, used to acknowledge peers
     102              :     // that are already up to date and therefore never send a want.
     103              :     std::string advertisedHead_ {};
     104              :     bool sawWant_ {false};
     105              :     bool upToDateNotified_ {false};
     106              :     std::string common_ {};
     107              :     std::vector<std::string> haveRefs_ {};
     108              :     std::string cachedPkt_ {};
     109              :     std::atomic_bool isDestroying_ {false};
     110              :     onFetchedCb onFetchedCb_ {};
     111              : };
     112              : 
     113              : bool
     114        26674 : GitServer::Impl::parseOrder(std::string_view buf)
     115              : {
     116        26674 :     std::string pkt = std::move(cachedPkt_);
     117        26609 :     if (!buf.empty())
     118         6174 :         pkt += buf;
     119              : 
     120              :     // Parse pkt len
     121              :     // Reference: https://github.com/git/git/blob/master/Documentation/technical/protocol-common.txt#L51
     122              :     // The first four bytes define the length of the packet and 0000 is a FLUSH pkt
     123              : 
     124        26616 :     unsigned int pkt_len = 0;
     125        26616 :     auto [p, ec] = std::from_chars(pkt.data(), pkt.data() + 4, pkt_len, 16);
     126        26693 :     if (ec != std::errc()) {
     127            0 :         JAMI_ERROR("[Account {}] [Conversation {}] [GitServer {}] Unable to parse packet size",
     128              :                    accountId_,
     129              :                    repositoryId_,
     130              :                    fmt::ptr(this));
     131              :     }
     132        26693 :     if (pkt_len != pkt.size()) {
     133              :         // Store next packet part
     134        23272 :         if (pkt_len == 0) {
     135              :             // FLUSH_PKT
     136         4179 :             pkt_len = 4;
     137              :         }
     138        23272 :         cachedPkt_ = pkt.substr(pkt_len);
     139              :     }
     140              : 
     141        26560 :     auto pack = std::string_view(pkt).substr(4, pkt_len - 4);
     142        26621 :     if (pack == DONE_CMD) {
     143              :         // Reference:
     144              :         // https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L390 Do
     145              :         // not do multi-ack, just send ACK + pack file
     146              :         // In case of no common base, send NAK
     147         1419 :         JAMI_LOG("[Account {}] [Conversation {}] [GitServer {}] Peer negotiation is done. Answering to want order",
     148              :                  accountId_,
     149              :                  repositoryId_,
     150              :                  fmt::ptr(this));
     151              :         bool sendData;
     152         1419 :         if (common_.empty())
     153          217 :             sendData = NAK();
     154              :         else
     155         1202 :             sendData = ACKFirst();
     156         1419 :         if (sendData)
     157         1419 :             sendPackData();
     158         1419 :         return !cachedPkt_.empty();
     159        25204 :     } else if (pack.empty()) {
     160         4177 :         if (!haveRefs_.empty()) {
     161              :             // Reference:
     162              :             // https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L390
     163              :             // Do not do multi-ack, just send ACK + pack file In case of no common base ACK
     164          768 :             ACKCommon();
     165          770 :             NAK();
     166              :         } else {
     167              :             // A flush right after the reference advertisement, with neither a want nor a have,
     168              :             // is the peer telling us it already owns everything we announced. No pack is sent,
     169              :             // so the regular fetch acknowledgement never fires and the messages we advertised
     170              :             // would stay marked as sending forever. Acknowledge the announced HEAD instead.
     171         3411 :             notifyUpToDate();
     172              :         }
     173         4182 :         return !cachedPkt_.empty();
     174              :     }
     175              : 
     176        21033 :     auto lim = pack.find(' ');
     177        21051 :     auto cmd = pack.substr(0, lim);
     178        21064 :     auto dat = (lim < pack.size()) ? pack.substr(lim + 1) : std::string_view {};
     179        21070 :     if (cmd == UPLOAD_PACK_CMD) {
     180              :         // Cf: https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L166
     181              :         // References discovery
     182         1995 :         JAMI_LOG("[Account {}] [Conversation {}] [GitServer {}] Upload pack command detected.",
     183              :                  accountId_,
     184              :                  repositoryId_,
     185              :                  fmt::ptr(this));
     186         1995 :         auto version = 1;
     187         1995 :         auto parameters = getParameters(dat);
     188         1996 :         auto versionIt = parameters.find("version");
     189         1995 :         bool sendVersion = false;
     190         1995 :         if (versionIt != parameters.end()) {
     191            0 :             auto [p, ec] = std::from_chars(versionIt->second.data(),
     192            0 :                                            versionIt->second.data() + versionIt->second.size(),
     193              :                                            version);
     194            0 :             if (ec == std::errc()) {
     195            0 :                 sendVersion = true;
     196              :             } else {
     197            0 :                 JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Invalid version detected: {}",
     198              :                              accountId_,
     199              :                              repositoryId_,
     200              :                              fmt::ptr(this),
     201              :                              versionIt->second);
     202              :             }
     203              :         }
     204         1995 :         if (version == 1) {
     205         1995 :             sendReferenceCapabilities(sendVersion);
     206              :         } else {
     207            0 :             JAMI_ERROR("[Account {}] [Conversation {}] [GitServer {}] That protocol version is not yet supported "
     208              :                        "(version: {:d})",
     209              :                        accountId_,
     210              :                        repositoryId_,
     211              :                        fmt::ptr(this),
     212              :                        version);
     213              :         }
     214        21048 :     } else if (cmd == WANT_CMD) {
     215              :         // Reference:
     216              :         // https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L229
     217              :         // TODO can have more want
     218         1418 :         wantedReference_ = dat.substr(0, 40);
     219         1418 :         sawWant_ = true;
     220         1418 :         JAMI_LOG("[Account {}] [Conversation {}] [GitServer {}] Peer want ref: {}",
     221              :                  accountId_,
     222              :                  repositoryId_,
     223              :                  fmt::ptr(this),
     224              :                  wantedReference_);
     225        17632 :     } else if (cmd == HAVE_CMD) {
     226        17649 :         const auto& commit = haveRefs_.emplace_back(dat.substr(0, 40));
     227        17683 :         if (common_.empty()) {
     228              :             // Detect first common commit
     229              :             // Reference:
     230              :             // https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L390
     231              :             // TODO do not open repository every time
     232              :             git_repository* repo;
     233         1202 :             if (git_repository_open(&repo, repository_.c_str()) != 0) {
     234            0 :                 JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to open {}",
     235              :                              accountId_,
     236              :                              repositoryId_,
     237              :                              fmt::ptr(this),
     238              :                              repository_);
     239            0 :                 return !cachedPkt_.empty();
     240              :             }
     241         1201 :             GitRepository rep {repo};
     242              :             git_oid commit_id;
     243         1202 :             if (git_oid_fromstr(&commit_id, commit.c_str()) == 0) {
     244              :                 // Reference found
     245         1202 :                 common_ = commit;
     246              :             }
     247         1201 :         }
     248              :     } else {
     249            1 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unwanted packet received: {}",
     250              :                      accountId_,
     251              :                      repositoryId_,
     252              :                      fmt::ptr(this),
     253              :                      pkt);
     254              :     }
     255        21083 :     return !cachedPkt_.empty();
     256        26682 : }
     257              : 
     258              : std::string
     259        29415 : toGitHex(size_t value)
     260              : {
     261       117613 :     return fmt::format(FMT_COMPILE("{:04x}"), value & 0x0FFFF);
     262              : }
     263              : 
     264              : void
     265         1995 : GitServer::Impl::sendReferenceCapabilities(bool sendVersion)
     266              : {
     267              :     // Get references
     268              :     // First, get the HEAD reference
     269              :     // https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L166
     270              :     git_repository* repo;
     271         1995 :     if (git_repository_open(&repo, repository_.c_str()) != 0) {
     272            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to open {}",
     273              :                      accountId_,
     274              :                      repositoryId_,
     275              :                      fmt::ptr(this),
     276              :                      repository_);
     277            0 :         dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
     278            0 :         return;
     279              :     }
     280         1996 :     GitRepository rep {repo};
     281              : 
     282              :     // Answer with the version number
     283              :     // **** When the client initially connects the server will immediately respond
     284              :     // **** with a version number (if "version=1" is sent as an Extra Parameter),
     285         1995 :     std::error_code ec;
     286         1995 :     if (sendVersion) {
     287            0 :         constexpr auto toSend = "000eversion 1\0"sv;
     288            0 :         socket_->write(reinterpret_cast<const unsigned char*>(toSend.data()), toSend.size(), ec);
     289            0 :         if (ec) {
     290            0 :             JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
     291              :                          accountId_,
     292              :                          repositoryId_,
     293              :                          fmt::ptr(this),
     294              :                          repository_,
     295              :                          ec.message());
     296            0 :             dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
     297            0 :             return;
     298              :         }
     299              :     }
     300              : 
     301              :     git_oid commit_id;
     302         1995 :     if (git_reference_name_to_id(&commit_id, rep.get(), "HEAD") < 0) {
     303            0 :         JAMI_ERROR("[Account {}] [Conversation {}] [GitServer {}] Unable to get reference for HEAD",
     304              :                    accountId_,
     305              :                    repositoryId_,
     306              :                    fmt::ptr(this));
     307            0 :         dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
     308            0 :         return;
     309              :     }
     310         1996 :     std::string_view currentHead = git_oid_tostr_s(&commit_id);
     311         1996 :     advertisedHead_ = currentHead;
     312              : 
     313              :     // Send references
     314         1996 :     std::ostringstream packet;
     315         1995 :     packet << toGitHex(5 + currentHead.size() + SERVER_CAPABILITIES.size());
     316         1994 :     packet << currentHead << SERVER_CAPABILITIES << "\n";
     317              : 
     318              :     // Now, add other references
     319              :     git_strarray refs;
     320         1996 :     if (git_reference_list(&refs, rep.get()) == 0) {
     321        27376 :         for (std::size_t i = 0; i < refs.count; ++i) {
     322        25382 :             std::string_view ref = refs.strings[i];
     323        25331 :             if (git_reference_name_to_id(&commit_id, rep.get(), ref.data()) < 0) {
     324            0 :                 JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to get reference for {}",
     325              :                              accountId_,
     326              :                              repositoryId_,
     327              :                              fmt::ptr(this),
     328              :                              ref);
     329            0 :                 continue;
     330              :             }
     331        25404 :             currentHead = git_oid_tostr_s(&commit_id);
     332              : 
     333        25340 :             packet << toGitHex(6 /* size + space + \n */ + currentHead.size() + ref.size());
     334        25268 :             packet << currentHead << " " << ref << "\n";
     335              :         }
     336              :     }
     337         1994 :     git_strarray_dispose(&refs);
     338              : 
     339              :     // And add FLUSH
     340         1995 :     packet << FLUSH_PKT;
     341         1996 :     auto toSend = packet.str();
     342         1995 :     socket_->write(reinterpret_cast<const unsigned char*>(toSend.data()), toSend.size(), ec);
     343         1996 :     if (ec) {
     344            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
     345              :                      accountId_,
     346              :                      repositoryId_,
     347              :                      fmt::ptr(this),
     348              :                      repository_,
     349              :                      ec.message());
     350            0 :         dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
     351              :     }
     352         1996 : }
     353              : 
     354              : void
     355         3412 : GitServer::Impl::notifyUpToDate()
     356              : {
     357         3412 :     if (sawWant_ || upToDateNotified_ || advertisedHead_.empty())
     358         2866 :         return;
     359          546 :     upToDateNotified_ = true;
     360          546 :     JAMI_LOG("[Account {}] [Conversation {}] [GitServer {}] Peer is already up to date at {}",
     361              :              accountId_,
     362              :              repositoryId_,
     363              :              fmt::ptr(this),
     364              :              advertisedHead_);
     365          546 :     if (onFetchedCb_)
     366          546 :         onFetchedCb_(advertisedHead_);
     367              : }
     368              : 
     369              : void
     370          767 : GitServer::Impl::ACKCommon()
     371              : {
     372          767 :     std::error_code ec;
     373              :     // Ack common base
     374          768 :     if (!common_.empty()) {
     375         1540 :         auto toSend = fmt::format(FMT_COMPILE("{:04x}ACK {} continue\n"),
     376          769 :                                   18 + common_.size() /* size + ACK + space * 2 + continue + \n */,
     377          768 :                                   common_);
     378          770 :         socket_->write(reinterpret_cast<const unsigned char*>(toSend.c_str()), toSend.size(), ec);
     379          770 :         if (ec) {
     380            0 :             JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
     381              :                          accountId_,
     382              :                          repositoryId_,
     383              :                          fmt::ptr(this),
     384              :                          repository_,
     385              :                          ec.message());
     386            0 :             dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
     387              :         }
     388          770 :     }
     389          770 : }
     390              : 
     391              : bool
     392         1202 : GitServer::Impl::ACKFirst()
     393              : {
     394         1202 :     std::error_code ec;
     395              :     // Ack common base
     396         1202 :     if (!common_.empty()) {
     397         2404 :         auto toSend = fmt::format(FMT_COMPILE("{:04x}ACK {}\n"),
     398         1202 :                                   9 + common_.size() /* size + ACK + space + \n */,
     399         1202 :                                   common_);
     400         1202 :         socket_->write(reinterpret_cast<const unsigned char*>(toSend.c_str()), toSend.size(), ec);
     401         1202 :         if (ec) {
     402            0 :             JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
     403              :                          accountId_,
     404              :                          repositoryId_,
     405              :                          fmt::ptr(this),
     406              :                          repository_,
     407              :                          ec.message());
     408            0 :             dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
     409            0 :             return false;
     410              :         }
     411         1202 :     }
     412         1202 :     return true;
     413              : }
     414              : 
     415              : bool
     416          987 : GitServer::Impl::NAK()
     417              : {
     418          987 :     std::error_code ec;
     419              :     // NAK
     420          987 :     socket_->write(reinterpret_cast<const unsigned char*>(NAK_PKT.data()), NAK_PKT.size(), ec);
     421          987 :     if (ec) {
     422            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
     423              :                      accountId_,
     424              :                      repositoryId_,
     425              :                      fmt::ptr(this),
     426              :                      repository_,
     427              :                      ec.message());
     428            0 :         dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
     429            0 :         return false;
     430              :     }
     431          987 :     return true;
     432              : }
     433              : 
     434              : void
     435         1419 : GitServer::Impl::sendPackData()
     436              : {
     437              :     git_repository* repo_ptr;
     438         1419 :     if (git_repository_open(&repo_ptr, repository_.c_str()) != 0) {
     439            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to open {}",
     440              :                      accountId_,
     441              :                      repositoryId_,
     442              :                      fmt::ptr(this),
     443              :                      repository_);
     444            0 :         return;
     445              :     }
     446         1419 :     GitRepository repo {repo_ptr};
     447              : 
     448              :     git_packbuilder* pb_ptr;
     449         1419 :     if (git_packbuilder_new(&pb_ptr, repo.get()) != 0) {
     450            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to open packbuilder for {}",
     451              :                      accountId_,
     452              :                      repositoryId_,
     453              :                      fmt::ptr(this),
     454              :                      repository_);
     455            0 :         return;
     456              :     }
     457         1419 :     GitPackBuilder pb {pb_ptr};
     458              : 
     459         1419 :     std::string fetched = wantedReference_;
     460              :     git_oid oid;
     461         1419 :     if (git_oid_fromstr(&oid, fetched.c_str()) < 0) {
     462            0 :         JAMI_ERROR("[Account {}] [Conversation {}] [GitServer {}] Unable to get reference for commit {}",
     463              :                    accountId_,
     464              :                    repositoryId_,
     465              :                    fmt::ptr(this),
     466              :                    fetched);
     467            0 :         return;
     468              :     }
     469              : 
     470         1419 :     git_revwalk* walker_ptr = nullptr;
     471         1419 :     if (git_revwalk_new(&walker_ptr, repo.get()) < 0 || git_revwalk_push(walker_ptr, &oid) < 0) {
     472            0 :         if (walker_ptr)
     473            0 :             git_revwalk_free(walker_ptr);
     474            0 :         return;
     475              :     }
     476         1419 :     GitRevWalker walker {walker_ptr};
     477         1419 :     git_revwalk_sorting(walker.get(), GIT_SORT_TOPOLOGICAL);
     478              :     // Add first commit
     479         1419 :     std::set<std::string> parents;
     480         1419 :     auto haveCommit = false;
     481              : 
     482         3768 :     while (!git_revwalk_next(&oid, walker.get())) {
     483              :         // log until have refs
     484         3552 :         std::string id = git_oid_tostr_s(&oid);
     485         3550 :         haveCommit |= std::find(haveRefs_.begin(), haveRefs_.end(), id) != haveRefs_.end();
     486         3550 :         auto itParents = std::find(parents.begin(), parents.end(), id);
     487         3548 :         if (itParents != parents.end())
     488         2132 :             parents.erase(itParents);
     489         3549 :         if (haveCommit && parents.size() == 0 /* We are sure that all commits are there */)
     490         1201 :             break;
     491         2348 :         if (git_packbuilder_insert_commit(pb.get(), &oid) != 0) {
     492            0 :             JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to open insert commit {} for {}",
     493              :                          accountId_,
     494              :                          repositoryId_,
     495              :                          fmt::ptr(this),
     496              :                          git_oid_tostr_s(&oid),
     497              :                          repository_);
     498            0 :             return;
     499              :         }
     500              : 
     501              :         // Get next commit to pack
     502              :         git_commit* commit_ptr;
     503         2350 :         if (git_commit_lookup(&commit_ptr, repo.get(), &oid) < 0) {
     504            0 :             JAMI_ERROR("[Account {}] [Conversation {}] [GitServer {}] Unable to look up current commit",
     505              :                        accountId_,
     506              :                        repositoryId_,
     507              :                        fmt::ptr(this));
     508            0 :             return;
     509              :         }
     510         2350 :         GitCommit commit {commit_ptr};
     511         2350 :         auto parentsCount = git_commit_parentcount(commit.get());
     512         4494 :         for (unsigned int p = 0; p < parentsCount; ++p) {
     513              :             // make sure to explore all branches
     514         2145 :             const git_oid* pid = git_commit_parent_id(commit.get(), p);
     515         2145 :             if (pid)
     516         2145 :                 parents.emplace(git_oid_tostr_s(pid));
     517              :         }
     518         3550 :     }
     519              : 
     520         1419 :     git_buf data = {};
     521         1419 :     if (git_packbuilder_write_buf(&data, pb.get()) != 0) {
     522            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to write pack data for {}",
     523              :                      accountId_,
     524              :                      repositoryId_,
     525              :                      fmt::ptr(this),
     526              :                      repository_);
     527            0 :         return;
     528              :     }
     529              : 
     530         1419 :     std::size_t sent = 0;
     531         1419 :     std::size_t len = data.size;
     532         1419 :     std::error_code ec;
     533         1418 :     std::vector<uint8_t> toSendData;
     534              :     do {
     535              :         // cf https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L166
     536              :         // In 'side-band-64k' mode it will send up to 65519 data bytes plus 1 control code, for a
     537              :         // total of up to 65520 bytes in a pkt-line.
     538         2095 :         std::size_t pkt_size = std::min(static_cast<std::size_t>(65515), len - sent);
     539         2094 :         std::string toSendHeader = toGitHex(pkt_size + 5);
     540         2095 :         toSendData.clear();
     541         2094 :         toSendData.reserve(pkt_size + 5);
     542         2094 :         toSendData.insert(toSendData.end(), toSendHeader.begin(), toSendHeader.end());
     543         2094 :         toSendData.push_back(0x1);
     544         2093 :         toSendData.insert(toSendData.end(), data.ptr + sent, data.ptr + sent + pkt_size);
     545              : 
     546         2095 :         socket_->write(reinterpret_cast<const unsigned char*>(toSendData.data()), toSendData.size(), ec);
     547         2096 :         if (ec) {
     548            2 :             JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
     549              :                          accountId_,
     550              :                          repositoryId_,
     551              :                          fmt::ptr(this),
     552              :                          repository_,
     553              :                          ec.message());
     554            2 :             git_buf_dispose(&data);
     555            2 :             return;
     556              :         }
     557         2094 :         sent += pkt_size;
     558         4189 :     } while (sent < len);
     559         1416 :     git_buf_dispose(&data);
     560         1417 :     toSendData = {};
     561              : 
     562              :     // And finish by a little FLUSH
     563         1417 :     socket_->write(reinterpret_cast<const uint8_t*>(FLUSH_PKT.data()), FLUSH_PKT.size(), ec);
     564         1417 :     if (ec) {
     565            0 :         JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
     566              :                      accountId_,
     567              :                      repositoryId_,
     568              :                      fmt::ptr(this),
     569              :                      repository_,
     570              :                      ec.message());
     571              :     }
     572              : 
     573              :     // Clear sent data
     574         1417 :     haveRefs_.clear();
     575         1417 :     wantedReference_.clear();
     576         1417 :     common_.clear();
     577         1417 :     if (onFetchedCb_)
     578         1417 :         onFetchedCb_(fetched);
     579         1429 : }
     580              : 
     581              : std::map<std::string, std::string>
     582         1995 : GitServer::Impl::getParameters(std::string_view pkt_line)
     583              : {
     584         1995 :     std::map<std::string, std::string> parameters;
     585         1996 :     std::string key, value;
     586         1996 :     auto isKey = true;
     587         1996 :     auto nullChar = 0;
     588       224532 :     for (auto letter : pkt_line) {
     589       222554 :         if (letter == '\0') {
     590              :             // parameters such as host or version are after the first \0
     591         3991 :             if (nullChar != 0 && !key.empty()) {
     592         1996 :                 parameters.try_emplace(std::move(key), std::move(value));
     593              :             }
     594         3990 :             nullChar += 1;
     595         3990 :             isKey = true;
     596         3990 :             key.clear();
     597         3992 :             value.clear();
     598       218563 :         } else if (letter == '=') {
     599         1996 :             isKey = false;
     600       216567 :         } else if (nullChar != 0) {
     601       134995 :             if (isKey) {
     602         7983 :                 key += letter;
     603              :             } else {
     604       127012 :                 value += letter;
     605              :             }
     606              :         }
     607              :     }
     608         3992 :     return parameters;
     609         1996 : }
     610              : 
     611         1046 : GitServer::GitServer(const std::string& accountId,
     612              :                      const std::string& conversationId,
     613         1046 :                      const std::shared_ptr<dhtnet::ChannelSocketInterface>& client)
     614              : {
     615         1046 :     auto path = (fileutils::get_data_dir() / accountId / "conversations" / conversationId).string();
     616         1046 :     pimpl_ = std::make_unique<GitServer::Impl>(accountId, conversationId, path, client);
     617         1046 : }
     618              : 
     619         1046 : GitServer::~GitServer()
     620              : {
     621         1046 :     stop();
     622         1046 :     pimpl_.reset();
     623         1045 : }
     624              : 
     625              : void
     626         1043 : GitServer::setOnFetched(const onFetchedCb& cb)
     627              : {
     628         1043 :     if (!pimpl_)
     629            0 :         return;
     630         1043 :     pimpl_->onFetchedCb_ = cb;
     631              : }
     632              : 
     633              : void
     634         1531 : GitServer::stop()
     635              : {
     636         1531 :     pimpl_->stop();
     637         1537 : }
     638              : 
     639              : } // namespace jami
        

Generated by: LCOV version 2.0-1