Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 54 additions & 10 deletions src/subcommand/push_subcommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
#include <iostream>

#include <git2/remote.h>
#include <git2/types.h>

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

sub->add_option("<remote>", m_remote_name, "The remote to push to")->default_val("origin");

sub->add_option("<branch>", m_branch_name, "The branch to push");
sub->add_option("<refspec>", m_refspecs, "The refspec(s) to push");
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>."
);


sub->callback(
[this]()
Expand All @@ -37,25 +46,60 @@ void push_subcommand::run()
push_opts.callbacks.push_transfer_progress = push_transfer_progress;
push_opts.callbacks.push_update_reference = push_update_reference;

if (m_refspecs.empty())
if (m_branches_flag)
{
try
auto iter = repo.iterate_branches(GIT_BRANCH_LOCAL);
auto br = iter.next();
while (br)
{
auto head_ref = repo.head();
std::string short_name = head_ref.short_name();
std::string refspec = "refs/heads/" + short_name;
std::string refspec = "refs/heads/" + std::string(br->name());
m_refspecs.push_back(refspec);
br = iter.next();
}
}
else if (m_refspecs.empty())
{
std::string branch;
if (!m_branch_name.empty())
{
branch = m_branch_name;
}
catch (...)
else
{
std::cerr << "Could not determine current branch to push." << std::endl;
return;
try
{
auto head_ref = repo.head();
branch = head_ref.short_name();
}
catch (...)
{
std::cerr << "Could not determine current branch to push." << std::endl;
return;
}
}
std::string refspec = "refs/heads/" + branch;
m_refspecs.push_back(refspec);
}
git_strarray_wrapper refspecs_wrapper(m_refspecs);
git_strarray* refspecs_ptr = nullptr;
refspecs_ptr = refspecs_wrapper;

remote.push(refspecs_ptr, &push_opts);
std::cout << "Pushed to " << remote_name << std::endl;

std::cout << "To " << remote.url() << std::endl;
for (const auto& refspec : m_refspecs)
{
std::string_view ref_view(refspec);
std::string_view prefix = "refs/heads/";
std::string short_name;
if (ref_view.substr(0, prefix.size()) == prefix)
{
short_name = ref_view.substr(prefix.size());
}
else
{
short_name = refspec;
}
std::cout << " * " << short_name << " -> " << short_name << std::endl;
}
}
2 changes: 2 additions & 0 deletions src/subcommand/push_subcommand.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@ class push_subcommand
private:

std::string m_remote_name;
std::string m_branch_name;
std::vector<std::string> m_refspecs;
bool m_branches_flag = false;
};
3 changes: 3 additions & 0 deletions src/utils/ansi_code.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ namespace ansi_code
const std::string hide_cursor = "\e[?25l";
const std::string show_cursor = "\e[?25h";

const std::string bold = "\033[1m";
const std::string reset = "\033[0m";

// Functions.
std::string cursor_to_row(size_t row);

Expand Down
8 changes: 3 additions & 5 deletions src/utils/progress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,9 @@ int push_update_reference(const char* refname, const char* status, void*)
{
if (status)
{
std::cout << " " << refname << " " << status << std::endl;
}
else
{
std::cout << " " << refname << std::endl;
std::cout << " ! [remote rejected] " << refname << " (" << status << ")" << std::endl;
return -1;
}

return 0;
}
9 changes: 9 additions & 0 deletions src/utils/progress.hpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
#pragma once

#include <string>

#include <git2.h>

int sideband_progress(const char* str, int len, void*);
int fetch_progress(const git_indexer_progress* stats, void* payload);
void checkout_progress(const char* path, size_t cur, size_t tot, void* payload);
int update_refs(const char* refname, const git_oid* a, const git_oid* b, git_refspec*, void*);
int push_transfer_progress(unsigned int current, unsigned int total, size_t bytes, void*);

struct push_update_payload
{
std::string url;
bool header_printed = false;
};

int push_update_reference(const char* refname, const char* status, void*);
97 changes: 96 additions & 1 deletion test/test_push.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,99 @@ def test_push_private_repo(
assert p_push.returncode == 0
assert p_push.stdout.count("Username:") == 2
assert p_push.stdout.count("Password:") == 2
assert "Pushed to origin" in p_push.stdout
assert " * [new branch] test-" in p_push.stdout


def test_push_branch_private_repo(
git2cpp_path, tmp_path, run_in_tmp_path, private_test_repo, commit_env_config
):
"""Test push with an explicit branch name: git2cpp push <remote> <branch>."""
branch_name = f"test-{uuid4()}"

username = "abc"
password = private_test_repo["token"]
input = f"{username}\n{password}"
repo_path = tmp_path / private_test_repo["repo_name"]
url = private_test_repo["https_url"]

# Clone the private repo.
clone_cmd = [git2cpp_path, "clone", url]
p_clone = subprocess.run(clone_cmd, capture_output=True, text=True, input=input)
assert p_clone.returncode == 0
assert repo_path.exists()

# Create a new branch and commit on it.
checkout_cmd = [git2cpp_path, "checkout", "-b", branch_name]
p_checkout = subprocess.run(checkout_cmd, cwd=repo_path, capture_output=True, text=True)
assert p_checkout.returncode == 0

(repo_path / "push_branch_file.txt").write_text("push branch test")
subprocess.run([git2cpp_path, "add", "push_branch_file.txt"], cwd=repo_path, check=True)
subprocess.run([git2cpp_path, "commit", "-m", "branch commit"], cwd=repo_path, check=True)

# Switch back to main so HEAD is NOT on the branch we want to push.
subprocess.run(
[git2cpp_path, "checkout", "main"], capture_output=True, check=True, cwd=repo_path
)

status_cmd = [git2cpp_path, "status"]
p_status = subprocess.run(status_cmd, cwd=repo_path, capture_output=True, text=True)
assert p_status.returncode == 0
assert "On branch main" in p_status.stdout

# Push specifying the branch explicitly (HEAD is on main, not the test branch).
input = f"{username}\n{password}"
push_cmd = [git2cpp_path, "push", "origin", branch_name]
p_push = subprocess.run(push_cmd, cwd=repo_path, capture_output=True, text=True, input=input)
assert p_push.returncode == 0
# assert " * [new branch] test-" in p_push.stdout
print("\n\n", p_push.stdout)


def test_push_branches_flag_private_repo(
git2cpp_path, tmp_path, run_in_tmp_path, private_test_repo, commit_env_config
):
"""Test push --branches pushes all local branches."""
branch_a = f"test-a-{uuid4()}"
branch_b = f"test-b-{uuid4()}"

username = "abc"
password = private_test_repo["token"]
input = f"{username}\n{password}"
repo_path = tmp_path / private_test_repo["repo_name"]
url = private_test_repo["https_url"]

# Clone the private repo.
clone_cmd = [git2cpp_path, "clone", url]
p_clone = subprocess.run(clone_cmd, capture_output=True, text=True, input=input)
assert p_clone.returncode == 0
assert repo_path.exists()

# Create two extra branches with commits.
for branch_name in [branch_a, branch_b]:
subprocess.run(
[git2cpp_path, "checkout", "-b", branch_name],
capture_output=True,
check=True,
cwd=repo_path,
)
(repo_path / f"{branch_name}.txt").write_text(f"content for {branch_name}")
subprocess.run([git2cpp_path, "add", f"{branch_name}.txt"], cwd=repo_path, check=True)
subprocess.run(
[git2cpp_path, "commit", "-m", f"commit on {branch_name}"],
cwd=repo_path,
check=True,
)

# Go back to main.
subprocess.run(
[git2cpp_path, "checkout", "main"], capture_output=True, check=True, cwd=repo_path
)

# Push all branches at once.
input = f"{username}\n{password}"
push_cmd = [git2cpp_path, "push", "origin", "--branches"]
p_push = subprocess.run(push_cmd, cwd=repo_path, capture_output=True, text=True, input=input)
assert p_push.returncode == 0
# assert " * [new branch] test-" in p_push.stdout
print("\n\n", p_push.stdout)
Loading