Skip to content

Commit 35d4c0b

Browse files
committed
Add branches to push subcommand
1 parent f2e1dfb commit 35d4c0b

File tree

12 files changed

+362
-31
lines changed

12 files changed

+362
-31
lines changed

src/subcommand/push_subcommand.cpp

Lines changed: 125 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
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"
9+
#include "../utils/common.hpp"
710
#include "../utils/credentials.hpp"
811
#include "../utils/progress.hpp"
912
#include "../wasm/scope.hpp"
@@ -14,8 +17,14 @@ push_subcommand::push_subcommand(const libgit2_object&, CLI::App& app)
1417
auto* sub = app.add_subcommand("push", "Update remote refs along with associated objects");
1518

1619
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");
20+
sub->add_option("<refspec>", m_refspecs, "The refspec(s) to push")
21+
->expected(0,-1);
22+
sub->add_flag(
23+
"--all,--branches",
24+
m_branches_flag,
25+
"Push all branches (i.e. refs under " + ansi_code::bold + "refs/heads/" + ansi_code::reset
26+
+ "); cannot be used with other <refspec>."
27+
);
1928

2029
sub->callback(
2130
[this]()
@@ -40,25 +49,132 @@ void push_subcommand::run()
4049
push_opts.callbacks.push_transfer_progress = push_transfer_progress;
4150
push_opts.callbacks.push_update_reference = push_update_reference;
4251

43-
if (m_refspecs.empty())
52+
if (m_branches_flag)
53+
{
54+
auto iter = repo.iterate_branches(GIT_BRANCH_LOCAL);
55+
auto br = iter.next();
56+
while (br)
57+
{
58+
std::string refspec = "refs/heads/" + std::string(br->name());
59+
m_refspecs.push_back(refspec);
60+
br = iter.next();
61+
}
62+
}
63+
else if (m_refspecs.empty())
4464
{
65+
std::string branch;
4566
try
4667
{
4768
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);
69+
branch = head_ref.short_name();
5170
}
5271
catch (...)
5372
{
5473
std::cerr << "Could not determine current branch to push." << std::endl;
5574
return;
5675
}
76+
std::string refspec = "refs/heads/" + branch;
77+
m_refspecs.push_back(refspec);
78+
}
79+
else
80+
{
81+
for (auto& r : m_refspecs)
82+
{
83+
if (r.rfind("refs/", 0) == 0)
84+
{
85+
continue;
86+
}
87+
88+
r = "refs/heads/" + r;
89+
}
5790
}
5891
git_strarray_wrapper refspecs_wrapper(m_refspecs);
59-
git_strarray* refspecs_ptr = nullptr;
60-
refspecs_ptr = refspecs_wrapper;
92+
git_strarray* refspecs_ptr = refspecs_wrapper;
93+
94+
// Take a snapshot of repo's references to check which ones are new after push
95+
auto repo_refs = repo.reference_list();
96+
std::unordered_map<std::string, git_oid> remote_refs_before_oids;
97+
const std::string prefix = std::string("refs/remotes/") + remote_name + "/";
98+
for (const auto &r : repo_refs)
99+
{
100+
if (r.size() > prefix.size() && r.compare(0, prefix.size(), prefix) == 0)
101+
{
102+
// r is like "refs/remotes/origin/main"
103+
std::string short_name = r.substr(prefix.size()); // "main" or "feature/x"
104+
std::string canonical_remote_ref = std::string("refs/heads/") + short_name;
105+
106+
git_oid oid = repo.ref_name_to_id(r);
107+
remote_refs_before_oids.emplace(std::move(canonical_remote_ref), oid);
108+
}
109+
}
61110

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

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/subcommand/revlist_subcommand.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,9 @@ void revlist_subcommand::run()
4242

4343
std::size_t i = 0;
4444
git_oid commit_oid;
45-
char buf[GIT_OID_SHA1_HEXSIZE + 1];
4645
while (!walker.next(commit_oid) && i < m_max_count_flag)
4746
{
48-
git_oid_fmt(buf, &commit_oid);
49-
buf[GIT_OID_SHA1_HEXSIZE] = '\0';
50-
std::cout << buf << std::endl;
47+
std::cout << oid_to_hex(commit_oid) << std::endl;
5148
++i;
5249
}
5350
}

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/common.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,11 @@ std::string trim(const std::string& str)
142142
auto s = std::regex_replace(str, std::regex("^\\s+"), "");
143143
return std::regex_replace(s, std::regex("\\s+$"), "");
144144
}
145+
146+
std::string oid_to_hex(const git_oid& oid)
147+
{
148+
char oid_str[GIT_OID_SHA1_HEXSIZE + 1];
149+
git_oid_fmt(oid_str, &oid);
150+
oid_str[GIT_OID_SHA1_HEXSIZE] = '\0';
151+
return std::string(oid_str);
152+
}

src/utils/common.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,5 @@ std::vector<std::string> split_input_at_newlines(std::string_view str);
7979

8080
// Remove whitespace from start and end of a string.
8181
std::string trim(const std::string& str);
82+
83+
std::string oid_to_hex(const git_oid& oid);

src/utils/progress.cpp

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include <iostream>
55
#include <string_view>
66

7+
#include "../utils/common.hpp"
8+
79
int sideband_progress(const char* str, int len, void*)
810
{
911
printf("remote: %.*s", len, str);
@@ -83,10 +85,7 @@ void checkout_progress(const char* path, size_t cur, size_t tot, void* payload)
8385

8486
int update_refs(const char* refname, const git_oid* a, const git_oid* b, git_refspec*, void*)
8587
{
86-
char a_str[GIT_OID_SHA1_HEXSIZE + 1], b_str[GIT_OID_SHA1_HEXSIZE + 1];
87-
88-
git_oid_fmt(b_str, b);
89-
b_str[GIT_OID_SHA1_HEXSIZE] = '\0';
88+
std::string b_str = oid_to_hex(*b);
9089

9190
if (git_oid_is_zero(a))
9291
{
@@ -114,8 +113,7 @@ int update_refs(const char* refname, const git_oid* a, const git_oid* b, git_ref
114113
}
115114
else
116115
{
117-
git_oid_fmt(a_str, a);
118-
a_str[GIT_OID_SHA1_HEXSIZE] = '\0';
116+
std::string a_str = oid_to_hex(*a);
119117

120118
std::cout << "[updated] " << std::string(a_str, 10) << ".." << std::string(b_str, 10) << " "
121119
<< refname << std::endl;
@@ -139,11 +137,9 @@ int push_update_reference(const char* refname, const char* status, void*)
139137
{
140138
if (status)
141139
{
142-
std::cout << " " << refname << " " << status << std::endl;
143-
}
144-
else
145-
{
146-
std::cout << " " << refname << std::endl;
140+
std::cout << " ! [remote rejected] " << refname << " (" << status << ")" << std::endl;
141+
return -1;
147142
}
143+
148144
return 0;
149145
}

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;

0 commit comments

Comments
 (0)