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 1052 : Impl(const std::string& accountId,
47 : const std::string& repositoryId,
48 : const std::string& repository,
49 : const std::shared_ptr<dhtnet::ChannelSocketInterface>& socket)
50 1052 : : accountId_(accountId)
51 1052 : , repositoryId_(repositoryId)
52 1052 : , repository_(repository)
53 1052 : , socket_(socket)
54 : {
55 1052 : JAMI_DEBUG("[Account {}] [Conversation {}] [GitServer {}] created", accountId_, repositoryId_, fmt::ptr(this));
56 : // Check at least if repository is correct
57 : git_repository* repo;
58 1052 : if (git_repository_open(&repo, repository_.c_str()) != 0) {
59 62 : dht::ThreadPool::io().run([socket = socket_] { socket->shutdown(); });
60 31 : return;
61 : }
62 1021 : git_repository_free(repo);
63 :
64 1021 : socket_->setOnRecv([this](const uint8_t* buf, std::size_t len) {
65 6347 : if (parseOrder(std::string_view((const char*) buf, len)))
66 21726 : while (parseOrder())
67 : ;
68 6347 : return len;
69 : });
70 0 : }
71 1052 : ~Impl()
72 : {
73 1052 : stop();
74 1052 : JAMI_DEBUG("[Account {}] [Conversation {}] [GitServer {}] destroyed", accountId_, repositoryId_, fmt::ptr(this));
75 1052 : }
76 2597 : void stop()
77 : {
78 2597 : if (isDestroying_.exchange(true))
79 1552 : 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 1049 : socket_->setOnRecv({});
84 2100 : 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 28093 : GitServer::Impl::parseOrder(std::string_view buf)
115 : {
116 28093 : std::string pkt = std::move(cachedPkt_);
117 28006 : if (!buf.empty())
118 6343 : 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 28014 : unsigned int pkt_len = 0;
125 28014 : auto [p, ec] = std::from_chars(pkt.data(), pkt.data() + 4, pkt_len, 16);
126 28075 : 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 28075 : if (pkt_len != pkt.size()) {
133 : // Store next packet part
134 24551 : if (pkt_len == 0) {
135 : // FLUSH_PKT
136 4331 : pkt_len = 4;
137 : }
138 24551 : cachedPkt_ = pkt.substr(pkt_len);
139 : }
140 :
141 27996 : auto pack = std::string_view(pkt).substr(4, pkt_len - 4);
142 28035 : 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 1512 : 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 1514 : if (common_.empty())
153 241 : sendData = NAK();
154 : else
155 1273 : sendData = ACKFirst();
156 1514 : if (sendData)
157 1514 : sendPackData();
158 1514 : return !cachedPkt_.empty();
159 26519 : } else if (pack.empty()) {
160 4333 : 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 814 : ACKCommon();
165 814 : 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 3517 : notifyUpToDate();
172 : }
173 4332 : return !cachedPkt_.empty();
174 : }
175 :
176 22192 : auto lim = pack.find(' ');
177 22207 : auto cmd = pack.substr(0, lim);
178 22225 : auto dat = (lim < pack.size()) ? pack.substr(lim + 1) : std::string_view {};
179 22218 : if (cmd == UPLOAD_PACK_CMD) {
180 : // Cf: https://github.com/git/git/blob/master/Documentation/technical/pack-protocol.txt#L166
181 : // References discovery
182 2014 : JAMI_LOG("[Account {}] [Conversation {}] [GitServer {}] Upload pack command detected.",
183 : accountId_,
184 : repositoryId_,
185 : fmt::ptr(this));
186 2014 : auto version = 1;
187 2014 : auto parameters = getParameters(dat);
188 2013 : auto versionIt = parameters.find("version");
189 2012 : bool sendVersion = false;
190 2012 : 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 2014 : if (version == 1) {
205 2014 : 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 22224 : } 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 1514 : wantedReference_ = dat.substr(0, 40);
219 1514 : sawWant_ = true;
220 1514 : JAMI_LOG("[Account {}] [Conversation {}] [GitServer {}] Peer want ref: {}",
221 : accountId_,
222 : repositoryId_,
223 : fmt::ptr(this),
224 : wantedReference_);
225 18679 : } else if (cmd == HAVE_CMD) {
226 18688 : const auto& commit = haveRefs_.emplace_back(dat.substr(0, 40));
227 18721 : 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 1273 : 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 1273 : GitRepository rep {repo};
242 : git_oid commit_id;
243 1273 : if (git_oid_fromstr(&commit_id, commit.c_str()) == 0) {
244 : // Reference found
245 1272 : common_ = commit;
246 : }
247 1272 : }
248 : } else {
249 0 : JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unwanted packet received: {}",
250 : accountId_,
251 : repositoryId_,
252 : fmt::ptr(this),
253 : pkt);
254 : }
255 22245 : return !cachedPkt_.empty();
256 28081 : }
257 :
258 : std::string
259 27421 : toGitHex(size_t value)
260 : {
261 109648 : return fmt::format(FMT_COMPILE("{:04x}"), value & 0x0FFFF);
262 : }
263 :
264 : void
265 2014 : 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 2014 : 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 2014 : 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 2014 : std::error_code ec;
286 2013 : 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 2013 : 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 2014 : std::string_view currentHead = git_oid_tostr_s(&commit_id);
311 2013 : advertisedHead_ = currentHead;
312 :
313 : // Send references
314 2013 : std::ostringstream packet;
315 2014 : packet << toGitHex(5 + currentHead.size() + SERVER_CAPABILITIES.size());
316 2014 : packet << currentHead << SERVER_CAPABILITIES << "\n";
317 :
318 : // Now, add other references
319 : git_strarray refs;
320 2014 : if (git_reference_list(&refs, rep.get()) == 0) {
321 25287 : for (std::size_t i = 0; i < refs.count; ++i) {
322 23274 : std::string_view ref = refs.strings[i];
323 23242 : 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 23276 : currentHead = git_oid_tostr_s(&commit_id);
332 :
333 23242 : packet << toGitHex(6 /* size + space + \n */ + currentHead.size() + ref.size());
334 23198 : packet << currentHead << " " << ref << "\n";
335 : }
336 : }
337 2013 : git_strarray_dispose(&refs);
338 :
339 : // And add FLUSH
340 2014 : packet << FLUSH_PKT;
341 2014 : auto toSend = packet.str();
342 2014 : socket_->write(reinterpret_cast<const unsigned char*>(toSend.data()), toSend.size(), ec);
343 2014 : 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 2013 : }
353 :
354 : void
355 3519 : GitServer::Impl::notifyUpToDate()
356 : {
357 3519 : if (sawWant_ || upToDateNotified_ || advertisedHead_.empty())
358 3046 : return;
359 472 : upToDateNotified_ = true;
360 472 : JAMI_LOG("[Account {}] [Conversation {}] [GitServer {}] Peer is already up to date at {}",
361 : accountId_,
362 : repositoryId_,
363 : fmt::ptr(this),
364 : advertisedHead_);
365 473 : if (onFetchedCb_)
366 473 : onFetchedCb_(advertisedHead_);
367 : }
368 :
369 : void
370 814 : GitServer::Impl::ACKCommon()
371 : {
372 814 : std::error_code ec;
373 : // Ack common base
374 814 : if (!common_.empty()) {
375 1628 : auto toSend = fmt::format(FMT_COMPILE("{:04x}ACK {} continue\n"),
376 814 : 18 + common_.size() /* size + ACK + space * 2 + continue + \n */,
377 814 : common_);
378 814 : socket_->write(reinterpret_cast<const unsigned char*>(toSend.c_str()), toSend.size(), ec);
379 814 : 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 814 : }
389 814 : }
390 :
391 : bool
392 1273 : GitServer::Impl::ACKFirst()
393 : {
394 1273 : std::error_code ec;
395 : // Ack common base
396 1273 : if (!common_.empty()) {
397 2546 : auto toSend = fmt::format(FMT_COMPILE("{:04x}ACK {}\n"),
398 1273 : 9 + common_.size() /* size + ACK + space + \n */,
399 1273 : common_);
400 1273 : socket_->write(reinterpret_cast<const unsigned char*>(toSend.c_str()), toSend.size(), ec);
401 1273 : 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 1273 : }
412 1273 : return true;
413 : }
414 :
415 : bool
416 1055 : GitServer::Impl::NAK()
417 : {
418 1055 : std::error_code ec;
419 : // NAK
420 1055 : socket_->write(reinterpret_cast<const unsigned char*>(NAK_PKT.data()), NAK_PKT.size(), ec);
421 1055 : 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 1055 : return true;
432 : }
433 :
434 : void
435 1514 : GitServer::Impl::sendPackData()
436 : {
437 : git_repository* repo_ptr;
438 1514 : 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 1514 : GitRepository repo {repo_ptr};
447 :
448 : git_packbuilder* pb_ptr;
449 1514 : 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 1514 : GitPackBuilder pb {pb_ptr};
458 :
459 1514 : std::string fetched = wantedReference_;
460 : git_oid oid;
461 1513 : 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 1514 : git_revwalk* walker_ptr = nullptr;
471 1514 : 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 1514 : GitRevWalker walker {walker_ptr};
477 1514 : git_revwalk_sorting(walker.get(), GIT_SORT_TOPOLOGICAL);
478 : // Add first commit
479 1512 : std::set<std::string> parents;
480 1513 : auto haveCommit = false;
481 :
482 3966 : while (!git_revwalk_next(&oid, walker.get())) {
483 : // log until have refs
484 3727 : std::string id = git_oid_tostr_s(&oid);
485 3723 : haveCommit |= std::find(haveRefs_.begin(), haveRefs_.end(), id) != haveRefs_.end();
486 3726 : auto itParents = std::find(parents.begin(), parents.end(), id);
487 3727 : if (itParents != parents.end())
488 2213 : parents.erase(itParents);
489 3726 : if (haveCommit && parents.size() == 0 /* We are sure that all commits are there */)
490 1272 : break;
491 2454 : 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 2454 : 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 2454 : GitCommit commit {commit_ptr};
511 2453 : auto parentsCount = git_commit_parentcount(commit.get());
512 4677 : for (unsigned int p = 0; p < parentsCount; ++p) {
513 : // make sure to explore all branches
514 2223 : const git_oid* pid = git_commit_parent_id(commit.get(), p);
515 2224 : if (pid)
516 2224 : parents.emplace(git_oid_tostr_s(pid));
517 : }
518 3726 : }
519 :
520 1511 : git_buf data = {};
521 1511 : 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 1514 : std::size_t sent = 0;
531 1514 : std::size_t len = data.size;
532 1514 : std::error_code ec;
533 1512 : 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 2163 : std::size_t pkt_size = std::min(static_cast<std::size_t>(65515), len - sent);
539 2165 : std::string toSendHeader = toGitHex(pkt_size + 5);
540 2161 : toSendData.clear();
541 2161 : toSendData.reserve(pkt_size + 5);
542 2163 : toSendData.insert(toSendData.end(), toSendHeader.begin(), toSendHeader.end());
543 2164 : toSendData.push_back(0x1);
544 2165 : toSendData.insert(toSendData.end(), data.ptr + sent, data.ptr + sent + pkt_size);
545 :
546 2165 : socket_->write(reinterpret_cast<const unsigned char*>(toSendData.data()), toSendData.size(), ec);
547 2165 : if (ec) {
548 0 : JAMI_WARNING("[Account {}] [Conversation {}] [GitServer {}] Unable to send data for {}: {}",
549 : accountId_,
550 : repositoryId_,
551 : fmt::ptr(this),
552 : repository_,
553 : ec.message());
554 0 : git_buf_dispose(&data);
555 0 : return;
556 : }
557 2165 : sent += pkt_size;
558 4328 : } while (sent < len);
559 1512 : git_buf_dispose(&data);
560 1514 : toSendData = {};
561 :
562 : // And finish by a little FLUSH
563 1514 : socket_->write(reinterpret_cast<const uint8_t*>(FLUSH_PKT.data()), FLUSH_PKT.size(), ec);
564 1514 : 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 1514 : haveRefs_.clear();
575 1513 : wantedReference_.clear();
576 1512 : common_.clear();
577 1514 : if (onFetchedCb_)
578 1514 : onFetchedCb_(fetched);
579 1514 : }
580 :
581 : std::map<std::string, std::string>
582 2014 : GitServer::Impl::getParameters(std::string_view pkt_line)
583 : {
584 2014 : std::map<std::string, std::string> parameters;
585 2014 : std::string key, value;
586 2014 : auto isKey = true;
587 2014 : auto nullChar = 0;
588 226870 : for (auto letter : pkt_line) {
589 224874 : if (letter == '\0') {
590 : // parameters such as host or version are after the first \0
591 4028 : if (nullChar != 0 && !key.empty()) {
592 2014 : parameters.try_emplace(std::move(key), std::move(value));
593 : }
594 4026 : nullChar += 1;
595 4026 : isKey = true;
596 4026 : key.clear();
597 4026 : value.clear();
598 220846 : } else if (letter == '=') {
599 2014 : isKey = false;
600 218832 : } else if (nullChar != 0) {
601 136397 : if (isKey) {
602 8054 : key += letter;
603 : } else {
604 128343 : value += letter;
605 : }
606 : }
607 : }
608 4025 : return parameters;
609 2012 : }
610 :
611 1052 : GitServer::GitServer(const std::string& accountId,
612 : const std::string& conversationId,
613 1052 : const std::shared_ptr<dhtnet::ChannelSocketInterface>& client)
614 : {
615 1052 : auto path = (fileutils::get_data_dir() / accountId / "conversations" / conversationId).string();
616 1052 : pimpl_ = std::make_unique<GitServer::Impl>(accountId, conversationId, path, client);
617 1052 : }
618 :
619 1052 : GitServer::~GitServer()
620 : {
621 1052 : stop();
622 1052 : pimpl_.reset();
623 1052 : }
624 :
625 : void
626 1049 : GitServer::setOnFetched(const onFetchedCb& cb)
627 : {
628 1049 : if (!pimpl_)
629 0 : return;
630 1049 : pimpl_->onFetchedCb_ = cb;
631 : }
632 :
633 : void
634 1548 : GitServer::stop()
635 : {
636 1548 : pimpl_->stop();
637 1552 : }
638 :
639 : } // namespace jami
|