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
144 changes: 133 additions & 11 deletions src/subcommand/push_subcommand.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
#include "../subcommand/push_subcommand.hpp"

#include <iostream>
#include <optional>
#include <unordered_map>
#include <string_view>

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

#include "../utils/ansi_code.hpp"
#include "../utils/credentials.hpp"
#include "../utils/progress.hpp"
#include "../wrapper/repository_wrapper.hpp"
Expand All @@ -13,8 +17,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 @@ -24,6 +35,15 @@ push_subcommand::push_subcommand(const libgit2_object&, CLI::App& app)
);
}

// TODO: put in common
static 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);
}

void push_subcommand::run()
{
auto directory = get_current_git_path();
Expand All @@ -37,25 +57,127 @@ 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;

// Take a snapshot of remote branches to check which ones are new after push
git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
callbacks.credentials = user_credentials;
credentials_payload creds_payload;
callbacks.payload = &creds_payload;
push_opts.callbacks.payload = &creds_payload;

auto remote_heads = remote.list_heads(&callbacks);

// Map with names of branches and their oids before push
std::unordered_map<std::string, git_oid> remote_heads_map;
for (const auto& h : remote_heads)
{
remote_heads_map.emplace(h.name, h.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 = "refs/heads/";
std::string local_short_name;
if (ref_view.substr(0, prefix.size()) == prefix)
{
local_short_name = ref_view.substr(prefix.size());
}
else
{
local_short_name = refspec;
}

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

std::string remote_branch = local_short_name;
std::string 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_name.substr(pos + 1);
remote_ref = "refs/heads/" + remote_branch;
}
}
}

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

git_oid remote_oid = iter->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; // TODO: pas comprenu pourquoi je ne peux pas faire local_oid_opt = ref_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;
}
}
}
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
32 changes: 30 additions & 2 deletions src/utils/credentials.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,53 @@ int user_credentials(
void* payload
)
{
credentials_payload* cached = payload ? static_cast<credentials_payload*>(payload) : nullptr;

// Check for cached credentials here, if desired.
// It might be necessary to make this function stateful to avoid repeating unnecessary checks.

*out = nullptr;

if (allowed_types & GIT_CREDENTIAL_USERPASS_PLAINTEXT)
{
std::string username = username_from_url ? username_from_url : "";
if (username.empty())
std::string username;
if (username_from_url && username_from_url[0] != '\0')
{
username = username_from_url;
}
else if (cached && cached->username.has_value())
{
username = *cached->username;
}
else
{
username = prompt_input("Username: ");
if (cached && !username.empty())
{
cached->username = username;
}
}

if (username.empty())
{
giterr_set_str(GIT_ERROR_HTTP, "No username specified");
return GIT_EAUTH;
}

std::string password = prompt_input("Password: ", false);
if (cached && cached->password.has_value())
{
password = *cached->password;
}
else
{
password = prompt_input("Password: ", false);
if (cached && !password.empty())
{
cached->password = password;
}
}

if (password.empty())
{
giterr_set_str(GIT_ERROR_HTTP, "No password specified");
Expand Down
9 changes: 9 additions & 0 deletions src/utils/credentials.hpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
#pragma once

#include <optional>
#include <string>

#include <git2/credential.h>

struct credentials_payload
{
std::optional<std::string> username;
std::optional<std::string> password;
};

// Libgit2 callback of type git_credential_acquire_cb to obtain user credentials
// (username and password) to authenticate remote https access.
int user_credentials(
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*);
40 changes: 38 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 Expand Up @@ -62,3 +60,41 @@ void remote_wrapper::push(const git_strarray* refspecs, const git_push_options*
{
throw_if_error(git_remote_push(*this, refspecs, opts));
}

void remote_wrapper::connect(git_direction direction, const git_remote_callbacks* callbacks) const
{
throw_if_error(git_remote_connect(*this, direction, callbacks, nullptr, nullptr));
}

std::vector<remote_head> remote_wrapper::list_heads(const git_remote_callbacks* callbacks = nullptr) const
{
std::vector<remote_head> result;

this->connect(GIT_DIRECTION_FETCH, callbacks);

const git_remote_head** heads = nullptr;
size_t heads_len = 0;
int err = git_remote_ls(&heads, &heads_len, *this);
if (err != 0)
{
git_remote_disconnect(*this);
throw_if_error(err);
}

for (size_t i = 0; i < heads_len; ++i)
{
const git_remote_head* h = heads[i];
if (!h || !h->name)
{
continue;
}

remote_head rh;
rh.name = std::string(h->name);
rh.oid = h->oid;
result.push_back(std::move(rh));
}

git_remote_disconnect(*this);
return result;
}
11 changes: 10 additions & 1 deletion src/wrapper/remote_wrapper.hpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
#pragma once

#include <string>
#include <string_view>
#include <vector>

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

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

struct remote_head
{
std::string name;
git_oid oid;
};

class remote_wrapper : public wrapper_base<git_remote>
{
public:
Expand All @@ -27,6 +33,9 @@ class remote_wrapper : public wrapper_base<git_remote>

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);
void connect(git_direction direction, const git_remote_callbacks* callbacks) const;

std::vector<remote_head> list_heads(const git_remote_callbacks* callbacks) const;

private:

Expand Down
Loading