Skip to content
Open
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
133 changes: 124 additions & 9 deletions src/subcommand/push_subcommand.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
#include "../subcommand/push_subcommand.hpp"

#include <iostream>
#include <unordered_map>

#include <git2/remote.h>

#include "../utils/ansi_code.hpp"
#include "../utils/common.hpp"
#include "../utils/credentials.hpp"
#include "../utils/progress.hpp"
#include "../wasm/scope.hpp"
Expand All @@ -14,8 +17,13 @@ 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("<refspec>", m_refspecs, "The refspec(s) to push");
sub->add_option("<refspec>", m_refspecs, "The refspec(s) to push")->expected(0, -1);
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 @@ -40,25 +48,132 @@ 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)
{
auto iter = repo.iterate_branches(GIT_BRANCH_LOCAL);
auto br = iter.next();
while (br)
{
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;
try
{
auto head_ref = repo.head();
std::string short_name = head_ref.short_name();
std::string refspec = "refs/heads/" + short_name;
m_refspecs.push_back(refspec);
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);
}
else
{
for (auto& r : m_refspecs)
{
if (r.rfind("refs/", 0) == 0)
{
continue;
}

r = "refs/heads/" + r;
}
}
git_strarray_wrapper refspecs_wrapper(m_refspecs);
git_strarray* refspecs_ptr = nullptr;
refspecs_ptr = refspecs_wrapper;
git_strarray* refspecs_ptr = refspecs_wrapper;

// Take a snapshot of repo's references to check which ones are new after push
auto repo_refs = repo.reference_list();
std::unordered_map<std::string, git_oid> remote_refs_before_oids;
const std::string prefix = std::string("refs/remotes/") + remote_name + "/";
for (const auto& r : repo_refs)
{
if (r.size() > prefix.size() && r.compare(0, prefix.size(), prefix) == 0)
{
// r is like "refs/remotes/origin/main"
std::string short_name = r.substr(prefix.size()); // "main" or "feature/x"
std::string canonical_remote_ref = std::string("refs/heads/") + short_name;

git_oid oid = repo.ref_name_to_id(r);
remote_refs_before_oids.emplace(std::move(canonical_remote_ref), oid);
}
}

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_local = "refs/heads/";
std::string local_short_name;
if (ref_view.size() >= prefix_local.size() && ref_view.substr(0, prefix_local.size()) == prefix_local)
{
local_short_name = std::string(ref_view.substr(prefix_local.size()));
}
else
{
local_short_name = std::string(refspec);
}

std::optional<std::string> upstream_opt = repo.branch_upstream_name(local_short_name);

std::string remote_branch = local_short_name;
std::string canonical_remote_ref = "refs/heads/" + local_short_name;
if (upstream_opt.has_value())
{
const std::string up_name = upstream_opt.value();
auto pos = up_name.find('/');
if (pos != std::string::npos && pos + 1 < up_name.size())
{
std::string up_remote = up_name.substr(0, pos);
std::string up_branch = up_name.substr(pos + 1);
if (up_remote == remote_name)
{
remote_branch = up_branch;
canonical_remote_ref = "refs/heads/" + remote_branch;
}
}
}

auto it = remote_refs_before_oids.find(canonical_remote_ref);
if (it == remote_refs_before_oids.end())
{
std::cout << " * [new branch] " << local_short_name << " -> " << remote_branch << std::endl;
continue;
}

git_oid remote_oid = it->second;

std::optional<git_oid> local_oid_opt;
if (auto ref_opt = repo.find_reference_dwim(("refs/heads/" + local_short_name)))
{
const git_oid* target = ref_opt->target();
local_oid_opt = *target;
}

if (!local_oid_opt)
{
std::cout << " " << local_short_name << " -> " << remote_branch << std::endl;
continue;
}
git_oid local_oid = local_oid_opt.value();

if (!git_oid_equal(&remote_oid, &local_oid))
{
std::string old_hex = oid_to_hex(remote_oid);
std::string new_hex = oid_to_hex(local_oid);
// TODO: check order of hex codes
std::cout << " " << old_hex.substr(0, 7) << ".." << new_hex.substr(0, 7) << " "
<< local_short_name << " -> " << local_short_name << std::endl;
}
}
}
1 change: 1 addition & 0 deletions src/subcommand/push_subcommand.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ class push_subcommand

std::string m_remote_name;
std::vector<std::string> m_refspecs;
bool m_branches_flag = false;
};
5 changes: 1 addition & 4 deletions src/subcommand/revlist_subcommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,9 @@ void revlist_subcommand::run()

std::size_t i = 0;
git_oid commit_oid;
char buf[GIT_OID_SHA1_HEXSIZE + 1];
while (!walker.next(commit_oid) && i < m_max_count_flag)
{
git_oid_fmt(buf, &commit_oid);
buf[GIT_OID_SHA1_HEXSIZE] = '\0';
std::cout << buf << std::endl;
std::cout << oid_to_hex(commit_oid) << std::endl;
++i;
}
}
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: 8 additions & 0 deletions src/utils/common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,11 @@ std::string trim(const std::string& str)
auto s = std::regex_replace(str, std::regex("^\\s+"), "");
return std::regex_replace(s, std::regex("\\s+$"), "");
}

std::string oid_to_hex(const git_oid& oid)
{
char oid_str[GIT_OID_SHA1_HEXSIZE + 1];
git_oid_fmt(oid_str, &oid);
oid_str[GIT_OID_SHA1_HEXSIZE] = '\0';
return std::string(oid_str);
}
2 changes: 2 additions & 0 deletions src/utils/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,5 @@ std::vector<std::string> split_input_at_newlines(std::string_view str);

// Remove whitespace from start and end of a string.
std::string trim(const std::string& str);

std::string oid_to_hex(const git_oid& oid);
18 changes: 7 additions & 11 deletions src/utils/progress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <iostream>
#include <string_view>

#include "../utils/common.hpp"

int sideband_progress(const char* str, int len, void*)
{
printf("remote: %.*s", len, str);
Expand Down Expand Up @@ -83,10 +85,7 @@ 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*)
{
char a_str[GIT_OID_SHA1_HEXSIZE + 1], b_str[GIT_OID_SHA1_HEXSIZE + 1];

git_oid_fmt(b_str, b);
b_str[GIT_OID_SHA1_HEXSIZE] = '\0';
std::string b_str = oid_to_hex(*b);

if (git_oid_is_zero(a))
{
Expand Down Expand Up @@ -114,8 +113,7 @@ int update_refs(const char* refname, const git_oid* a, const git_oid* b, git_ref
}
else
{
git_oid_fmt(a_str, a);
a_str[GIT_OID_SHA1_HEXSIZE] = '\0';
std::string a_str = oid_to_hex(*a);

std::cout << "[updated] " << std::string(a_str, 10) << ".." << std::string(b_str, 10) << " "
<< refname << std::endl;
Expand All @@ -139,11 +137,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;
}
2 changes: 0 additions & 2 deletions src/wrapper/remote_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
#include <string>
#include <vector>

#include <git2/remote.h>

#include "../utils/git_exception.hpp"

remote_wrapper::remote_wrapper(git_remote* remote)
Expand Down
1 change: 0 additions & 1 deletion src/wrapper/remote_wrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#include <vector>

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

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

Expand Down
35 changes: 35 additions & 0 deletions src/wrapper/repository_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,26 @@ std::optional<reference_wrapper> repository_wrapper::find_reference_dwim(std::st
return rc == 0 ? std::make_optional(reference_wrapper(ref)) : std::nullopt;
}

std::vector<std::string> repository_wrapper::reference_list() const
{
git_strarray refs = {0};
throw_if_error(git_reference_list(&refs, *this));
std::vector<std::string> result;
for (size_t i = 0; i < refs.count; ++i)
{
result.push_back(refs.strings[i]);
}
git_strarray_free(&refs);
return result;
}

const git_oid repository_wrapper::ref_name_to_id(std::string ref_name) const
{
git_oid ref_id;
throw_if_error(git_reference_name_to_id(&ref_id, *this, ref_name.c_str()));
return ref_id;
}

// Index

index_wrapper repository_wrapper::make_index()
Expand Down Expand Up @@ -194,6 +214,21 @@ std::optional<reference_wrapper> repository_wrapper::upstream() const
}
}

std::optional<std::string> repository_wrapper::branch_upstream_name(std::string local_branch) const
{
git_buf buf = GIT_BUF_INIT;
int error = git_branch_upstream_name(&buf, *this, local_branch.c_str());
if (error != 0)
{
git_buf_dispose(&buf);
return std::nullopt;
}

std::string result(buf.ptr ? buf.ptr : "");
git_buf_dispose(&buf);
return result;
}

branch_tracking_info repository_wrapper::get_tracking_info() const
{
branch_tracking_info info;
Expand Down
4 changes: 3 additions & 1 deletion src/wrapper/repository_wrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

#include <git2.h>

#include "../utils/common.hpp"
#include "../utils/git_exception.hpp"
#include "../wrapper/annotated_commit_wrapper.hpp"
#include "../wrapper/branch_wrapper.hpp"
Expand Down Expand Up @@ -63,6 +62,8 @@ class repository_wrapper : public wrapper_base<git_repository>
// References
reference_wrapper find_reference(std::string_view ref_name) const;
std::optional<reference_wrapper> find_reference_dwim(std::string_view ref_name) const;
std::vector<std::string> reference_list() const;
const git_oid ref_name_to_id(std::string ref_name) const;

// Index
index_wrapper make_index();
Expand All @@ -74,6 +75,7 @@ class repository_wrapper : public wrapper_base<git_repository>
branch_wrapper find_branch(std::string_view name) const;
branch_iterator iterate_branches(git_branch_t type) const;
std::optional<reference_wrapper> upstream() const;
std::optional<std::string> branch_upstream_name(std::string local_branch) const;
branch_tracking_info get_tracking_info() const;

// Commits
Expand Down
Loading