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
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ set(GIT2CPP_SRC
${GIT2CPP_SOURCE_DIR}/subcommand/revlist_subcommand.hpp
${GIT2CPP_SOURCE_DIR}/subcommand/revparse_subcommand.cpp
${GIT2CPP_SOURCE_DIR}/subcommand/revparse_subcommand.hpp
${GIT2CPP_SOURCE_DIR}/subcommand/stash_subcommand.cpp
${GIT2CPP_SOURCE_DIR}/subcommand/stash_subcommand.hpp
${GIT2CPP_SOURCE_DIR}/subcommand/status_subcommand.cpp
${GIT2CPP_SOURCE_DIR}/subcommand/status_subcommand.hpp
${GIT2CPP_SOURCE_DIR}/utils/ansi_code.cpp
Expand Down
2 changes: 2 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "subcommand/push_subcommand.hpp"
#include "subcommand/remote_subcommand.hpp"
#include "subcommand/reset_subcommand.hpp"
#include "subcommand/stash_subcommand.hpp"
#include "subcommand/status_subcommand.hpp"
#include "subcommand/revparse_subcommand.hpp"
#include "subcommand/revlist_subcommand.hpp"
Expand Down Expand Up @@ -50,6 +51,7 @@ int main(int argc, char** argv)
remote_subcommand remote(lg2_obj, app);
revparse_subcommand revparse(lg2_obj, app);
revlist_subcommand revlist(lg2_obj, app);
stash_subcommand stash(lg2_obj, app);

app.require_subcommand(/* min */ 0, /* max */ 1);

Expand Down
92 changes: 92 additions & 0 deletions src/subcommand/stash_subcommand.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#include <git2/deprecated.h>
#include <git2/oid.h>
#include <git2/stash.h>
#include <iostream>

#include <git2/remote.h>

#include "../subcommand/stash_subcommand.hpp"
#include "../subcommand/status_subcommand.hpp"
#include "../wrapper/repository_wrapper.hpp"

bool has_subcommand(CLI::App* cmd)
{
std::vector<std::string> subs = { "push", "pop", "list", "apply" };
return std::any_of(subs.begin(), subs.end(), [cmd](const std::string& s) { return cmd->got_subcommand(s); });
}

stash_subcommand::stash_subcommand(const libgit2_object&, CLI::App& app)
{
auto* stash = app.add_subcommand("stash", "Stash the changes in a dirty working directory away");
auto* push = stash->add_subcommand("push", "");
auto* list = stash->add_subcommand("list", "");
auto* pop = stash->add_subcommand("pop", "");
auto* apply = stash->add_subcommand("apply", "");

push->add_option("-m,--message", m_message, "");
pop->add_option("--index", m_index, "");
apply->add_option("--index", m_index, "");

stash->callback([this,stash]()
{
if (!has_subcommand(stash))
{
this->run_push();
}
});
push->callback([this]() { this->run_push(); });
list->callback([this]() { this->run_list(); });
pop->callback([this]() { this->run_pop(); });
apply->callback([this]() { this->run_apply(); });
}

void stash_subcommand::run_push()
{
auto directory = get_current_git_path();
auto repo = repository_wrapper::open(directory);
auto author_committer_signatures = signature_wrapper::get_default_signature_from_env(repo);

git_oid stash_id;
throw_if_error(git_stash_save(&stash_id, repo, author_committer_signatures.first, m_message.c_str(), GIT_STASH_DEFAULT));
auto stash = repo.find_commit(stash_id);
std::cout << "Saved working directory and index state " << stash.summary() << std::endl;
}

static int list_stash_cb(size_t index, const char* message, const git_oid* stash_id, void* payload)
{
std::cout << "stash@{" << index << "}: " << message << std::endl;
return 0;
}

void stash_subcommand::run_list()
{
auto directory = get_current_git_path();
auto repo = repository_wrapper::open(directory);

throw_if_error(git_stash_foreach(repo, list_stash_cb, NULL));
}

void stash_subcommand::run_pop()
{
auto directory = get_current_git_path();
auto repo = repository_wrapper::open(directory);

std::string stash_spec = "stash@{" + std::to_string(m_index) + "}";
auto stash_obj = repo.revparse_single(stash_spec);
git_oid stash_id = stash_obj->oid();
char id_string[GIT_OID_HEXSZ + 1];
git_oid_tostr(id_string, sizeof(id_string), &stash_id);

throw_if_error(git_stash_pop(repo, m_index, NULL));
status_run();
std::cout << "Dropped refs/stash@{" << m_index << "} (" << id_string << ")" << std::endl;
}

void stash_subcommand::run_apply()
{
auto directory = get_current_git_path();
auto repo = repository_wrapper::open(directory);

throw_if_error(git_stash_apply(repo, m_index, NULL));
status_run();
}
20 changes: 20 additions & 0 deletions src/subcommand/stash_subcommand.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once

#include <CLI/CLI.hpp>

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

class stash_subcommand
{
public:

explicit stash_subcommand(const libgit2_object&, CLI::App& app);
void run_push();
void run_list();
void run_pop();
void run_apply();

std::vector<std::string> m_options;
std::string m_message = "";
size_t m_index = 0;
};
18 changes: 11 additions & 7 deletions src/subcommand/status_subcommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,19 @@

#include "status_subcommand.hpp"
#include "../wrapper/status_wrapper.hpp"
#include "../wrapper/refs_wrapper.hpp"


status_subcommand::status_subcommand(const libgit2_object&, CLI::App& app)
{
auto *sub = app.add_subcommand("status", "Show modified files in working directory, staged for your next commit");

sub->add_flag("-s,--short", m_short_flag, "Give the output in the short-format.");
sub->add_flag("--long", m_long_flag, "Give the output in the long-format. This is the default.");
sub->add_flag("-s,--short", m_fl.m_short_flag, "Give the output in the short-format.");
sub->add_flag("--long", m_fl.m_long_flag, "Give the output in the long-format. This is the default.");
// sub->add_flag("--porcelain[=<version>]", porcelain, "Give the output in an easy-to-parse format for scripts.
// This is similar to the short output, but will remain stable across Git versions and regardless of user configuration.
// See below for details. The version parameter is used to specify the format version. This is optional and defaults
// to the original version v1 format.");
sub->add_flag("-b,--branch", m_branch_flag, "Show the branch and tracking info even in short-format.");
sub->add_flag("-b,--branch", m_fl.m_branch_flag, "Show the branch and tracking info even in short-format.");

sub->callback([this]() { this->run(); });
};
Expand Down Expand Up @@ -163,6 +162,11 @@ void print_not_tracked(const std::vector<print_entry>& entries_to_print, const s
}

void status_subcommand::run()
{
status_run(m_fl);
}

void status_run(status_subcommand_flags fl)
{
auto directory = get_current_git_path();
auto repo = repository_wrapper::open(directory);
Expand All @@ -175,11 +179,11 @@ void status_subcommand::run()
std::vector<std::string> ignored_to_print{};

output_format of = output_format::DEFAULT;
if (m_short_flag)
if (fl.m_short_flag)
{
of = output_format::SHORT;
}
if (m_long_flag)
if (fl.m_long_flag)
{
of = output_format::LONG;
}
Expand All @@ -206,7 +210,7 @@ void status_subcommand::run()
}
else
{
if (m_branch_flag)
if (fl.m_branch_flag)
{
std::cout << "## " << branch_name << std::endl;
}
Expand Down
13 changes: 10 additions & 3 deletions src/subcommand/status_subcommand.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

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

struct status_subcommand_flags
{
bool m_branch_flag = false;
bool m_long_flag = false;
bool m_short_flag = false;
};

class status_subcommand
{
public:
Expand All @@ -12,7 +19,7 @@ class status_subcommand
void run();

private:
bool m_branch_flag = false;
bool m_long_flag = false;
bool m_short_flag = false;
status_subcommand_flags m_fl;
};

void status_run(status_subcommand_flags fl = {});
6 changes: 6 additions & 0 deletions src/wrapper/commit_wrapper.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "../wrapper/commit_wrapper.hpp"
#include <git2/commit.h>

commit_wrapper::commit_wrapper(git_commit* commit)
: base_type(commit)
Expand Down Expand Up @@ -27,6 +28,11 @@ std::string commit_wrapper::commit_oid_tostr() const
return git_oid_tostr(buf, sizeof(buf), &this->oid());
}

std::string commit_wrapper::summary() const
{
return git_commit_summary(*this);
}

commit_list_wrapper commit_wrapper::get_parents_list() const
{
size_t parent_count = git_commit_parentcount(*this);
Expand Down
2 changes: 2 additions & 0 deletions src/wrapper/commit_wrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class commit_wrapper : public wrapper_base<git_commit>
const git_oid& oid() const;
std::string commit_oid_tostr() const;

std::string summary() const;

commit_list_wrapper get_parents_list() const;

private:
Expand Down
139 changes: 139 additions & 0 deletions test/test_stash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import subprocess

import pytest


def test_stash_push(xtl_clone, commit_env_config, git2cpp_path, tmp_path):
assert (tmp_path / "xtl").exists()
xtl_path = tmp_path / "xtl"

p = xtl_path / "mook_file.txt"
p.write_text("blabla")

cmd_add = [git2cpp_path, "add", "mook_file.txt"]
p_add = subprocess.run(cmd_add, cwd=xtl_path, text=True)
assert p_add.returncode == 0

stash_path = xtl_path / ".git/refs/stash"
assert not stash_path.exists()

cmd_stash = [git2cpp_path, "stash"]
p_stash = subprocess.run(cmd_stash, capture_output=True, cwd=xtl_path, text=True)
assert p_stash.returncode == 0
assert stash_path.exists()


def test_stash_list(xtl_clone, commit_env_config, git2cpp_path, tmp_path):
assert (tmp_path / "xtl").exists()
xtl_path = tmp_path / "xtl"

p = xtl_path / "mook_file.txt"
p.write_text("blabla")

cmd_add = [git2cpp_path, "add", "mook_file.txt"]
p_add = subprocess.run(cmd_add, cwd=xtl_path, text=True)
assert p_add.returncode == 0

cmd_list = [git2cpp_path, "stash", "list"]
p_list = subprocess.run(cmd_list, capture_output=True, cwd=xtl_path, text=True)
assert p_list.returncode == 0
assert "stash@{0}" not in p_list.stdout

cmd_stash = [git2cpp_path, "stash"]
p_stash = subprocess.run(cmd_stash, capture_output=True, cwd=xtl_path, text=True)
assert p_stash.returncode == 0

p_list_2 = subprocess.run(cmd_list, capture_output=True, cwd=xtl_path, text=True)
assert p_list_2.returncode == 0
assert "stash@{0}" in p_list_2.stdout


@pytest.mark.parametrize("index_flag", ["", "--index"])
def test_stash_pop(xtl_clone, commit_env_config, git2cpp_path, tmp_path, index_flag):
assert (tmp_path / "xtl").exists()
xtl_path = tmp_path / "xtl"

index = 0 if index_flag == "" else 1

for i in range(index + 1):
p = xtl_path / f"mook_file_{i}.txt"
p.write_text(f"blabla{i}")

cmd_add = [git2cpp_path, "add", f"mook_file_{i}.txt"]
p_add = subprocess.run(cmd_add, cwd=xtl_path, text=True)
assert p_add.returncode == 0

cmd_stash = [git2cpp_path, "stash"]
p_stash = subprocess.run(
cmd_stash, capture_output=True, cwd=xtl_path, text=True
)
assert p_stash.returncode == 0

cmd_status = [git2cpp_path, "status"]
p_status = subprocess.run(
cmd_status, capture_output=True, cwd=xtl_path, text=True
)
assert p_status.returncode == 0
assert "mook_file" not in p_status.stdout

cmd_pop = [git2cpp_path, "stash", "pop"]
if index_flag != "":
cmd_pop.append(index_flag)
cmd_pop.append("1")
p_pop = subprocess.run(cmd_pop, capture_output=True, cwd=xtl_path, text=True)
assert p_pop.returncode == 0
assert "mook_file_0" in p_pop.stdout
assert "Dropped refs/stash@{" + str(index) + "}" in p_pop.stdout

cmd_list = [git2cpp_path, "stash", "list"]
p_list = subprocess.run(cmd_list, capture_output=True, cwd=xtl_path, text=True)
assert p_list.returncode == 0
if index_flag == "":
assert p_list.stdout == ""
else:
assert "stash@{0}" in p_list.stdout
assert "stash@{1}" not in p_list.stdout


@pytest.mark.parametrize("index_flag", ["", "--index"])
def test_stash_apply(xtl_clone, commit_env_config, git2cpp_path, tmp_path, index_flag):
assert (tmp_path / "xtl").exists()
xtl_path = tmp_path / "xtl"

index = 0 if index_flag == "" else 1

for i in range(index + 1):
p = xtl_path / f"mook_file_{i}.txt"
p.write_text(f"blabla{i}")

cmd_add = [git2cpp_path, "add", f"mook_file_{i}.txt"]
p_add = subprocess.run(cmd_add, cwd=xtl_path, text=True)
assert p_add.returncode == 0

cmd_stash = [git2cpp_path, "stash"]
p_stash = subprocess.run(
cmd_stash, capture_output=True, cwd=xtl_path, text=True
)
assert p_stash.returncode == 0

cmd_status = [git2cpp_path, "status"]
p_status = subprocess.run(
cmd_status, capture_output=True, cwd=xtl_path, text=True
)
assert p_status.returncode == 0
assert "mook_file" not in p_status.stdout

cmd_apply = [git2cpp_path, "stash", "apply"]
if index_flag != "":
cmd_apply.append(index_flag)
cmd_apply.append("1")
p_apply = subprocess.run(cmd_apply, capture_output=True, cwd=xtl_path, text=True)
assert p_apply.returncode == 0
assert "mook_file_0" in p_apply.stdout

cmd_list = [git2cpp_path, "stash", "list"]
p_list = subprocess.run(cmd_list, capture_output=True, cwd=xtl_path, text=True)
assert p_list.returncode == 0
assert "stash@{0}" in p_list.stdout
if index_flag != "":
assert "stash@{1}" in p_list.stdout