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

#include <algorithm>
#include <iostream>
#include <optional>
#include <string>
#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 +18,14 @@ 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_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,135 @@ 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);
}
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);
// }

// Take a snapshot of repo's references to check which ones are new after push
auto repo_refs = repo.reference_list();
std::vector<std::string> repo_refs_remote;
for (int i = 0; i < repo_refs.size(); ++i)
{
std::string prefix_remote = "refs/remote/";
if (repo_refs[i].substr(0, prefix_remote.size()) == prefix_remote)
{
std::string remote_short_name = repo_refs[i].substr(prefix_remote.size());
repo_refs_remote.push_back(remote_short_name);
}
}

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.substr(0, prefix_local.size()) == prefix_local)
{
local_short_name = ref_view.substr(prefix_local.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 = std::find(repo_refs_remote.begin(), repo_refs_remote.end(), remote_ref);
if (iter == repo_refs_remote.end())
{
std::cout << " * [new branch] " << local_short_name << " -> " << remote_branch << std::endl;
continue;
}

git_oid remote_oid = repo.ref_name_to_id(*iter);

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;
}
}
}
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;
};
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*);
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
45 changes: 44 additions & 1 deletion src/wrapper/repository_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
#include <algorithm>
#include <fstream>
#include <iostream>
#include <optional>
#include <string_view>

#include <git2/buffer.h>
#include <git2/oid.h>
#include <git2/refs.h>
#include <git2/strarray.h>
#include <git2/types.h>

#include "../utils/git_exception.hpp"
#include "../wrapper/commit_wrapper.hpp"
Expand Down Expand Up @@ -134,6 +142,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* array;
throw_if_error(git_reference_list(array, *this));
std::vector<std::string> result;
for (size_t i = 0; i < array->count; ++i)
{
result.push_back(array->strings[i]);
}
git_strarray_free(array);
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 +222,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 Expand Up @@ -426,7 +469,7 @@ size_t repository_wrapper::shallow_depth_from_head() const
if (parent_list.size() > 0u)
{
has_parent = true;
for (size_t j = 0u; parent_list.size(); j++)
for (size_t j = 0u; j < parent_list.size(); ++j)
{
const commit_wrapper& c = parent_list[j];
temp_commits_list.push_back(std::move(const_cast<commit_wrapper&>(c)));
Expand Down
Loading
Loading