Skip to content

Commit 66f629b

Browse files
committed
Add branches to push subcommand
1 parent c8109c6 commit 66f629b

File tree

12 files changed

+355
-24
lines changed

12 files changed

+355
-24
lines changed

src/subcommand/push_subcommand.cpp

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

33
#include <iostream>
4+
#include <optional>
5+
#include <unordered_map>
6+
#include <string_view>
47

5-
#include <git2/remote.h>
8+
#include <git2.h>
69

10+
#include "../utils/ansi_code.hpp"
711
#include "../utils/credentials.hpp"
812
#include "../utils/progress.hpp"
913
#include "../wrapper/repository_wrapper.hpp"
@@ -13,8 +17,15 @@ push_subcommand::push_subcommand(const libgit2_object&, CLI::App& app)
1317
auto* sub = app.add_subcommand("push", "Update remote refs along with associated objects");
1418

1519
sub->add_option("<remote>", m_remote_name, "The remote to push to")->default_val("origin");
16-
20+
sub->add_option("<branch>", m_branch_name, "The branch to push");
1721
sub->add_option("<refspec>", m_refspecs, "The refspec(s) to push");
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+
);
28+
1829

1930
sub->callback(
2031
[this]()
@@ -24,6 +35,15 @@ push_subcommand::push_subcommand(const libgit2_object&, CLI::App& app)
2435
);
2536
}
2637

38+
// TODO: put in common
39+
static std::string oid_to_hex(const git_oid& oid)
40+
{
41+
char oid_str[GIT_OID_SHA1_HEXSIZE + 1];
42+
git_oid_fmt(oid_str, &oid);
43+
oid_str[GIT_OID_SHA1_HEXSIZE] = '\0';
44+
return std::string(oid_str);
45+
}
46+
2747
void push_subcommand::run()
2848
{
2949
auto directory = get_current_git_path();
@@ -37,25 +57,127 @@ void push_subcommand::run()
3757
push_opts.callbacks.push_transfer_progress = push_transfer_progress;
3858
push_opts.callbacks.push_update_reference = push_update_reference;
3959

40-
if (m_refspecs.empty())
60+
if (m_branches_flag)
4161
{
42-
try
62+
auto iter = repo.iterate_branches(GIT_BRANCH_LOCAL);
63+
auto br = iter.next();
64+
while (br)
4365
{
44-
auto head_ref = repo.head();
45-
std::string short_name = head_ref.short_name();
46-
std::string refspec = "refs/heads/" + short_name;
66+
std::string refspec = "refs/heads/" + std::string(br->name());
4767
m_refspecs.push_back(refspec);
68+
br = iter.next();
69+
}
70+
}
71+
else if (m_refspecs.empty())
72+
{
73+
std::string branch;
74+
if (!m_branch_name.empty())
75+
{
76+
branch = m_branch_name;
4877
}
49-
catch (...)
78+
else
5079
{
51-
std::cerr << "Could not determine current branch to push." << std::endl;
52-
return;
80+
try
81+
{
82+
auto head_ref = repo.head();
83+
branch = head_ref.short_name();
84+
}
85+
catch (...)
86+
{
87+
std::cerr << "Could not determine current branch to push." << std::endl;
88+
return;
89+
}
5390
}
91+
std::string refspec = "refs/heads/" + branch;
92+
m_refspecs.push_back(refspec);
5493
}
5594
git_strarray_wrapper refspecs_wrapper(m_refspecs);
5695
git_strarray* refspecs_ptr = nullptr;
5796
refspecs_ptr = refspecs_wrapper;
5897

98+
// Take a snapshot of remote branches to check which ones are new after push
99+
git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
100+
callbacks.credentials = user_credentials;
101+
credentials_payload creds_payload;
102+
callbacks.payload = &creds_payload;
103+
push_opts.callbacks.payload = &creds_payload;
104+
105+
auto remote_heads = remote.list_heads(&callbacks);
106+
107+
// Map with names of branches and their oids before push
108+
std::unordered_map<std::string, git_oid> remote_heads_map;
109+
for (const auto& h : remote_heads)
110+
{
111+
remote_heads_map.emplace(h.name, h.oid);
112+
}
113+
59114
remote.push(refspecs_ptr, &push_opts);
60-
std::cout << "Pushed to " << remote_name << std::endl;
115+
116+
std::cout << "To " << remote.url() << std::endl;
117+
for (const auto& refspec : m_refspecs)
118+
{
119+
std::string_view ref_view(refspec);
120+
std::string_view prefix = "refs/heads/";
121+
std::string local_short_name;
122+
if (ref_view.substr(0, prefix.size()) == prefix)
123+
{
124+
local_short_name = ref_view.substr(prefix.size());
125+
}
126+
else
127+
{
128+
local_short_name = refspec;
129+
}
130+
131+
std::optional<std::string> upstream_opt = repo.branch_upstream_name(local_short_name);
132+
133+
std::string remote_branch = local_short_name;
134+
std::string remote_ref = "refs/heads/" + local_short_name;
135+
if (upstream_opt.has_value())
136+
{
137+
const std::string up_name = upstream_opt.value();
138+
auto pos = up_name.find('/');
139+
if (pos != std::string::npos && pos + 1 < up_name.size())
140+
{
141+
std::string up_remote = up_name.substr(0, pos);
142+
std::string up_branch = up_name.substr(pos + 1);
143+
if(up_remote == remote_name)
144+
{
145+
remote_branch = up_name.substr(pos + 1);
146+
remote_ref = "refs/heads/" + remote_branch;
147+
}
148+
}
149+
}
150+
151+
auto iter = remote_heads_map.find(remote_ref);
152+
if (iter == remote_heads_map.end())
153+
{
154+
std::cout << " * [new branch] " << local_short_name << " -> " << remote_branch << std::endl;
155+
continue;
156+
}
157+
158+
git_oid remote_oid = iter->second;
159+
160+
std::optional<git_oid> local_oid_opt;
161+
if (auto ref_opt = repo.find_reference_dwim(("refs/heads/" + local_short_name)))
162+
{
163+
const git_oid* target = ref_opt->target();
164+
local_oid_opt = *target; // TODO: pas comprenu pourquoi je ne peux pas faire local_oid_opt = ref_opt->target();
165+
}
166+
167+
if (!local_oid_opt)
168+
{
169+
std::cout << " " << local_short_name << " -> " << remote_branch << std::endl;
170+
continue;
171+
}
172+
git_oid local_oid = local_oid_opt.value();
173+
174+
if (!git_oid_equal(&remote_oid, &local_oid))
175+
{
176+
std::string old_hex = oid_to_hex(remote_oid);
177+
std::string new_hex = oid_to_hex(local_oid);
178+
// TODO: check order of hex codes
179+
std::cout << " " << old_hex.substr(0, 7) << ".." << new_hex.substr(0, 7)
180+
<< " " << local_short_name << " -> " << local_short_name << std::endl;
181+
}
182+
}
61183
}

src/subcommand/push_subcommand.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,7 @@ class push_subcommand
1717
private:
1818

1919
std::string m_remote_name;
20+
std::string m_branch_name;
2021
std::vector<std::string> m_refspecs;
22+
bool m_branches_flag = false;
2123
};

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

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,53 @@ int user_credentials(
1515
void* payload
1616
)
1717
{
18+
credentials_payload* cached = payload ? static_cast<credentials_payload*>(payload) : nullptr;
19+
1820
// Check for cached credentials here, if desired.
1921
// It might be necessary to make this function stateful to avoid repeating unnecessary checks.
2022

2123
*out = nullptr;
2224

2325
if (allowed_types & GIT_CREDENTIAL_USERPASS_PLAINTEXT)
2426
{
25-
std::string username = username_from_url ? username_from_url : "";
26-
if (username.empty())
27+
std::string username;
28+
if (username_from_url && username_from_url[0] != '\0')
29+
{
30+
username = username_from_url;
31+
}
32+
else if (cached && cached->username.has_value())
33+
{
34+
username = *cached->username;
35+
}
36+
else
2737
{
2838
username = prompt_input("Username: ");
39+
if (cached && !username.empty())
40+
{
41+
cached->username = username;
42+
}
2943
}
44+
3045
if (username.empty())
3146
{
3247
giterr_set_str(GIT_ERROR_HTTP, "No username specified");
3348
return GIT_EAUTH;
3449
}
3550

3651
std::string password = prompt_input("Password: ", false);
52+
if (cached && cached->password.has_value())
53+
{
54+
password = *cached->password;
55+
}
56+
else
57+
{
58+
password = prompt_input("Password: ", false);
59+
if (cached && !password.empty())
60+
{
61+
cached->password = password;
62+
}
63+
}
64+
3765
if (password.empty())
3866
{
3967
giterr_set_str(GIT_ERROR_HTTP, "No password specified");

src/utils/credentials.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
#pragma once
22

3+
#include <optional>
4+
#include <string>
5+
36
#include <git2/credential.h>
47

8+
struct credentials_payload
9+
{
10+
std::optional<std::string> username;
11+
std::optional<std::string> password;
12+
};
13+
514
// Libgit2 callback of type git_credential_acquire_cb to obtain user credentials
615
// (username and password) to authenticate remote https access.
716
int user_credentials(

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/utils/progress.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
#pragma once
22

3+
#include <string>
4+
35
#include <git2.h>
46

57
int sideband_progress(const char* str, int len, void*);
68
int fetch_progress(const git_indexer_progress* stats, void* payload);
79
void checkout_progress(const char* path, size_t cur, size_t tot, void* payload);
810
int update_refs(const char* refname, const git_oid* a, const git_oid* b, git_refspec*, void*);
911
int push_transfer_progress(unsigned int current, unsigned int total, size_t bytes, void*);
12+
13+
struct push_update_payload
14+
{
15+
std::string url;
16+
bool header_printed = false;
17+
};
18+
1019
int push_update_reference(const char* refname, const char* status, void*);

src/wrapper/remote_wrapper.cpp

Lines changed: 38 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)
@@ -62,3 +60,41 @@ void remote_wrapper::push(const git_strarray* refspecs, const git_push_options*
6260
{
6361
throw_if_error(git_remote_push(*this, refspecs, opts));
6462
}
63+
64+
void remote_wrapper::connect(git_direction direction, const git_remote_callbacks* callbacks) const
65+
{
66+
throw_if_error(git_remote_connect(*this, direction, callbacks, nullptr, nullptr));
67+
}
68+
69+
std::vector<remote_head> remote_wrapper::list_heads(const git_remote_callbacks* callbacks = nullptr) const
70+
{
71+
std::vector<remote_head> result;
72+
73+
this->connect(GIT_DIRECTION_FETCH, callbacks);
74+
75+
const git_remote_head** heads = nullptr;
76+
size_t heads_len = 0;
77+
int err = git_remote_ls(&heads, &heads_len, *this);
78+
if (err != 0)
79+
{
80+
git_remote_disconnect(*this);
81+
throw_if_error(err);
82+
}
83+
84+
for (size_t i = 0; i < heads_len; ++i)
85+
{
86+
const git_remote_head* h = heads[i];
87+
if (!h || !h->name)
88+
{
89+
continue;
90+
}
91+
92+
remote_head rh;
93+
rh.name = std::string(h->name);
94+
rh.oid = h->oid;
95+
result.push_back(std::move(rh));
96+
}
97+
98+
git_remote_disconnect(*this);
99+
return result;
100+
}

src/wrapper/remote_wrapper.hpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
#pragma once
22

3+
#include <string>
34
#include <string_view>
45
#include <vector>
56

67
#include <git2.h>
7-
#include <git2/remote.h>
88

99
#include "../wrapper/wrapper_base.hpp"
1010

11+
struct remote_head
12+
{
13+
std::string name;
14+
git_oid oid;
15+
};
16+
1117
class remote_wrapper : public wrapper_base<git_remote>
1218
{
1319
public:
@@ -27,6 +33,9 @@ class remote_wrapper : public wrapper_base<git_remote>
2733

2834
void fetch(const git_strarray* refspecs, const git_fetch_options* opts, const char* reflog_message);
2935
void push(const git_strarray* refspecs, const git_push_options* opts);
36+
void connect(git_direction direction, const git_remote_callbacks* callbacks) const;
37+
38+
std::vector<remote_head> list_heads(const git_remote_callbacks* callbacks) const;
3039

3140
private:
3241

0 commit comments

Comments
 (0)