Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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;
Comment thread
JohanMabille marked this conversation as resolved.
Outdated
};

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