Skip to content

Commit 324e7c2

Browse files
jjerphanJohanMabilleSandrineP
authored
feat: Add remote management, fetch, and push subcommands (#59)
* 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> * Address review comments Signed-off-by: Julien Jerphanion <git@jjerphan.xyz> Co-authored-by: Johan Mabille <johan.mabille@gmail.com> * edit remote * small fix in test * another small fix --------- Signed-off-by: Julien Jerphanion <git@jjerphan.xyz> Co-authored-by: Johan Mabille <johan.mabille@gmail.com> Co-authored-by: Sandrine Pataut <pataut.sandrine@gmail.com>
1 parent 6342fb2 commit 324e7c2

18 files changed

+1140
-79
lines changed

CMakeLists.txt

Lines changed: 10 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
@@ -68,6 +74,8 @@ set(GIT2CPP_SRC
6874
${GIT2CPP_SOURCE_DIR}/utils/git_exception.hpp
6975
${GIT2CPP_SOURCE_DIR}/utils/output.cpp
7076
${GIT2CPP_SOURCE_DIR}/utils/output.hpp
77+
${GIT2CPP_SOURCE_DIR}/utils/progress.cpp
78+
${GIT2CPP_SOURCE_DIR}/utils/progress.hpp
7179
${GIT2CPP_SOURCE_DIR}/utils/terminal_pager.cpp
7280
${GIT2CPP_SOURCE_DIR}/utils/terminal_pager.hpp
7381
${GIT2CPP_SOURCE_DIR}/wrapper/annotated_commit_wrapper.cpp
@@ -82,6 +90,8 @@ set(GIT2CPP_SRC
8290
${GIT2CPP_SOURCE_DIR}/wrapper/object_wrapper.hpp
8391
${GIT2CPP_SOURCE_DIR}/wrapper/refs_wrapper.cpp
8492
${GIT2CPP_SOURCE_DIR}/wrapper/refs_wrapper.hpp
93+
${GIT2CPP_SOURCE_DIR}/wrapper/remote_wrapper.cpp
94+
${GIT2CPP_SOURCE_DIR}/wrapper/remote_wrapper.hpp
8595
${GIT2CPP_SOURCE_DIR}/wrapper/repository_wrapper.cpp
8696
${GIT2CPP_SOURCE_DIR}/wrapper/repository_wrapper.hpp
8797
${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

src/subcommand/clone_subcommand.cpp

Lines changed: 6 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "../subcommand/clone_subcommand.hpp"
44
#include "../utils/output.hpp"
5+
#include "../utils/progress.hpp"
56
#include "../wrapper/repository_wrapper.hpp"
67

78
clone_subcommand::clone_subcommand(const libgit2_object&, CLI::App& app)
@@ -10,81 +11,11 @@ clone_subcommand::clone_subcommand(const libgit2_object&, CLI::App& app)
1011

1112
sub->add_option("<repository>", m_repository, "The (possibly remote) repository to clone from.")->required();
1213
sub->add_option("<directory>", m_directory, "The name of a new directory to clone into.");
14+
sub->add_flag("--bare", m_bare, "Create a bare Git repository.");
1315

1416
sub->callback([this]() { this->run(); });
1517
}
1618

17-
namespace
18-
{
19-
int sideband_progress(const char* str, int len, void*)
20-
{
21-
printf("remote: %.*s", len, str);
22-
fflush(stdout);
23-
return 0;
24-
}
25-
26-
int fetch_progress(const git_indexer_progress* stats, void* payload)
27-
{
28-
static bool done = false;
29-
30-
// We need to copy stats into payload even if the fetch is done,
31-
// because the checkout_progress callback will be called with the
32-
// same payload and needs the data to be up do date.
33-
auto* pr = reinterpret_cast<git_indexer_progress*>(payload);
34-
*pr = *stats;
35-
36-
if (done)
37-
{
38-
return 0;
39-
}
40-
41-
int network_percent = pr->total_objects > 0 ?
42-
(100 * pr->received_objects / pr->total_objects)
43-
: 0;
44-
size_t mbytes = pr->received_bytes / (1024*1024);
45-
46-
std::cout << "Receiving objects: " << std::setw(4) << network_percent
47-
<< "% (" << pr->received_objects << "/" << pr->total_objects << "), "
48-
<< mbytes << " MiB";
49-
50-
if (pr->received_objects == pr->total_objects)
51-
{
52-
std::cout << ", done." << std::endl;
53-
done = true;
54-
}
55-
else
56-
{
57-
std::cout << '\r';
58-
}
59-
return 0;
60-
}
61-
62-
void checkout_progress(const char* path, size_t cur, size_t tot, void* payload)
63-
{
64-
static bool done = false;
65-
if (done)
66-
{
67-
return;
68-
}
69-
auto* pr = reinterpret_cast<git_indexer_progress*>(payload);
70-
int deltas_percent = pr->total_deltas > 0 ?
71-
(100 * pr->indexed_deltas / pr->total_deltas)
72-
: 0;
73-
74-
std::cout << "Resolving deltas: " << std::setw(4) << deltas_percent
75-
<< "% (" << pr->indexed_deltas << "/" << pr->total_deltas << ")";
76-
if (pr->indexed_deltas == pr->total_deltas)
77-
{
78-
std::cout << ", done." << std::endl;
79-
done = true;
80-
}
81-
else
82-
{
83-
std::cout << '\r';
84-
}
85-
}
86-
}
87-
8819
void clone_subcommand::run()
8920
{
9021
git_indexer_progress pd;
@@ -94,9 +25,10 @@ void clone_subcommand::run()
9425
checkout_opts.progress_cb = checkout_progress;
9526
checkout_opts.progress_payload = &pd;
9627
clone_opts.checkout_opts = checkout_opts;
97-
clone_opts.fetch_opts.callbacks.sideband_progress = sideband_progress;
98-
clone_opts.fetch_opts.callbacks.transfer_progress = fetch_progress;
99-
clone_opts.fetch_opts.callbacks.payload = &pd;
28+
clone_opts.fetch_opts.callbacks.sideband_progress = sideband_progress;
29+
clone_opts.fetch_opts.callbacks.transfer_progress = fetch_progress;
30+
clone_opts.fetch_opts.callbacks.payload = &pd;
31+
clone_opts.bare = m_bare ? 1 : 0;
10032

10133
std::string short_name = m_directory;
10234
if (m_directory.empty())

src/subcommand/clone_subcommand.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ class clone_subcommand
1515

1616
std::string m_repository = {};
1717
std::string m_directory = {};
18+
bool m_bare = false;
1819
};
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#include <iostream>
2+
3+
#include <git2/remote.h>
4+
5+
#include "../subcommand/fetch_subcommand.hpp"
6+
#include "../utils/output.hpp"
7+
#include "../utils/progress.hpp"
8+
#include "../wrapper/repository_wrapper.hpp"
9+
10+
fetch_subcommand::fetch_subcommand(const libgit2_object&, CLI::App& app)
11+
{
12+
auto* sub = app.add_subcommand("fetch", "Download objects and refs from another repository");
13+
14+
sub->add_option("<remote>", m_remote_name, "The remote to fetch from")
15+
->default_val("origin");
16+
17+
sub->callback([this]() { this->run(); });
18+
}
19+
20+
void fetch_subcommand::run()
21+
{
22+
auto directory = get_current_git_path();
23+
auto repo = repository_wrapper::open(directory);
24+
25+
// Find the remote (default to origin if not specified)
26+
std::string remote_name = m_remote_name.empty() ? "origin" : m_remote_name;
27+
auto remote = repo.find_remote(remote_name);
28+
29+
git_indexer_progress pd = {0};
30+
git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
31+
fetch_opts.callbacks.sideband_progress = sideband_progress;
32+
fetch_opts.callbacks.transfer_progress = fetch_progress;
33+
fetch_opts.callbacks.payload = &pd;
34+
fetch_opts.callbacks.update_refs = update_refs;
35+
36+
cursor_hider ch;
37+
38+
// Perform the fetch
39+
remote.fetch(nullptr, &fetch_opts, "fetch");
40+
41+
// Show statistics
42+
const git_indexer_progress* stats = git_remote_stats(remote);
43+
if (stats->local_objects > 0)
44+
{
45+
std::cout << "\rReceived " << stats->indexed_objects << "/" << stats->total_objects
46+
<< " objects in " << stats->received_bytes << " bytes (used "
47+
<< stats->local_objects << " local objects)" << std::endl;
48+
}
49+
else
50+
{
51+
std::cout << "\rReceived " << stats->indexed_objects << "/" << stats->total_objects
52+
<< " objects in " << stats->received_bytes << " bytes" << std::endl;
53+
}
54+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#pragma once
2+
3+
#include <string>
4+
5+
#include <CLI/CLI.hpp>
6+
7+
#include "../utils/common.hpp"
8+
9+
class fetch_subcommand
10+
{
11+
public:
12+
13+
explicit fetch_subcommand(const libgit2_object&, CLI::App& app);
14+
void run();
15+
16+
private:
17+
18+
std::string m_remote_name;
19+
};

src/subcommand/push_subcommand.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#include <iostream>
2+
3+
#include <git2/remote.h>
4+
5+
#include "../subcommand/push_subcommand.hpp"
6+
#include "../utils/progress.hpp"
7+
#include "../wrapper/repository_wrapper.hpp"
8+
9+
push_subcommand::push_subcommand(const libgit2_object&, CLI::App& app)
10+
{
11+
auto* sub = app.add_subcommand("push", "Update remote refs along with associated objects");
12+
13+
sub->add_option("<remote>", m_remote_name, "The remote to push to")
14+
->default_val("origin");
15+
16+
sub->add_option("<refspec>", m_refspecs, "The refspec(s) to push");
17+
18+
sub->callback([this]() { this->run(); });
19+
}
20+
21+
void push_subcommand::run()
22+
{
23+
auto directory = get_current_git_path();
24+
auto repo = repository_wrapper::open(directory);
25+
26+
std::string remote_name = m_remote_name.empty() ? "origin" : m_remote_name;
27+
auto remote = repo.find_remote(remote_name);
28+
29+
git_push_options push_opts = GIT_PUSH_OPTIONS_INIT;
30+
push_opts.callbacks.push_transfer_progress = push_transfer_progress;
31+
push_opts.callbacks.push_update_reference = push_update_reference;
32+
33+
if (m_refspecs.empty())
34+
{
35+
try
36+
{
37+
auto head_ref = repo.head();
38+
std::string short_name = head_ref.short_name();
39+
std::string refspec = "refs/heads/" + short_name;
40+
m_refspecs.push_back(refspec);
41+
}
42+
catch (...)
43+
{
44+
std::cerr << "Could not determine current branch to push." << std::endl;
45+
return;
46+
}
47+
}
48+
git_strarray_wrapper refspecs_wrapper(m_refspecs);
49+
git_strarray* refspecs_ptr = nullptr;
50+
refspecs_ptr = refspecs_wrapper;
51+
52+
remote.push(refspecs_ptr, &push_opts);
53+
std::cout << "Pushed to " << remote_name << std::endl;
54+
}

src/subcommand/push_subcommand.hpp

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

0 commit comments

Comments
 (0)