Skip to content
Merged
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
196 changes: 178 additions & 18 deletions src/subcommand/push_subcommand.cpp
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
#include "../subcommand/push_subcommand.hpp"

#include <iostream>
#include <unordered_map>
#include <utility>

#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"
#include "../wrapper/repository_wrapper.hpp"

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 @@ -25,6 +33,131 @@ push_subcommand::push_subcommand(const libgit2_object&, CLI::App& app)
);
}

void push_subcommand::fill_refspec(repository_wrapper& repo)
{
const std::string prefix = std::string("refs/heads/");
if (m_branches_flag)
{
auto iter = repo.iterate_branches(GIT_BRANCH_LOCAL);
auto br = iter.next();
while (br)
{
std::string refspec = std::string(br->name());
if (refspec.starts_with(prefix))
{
refspec = refspec.substr(prefix.size());
}
m_refspecs.push_back(refspec);
br = iter.next();
}
}
else if (m_refspecs.empty())
{
std::string branch;
try
{
auto head_ref = repo.head();
branch = head_ref.short_name();
}
catch (...)
{
std::cerr << "Could not determine current branch to push." << std::endl;
return;
}
m_refspecs.push_back(branch);
}
else
{
for (auto& refspec : m_refspecs)
{
if (refspec.starts_with(prefix))
{
refspec = refspec.substr(prefix.size());
}
}
}
}

std::unordered_map<std::string, git_oid> get_remotes(repository_wrapper& repo, std::string remote_name)
{
std::vector<std::string> repo_refs = repo.refs_list();
std::unordered_map<std::string, git_oid> remotes_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"

git_oid oid = repo.ref_name_to_id(r);
remotes_oids.emplace(short_name, oid);
}
}
return remotes_oids;
}

std::unordered_map<std::string, git_oid> diff_branches(
std::unordered_map<std::string, git_oid> remotes_before_push,
std::unordered_map<std::string, git_oid> remotes_after_push
)
{
std::unordered_map<std::string, git_oid> new_branches;
for (const auto& br : remotes_after_push)
{
const std::string name = br.first;
const git_oid& oid = br.second;
if (!remotes_before_push.contains(name))
{
new_branches.emplace(name, oid);
}
}
return new_branches;
}

std::pair<std::vector<std::string>, std::vector<std::string>>
split_refspecs(std::vector<std::string> refspecs, std::unordered_map<std::string, git_oid> new_branches)
{
std::vector<std::string> new_pushed_refspecs;
std::vector<std::string> existing_refspecs;

for (const auto refspec : refspecs)
{
if (!new_branches.contains(refspec))
{
existing_refspecs.push_back(refspec);
}
else
{
new_pushed_refspecs.push_back(refspec);
}
}

return std::make_pair(new_pushed_refspecs, existing_refspecs);
}

std::pair<std::string, std::string>
get_branch_names(repository_wrapper& repo, std::string remote_name, std::string refspec)
{
std::optional<std::string> upstream_opt = repo.branch_upstream_name(refspec);
std::string remote_branch = refspec;
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;
}
}
}
return std::make_pair(refspec, remote_branch);
}

void push_subcommand::run()
{
wasm_http_transport_scope transport; // Enables wasm http(s) transport.
Expand All @@ -40,25 +173,52 @@ 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())
fill_refspec(repo);
std::vector<std::string> refspecs_push;
for (auto refspec : m_refspecs)
{
try
refspecs_push.push_back("refs/heads/" + refspec);
}
git_strarray_wrapper refspecs_wrapper(refspecs_push);
git_strarray* refspecs_ptr = refspecs_wrapper;

auto remotes_before_push = get_remotes(repo, remote_name);

remote.push(refspecs_ptr, &push_opts);

auto remotes_after_push = get_remotes(repo, remote_name);

auto new_branches = diff_branches(remotes_before_push, remotes_after_push);
auto [new_pushed_refspecs, existing_refspecs] = split_refspecs(m_refspecs, new_branches);

std::cout << "To " << remote.url() << std::endl;
for (const auto& refspec : new_pushed_refspecs)
{
auto [local_short_name, remote_branch] = get_branch_names(repo, remote_name, refspec);

std::cout << " * [new branch] " << local_short_name << " -> " << remote_branch << std::endl;
}

for (const auto& refspec : existing_refspecs)
{
auto [local_short_name, remote_branch] = get_branch_names(repo, remote_name, refspec);

git_oid remote_oid = remotes_before_push[refspec];

git_oid local_oid;
if (auto ref_opt = repo.find_reference_dwim(("refs/heads/" + local_short_name)))
{
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);
const git_oid* target = ref_opt->target();
local_oid = *target;
}
catch (...)

if (!git_oid_equal(&remote_oid, &local_oid))
{
std::cerr << "Could not determine current branch to push." << std::endl;
return;
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 << " -> " << remote_branch << std::endl;
}
}
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;
}
4 changes: 4 additions & 0 deletions src/subcommand/push_subcommand.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <CLI/CLI.hpp>

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

class push_subcommand
{
Expand All @@ -14,8 +15,11 @@ class push_subcommand
explicit push_subcommand(const libgit2_object&, CLI::App& app);
void run();

void fill_refspec(repository_wrapper& repo);

private:

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
Loading