Skip to content

Commit fa573f4

Browse files
committed
feat: Add remote management, fetch, and push subcommands
- Implement remote add/remove/rename/set-url/show operations - Add fetch and push subcommands for remote synchronization - Create remote_wrapper class for RAII management - Add comprehensive test suite (19 tests, all passing) - Fix CMakeLists.txt to find CLI11 in conda/pixi environment Signed-off-by: Julien Jerphanion <git@jjerphan.xyz>
1 parent 9e6938b commit fa573f4

13 files changed

+961
-0
lines changed

CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,18 @@ set(GIT2CPP_SRC
5050
${GIT2CPP_SOURCE_DIR}/subcommand/clone_subcommand.hpp
5151
${GIT2CPP_SOURCE_DIR}/subcommand/commit_subcommand.cpp
5252
${GIT2CPP_SOURCE_DIR}/subcommand/commit_subcommand.hpp
53+
${GIT2CPP_SOURCE_DIR}/subcommand/fetch_subcommand.cpp
54+
${GIT2CPP_SOURCE_DIR}/subcommand/fetch_subcommand.hpp
5355
${GIT2CPP_SOURCE_DIR}/subcommand/init_subcommand.cpp
5456
${GIT2CPP_SOURCE_DIR}/subcommand/init_subcommand.hpp
5557
${GIT2CPP_SOURCE_DIR}/subcommand/log_subcommand.cpp
5658
${GIT2CPP_SOURCE_DIR}/subcommand/log_subcommand.hpp
5759
${GIT2CPP_SOURCE_DIR}/subcommand/merge_subcommand.cpp
5860
${GIT2CPP_SOURCE_DIR}/subcommand/merge_subcommand.hpp
61+
${GIT2CPP_SOURCE_DIR}/subcommand/push_subcommand.cpp
62+
${GIT2CPP_SOURCE_DIR}/subcommand/push_subcommand.hpp
63+
${GIT2CPP_SOURCE_DIR}/subcommand/remote_subcommand.cpp
64+
${GIT2CPP_SOURCE_DIR}/subcommand/remote_subcommand.hpp
5965
${GIT2CPP_SOURCE_DIR}/subcommand/reset_subcommand.cpp
6066
${GIT2CPP_SOURCE_DIR}/subcommand/reset_subcommand.hpp
6167
${GIT2CPP_SOURCE_DIR}/subcommand/status_subcommand.cpp
@@ -82,6 +88,8 @@ set(GIT2CPP_SRC
8288
${GIT2CPP_SOURCE_DIR}/wrapper/object_wrapper.hpp
8389
${GIT2CPP_SOURCE_DIR}/wrapper/refs_wrapper.cpp
8490
${GIT2CPP_SOURCE_DIR}/wrapper/refs_wrapper.hpp
91+
${GIT2CPP_SOURCE_DIR}/wrapper/remote_wrapper.cpp
92+
${GIT2CPP_SOURCE_DIR}/wrapper/remote_wrapper.hpp
8593
${GIT2CPP_SOURCE_DIR}/wrapper/repository_wrapper.cpp
8694
${GIT2CPP_SOURCE_DIR}/wrapper/repository_wrapper.hpp
8795
${GIT2CPP_SOURCE_DIR}/wrapper/signature_wrapper.cpp

src/main.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@
1010
#include "subcommand/checkout_subcommand.hpp"
1111
#include "subcommand/clone_subcommand.hpp"
1212
#include "subcommand/commit_subcommand.hpp"
13+
#include "subcommand/fetch_subcommand.hpp"
1314
#include "subcommand/init_subcommand.hpp"
1415
#include "subcommand/log_subcommand.hpp"
1516
#include "subcommand/merge_subcommand.hpp"
17+
#include "subcommand/push_subcommand.hpp"
18+
#include "subcommand/remote_subcommand.hpp"
1619
#include "subcommand/reset_subcommand.hpp"
1720
#include "subcommand/status_subcommand.hpp"
1821

@@ -35,9 +38,12 @@ int main(int argc, char** argv)
3538
checkout_subcommand checkout(lg2_obj, app);
3639
clone_subcommand clone(lg2_obj, app);
3740
commit_subcommand commit(lg2_obj, app);
41+
fetch_subcommand fetch(lg2_obj, app);
3842
reset_subcommand reset(lg2_obj, app);
3943
log_subcommand log(lg2_obj, app);
4044
merge_subcommand merge(lg2_obj, app);
45+
push_subcommand push(lg2_obj, app);
46+
remote_subcommand remote(lg2_obj, app);
4147

4248
app.require_subcommand(/* min */ 0, /* max */ 1);
4349

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#include <iostream>
2+
#include <iomanip>
3+
4+
#include <git2/remote.h>
5+
6+
#include "../subcommand/fetch_subcommand.hpp"
7+
#include "../wrapper/repository_wrapper.hpp"
8+
#include "../wrapper/remote_wrapper.hpp"
9+
#include "../utils/output.hpp"
10+
#include "../utils/git_exception.hpp"
11+
12+
namespace
13+
{
14+
int sideband_progress(const char* str, int len, void*)
15+
{
16+
printf("remote: %.*s", len, str);
17+
fflush(stdout);
18+
return 0;
19+
}
20+
21+
int fetch_progress(const git_indexer_progress* stats, void* payload)
22+
{
23+
static bool done = false;
24+
25+
auto* pr = reinterpret_cast<git_indexer_progress*>(payload);
26+
*pr = *stats;
27+
28+
if (done)
29+
{
30+
return 0;
31+
}
32+
33+
int network_percent = pr->total_objects > 0 ?
34+
(100 * pr->received_objects / pr->total_objects)
35+
: 0;
36+
size_t mbytes = pr->received_bytes / (1024*1024);
37+
38+
std::cout << "Receiving objects: " << std::setw(4) << network_percent
39+
<< "% (" << pr->received_objects << "/" << pr->total_objects << "), "
40+
<< mbytes << " MiB";
41+
42+
if (pr->received_objects == pr->total_objects)
43+
{
44+
std::cout << ", done." << std::endl;
45+
done = true;
46+
}
47+
else
48+
{
49+
std::cout << '\r';
50+
}
51+
return 0;
52+
}
53+
54+
int update_refs(const char* refname, const git_oid* a, const git_oid* b, git_refspec*, void*)
55+
{
56+
char a_str[GIT_OID_SHA1_HEXSIZE+1], b_str[GIT_OID_SHA1_HEXSIZE+1];
57+
58+
git_oid_fmt(b_str, b);
59+
b_str[GIT_OID_SHA1_HEXSIZE] = '\0';
60+
61+
if (git_oid_is_zero(a))
62+
{
63+
printf("[new] %.20s %s\n", b_str, refname);
64+
}
65+
else
66+
{
67+
git_oid_fmt(a_str, a);
68+
a_str[GIT_OID_SHA1_HEXSIZE] = '\0';
69+
printf("[updated] %.10s..%.10s %s\n", a_str, b_str, refname);
70+
}
71+
72+
return 0;
73+
}
74+
}
75+
76+
fetch_subcommand::fetch_subcommand(const libgit2_object&, CLI::App& app)
77+
{
78+
auto* sub = app.add_subcommand("fetch", "Download objects and refs from another repository");
79+
80+
sub->add_option("<remote>", m_remote_name, "The remote to fetch from")
81+
->default_val("origin");
82+
83+
sub->callback([this]() { this->run(); });
84+
}
85+
86+
void fetch_subcommand::run()
87+
{
88+
auto directory = get_current_git_path();
89+
auto repo = repository_wrapper::open(directory);
90+
91+
// Find the remote (default to origin if not specified)
92+
std::string remote_name = m_remote_name.empty() ? "origin" : m_remote_name;
93+
auto remote = repo.find_remote(remote_name);
94+
95+
git_indexer_progress pd = {0};
96+
git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
97+
fetch_opts.callbacks.sideband_progress = sideband_progress;
98+
fetch_opts.callbacks.transfer_progress = fetch_progress;
99+
fetch_opts.callbacks.payload = &pd;
100+
fetch_opts.callbacks.update_refs = update_refs;
101+
102+
cursor_hider ch;
103+
104+
// Perform the fetch
105+
throw_if_error(git_remote_fetch(remote, nullptr, &fetch_opts, "fetch"));
106+
107+
// Show statistics
108+
const git_indexer_progress* stats = git_remote_stats(remote);
109+
if (stats->local_objects > 0)
110+
{
111+
std::cout << "\rReceived " << stats->indexed_objects << "/" << stats->total_objects
112+
<< " objects in " << stats->received_bytes << " bytes (used "
113+
<< stats->local_objects << " local objects)" << std::endl;
114+
}
115+
else
116+
{
117+
std::cout << "\rReceived " << stats->indexed_objects << "/" << stats->total_objects
118+
<< " objects in " << stats->received_bytes << " bytes" << std::endl;
119+
}
120+
}
121+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#pragma once
2+
3+
#include <string>
4+
5+
#include <CLI/CLI.hpp>
6+
7+
#include "../utils/common.hpp"
8+
#include "../wrapper/repository_wrapper.hpp"
9+
10+
class fetch_subcommand
11+
{
12+
public:
13+
14+
explicit fetch_subcommand(const libgit2_object&, CLI::App& app);
15+
void run();
16+
17+
private:
18+
19+
std::string m_remote_name;
20+
};
21+
22+
23+

src/subcommand/push_subcommand.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#include <iostream>
2+
3+
#include <git2/remote.h>
4+
5+
#include "../subcommand/push_subcommand.hpp"
6+
#include "../wrapper/repository_wrapper.hpp"
7+
#include "../wrapper/remote_wrapper.hpp"
8+
#include "../utils/git_exception.hpp"
9+
#include "../utils/common.hpp"
10+
11+
namespace
12+
{
13+
int push_transfer_progress(unsigned int current, unsigned int total, size_t bytes, void*)
14+
{
15+
if (total > 0)
16+
{
17+
int percent = (100 * current) / total;
18+
std::cout << "Writing objects: " << percent << "% (" << current
19+
<< "/" << total << "), " << bytes << " bytes\r";
20+
}
21+
return 0;
22+
}
23+
24+
int push_update_reference(const char* refname, const char* status, void*)
25+
{
26+
if (status)
27+
{
28+
std::cout << " " << refname << " " << status << std::endl;
29+
}
30+
else
31+
{
32+
std::cout << " " << refname << std::endl;
33+
}
34+
return 0;
35+
}
36+
}
37+
38+
push_subcommand::push_subcommand(const libgit2_object&, CLI::App& app)
39+
{
40+
auto* sub = app.add_subcommand("push", "Update remote refs along with associated objects");
41+
42+
sub->add_option("<remote>", m_remote_name, "The remote to push to")
43+
->default_val("origin");
44+
45+
sub->add_option("<refspec>", m_refspecs, "The refspec(s) to push");
46+
47+
sub->callback([this]() { this->run(); });
48+
}
49+
50+
void push_subcommand::run()
51+
{
52+
auto directory = get_current_git_path();
53+
auto repo = repository_wrapper::open(directory);
54+
55+
std::string remote_name = m_remote_name.empty() ? "origin" : m_remote_name;
56+
auto remote = repo.find_remote(remote_name);
57+
58+
git_push_options push_opts = GIT_PUSH_OPTIONS_INIT;
59+
push_opts.callbacks.push_transfer_progress = push_transfer_progress;
60+
push_opts.callbacks.push_update_reference = push_update_reference;
61+
62+
git_strarray_wrapper refspecs_wrapper(m_refspecs);
63+
git_strarray* refspecs_ptr = m_refspecs.empty() ? nullptr : refspecs_wrapper;
64+
65+
throw_if_error(git_remote_push(remote, refspecs_ptr, &push_opts));
66+
std::cout << "Pushed to " << remote_name << std::endl;
67+
}
68+

src/subcommand/push_subcommand.hpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#pragma once
2+
3+
#include <string>
4+
#include <vector>
5+
6+
#include <CLI/CLI.hpp>
7+
8+
#include "../utils/common.hpp"
9+
#include "../wrapper/repository_wrapper.hpp"
10+
11+
class push_subcommand
12+
{
13+
public:
14+
15+
explicit push_subcommand(const libgit2_object&, CLI::App& app);
16+
void run();
17+
18+
private:
19+
20+
std::string m_remote_name;
21+
std::vector<std::string> m_refspecs;
22+
};
23+
24+
25+

0 commit comments

Comments
 (0)