From 50621159640688d14fec25464b3ee7b4bae6f16a Mon Sep 17 00:00:00 2001 From: Sandrine Pataut Date: Tue, 24 Mar 2026 10:13:11 +0100 Subject: [PATCH 1/4] Add branches to push subcommand --- src/subcommand/push_subcommand.cpp | 64 +++++++++++++++++--- src/subcommand/push_subcommand.hpp | 2 + src/utils/ansi_code.hpp | 3 + src/utils/progress.cpp | 8 +-- src/utils/progress.hpp | 9 +++ test/test_push.py | 97 +++++++++++++++++++++++++++++- 6 files changed, 167 insertions(+), 16 deletions(-) diff --git a/src/subcommand/push_subcommand.cpp b/src/subcommand/push_subcommand.cpp index 9e2af17..e6e7e4a 100644 --- a/src/subcommand/push_subcommand.cpp +++ b/src/subcommand/push_subcommand.cpp @@ -3,7 +3,9 @@ #include #include +#include +#include "../utils/ansi_code.hpp" #include "../utils/credentials.hpp" #include "../utils/progress.hpp" #include "../wrapper/repository_wrapper.hpp" @@ -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("", m_remote_name, "The remote to push to")->default_val("origin"); - + sub->add_option("", m_branch_name, "The branch to push"); sub->add_option("", 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 ." + ); + sub->callback( [this]() @@ -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; + } } diff --git a/src/subcommand/push_subcommand.hpp b/src/subcommand/push_subcommand.hpp index 07c301e..c4450bf 100644 --- a/src/subcommand/push_subcommand.hpp +++ b/src/subcommand/push_subcommand.hpp @@ -17,5 +17,7 @@ class push_subcommand private: std::string m_remote_name; + std::string m_branch_name; std::vector m_refspecs; + bool m_branches_flag = false; }; diff --git a/src/utils/ansi_code.hpp b/src/utils/ansi_code.hpp index 90b1e25..becc5a9 100644 --- a/src/utils/ansi_code.hpp +++ b/src/utils/ansi_code.hpp @@ -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); diff --git a/src/utils/progress.cpp b/src/utils/progress.cpp index 12b7c63..9af2d13 100644 --- a/src/utils/progress.cpp +++ b/src/utils/progress.cpp @@ -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; } diff --git a/src/utils/progress.hpp b/src/utils/progress.hpp index 861c8d9..fc70509 100644 --- a/src/utils/progress.hpp +++ b/src/utils/progress.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include int sideband_progress(const char* str, int len, void*); @@ -7,4 +9,11 @@ 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*); diff --git a/test/test_push.py b/test/test_push.py index 313f201..6e92715 100644 --- a/test/test_push.py +++ b/test/test_push.py @@ -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 .""" + 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) From 339c8347e89d229b5ea5d4129894bbb9aab8e67d Mon Sep 17 00:00:00 2001 From: Sandrine Pataut Date: Wed, 25 Mar 2026 15:17:11 +0100 Subject: [PATCH 2/4] bla --- src/subcommand/push_subcommand.cpp | 2 +- test/test_push.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/subcommand/push_subcommand.cpp b/src/subcommand/push_subcommand.cpp index e6e7e4a..3cb1dd7 100644 --- a/src/subcommand/push_subcommand.cpp +++ b/src/subcommand/push_subcommand.cpp @@ -100,6 +100,6 @@ void push_subcommand::run() { short_name = refspec; } - std::cout << " * " << short_name << " -> " << short_name << std::endl; + std::cout << " * " << short_name << " -> " << short_name << std::endl; // " * [new branch] test-" } } diff --git a/test/test_push.py b/test/test_push.py index 6e92715..236574f 100644 --- a/test/test_push.py +++ b/test/test_push.py @@ -61,7 +61,8 @@ 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 " * [new branch] test-" in p_push.stdout + assert " * test-" in p_push.stdout # " * [new branch] test-" + print(p_push.stdout) def test_push_branch_private_repo( From c94bdc3e8573a13ea4de188099f8042bfc8a9ce0 Mon Sep 17 00:00:00 2001 From: Sandrine Pataut Date: Wed, 1 Apr 2026 10:51:03 +0200 Subject: [PATCH 3/4] backup --- src/subcommand/push_subcommand.cpp | 45 +++++++++++++++++++++++++++++- src/wrapper/remote_wrapper.cpp | 39 ++++++++++++++++++++++++++ src/wrapper/remote_wrapper.hpp | 4 +++ src/wrapper/repository_wrapper.cpp | 18 ++++++++++++ src/wrapper/repository_wrapper.hpp | 2 +- test/test_push.py | 7 +++-- 6 files changed, 110 insertions(+), 5 deletions(-) diff --git a/src/subcommand/push_subcommand.cpp b/src/subcommand/push_subcommand.cpp index 3cb1dd7..bdca73e 100644 --- a/src/subcommand/push_subcommand.cpp +++ b/src/subcommand/push_subcommand.cpp @@ -1,7 +1,9 @@ #include "../subcommand/push_subcommand.hpp" #include +#include +#include #include #include @@ -33,6 +35,14 @@ push_subcommand::push_subcommand(const libgit2_object&, CLI::App& app) ); } +int credential_cb(git_cred **out, const char *url, const char *username_from_url, unsigned int allowed_types, void *payload) { + // Replace with your actual credentials + const char* username = user_credentials; + const char *password = "your_password_or_token"; + + return git_cred_userpass_plaintext_new(out, username, password); +} + void push_subcommand::run() { auto directory = get_current_git_path(); @@ -41,6 +51,11 @@ void push_subcommand::run() std::string remote_name = m_remote_name.empty() ? "origin" : m_remote_name; auto remote = repo.find_remote(remote_name); + git_direction direction = GIT_DIRECTION_FETCH; + + // remote.connect(direction, ); + // auto remote_branches_ante_push = remote.ls(); + git_push_options push_opts = GIT_PUSH_OPTIONS_INIT; push_opts.callbacks.credentials = user_credentials; push_opts.callbacks.push_transfer_progress = push_transfer_progress; @@ -85,6 +100,7 @@ void push_subcommand::run() refspecs_ptr = refspecs_wrapper; remote.push(refspecs_ptr, &push_opts); + auto remote_branches_post_push = remote.ls(); std::cout << "To " << remote.url() << std::endl; for (const auto& refspec : m_refspecs) @@ -100,6 +116,33 @@ void push_subcommand::run() { short_name = refspec; } - std::cout << " * " << short_name << " -> " << short_name << std::endl; // " * [new branch] test-" + + // std::optional branch_upstream_name = repo.branch_upstream_name(short_name); + std::string upstream_name; + upstream_name = short_name; + // if (branch_upstream_name.has_value()) + // { + // upstream_name = branch_upstream_name.value(); + // } + // else + // { + // // ??? + // } + // if (std::find(remote_branches.begin(), remote_branches.end(), short_name) == remote_branches.end()) + // { + // std::cout << " * [new branch] " << short_name << " -> " << short_name << std::endl; + // } + // + // if (std::find(remote_branches.begin(), remote_branches.end(), short_name) == remote_branches.end()) + // { + // std::cout << " * [new branch] " << short_name << " -> " << short_name << std::endl; + // } + + auto ref = repo.find_reference(ref_view); + if (!ref.is_remote()) + { + std::cout << " * [new branch] " << short_name << " -> " << upstream_name << std::endl; + } + // std::cout << " * [new branch] " << short_name << " -> " << short_name << std::endl; } } diff --git a/src/wrapper/remote_wrapper.cpp b/src/wrapper/remote_wrapper.cpp index 3f603dd..f481712 100644 --- a/src/wrapper/remote_wrapper.cpp +++ b/src/wrapper/remote_wrapper.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "../utils/git_exception.hpp" @@ -53,6 +54,44 @@ std::vector remote_wrapper::refspecs() const return result; } +std::vector remote_wrapper::ls() const +{ + const git_remote_head** remote_heads; + size_t remote_heads_size; + throw_if_error(git_remote_ls(&remote_heads, &remote_heads_size, *this)); + + std::vector remote_heads_vec; + for (size_t i = 0; i < remote_heads_size; i++) + { + remote_heads_vec.push_back(remote_heads[i]); + } + return remote_heads_vec; +} + + +// std::vector remote_wrapper::ls() const +// { +// const git_remote_head** remote_heads; +// size_t remote_heads_size; +// throw_if_error(git_remote_ls(&remote_heads, &remote_heads_size, *this)); + +// std::vector remote_branches; +// for (size_t i = 0; i < remote_heads_size; i++) +// { +// const git_remote_head* head = remote_heads[i]; +// if (!head->local) +// { +// remote_branches.push_back(head->name); +// } +// } +// return remote_branches; +// } + +void remote_wrapper::connect(git_direction direction, const git_remote_callbacks* callbacks) +{ + throw_if_error(git_remote_connect(*this, direction, callbacks, NULL, NULL)); +} + void remote_wrapper::fetch(const git_strarray* refspecs, const git_fetch_options* opts, const char* reflog_message) { throw_if_error(git_remote_fetch(*this, refspecs, opts, reflog_message)); diff --git a/src/wrapper/remote_wrapper.hpp b/src/wrapper/remote_wrapper.hpp index a933fb8..776e304 100644 --- a/src/wrapper/remote_wrapper.hpp +++ b/src/wrapper/remote_wrapper.hpp @@ -25,6 +25,10 @@ class remote_wrapper : public wrapper_base std::vector refspecs() const; + std::vector ls() const; + // std::vector ls() const; + + void connect(git_direction direction, const git_remote_callbacks* callbacks); void fetch(const git_strarray* refspecs, const git_fetch_options* opts, const char* reflog_message); void push(const git_strarray* refspecs, const git_push_options* opts); diff --git a/src/wrapper/repository_wrapper.cpp b/src/wrapper/repository_wrapper.cpp index ccc0408..86bf068 100644 --- a/src/wrapper/repository_wrapper.cpp +++ b/src/wrapper/repository_wrapper.cpp @@ -3,6 +3,9 @@ #include #include #include +#include +#include +#include #include "../utils/git_exception.hpp" #include "../wrapper/commit_wrapper.hpp" @@ -194,6 +197,21 @@ std::optional repository_wrapper::upstream() const } } +std::optional repository_wrapper::branch_upstream_name(std::string local_branch) const +{ + git_buf buf; + int error = git_branch_upstream_name(&buf, *this, local_branch.c_str()); + if (error != 0) + { + return std::nullopt; + } + + std::optional result; + result = std::string_view(buf.ptr); + git_buf_dispose(&buf); + return result; +} + branch_tracking_info repository_wrapper::get_tracking_info() const { branch_tracking_info info; diff --git a/src/wrapper/repository_wrapper.hpp b/src/wrapper/repository_wrapper.hpp index d630343..429617b 100644 --- a/src/wrapper/repository_wrapper.hpp +++ b/src/wrapper/repository_wrapper.hpp @@ -6,7 +6,6 @@ #include -#include "../utils/common.hpp" #include "../utils/git_exception.hpp" #include "../wrapper/annotated_commit_wrapper.hpp" #include "../wrapper/branch_wrapper.hpp" @@ -74,6 +73,7 @@ class repository_wrapper : public wrapper_base branch_wrapper find_branch(std::string_view name) const; branch_iterator iterate_branches(git_branch_t type) const; std::optional upstream() const; + std::optional branch_upstream_name(std::string local_branch) const; branch_tracking_info get_tracking_info() const; // Commits diff --git a/test/test_push.py b/test/test_push.py index 236574f..77698ac 100644 --- a/test/test_push.py +++ b/test/test_push.py @@ -61,7 +61,7 @@ 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 " * test-" in p_push.stdout # " * [new branch] test-" + # assert " * [new branch] test-" in p_push.stdout print(p_push.stdout) @@ -107,7 +107,7 @@ def test_push_branch_private_repo( 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 + assert " * [new branch] test-" in p_push.stdout print("\n\n", p_push.stdout) @@ -156,5 +156,6 @@ def test_push_branches_flag_private_repo( 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 + assert " * [new branch] test-" in p_push.stdout + # assert "main" not in p_push.stdout print("\n\n", p_push.stdout) From ff36af87f77960685dd25db341e515c90b92ad40 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 08:58:01 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/subcommand/push_subcommand.cpp | 5 +++-- src/wrapper/remote_wrapper.cpp | 7 +++---- src/wrapper/repository_wrapper.cpp | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/subcommand/push_subcommand.cpp b/src/subcommand/push_subcommand.cpp index bdca73e..3b1d80e 100644 --- a/src/subcommand/push_subcommand.cpp +++ b/src/subcommand/push_subcommand.cpp @@ -35,10 +35,11 @@ push_subcommand::push_subcommand(const libgit2_object&, CLI::App& app) ); } -int credential_cb(git_cred **out, const char *url, const char *username_from_url, unsigned int allowed_types, void *payload) { +int credential_cb(git_cred** out, const char* url, const char* username_from_url, unsigned int allowed_types, void* payload) +{ // Replace with your actual credentials const char* username = user_credentials; - const char *password = "your_password_or_token"; + const char* password = "your_password_or_token"; return git_cred_userpass_plaintext_new(out, username, password); } diff --git a/src/wrapper/remote_wrapper.cpp b/src/wrapper/remote_wrapper.cpp index f481712..5ce7b16 100644 --- a/src/wrapper/remote_wrapper.cpp +++ b/src/wrapper/remote_wrapper.cpp @@ -62,13 +62,12 @@ std::vector remote_wrapper::ls() const std::vector remote_heads_vec; for (size_t i = 0; i < remote_heads_size; i++) - { - remote_heads_vec.push_back(remote_heads[i]); - } + { + remote_heads_vec.push_back(remote_heads[i]); + } return remote_heads_vec; } - // std::vector remote_wrapper::ls() const // { // const git_remote_head** remote_heads; diff --git a/src/wrapper/repository_wrapper.cpp b/src/wrapper/repository_wrapper.cpp index 86bf068..8b43a2b 100644 --- a/src/wrapper/repository_wrapper.cpp +++ b/src/wrapper/repository_wrapper.cpp @@ -5,6 +5,7 @@ #include #include #include + #include #include "../utils/git_exception.hpp"