Skip to content

Commit 667a09e

Browse files
committed
Add branches to push subcommand
1 parent c8109c6 commit 667a09e

File tree

4 files changed

+131
-9
lines changed

4 files changed

+131
-9
lines changed

src/subcommand/push_subcommand.cpp

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,22 @@
33
#include <iostream>
44

55
#include <git2/remote.h>
6+
#include <git2/types.h>
67

78
#include "../utils/credentials.hpp"
89
#include "../utils/progress.hpp"
10+
#include "../utils/ansi_code.hpp"
911
#include "../wrapper/repository_wrapper.hpp"
1012

1113
push_subcommand::push_subcommand(const libgit2_object&, CLI::App& app)
1214
{
1315
auto* sub = app.add_subcommand("push", "Update remote refs along with associated objects");
1416

1517
sub->add_option("<remote>", m_remote_name, "The remote to push to")->default_val("origin");
16-
18+
sub->add_option("<branch>", m_branch_name, "The branch to push");
1719
sub->add_option("<refspec>", m_refspecs, "The refspec(s) to push");
20+
sub->add_flag("--all,--branches", m_branches_flag, "Push all branches (i.e. refs under " + ansi_code::bold + "refs/heads/" + ansi_code::reset + "); cannot be used with other <refspec>.");
21+
1822

1923
sub->callback(
2024
[this]()
@@ -37,19 +41,38 @@ void push_subcommand::run()
3741
push_opts.callbacks.push_transfer_progress = push_transfer_progress;
3842
push_opts.callbacks.push_update_reference = push_update_reference;
3943

40-
if (m_refspecs.empty())
44+
if (m_branches_flag)
4145
{
42-
try
46+
auto iter = repo.iterate_branches(GIT_BRANCH_LOCAL);
47+
auto br = iter.next();
48+
while (br)
4349
{
44-
auto head_ref = repo.head();
45-
std::string short_name = head_ref.short_name();
46-
std::string refspec = "refs/heads/" + short_name;
50+
std::string refspec = "refs/head/" + std::string(br->name());
4751
m_refspecs.push_back(refspec);
52+
br = iter.next();
53+
}
54+
}
55+
else if (m_refspecs.empty())
56+
{
57+
std::string branch;
58+
if (!m_branch_name.empty())
59+
{
60+
branch = m_branch_name;
4861
}
49-
catch (...)
62+
else
5063
{
51-
std::cerr << "Could not determine current branch to push." << std::endl;
52-
return;
64+
try
65+
{
66+
auto head_ref = repo.head();
67+
branch = head_ref.short_name();
68+
std::string refspec = "refs/heads/" + branch;
69+
m_refspecs.push_back(refspec);
70+
}
71+
catch (...)
72+
{
73+
std::cerr << "Could not determine current branch to push." << std::endl;
74+
return;
75+
}
5376
}
5477
}
5578
git_strarray_wrapper refspecs_wrapper(m_refspecs);

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

test/test_push.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,97 @@ def test_push_private_repo(
6262
assert p_push.stdout.count("Username:") == 2
6363
assert p_push.stdout.count("Password:") == 2
6464
assert "Pushed to origin" in p_push.stdout
65+
print(p_push.stdout)
66+
67+
68+
# def test_push_branch_private_repo(
69+
# git2cpp_path, tmp_path, run_in_tmp_path, private_test_repo, commit_env_config
70+
# ):
71+
# """Test push with an explicit branch name: git2cpp push <remote> <branch>."""
72+
# branch_name = f"test-{uuid4()}"
73+
74+
# username = "abc"
75+
# password = private_test_repo["token"]
76+
# input = f"{username}\n{password}"
77+
# repo_path = tmp_path / private_test_repo["repo_name"]
78+
# url = private_test_repo["https_url"]
79+
80+
# # Clone the private repo.
81+
# clone_cmd = [git2cpp_path, "clone", url]
82+
# p_clone = subprocess.run(clone_cmd, capture_output=True, text=True, input=input)
83+
# assert p_clone.returncode == 0
84+
# assert repo_path.exists()
85+
86+
# # Create a new branch and commit on it.
87+
# checkout_cmd = [git2cpp_path, "checkout", "-b", branch_name]
88+
# p_checkout = subprocess.run(checkout_cmd, cwd=repo_path, capture_output=True, text=True)
89+
# assert p_checkout.returncode == 0
90+
91+
# (repo_path / "push_branch_file.txt").write_text("push branch test")
92+
# subprocess.run([git2cpp_path, "add", "push_branch_file.txt"], cwd=repo_path, check=True)
93+
# subprocess.run([git2cpp_path, "commit", "-m", "branch commit"], cwd=repo_path, check=True)
94+
95+
# # Switch back to main so HEAD is NOT on the branch we want to push.
96+
# subprocess.run(
97+
# [git2cpp_path, "checkout", "main"], capture_output=True, check=True, cwd=repo_path
98+
# )
99+
100+
# status_cmd = [git2cpp_path, "status"]
101+
# p_status = subprocess.run(status_cmd, cwd=repo_path, capture_output=True, text=True)
102+
# assert p_status.returncode == 0
103+
# assert "On branch main" in p_status.stdout
104+
105+
# # Push specifying the branch explicitly (HEAD is on main, not the test branch).
106+
# input = f"{username}\n{password}"
107+
# push_cmd = [git2cpp_path, "push", "origin", branch_name]
108+
# p_push = subprocess.run(push_cmd, cwd=repo_path, capture_output=True, text=True, input=input)
109+
# assert p_push.returncode == 0
110+
# assert "Pushed to origin" in p_push.stdout
111+
112+
113+
# def test_push_branches_flag_private_repo(
114+
# git2cpp_path, tmp_path, run_in_tmp_path, private_test_repo, commit_env_config
115+
# ):
116+
# """Test push --branches pushes all local branches."""
117+
# branch_a = f"test-a-{uuid4()}"
118+
# branch_b = f"test-b-{uuid4()}"
119+
120+
# username = "abc"
121+
# password = private_test_repo["token"]
122+
# input = f"{username}\n{password}"
123+
# repo_path = tmp_path / private_test_repo["repo_name"]
124+
# url = private_test_repo["https_url"]
125+
126+
# # Clone the private repo.
127+
# clone_cmd = [git2cpp_path, "clone", url]
128+
# p_clone = subprocess.run(clone_cmd, capture_output=True, text=True, input=input)
129+
# assert p_clone.returncode == 0
130+
# assert repo_path.exists()
131+
132+
# # Create two extra branches with commits.
133+
# for branch_name in [branch_a, branch_b]:
134+
# subprocess.run(
135+
# [git2cpp_path, "checkout", "-b", branch_name],
136+
# capture_output=True,
137+
# check=True,
138+
# cwd=repo_path,
139+
# )
140+
# (repo_path / f"{branch_name}.txt").write_text(f"content for {branch_name}")
141+
# subprocess.run([git2cpp_path, "add", f"{branch_name}.txt"], cwd=repo_path, check=True)
142+
# subprocess.run(
143+
# [git2cpp_path, "commit", "-m", f"commit on {branch_name}"],
144+
# cwd=repo_path,
145+
# check=True,
146+
# )
147+
148+
# # Go back to main.
149+
# subprocess.run(
150+
# [git2cpp_path, "checkout", "main"], capture_output=True, check=True, cwd=repo_path
151+
# )
152+
153+
# # Push all branches at once.
154+
# input = f"{username}\n{password}"
155+
# push_cmd = [git2cpp_path, "push", "origin", "--branches"]
156+
# p_push = subprocess.run(push_cmd, cwd=repo_path, capture_output=True, text=True, input=input)
157+
# assert p_push.returncode == 0
158+
# assert "Pushed to origin" in p_push.stdout

0 commit comments

Comments
 (0)