Skip to content

Commit 07a6ea8

Browse files
committed
Add branches to push subcommand
1 parent f2e1dfb commit 07a6ea8

File tree

9 files changed

+356
-21
lines changed

9 files changed

+356
-21
lines changed

src/subcommand/push_subcommand.cpp

Lines changed: 134 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
#include "../subcommand/push_subcommand.hpp"
22

33
#include <iostream>
4+
#include <unordered_map>
45

56
#include <git2/remote.h>
67

8+
#include "../utils/ansi_code.hpp"
79
#include "../utils/credentials.hpp"
810
#include "../utils/progress.hpp"
911
#include "../wasm/scope.hpp"
@@ -14,8 +16,14 @@ push_subcommand::push_subcommand(const libgit2_object&, CLI::App& app)
1416
auto* sub = app.add_subcommand("push", "Update remote refs along with associated objects");
1517

1618
sub->add_option("<remote>", m_remote_name, "The remote to push to")->default_val("origin");
17-
18-
sub->add_option("<refspec>", m_refspecs, "The refspec(s) to push");
19+
sub->add_option("<refspec>", m_refspecs, "The refspec(s) to push")
20+
->expected(0,-1);
21+
sub->add_flag(
22+
"--all,--branches",
23+
m_branches_flag,
24+
"Push all branches (i.e. refs under " + ansi_code::bold + "refs/heads/" + ansi_code::reset
25+
+ "); cannot be used with other <refspec>."
26+
);
1927

2028
sub->callback(
2129
[this]()
@@ -25,6 +33,15 @@ push_subcommand::push_subcommand(const libgit2_object&, CLI::App& app)
2533
);
2634
}
2735

36+
// TODO: put in common
37+
static std::string oid_to_hex(const git_oid& oid)
38+
{
39+
char oid_str[GIT_OID_SHA1_HEXSIZE + 1];
40+
git_oid_fmt(oid_str, &oid);
41+
oid_str[GIT_OID_SHA1_HEXSIZE] = '\0';
42+
return std::string(oid_str);
43+
}
44+
2845
void push_subcommand::run()
2946
{
3047
wasm_http_transport_scope transport; // Enables wasm http(s) transport.
@@ -40,25 +57,133 @@ void push_subcommand::run()
4057
push_opts.callbacks.push_transfer_progress = push_transfer_progress;
4158
push_opts.callbacks.push_update_reference = push_update_reference;
4259

43-
if (m_refspecs.empty())
60+
if (m_branches_flag)
61+
{
62+
auto iter = repo.iterate_branches(GIT_BRANCH_LOCAL);
63+
auto br = iter.next();
64+
while (br)
65+
{
66+
std::string refspec = "refs/heads/" + std::string(br->name());
67+
m_refspecs.push_back(refspec);
68+
br = iter.next();
69+
}
70+
}
71+
else if (m_refspecs.empty())
4472
{
73+
std::string branch;
4574
try
4675
{
4776
auto head_ref = repo.head();
48-
std::string short_name = head_ref.short_name();
49-
std::string refspec = "refs/heads/" + short_name;
50-
m_refspecs.push_back(refspec);
77+
branch = head_ref.short_name();
5178
}
5279
catch (...)
5380
{
5481
std::cerr << "Could not determine current branch to push." << std::endl;
5582
return;
5683
}
84+
std::string refspec = "refs/heads/" + branch;
85+
m_refspecs.push_back(refspec);
86+
}
87+
else
88+
{
89+
for (auto& r : m_refspecs)
90+
{
91+
if (r.rfind("refs/", 0) == 0)
92+
{
93+
continue;
94+
}
95+
96+
r = "refs/heads/" + r;
97+
}
5798
}
5899
git_strarray_wrapper refspecs_wrapper(m_refspecs);
59-
git_strarray* refspecs_ptr = nullptr;
60-
refspecs_ptr = refspecs_wrapper;
100+
git_strarray* refspecs_ptr = refspecs_wrapper;
101+
102+
// Take a snapshot of repo's references to check which ones are new after push
103+
auto repo_refs = repo.reference_list();
104+
std::unordered_map<std::string, git_oid> remote_refs_before_oids;
105+
const std::string prefix = std::string("refs/remotes/") + remote_name + "/";
106+
for (const auto &r : repo_refs)
107+
{
108+
if (r.size() > prefix.size() && r.compare(0, prefix.size(), prefix) == 0)
109+
{
110+
// r is like "refs/remotes/origin/main"
111+
std::string short_name = r.substr(prefix.size()); // "main" or "feature/x"
112+
std::string canonical_remote_ref = std::string("refs/heads/") + short_name;
113+
114+
git_oid oid = repo.ref_name_to_id(r);
115+
remote_refs_before_oids.emplace(std::move(canonical_remote_ref), oid);
116+
}
117+
}
61118

62119
remote.push(refspecs_ptr, &push_opts);
63-
std::cout << "Pushed to " << remote_name << std::endl;
120+
121+
std::cout << "To " << remote.url() << std::endl;
122+
for (const auto& refspec : m_refspecs)
123+
{
124+
std::string_view ref_view(refspec);
125+
std::string_view prefix_local = "refs/heads/";
126+
std::string local_short_name;
127+
if (ref_view.size() >= prefix_local.size() && ref_view.substr(0, prefix_local.size()) == prefix_local)
128+
{
129+
local_short_name = std::string(ref_view.substr(prefix_local.size()));
130+
}
131+
else
132+
{
133+
local_short_name = std::string(refspec);
134+
}
135+
136+
std::optional<std::string> upstream_opt = repo.branch_upstream_name(local_short_name);
137+
138+
std::string remote_branch = local_short_name;
139+
std::string canonical_remote_ref = "refs/heads/" + local_short_name;
140+
if (upstream_opt.has_value())
141+
{
142+
const std::string up_name = upstream_opt.value();
143+
auto pos = up_name.find('/');
144+
if (pos != std::string::npos && pos + 1 < up_name.size())
145+
{
146+
std::string up_remote = up_name.substr(0, pos);
147+
std::string up_branch = up_name.substr(pos + 1);
148+
if (up_remote == remote_name)
149+
{
150+
remote_branch = up_branch;
151+
canonical_remote_ref = "refs/heads/" + remote_branch;
152+
}
153+
}
154+
}
155+
156+
auto it = remote_refs_before_oids.find(canonical_remote_ref);
157+
if (it == remote_refs_before_oids.end())
158+
{
159+
std::cout << " * [new branch] " << local_short_name << " -> " << remote_branch << std::endl;
160+
continue;
161+
}
162+
163+
git_oid remote_oid = it->second;
164+
165+
std::optional<git_oid> local_oid_opt;
166+
if (auto ref_opt = repo.find_reference_dwim(("refs/heads/" + local_short_name)))
167+
{
168+
const git_oid* target = ref_opt->target();
169+
local_oid_opt = *target; // TODO: pas comprenu pourquoi je ne peux pas faire local_oid_opt =
170+
// ref_opt->target();
171+
}
172+
173+
if (!local_oid_opt)
174+
{
175+
std::cout << " " << local_short_name << " -> " << remote_branch << std::endl;
176+
continue;
177+
}
178+
git_oid local_oid = local_oid_opt.value();
179+
180+
if (!git_oid_equal(&remote_oid, &local_oid))
181+
{
182+
std::string old_hex = oid_to_hex(remote_oid);
183+
std::string new_hex = oid_to_hex(local_oid);
184+
// TODO: check order of hex codes
185+
std::cout << " " << old_hex.substr(0, 7) << ".." << new_hex.substr(0, 7) << " "
186+
<< local_short_name << " -> " << local_short_name << std::endl;
187+
}
188+
}
64189
}

src/subcommand/push_subcommand.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ class push_subcommand
1818

1919
std::string m_remote_name;
2020
std::vector<std::string> m_refspecs;
21+
bool m_branches_flag = false;
2122
};

src/utils/ansi_code.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ namespace ansi_code
1919
const std::string hide_cursor = "\e[?25l";
2020
const std::string show_cursor = "\e[?25h";
2121

22+
const std::string bold = "\033[1m";
23+
const std::string reset = "\033[0m";
24+
2225
// Functions.
2326
std::string cursor_to_row(size_t row);
2427

src/utils/progress.cpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,9 @@ int push_update_reference(const char* refname, const char* status, void*)
139139
{
140140
if (status)
141141
{
142-
std::cout << " " << refname << " " << status << std::endl;
143-
}
144-
else
145-
{
146-
std::cout << " " << refname << std::endl;
142+
std::cout << " ! [remote rejected] " << refname << " (" << status << ")" << std::endl;
143+
return -1;
147144
}
145+
148146
return 0;
149147
}

src/wrapper/remote_wrapper.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
#include <string>
44
#include <vector>
55

6-
#include <git2/remote.h>
7-
86
#include "../utils/git_exception.hpp"
97

108
remote_wrapper::remote_wrapper(git_remote* remote)

src/wrapper/remote_wrapper.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
#include <vector>
55

66
#include <git2.h>
7-
#include <git2/remote.h>
87

98
#include "../wrapper/wrapper_base.hpp"
109

src/wrapper/repository_wrapper.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,26 @@ std::optional<reference_wrapper> repository_wrapper::find_reference_dwim(std::st
134134
return rc == 0 ? std::make_optional(reference_wrapper(ref)) : std::nullopt;
135135
}
136136

137+
std::vector<std::string> repository_wrapper::reference_list() const
138+
{
139+
git_strarray refs = {0};
140+
throw_if_error(git_reference_list(&refs, *this));
141+
std::vector<std::string> result;
142+
for (size_t i = 0; i < refs.count; ++i)
143+
{
144+
result.push_back(refs.strings[i]);
145+
}
146+
git_strarray_free(&refs);
147+
return result;
148+
}
149+
150+
const git_oid repository_wrapper::ref_name_to_id(std::string ref_name) const
151+
{
152+
git_oid ref_id;
153+
throw_if_error(git_reference_name_to_id(&ref_id, *this, ref_name.c_str()));
154+
return ref_id;
155+
}
156+
137157
// Index
138158

139159
index_wrapper repository_wrapper::make_index()
@@ -194,6 +214,21 @@ std::optional<reference_wrapper> repository_wrapper::upstream() const
194214
}
195215
}
196216

217+
std::optional<std::string> repository_wrapper::branch_upstream_name(std::string local_branch) const
218+
{
219+
git_buf buf = GIT_BUF_INIT;
220+
int error = git_branch_upstream_name(&buf, *this, local_branch.c_str());
221+
if (error != 0)
222+
{
223+
git_buf_dispose(&buf);
224+
return std::nullopt;
225+
}
226+
227+
std::string result(buf.ptr ? buf.ptr : "");
228+
git_buf_dispose(&buf);
229+
return result;
230+
}
231+
197232
branch_tracking_info repository_wrapper::get_tracking_info() const
198233
{
199234
branch_tracking_info info;

src/wrapper/repository_wrapper.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
#include <git2.h>
88

9-
#include "../utils/common.hpp"
109
#include "../utils/git_exception.hpp"
1110
#include "../wrapper/annotated_commit_wrapper.hpp"
1211
#include "../wrapper/branch_wrapper.hpp"
@@ -63,6 +62,8 @@ class repository_wrapper : public wrapper_base<git_repository>
6362
// References
6463
reference_wrapper find_reference(std::string_view ref_name) const;
6564
std::optional<reference_wrapper> find_reference_dwim(std::string_view ref_name) const;
65+
std::vector<std::string> reference_list() const;
66+
const git_oid ref_name_to_id(std::string ref_name) const;
6667

6768
// Index
6869
index_wrapper make_index();
@@ -74,6 +75,7 @@ class repository_wrapper : public wrapper_base<git_repository>
7475
branch_wrapper find_branch(std::string_view name) const;
7576
branch_iterator iterate_branches(git_branch_t type) const;
7677
std::optional<reference_wrapper> upstream() const;
78+
std::optional<std::string> branch_upstream_name(std::string local_branch) const;
7779
branch_tracking_info get_tracking_info() const;
7880

7981
// Commits

0 commit comments

Comments
 (0)