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
121 changes: 78 additions & 43 deletions src/subcommand/diff_subcommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ diff_subcommand::diff_subcommand(const libgit2_object&, CLI::App& app)
{
auto* sub = app.add_subcommand("diff", "Show changes between commits, commit and working tree, etc");

sub->add_option("<files>", m_files, "tree-ish objects to compare");
sub->add_option("<files>", m_files, "tree-ish objects to compare")
->expected(0, 2);

sub->add_flag("--stat", m_stat_flag, "Generate a diffstat");
sub->add_flag("--shortstat", m_shortstat_flag, "Output only the last line of --stat");
Expand All @@ -33,15 +34,16 @@ diff_subcommand::diff_subcommand(const libgit2_object&, CLI::App& app)
sub->add_flag("--patience", m_patience_flag, "Generate diff using patience algorithm");
sub->add_flag("--minimal", m_minimal_flag, "Spend extra time to find smallest diff");

// TODO: add the following flags after the "move" subcommand has been implemented (needed for the tests)
// sub->add_option("-M,--find-renames", m_rename_threshold, "Detect renames")
// ->expected(0,1)
// ->each([this](const std::string&) { m_find_renames_flag = true; });
// sub->add_option("-C,--find-copies", m_copy_threshold, "Detect copies")
// ->expected(0,1)
// ->each([this](const std::string&) { m_find_copies_flag = true; });
// sub->add_flag("--find-copies-harder", m_find_copies_harder_flag, "Detect copies from unmodified files");
// sub->add_flag("-B,--break-rewrites", m_break_rewrites_flag, "Detect file rewrites");
sub->add_option("-M,--find-renames", m_rename_threshold, "Detect renames")
->expected(0,1)
->default_val(50)
->each([this](const std::string&) { m_find_renames_flag = true; });
sub->add_option("-C,--find-copies", m_copy_threshold, "Detect copies")
->expected(0,1)
->default_val(50)
->each([this](const std::string&) { m_find_copies_flag = true; });
sub->add_flag("--find-copies-harder", m_find_copies_harder_flag, "Detect copies from unmodified files");
sub->add_flag("-B,--break-rewrites", m_break_rewrites_flag, "Detect file rewrites");

sub->add_option("-U,--unified", m_context_lines, "Lines of context");
sub->add_option("--inter-hunk-context", m_interhunk_lines, "Context between hunks");
Expand Down Expand Up @@ -142,7 +144,6 @@ static int colour_printer([[maybe_unused]] const git_diff_delta* delta, [[maybe_
bool use_colour = *reinterpret_cast<bool*>(payload);

// Only print origin for context/addition/deletion lines
// For other line types, content already includes everything
bool print_origin = (line->origin == GIT_DIFF_LINE_CONTEXT ||
line->origin == GIT_DIFF_LINE_ADDITION ||
line->origin == GIT_DIFF_LINE_DELETION);
Expand Down Expand Up @@ -172,6 +173,31 @@ static int colour_printer([[maybe_unused]] const git_diff_delta* delta, [[maybe_
std::cout << termcolor::reset;
}

// Print copy/rename headers ONLY after the "diff --git" line
if (line->origin == GIT_DIFF_LINE_FILE_HDR)
{
if (delta->status == GIT_DELTA_COPIED)
{
if (use_colour)
std::cout << termcolor::bold;
std::cout << "similarity index " << delta->similarity << "%\n";
std::cout << "copy from " << delta->old_file.path << "\n";
std::cout << "copy to " << delta->new_file.path << "\n";
if (use_colour)
std::cout << termcolor::reset;
}
else if (delta->status == GIT_DELTA_RENAMED)
{
if (use_colour)
std::cout << termcolor::bold;
std::cout << "similarity index " << delta->similarity << "%\n";
std::cout << "rename from " << delta->old_file.path << "\n";
std::cout << "rename to " << delta->new_file.path << "\n";
if (use_colour)
std::cout << termcolor::reset;
}
}

return 0;
}

Expand All @@ -183,33 +209,30 @@ void diff_subcommand::print_diff(diff_wrapper& diff, bool use_colour)
return;
}

// TODO: add the following flags after the "move" subcommand has been implemented (needed for the tests)
// if (m_find_renames_flag || m_find_copies_flag || m_find_copies_harder_flag || m_break_rewrites_flag)
// {
// git_diff_find_options find_opts;
// git_diff_find_options_init(&find_opts, GIT_DIFF_FIND_OPTIONS_VERSION);

// if (m_find_renames_flag)
// {
// find_opts.flags |= GIT_DIFF_FIND_RENAMES;
// find_opts.rename_threshold = m_rename_threshold;
// }
// if (m_find_copies_flag)
// {
// find_opts.flags |= GIT_DIFF_FIND_COPIES;
// find_opts.copy_threshold = m_copy_threshold;
// }
// if (m_find_copies_harder_flag)
// {
// find_opts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED;
// }
// if (m_break_rewrites_flag)
// {
// find_opts.flags |= GIT_DIFF_FIND_REWRITES;
// }

// diff.find_similar(&find_opts);
// }
if (m_find_renames_flag || m_find_copies_flag || m_find_copies_harder_flag || m_break_rewrites_flag)
{
git_diff_find_options find_opts = GIT_DIFF_FIND_OPTIONS_INIT;

if (m_find_renames_flag || m_find_copies_flag)
{
find_opts.flags |= GIT_DIFF_FIND_RENAMES;
find_opts.rename_threshold = (uint16_t)m_rename_threshold;
}
if (m_find_copies_flag)
{
find_opts.flags |= GIT_DIFF_FIND_COPIES;
find_opts.copy_threshold = (uint16_t)m_copy_threshold;
}
if (m_find_copies_harder_flag)
{
find_opts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED;
}
if (m_break_rewrites_flag)
{
find_opts.flags |= GIT_DIFF_FIND_REWRITES;
}
diff.find_similar(&find_opts);
}

git_diff_format_t format = GIT_DIFF_FORMAT_PATCH;
if (m_name_only_flag)
Expand All @@ -228,7 +251,7 @@ void diff_subcommand::print_diff(diff_wrapper& diff, bool use_colour)
diff.print(format, colour_printer, &use_colour);
}

diff_wrapper compute_diff_no_index(std::vector<std::string> files, git_diff_options& diffopts) //std::pair<buf_wrapper, diff_wrapper>
diff_wrapper compute_diff_no_index(std::vector<std::string> files, git_diff_options& diffopts)
{
if (files.size() != 2)
{
Expand All @@ -242,11 +265,11 @@ diff_wrapper compute_diff_no_index(std::vector<std::string> files, git_diff_opti

if (file1_str.empty())
{
throw git_exception("Cannot read file: " + files[0], git2cpp_error_code::GENERIC_ERROR); //TODO: check error code with git
throw git_exception("Cannot read file: " + files[0], git2cpp_error_code::GENERIC_ERROR);
}
if (file2_str.empty())
{
throw git_exception("Cannot read file: " + files[1], git2cpp_error_code::GENERIC_ERROR); //TODO: check error code with git
throw git_exception("Cannot read file: " + files[1], git2cpp_error_code::GENERIC_ERROR);
}

auto patch = patch_wrapper::patch_from_files(files[0], file1_str, files[1], file2_str, &diffopts);
Expand All @@ -263,6 +286,10 @@ void diff_subcommand::run()
git_diff_options diffopts;
git_diff_options_init(&diffopts, GIT_DIFF_OPTIONS_VERSION);

std::cerr << "DEBUG cached=" << m_cached_flag
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you intending to leave this in?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No ! That's a classic (I thought I removed all the printing for debugging...)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought you might be purposefully leaving me something easy to find!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I do that in the future, I'll make sure to leave a comment. So far I only (regularly) forgot to remove some prints...

<< " no_index=" << m_no_index_flag
<< " files=" << m_files.size() << "\n";

bool use_colour = false;
if (m_no_colour_flag)
{
Expand All @@ -280,6 +307,11 @@ void diff_subcommand::run()
use_colour = true;
}

if (m_cached_flag && m_no_index_flag)
{
throw git_exception("--cached and --no-index are incompatible", git2cpp_error_code::BAD_ARGUMENT);
}

if (m_no_index_flag)
{
auto diff = compute_diff_no_index(m_files, diffopts);
Expand All @@ -302,11 +334,14 @@ void diff_subcommand::run()
if (m_untracked_flag) { diffopts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; }
if (m_patience_flag) { diffopts.flags |= GIT_DIFF_PATIENCE; }
if (m_minimal_flag) { diffopts.flags |= GIT_DIFF_MINIMAL; }
if (m_find_copies_flag || m_find_copies_harder_flag || m_find_renames_flag)
{
diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
}

std::optional<tree_wrapper> tree1;
std::optional<tree_wrapper> tree2;

// TODO: throw error if m_files.size() > 2
if (m_files.size() >= 1)
{
tree1 = repo.treeish_to_tree(m_files[0]);
Expand All @@ -324,7 +359,7 @@ void diff_subcommand::run()
}
else if (m_cached_flag)
{
if (m_cached_flag || !tree1)
if (!tree1)
{
tree1 = repo.treeish_to_tree("HEAD");
}
Expand Down
20 changes: 10 additions & 10 deletions src/subcommand/diff_subcommand.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,16 @@ class diff_subcommand
bool m_patience_flag = false;
bool m_minimal_flag = false;

// int m_rename_threshold = 50;
// bool m_find_renames_flag = false;
// int m_copy_threshold = 50;
// bool m_find_copies_flag = false;
// bool m_find_copies_harder_flag = false;
// bool m_break_rewrites_flag = false;

int m_context_lines = 3;
int m_interhunk_lines = 0;
int m_abbrev = 7;
uint m_rename_threshold = 50;
bool m_find_renames_flag = false;
uint m_copy_threshold = 50;
bool m_find_copies_flag = false;
bool m_find_copies_harder_flag = false;
bool m_break_rewrites_flag = false;

uint m_context_lines = 3;
uint m_interhunk_lines = 0;
uint m_abbrev = 7;

bool m_colour_flag = true;
bool m_no_colour_flag = false;
Expand Down
28 changes: 24 additions & 4 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

import pytest

GIT2CPP_TEST_WASM = os.getenv('GIT2CPP_TEST_WASM') == "1"
GIT2CPP_TEST_WASM = os.getenv("GIT2CPP_TEST_WASM") == "1"

if GIT2CPP_TEST_WASM:
from .conftest_wasm import *


# Fixture to run test in current tmp_path
@pytest.fixture
def run_in_tmp_path(tmp_path):
Expand All @@ -21,9 +22,10 @@ def run_in_tmp_path(tmp_path):
@pytest.fixture(scope="session")
def git2cpp_path():
if GIT2CPP_TEST_WASM:
return 'git2cpp'
return "git2cpp"
else:
return Path(__file__).parent.parent / 'build' / 'git2cpp'
return Path(__file__).parent.parent / "build" / "git2cpp"


@pytest.fixture
def xtl_clone(git2cpp_path, tmp_path, run_in_tmp_path):
Expand All @@ -39,10 +41,28 @@ def commit_env_config(monkeypatch):
"GIT_AUTHOR_NAME": "Jane Doe",
"GIT_AUTHOR_EMAIL": "jane.doe@blabla.com",
"GIT_COMMITTER_NAME": "Jane Doe",
"GIT_COMMITTER_EMAIL": "jane.doe@blabla.com"
"GIT_COMMITTER_EMAIL": "jane.doe@blabla.com",
}
for key, value in config.items():
if GIT2CPP_TEST_WASM:
subprocess.run(["export", f"{key}='{value}'"], check=True)
else:
monkeypatch.setenv(key, value)


@pytest.fixture
def repo_init_with_commit(commit_env_config, git2cpp_path, tmp_path, run_in_tmp_path):
cmd_init = [git2cpp_path, "init", "."]
p_init = subprocess.run(cmd_init, capture_output=True, cwd=tmp_path, text=True)
assert p_init.returncode == 0

p = tmp_path / "initial.txt"
p.write_text("initial")

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

cmd_commit = [git2cpp_path, "commit", "-m", "Initial commit"]
p_commit = subprocess.run(cmd_commit, capture_output=True, cwd=tmp_path, text=True)
assert p_commit.returncode == 0
Loading