diff --git a/src/subcommand/diff_subcommand.cpp b/src/subcommand/diff_subcommand.cpp index 0e352e0..1ed8fb1 100644 --- a/src/subcommand/diff_subcommand.cpp +++ b/src/subcommand/diff_subcommand.cpp @@ -54,12 +54,12 @@ diff_subcommand::diff_subcommand(const libgit2_object&, CLI::App& app) sub->callback([this]() { this->run(); }); } -void diff_subcommand::print_stats(const diff_wrapper& diff, bool use_colour) +void print_stats(const diff_wrapper& diff, bool use_colour, bool stat_flag, bool shortstat_flag, bool numstat_flag, bool summary_flag) { git_diff_stats_format_t format; - if (m_stat_flag) + if (stat_flag) { - if (m_shortstat_flag || m_numstat_flag || m_summary_flag) + if (shortstat_flag || numstat_flag || summary_flag) { throw git_exception("Only one of --stat, --shortstat, --numstat and --summary should be provided.", git2cpp_error_code::BAD_ARGUMENT); } @@ -68,9 +68,9 @@ void diff_subcommand::print_stats(const diff_wrapper& diff, bool use_colour) format = GIT_DIFF_STATS_FULL; } } - else if (m_shortstat_flag) + else if (shortstat_flag) { - if (m_numstat_flag || m_summary_flag) + if (numstat_flag || summary_flag) { throw git_exception("Only one of --stat, --shortstat, --numstat and --summary should be provided.", git2cpp_error_code::BAD_ARGUMENT); } @@ -79,9 +79,9 @@ void diff_subcommand::print_stats(const diff_wrapper& diff, bool use_colour) format = GIT_DIFF_STATS_SHORT; } } - else if (m_numstat_flag) + else if (numstat_flag) { - if (m_summary_flag) + if (summary_flag) { throw git_exception("Only one of --stat, --shortstat, --numstat and --summary should be provided.", git2cpp_error_code::BAD_ARGUMENT); } @@ -90,7 +90,7 @@ void diff_subcommand::print_stats(const diff_wrapper& diff, bool use_colour) format = GIT_DIFF_STATS_NUMBER; } } - else if (m_summary_flag) + else if (summary_flag) { format = GIT_DIFF_STATS_INCLUDE_SUMMARY; } @@ -98,7 +98,7 @@ void diff_subcommand::print_stats(const diff_wrapper& diff, bool use_colour) auto stats = diff.get_stats(); auto buf = stats.to_buf(format, 80); - if (use_colour && m_stat_flag) + if (use_colour && stat_flag) { // Add colors to + and - characters std::string output(buf.ptr); @@ -179,7 +179,7 @@ void diff_subcommand::print_diff(diff_wrapper& diff, bool use_colour) { if (m_stat_flag || m_shortstat_flag || m_numstat_flag || m_summary_flag) { - print_stats(diff, use_colour); + print_stats(diff, use_colour, m_stat_flag, m_shortstat_flag, m_numstat_flag, m_summary_flag); return; } @@ -320,7 +320,7 @@ void diff_subcommand::run() { if (tree1.has_value() && tree2.has_value()) { - return repo.diff_tree_to_tree(std::move(tree1.value()), std::move(tree2.value()), &diffopts); + return repo.diff_tree_to_tree(tree1.value(), tree2.value(), &diffopts); } else if (m_cached_flag) { @@ -328,11 +328,11 @@ void diff_subcommand::run() { tree1 = repo.treeish_to_tree("HEAD"); } - return repo.diff_tree_to_index(std::move(tree1.value()), std::nullopt, &diffopts); + return repo.diff_tree_to_index(tree1.value(), std::nullopt, &diffopts); } else if (tree1) { - return repo.diff_tree_to_workdir_with_index(std::move(tree1.value()), &diffopts); + return repo.diff_tree_to_workdir_with_index(tree1.value(), &diffopts); } else { diff --git a/src/subcommand/diff_subcommand.hpp b/src/subcommand/diff_subcommand.hpp index 966e77c..5c2c23f 100644 --- a/src/subcommand/diff_subcommand.hpp +++ b/src/subcommand/diff_subcommand.hpp @@ -11,7 +11,6 @@ class diff_subcommand public: explicit diff_subcommand(const libgit2_object&, CLI::App& app); - void print_stats(const diff_wrapper& diff, bool use_colour); void print_diff(diff_wrapper& diff, bool use_colour); void run(); @@ -53,3 +52,5 @@ class diff_subcommand bool m_colour_flag = true; bool m_no_colour_flag = false; }; + +void print_stats(const diff_wrapper& diff, bool use_colour, bool stat_flag, bool shortstat_flag, bool numstat_flag, bool summary_flag); diff --git a/src/subcommand/stash_subcommand.cpp b/src/subcommand/stash_subcommand.cpp index 752ef5f..04bce61 100644 --- a/src/subcommand/stash_subcommand.cpp +++ b/src/subcommand/stash_subcommand.cpp @@ -5,13 +5,13 @@ #include +#include "../subcommand/diff_subcommand.hpp" #include "../subcommand/stash_subcommand.hpp" #include "../subcommand/status_subcommand.hpp" -#include "../wrapper/repository_wrapper.hpp" bool has_subcommand(CLI::App* cmd) { - std::vector subs = { "push", "pop", "list", "apply" }; + std::vector subs = { "push", "pop", "list", "apply", "show" }; return std::any_of(subs.begin(), subs.end(), [cmd](const std::string& s) { return cmd->got_subcommand(s); }); } @@ -22,10 +22,15 @@ stash_subcommand::stash_subcommand(const libgit2_object&, CLI::App& app) auto* list = stash->add_subcommand("list", ""); auto* pop = stash->add_subcommand("pop", ""); auto* apply = stash->add_subcommand("apply", ""); + auto* show = stash->add_subcommand("show", "Show the changes recorded in the stash as a diff"); push->add_option("-m,--message", m_message, ""); pop->add_option("--index", m_index, ""); apply->add_option("--index", m_index, ""); + show->add_flag("--stat", m_stat_flag, "Generate a diffstat"); + show->add_flag("--shortstat", m_shortstat_flag, "Output only the last line of --stat"); + show->add_flag("--numstat", m_numstat_flag, "Machine-friendly --stat"); + show->add_flag("--summary", m_summary_flag, "Output a condensed summary"); stash->callback([this,stash]() { @@ -38,6 +43,7 @@ stash_subcommand::stash_subcommand(const libgit2_object&, CLI::App& app) list->callback([this]() { this->run_list(); }); pop->callback([this]() { this->run_pop(); }); apply->callback([this]() { this->run_apply(); }); + show->callback([this]() { this->run_show(); }); } void stash_subcommand::run_push() @@ -66,14 +72,20 @@ void stash_subcommand::run_list() throw_if_error(git_stash_foreach(repo, list_stash_cb, NULL)); } +git_oid stash_subcommand::resolve_stash_commit(repository_wrapper& repo) +{ + 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(); + return stash_id; +} + 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(); + git_oid stash_id = resolve_stash_commit(repo); char id_string[GIT_OID_HEXSZ + 1]; git_oid_tostr(id_string, sizeof(id_string), &stash_id); @@ -90,3 +102,33 @@ void stash_subcommand::run_apply() throw_if_error(git_stash_apply(repo, m_index, NULL)); status_run(); } + +void stash_subcommand::run_show() +{ + auto directory = get_current_git_path(); + auto repo = repository_wrapper::open(directory); + + git_oid stash_id = resolve_stash_commit(repo); + commit_wrapper stash_commit = repo.find_commit(stash_id); + + if (git_commit_parentcount(stash_commit) < 1) + { + throw std::runtime_error("stash show: stash commit has no parents"); + } + + commit_wrapper parent_commit = stash_commit.get_parent(0); + + tree_wrapper stash_tree = stash_commit.tree(); + tree_wrapper parent_tree = parent_commit.tree(); + + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + + diff_wrapper diff = repo.diff_tree_to_tree(parent_tree, stash_tree, &diff_opts); + + bool use_colour = true; + if (!m_shortstat_flag && !m_numstat_flag && !m_summary_flag) + { + m_stat_flag = true; + } + print_stats(diff, use_colour, m_stat_flag, m_shortstat_flag, m_numstat_flag, m_summary_flag); +} diff --git a/src/subcommand/stash_subcommand.hpp b/src/subcommand/stash_subcommand.hpp index c6a13ce..3d07980 100644 --- a/src/subcommand/stash_subcommand.hpp +++ b/src/subcommand/stash_subcommand.hpp @@ -3,18 +3,28 @@ #include #include "../utils/common.hpp" +#include "../wrapper/repository_wrapper.hpp" class stash_subcommand { public: explicit stash_subcommand(const libgit2_object&, CLI::App& app); + git_oid resolve_stash_commit(repository_wrapper& repo); void run_push(); void run_list(); void run_pop(); void run_apply(); + void run_show(); + +private: std::vector m_options; std::string m_message = ""; size_t m_index = 0; + + bool m_stat_flag = false; + bool m_shortstat_flag = false; + bool m_numstat_flag = false; + bool m_summary_flag = false; }; diff --git a/src/wrapper/commit_wrapper.cpp b/src/wrapper/commit_wrapper.cpp index fc214cc..aa4cae3 100644 --- a/src/wrapper/commit_wrapper.cpp +++ b/src/wrapper/commit_wrapper.cpp @@ -1,6 +1,9 @@ -#include "../wrapper/commit_wrapper.hpp" #include +#include "../utils/git_exception.hpp" +#include "tree_wrapper.hpp" +#include "../wrapper/commit_wrapper.hpp" + commit_wrapper::commit_wrapper(git_commit* commit) : base_type(commit) { @@ -38,6 +41,13 @@ std::string commit_wrapper::summary() const return git_commit_summary(*this); } +commit_wrapper commit_wrapper::get_parent(size_t i) const +{ + git_commit* parent; + throw_if_error(git_commit_parent(&parent, *this, i)); + return commit_wrapper(parent); +} + commit_list_wrapper commit_wrapper::get_parents_list() const { size_t parent_count = git_commit_parentcount(*this); @@ -52,3 +62,10 @@ commit_list_wrapper commit_wrapper::get_parents_list() const } return commit_list_wrapper(std::move(parents_list)); } + +tree_wrapper commit_wrapper::tree() const +{ + git_tree* tree; + throw_if_error(git_commit_tree(&tree, *this)); + return tree_wrapper(tree); +} diff --git a/src/wrapper/commit_wrapper.hpp b/src/wrapper/commit_wrapper.hpp index 0db1066..c50b389 100644 --- a/src/wrapper/commit_wrapper.hpp +++ b/src/wrapper/commit_wrapper.hpp @@ -4,6 +4,7 @@ #include #include "../wrapper/wrapper_base.hpp" +#include "../wrapper/tree_wrapper.hpp" class commit_wrapper; using commit_list_wrapper = list_wrapper; @@ -27,8 +28,11 @@ class commit_wrapper : public wrapper_base std::string message() const; std::string summary() const; + commit_wrapper get_parent(size_t i) const; commit_list_wrapper get_parents_list() const; + tree_wrapper tree() const; + private: commit_wrapper(git_commit* commit); diff --git a/src/wrapper/repository_wrapper.cpp b/src/wrapper/repository_wrapper.cpp index 0e9c05b..e14863d 100644 --- a/src/wrapper/repository_wrapper.cpp +++ b/src/wrapper/repository_wrapper.cpp @@ -507,7 +507,7 @@ config_wrapper repository_wrapper::get_config() // Diff -diff_wrapper repository_wrapper::diff_tree_to_index(tree_wrapper old_tree, std::optional index, git_diff_options* diffopts) +diff_wrapper repository_wrapper::diff_tree_to_index(const tree_wrapper& old_tree, std::optional index, git_diff_options* diffopts) { git_diff* diff; git_index* idx = nullptr; @@ -519,21 +519,21 @@ diff_wrapper repository_wrapper::diff_tree_to_index(tree_wrapper old_tree, std:: return diff_wrapper(diff); } -diff_wrapper repository_wrapper::diff_tree_to_tree(tree_wrapper old_tree, tree_wrapper new_tree, git_diff_options* diffopts) +diff_wrapper repository_wrapper::diff_tree_to_tree(const tree_wrapper& old_tree, const tree_wrapper& new_tree, git_diff_options* diffopts) { git_diff* diff; throw_if_error(git_diff_tree_to_tree(&diff, *this, old_tree, new_tree, diffopts)); return diff_wrapper(diff); } -diff_wrapper repository_wrapper::diff_tree_to_workdir(tree_wrapper old_tree, git_diff_options* diffopts) +diff_wrapper repository_wrapper::diff_tree_to_workdir(const tree_wrapper& old_tree, git_diff_options* diffopts) { git_diff* diff; throw_if_error(git_diff_tree_to_workdir(&diff, *this, old_tree, diffopts)); return diff_wrapper(diff); } -diff_wrapper repository_wrapper::diff_tree_to_workdir_with_index(tree_wrapper old_tree, git_diff_options* diffopts) +diff_wrapper repository_wrapper::diff_tree_to_workdir_with_index(const tree_wrapper& old_tree, git_diff_options* diffopts) { git_diff* diff; throw_if_error(git_diff_tree_to_workdir_with_index(&diff, *this, old_tree, diffopts)); diff --git a/src/wrapper/repository_wrapper.hpp b/src/wrapper/repository_wrapper.hpp index 25e922b..18a25e7 100644 --- a/src/wrapper/repository_wrapper.hpp +++ b/src/wrapper/repository_wrapper.hpp @@ -116,10 +116,10 @@ class repository_wrapper : public wrapper_base config_wrapper get_config(); // Diff - diff_wrapper diff_tree_to_index(tree_wrapper old_tree, std::optional index, git_diff_options* diffopts); - diff_wrapper diff_tree_to_tree(tree_wrapper old_tree, tree_wrapper new_tree, git_diff_options* diffopts); - diff_wrapper diff_tree_to_workdir(tree_wrapper old_tree, git_diff_options* diffopts); - diff_wrapper diff_tree_to_workdir_with_index(tree_wrapper old_tree, git_diff_options* diffopts); + diff_wrapper diff_tree_to_index(const tree_wrapper& old_tree, std::optional index, git_diff_options* diffopts); + diff_wrapper diff_tree_to_tree(const tree_wrapper& old_tree, const tree_wrapper& new_tree, git_diff_options* diffopts); + diff_wrapper diff_tree_to_workdir(const tree_wrapper& old_tree, git_diff_options* diffopts); + diff_wrapper diff_tree_to_workdir_with_index(const tree_wrapper& old_tree, git_diff_options* diffopts); diff_wrapper diff_index_to_workdir(std::optional index, git_diff_options* diffopts); //Tags diff --git a/src/wrapper/tree_wrapper.hpp b/src/wrapper/tree_wrapper.hpp index 06d11c4..0b22638 100644 --- a/src/wrapper/tree_wrapper.hpp +++ b/src/wrapper/tree_wrapper.hpp @@ -19,5 +19,6 @@ class tree_wrapper : public wrapper_base tree_wrapper(git_tree* tree); + friend class commit_wrapper; friend class repository_wrapper; }; diff --git a/test/test_stash.py b/test/test_stash.py index 286324a..dadf045 100644 --- a/test/test_stash.py +++ b/test/test_stash.py @@ -137,3 +137,28 @@ def test_stash_apply(xtl_clone, commit_env_config, git2cpp_path, tmp_path, index assert "stash@{0}" in p_list.stdout if index_flag != "": assert "stash@{1}" in p_list.stdout + + +def test_stash_show(xtl_clone, commit_env_config, git2cpp_path, tmp_path): + assert (tmp_path / "xtl").exists() + xtl_path = tmp_path / "xtl" + + filename = "mook_show.txt" + p = xtl_path / filename + p.write_text("Hello") + + cmd_add = [git2cpp_path, "add", filename] + 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_show = [git2cpp_path, "stash", "show", "--stat"] + p_show = subprocess.run(cmd_show, capture_output=True, cwd=xtl_path, text=True) + assert p_show.returncode == 0 + + # A diffstat should mention the file and summary "file changed" + assert filename in p_show.stdout + assert "1 file changed" in p_show.stdout