Skip to content

Commit ae2bdc0

Browse files
authored
Add --find-renames and --find-copies to the diff command (#115)
* add --find-renames and --find-copies to the diff command * address review comments * address review comments
1 parent 1ab489a commit ae2bdc0

File tree

3 files changed

+244
-135
lines changed

3 files changed

+244
-135
lines changed

src/subcommand/diff_subcommand.cpp

Lines changed: 82 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ diff_subcommand::diff_subcommand(const libgit2_object&, CLI::App& app)
1212
{
1313
auto* sub = app.add_subcommand("diff", "Show changes between commits, commit and working tree, etc");
1414

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

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

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

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

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

176+
// Print copy/rename headers ONLY after the "diff --git" line
177+
if (line->origin == GIT_DIFF_LINE_FILE_HDR)
178+
{
179+
if (delta->status == GIT_DELTA_COPIED)
180+
{
181+
if (use_colour)
182+
{
183+
std::cout << termcolor::bold;
184+
}
185+
std::cout << "similarity index " << delta->similarity << "%\n";
186+
std::cout << "copy from " << delta->old_file.path << "\n";
187+
std::cout << "copy to " << delta->new_file.path << "\n";
188+
if (use_colour)
189+
{
190+
std::cout << termcolor::reset;
191+
}
192+
}
193+
else if (delta->status == GIT_DELTA_RENAMED)
194+
{
195+
if (use_colour)
196+
{
197+
std::cout << termcolor::bold;
198+
}
199+
std::cout << "similarity index " << delta->similarity << "%\n";
200+
std::cout << "rename from " << delta->old_file.path << "\n";
201+
std::cout << "rename to " << delta->new_file.path << "\n";
202+
if (use_colour)
203+
{
204+
std::cout << termcolor::reset;
205+
}
206+
}
207+
}
208+
175209
return 0;
176210
}
177211

@@ -183,33 +217,30 @@ void diff_subcommand::print_diff(diff_wrapper& diff, bool use_colour)
183217
return;
184218
}
185219

186-
// TODO: add the following flags after the "move" subcommand has been implemented (needed for the tests)
187-
// if (m_find_renames_flag || m_find_copies_flag || m_find_copies_harder_flag || m_break_rewrites_flag)
188-
// {
189-
// git_diff_find_options find_opts;
190-
// git_diff_find_options_init(&find_opts, GIT_DIFF_FIND_OPTIONS_VERSION);
191-
192-
// if (m_find_renames_flag)
193-
// {
194-
// find_opts.flags |= GIT_DIFF_FIND_RENAMES;
195-
// find_opts.rename_threshold = m_rename_threshold;
196-
// }
197-
// if (m_find_copies_flag)
198-
// {
199-
// find_opts.flags |= GIT_DIFF_FIND_COPIES;
200-
// find_opts.copy_threshold = m_copy_threshold;
201-
// }
202-
// if (m_find_copies_harder_flag)
203-
// {
204-
// find_opts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED;
205-
// }
206-
// if (m_break_rewrites_flag)
207-
// {
208-
// find_opts.flags |= GIT_DIFF_FIND_REWRITES;
209-
// }
210-
211-
// diff.find_similar(&find_opts);
212-
// }
220+
if (m_find_renames_flag || m_find_copies_flag || m_find_copies_harder_flag || m_break_rewrites_flag)
221+
{
222+
git_diff_find_options find_opts = GIT_DIFF_FIND_OPTIONS_INIT;
223+
224+
if (m_find_renames_flag || m_find_copies_flag)
225+
{
226+
find_opts.flags |= GIT_DIFF_FIND_RENAMES;
227+
find_opts.rename_threshold = m_rename_threshold;
228+
}
229+
if (m_find_copies_flag)
230+
{
231+
find_opts.flags |= GIT_DIFF_FIND_COPIES;
232+
find_opts.copy_threshold = m_copy_threshold;
233+
}
234+
if (m_find_copies_harder_flag)
235+
{
236+
find_opts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED;
237+
}
238+
if (m_break_rewrites_flag)
239+
{
240+
find_opts.flags |= GIT_DIFF_FIND_REWRITES;
241+
}
242+
diff.find_similar(&find_opts);
243+
}
213244

214245
git_diff_format_t format = GIT_DIFF_FORMAT_PATCH;
215246
if (m_name_only_flag)
@@ -228,7 +259,7 @@ void diff_subcommand::print_diff(diff_wrapper& diff, bool use_colour)
228259
diff.print(format, colour_printer, &use_colour);
229260
}
230261

231-
diff_wrapper compute_diff_no_index(std::vector<std::string> files, git_diff_options& diffopts) //std::pair<buf_wrapper, diff_wrapper>
262+
diff_wrapper compute_diff_no_index(std::vector<std::string> files, git_diff_options& diffopts)
232263
{
233264
if (files.size() != 2)
234265
{
@@ -242,11 +273,11 @@ diff_wrapper compute_diff_no_index(std::vector<std::string> files, git_diff_opti
242273

243274
if (file1_str.empty())
244275
{
245-
throw git_exception("Cannot read file: " + files[0], git2cpp_error_code::GENERIC_ERROR); //TODO: check error code with git
276+
throw git_exception("Cannot read file: " + files[0], git2cpp_error_code::GENERIC_ERROR);
246277
}
247278
if (file2_str.empty())
248279
{
249-
throw git_exception("Cannot read file: " + files[1], git2cpp_error_code::GENERIC_ERROR); //TODO: check error code with git
280+
throw git_exception("Cannot read file: " + files[1], git2cpp_error_code::GENERIC_ERROR);
250281
}
251282

252283
auto patch = patch_wrapper::patch_from_files(files[0], file1_str, files[1], file2_str, &diffopts);
@@ -280,6 +311,11 @@ void diff_subcommand::run()
280311
use_colour = true;
281312
}
282313

314+
if (m_cached_flag && m_no_index_flag)
315+
{
316+
throw git_exception("--cached and --no-index are incompatible", git2cpp_error_code::BAD_ARGUMENT);
317+
}
318+
283319
if (m_no_index_flag)
284320
{
285321
auto diff = compute_diff_no_index(m_files, diffopts);
@@ -302,11 +338,14 @@ void diff_subcommand::run()
302338
if (m_untracked_flag) { diffopts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; }
303339
if (m_patience_flag) { diffopts.flags |= GIT_DIFF_PATIENCE; }
304340
if (m_minimal_flag) { diffopts.flags |= GIT_DIFF_MINIMAL; }
341+
if (m_find_copies_flag || m_find_copies_harder_flag || m_find_renames_flag)
342+
{
343+
diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
344+
}
305345

306346
std::optional<tree_wrapper> tree1;
307347
std::optional<tree_wrapper> tree2;
308348

309-
// TODO: throw error if m_files.size() > 2
310349
if (m_files.size() >= 1)
311350
{
312351
tree1 = repo.treeish_to_tree(m_files[0]);
@@ -324,7 +363,7 @@ void diff_subcommand::run()
324363
}
325364
else if (m_cached_flag)
326365
{
327-
if (m_cached_flag || !tree1)
366+
if (!tree1)
328367
{
329368
tree1 = repo.treeish_to_tree("HEAD");
330369
}

src/subcommand/diff_subcommand.hpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,16 @@ class diff_subcommand
3838
bool m_patience_flag = false;
3939
bool m_minimal_flag = false;
4040

41-
// int m_rename_threshold = 50;
42-
// bool m_find_renames_flag = false;
43-
// int m_copy_threshold = 50;
44-
// bool m_find_copies_flag = false;
45-
// bool m_find_copies_harder_flag = false;
46-
// bool m_break_rewrites_flag = false;
47-
48-
int m_context_lines = 3;
49-
int m_interhunk_lines = 0;
50-
int m_abbrev = 7;
41+
uint16_t m_rename_threshold = 50;
42+
bool m_find_renames_flag = false;
43+
uint16_t m_copy_threshold = 50;
44+
bool m_find_copies_flag = false;
45+
bool m_find_copies_harder_flag = false;
46+
bool m_break_rewrites_flag = false;
47+
48+
uint m_context_lines = 3;
49+
uint m_interhunk_lines = 0;
50+
uint m_abbrev = 7;
5151

5252
bool m_colour_flag = true;
5353
bool m_no_colour_flag = false;

0 commit comments

Comments
 (0)