Commit fbb6c0c8 by Russell Belfer

Merge pull request #1764 from ethomson/status_renames_from_rewrites

Add rename from rewrites to status
parents 33d532dc e38f0d69
...@@ -107,7 +107,7 @@ typedef enum { ...@@ -107,7 +107,7 @@ typedef enum {
* - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX indicates that rename detection * - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX indicates that rename detection
* should be processed between the head and the index and enables * should be processed between the head and the index and enables
* the GIT_STATUS_INDEX_RENAMED as a possible status flag. * the GIT_STATUS_INDEX_RENAMED as a possible status flag.
* - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates tha rename * - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates that rename
* detection should be run between the index and the working directory * detection should be run between the index and the working directory
* and enabled GIT_STATUS_WT_RENAMED as a possible status flag. * and enabled GIT_STATUS_WT_RENAMED as a possible status flag.
* - GIT_STATUS_OPT_SORT_CASE_SENSITIVELY overrides the native case * - GIT_STATUS_OPT_SORT_CASE_SENSITIVELY overrides the native case
...@@ -116,6 +116,8 @@ typedef enum { ...@@ -116,6 +116,8 @@ typedef enum {
* - GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY overrides the native case * - GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY overrides the native case
* sensitivity for the file system and forces the output to be in * sensitivity for the file system and forces the output to be in
* case-insensitive order * case-insensitive order
* - GIT_STATUS_OPT_RENAMES_FROM_REWRITES indicates that rename detection
* should include rewritten files
* *
* Calling `git_status_foreach()` is like calling the extended version * Calling `git_status_foreach()` is like calling the extended version
* with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED, * with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED,
...@@ -134,6 +136,7 @@ typedef enum { ...@@ -134,6 +136,7 @@ typedef enum {
GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR = (1u << 8), GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR = (1u << 8),
GIT_STATUS_OPT_SORT_CASE_SENSITIVELY = (1u << 9), GIT_STATUS_OPT_SORT_CASE_SENSITIVELY = (1u << 9),
GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY = (1u << 10), GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY = (1u << 10),
GIT_STATUS_OPT_RENAMES_FROM_REWRITES = (1u << 11),
} git_status_opt_t; } git_status_opt_t;
#define GIT_STATUS_OPT_DEFAULTS \ #define GIT_STATUS_OPT_DEFAULTS \
......
...@@ -258,6 +258,26 @@ int git_diff_delta__casecmp(const void *a, const void *b) ...@@ -258,6 +258,26 @@ int git_diff_delta__casecmp(const void *a, const void *b)
return val ? val : ((int)da->status - (int)db->status); return val ? val : ((int)da->status - (int)db->status);
} }
GIT_INLINE(const char *) diff_delta__i2w_path(const git_diff_delta *delta)
{
return delta->old_file.path ?
delta->old_file.path : delta->new_file.path;
}
int git_diff_delta__i2w_cmp(const void *a, const void *b)
{
const git_diff_delta *da = a, *db = b;
int val = strcmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db));
return val ? val : ((int)da->status - (int)db->status);
}
int git_diff_delta__i2w_casecmp(const void *a, const void *b)
{
const git_diff_delta *da = a, *db = b;
int val = strcasecmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db));
return val ? val : ((int)da->status - (int)db->status);
}
bool git_diff_delta__should_skip( bool git_diff_delta__should_skip(
const git_diff_options *opts, const git_diff_delta *delta) const git_diff_options *opts, const git_diff_delta *delta)
{ {
...@@ -1276,7 +1296,7 @@ int git_diff__paired_foreach( ...@@ -1276,7 +1296,7 @@ int git_diff__paired_foreach(
git_diff_delta *h2i, *i2w; git_diff_delta *h2i, *i2w;
size_t i, j, i_max, j_max; size_t i, j, i_max, j_max;
int (*strcomp)(const char *, const char *) = git__strcmp; int (*strcomp)(const char *, const char *) = git__strcmp;
bool icase_mismatch; bool h2i_icase, i2w_icase, icase_mismatch;
i_max = head2idx ? head2idx->deltas.length : 0; i_max = head2idx ? head2idx->deltas.length : 0;
j_max = idx2wd ? idx2wd->deltas.length : 0; j_max = idx2wd ? idx2wd->deltas.length : 0;
...@@ -1291,24 +1311,35 @@ int git_diff__paired_foreach( ...@@ -1291,24 +1311,35 @@ int git_diff__paired_foreach(
* Therefore the main thing we need to do here is make sure the diffs * Therefore the main thing we need to do here is make sure the diffs
* are traversed in a compatible order. To do this, we temporarily * are traversed in a compatible order. To do this, we temporarily
* resort a mismatched diff to get the order correct. * resort a mismatched diff to get the order correct.
*
* In order to traverse renames in the index->workdir, we need to
* ensure that we compare the index name on both sides, so we
* always sort by the old name in the i2w list.
*/ */
h2i_icase = head2idx != NULL &&
(head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0;
i2w_icase = idx2wd != NULL &&
(idx2wd->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0;
icase_mismatch = icase_mismatch =
(head2idx != NULL && idx2wd != NULL && (head2idx != NULL && idx2wd != NULL && h2i_icase != i2w_icase);
((head2idx->opts.flags ^ idx2wd->opts.flags) & GIT_DIFF_DELTAS_ARE_ICASE));
if (icase_mismatch && h2i_icase) {
/* force case-sensitive delta sort */ git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp);
if (icase_mismatch) { git_vector_sort(&head2idx->deltas);
if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) {
git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp);
git_vector_sort(&head2idx->deltas);
} else {
git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__cmp);
git_vector_sort(&idx2wd->deltas);
}
} }
else if (head2idx != NULL && head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE)
if (i2w_icase && !icase_mismatch) {
strcomp = git__strcasecmp; strcomp = git__strcasecmp;
git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_casecmp);
git_vector_sort(&idx2wd->deltas);
} else if (idx2wd != NULL) {
git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_cmp);
git_vector_sort(&idx2wd->deltas);
}
for (i = 0, j = 0; i < i_max || j < j_max; ) { for (i = 0, j = 0; i < i_max || j < j_max; ) {
h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL; h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL;
i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL; i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL;
...@@ -1332,14 +1363,16 @@ int git_diff__paired_foreach( ...@@ -1332,14 +1363,16 @@ int git_diff__paired_foreach(
} }
/* restore case-insensitive delta sort */ /* restore case-insensitive delta sort */
if (icase_mismatch) { if (icase_mismatch && h2i_icase) {
if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) { git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp);
git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp); git_vector_sort(&head2idx->deltas);
git_vector_sort(&head2idx->deltas); }
} else {
git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__casecmp); /* restore idx2wd sort by new path */
git_vector_sort(&idx2wd->deltas); if (idx2wd != NULL) {
} git_vector_set_cmp(&idx2wd->deltas,
i2w_icase ? git_diff_delta__casecmp : git_diff_delta__cmp);
git_vector_sort(&idx2wd->deltas);
} }
return 0; return 0;
......
...@@ -225,24 +225,6 @@ static git_status_list *git_status_list_alloc(git_index *index) ...@@ -225,24 +225,6 @@ static git_status_list *git_status_list_alloc(git_index *index)
return status; return status;
} }
/*
static int newfile_cmp(const void *a, const void *b)
{
const git_diff_delta *delta_a = a;
const git_diff_delta *delta_b = b;
return git__strcmp(delta_a->new_file.path, delta_b->new_file.path);
}
static int newfile_casecmp(const void *a, const void *b)
{
const git_diff_delta *delta_a = a;
const git_diff_delta *delta_b = b;
return git__strcasecmp(delta_a->new_file.path, delta_b->new_file.path);
}
*/
int git_status_list_new( int git_status_list_new(
git_status_list **out, git_status_list **out,
git_repository *repo, git_repository *repo,
...@@ -251,7 +233,7 @@ int git_status_list_new( ...@@ -251,7 +233,7 @@ int git_status_list_new(
git_index *index = NULL; git_index *index = NULL;
git_status_list *status = NULL; git_status_list *status = NULL;
git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT; git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT;
git_diff_find_options findopts_i2w = GIT_DIFF_FIND_OPTIONS_INIT; git_diff_find_options findopt = GIT_DIFF_FIND_OPTIONS_INIT;
git_tree *head = NULL; git_tree *head = NULL;
git_status_show_t show = git_status_show_t show =
opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR; opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
...@@ -284,6 +266,7 @@ int git_status_list_new( ...@@ -284,6 +266,7 @@ int git_status_list_new(
} }
diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE; diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE;
findopt.flags = GIT_DIFF_FIND_FOR_UNTRACKED;
if ((flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0) if ((flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED; diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED;
...@@ -300,7 +283,9 @@ int git_status_list_new( ...@@ -300,7 +283,9 @@ int git_status_list_new(
if ((flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) if ((flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES; diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES;
findopts_i2w.flags |= GIT_DIFF_FIND_FOR_UNTRACKED; if ((flags & GIT_STATUS_OPT_RENAMES_FROM_REWRITES) != 0)
findopt.flags = findopt.flags | GIT_DIFF_FIND_AND_BREAK_REWRITES |
GIT_DIFF_FIND_RENAMES_FROM_REWRITES;
if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) { if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) {
if ((error = git_diff_tree_to_index( if ((error = git_diff_tree_to_index(
...@@ -308,7 +293,7 @@ int git_status_list_new( ...@@ -308,7 +293,7 @@ int git_status_list_new(
goto done; goto done;
if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 && if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 &&
(error = git_diff_find_similar(status->head2idx, NULL)) < 0) (error = git_diff_find_similar(status->head2idx, &findopt)) < 0)
goto done; goto done;
} }
...@@ -318,7 +303,7 @@ int git_status_list_new( ...@@ -318,7 +303,7 @@ int git_status_list_new(
goto done; goto done;
if ((flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 && if ((flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 &&
(error = git_diff_find_similar(status->idx2wd, &findopts_i2w)) < 0) (error = git_diff_find_similar(status->idx2wd, &findopt)) < 0)
goto done; goto done;
} }
......
...@@ -153,6 +153,65 @@ void test_status_renames__head2index_two(void) ...@@ -153,6 +153,65 @@ void test_status_renames__head2index_two(void)
git_index_free(index); git_index_free(index);
} }
void test_status_renames__head2index_no_rename_from_rewrite(void)
{
git_index *index;
git_status_list *statuslist;
git_status_options opts = GIT_STATUS_OPTIONS_INIT;
struct status_entry expected[] = {
{ GIT_STATUS_INDEX_MODIFIED, "ikeepsix.txt", "ikeepsix.txt" },
{ GIT_STATUS_INDEX_MODIFIED, "sixserving.txt", "sixserving.txt" },
};
opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
cl_git_pass(git_repository_index(&index, g_repo));
rename_file(g_repo, "ikeepsix.txt", "_temp_.txt");
rename_file(g_repo, "sixserving.txt", "ikeepsix.txt");
rename_file(g_repo, "_temp_.txt", "sixserving.txt");
cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt"));
cl_git_pass(git_index_add_bypath(index, "sixserving.txt"));
cl_git_pass(git_index_write(index));
cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
test_status(statuslist, expected, 2);
git_status_list_free(statuslist);
git_index_free(index);
}
void test_status_renames__head2index_rename_from_rewrite(void)
{
git_index *index;
git_status_list *statuslist;
git_status_options opts = GIT_STATUS_OPTIONS_INIT;
struct status_entry expected[] = {
{ GIT_STATUS_INDEX_RENAMED, "sixserving.txt", "ikeepsix.txt" },
{ GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", "sixserving.txt" },
};
opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES;
cl_git_pass(git_repository_index(&index, g_repo));
rename_file(g_repo, "ikeepsix.txt", "_temp_.txt");
rename_file(g_repo, "sixserving.txt", "ikeepsix.txt");
rename_file(g_repo, "_temp_.txt", "sixserving.txt");
cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt"));
cl_git_pass(git_index_add_bypath(index, "sixserving.txt"));
cl_git_pass(git_index_write(index));
cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
test_status(statuslist, expected, 2);
git_status_list_free(statuslist);
git_index_free(index);
}
void test_status_renames__index2workdir_one(void) void test_status_renames__index2workdir_one(void)
{ {
git_status_list *statuslist; git_status_list *statuslist;
...@@ -197,6 +256,32 @@ void test_status_renames__index2workdir_two(void) ...@@ -197,6 +256,32 @@ void test_status_renames__index2workdir_two(void)
git_status_list_free(statuslist); git_status_list_free(statuslist);
} }
void test_status_renames__index2workdir_rename_from_rewrite(void)
{
git_index *index;
git_status_list *statuslist;
git_status_options opts = GIT_STATUS_OPTIONS_INIT;
struct status_entry expected[] = {
{ GIT_STATUS_WT_RENAMED, "sixserving.txt", "ikeepsix.txt" },
{ GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "sixserving.txt" },
};
opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES;
cl_git_pass(git_repository_index(&index, g_repo));
rename_file(g_repo, "ikeepsix.txt", "_temp_.txt");
rename_file(g_repo, "sixserving.txt", "ikeepsix.txt");
rename_file(g_repo, "_temp_.txt", "sixserving.txt");
cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
test_status(statuslist, expected, 2);
git_status_list_free(statuslist);
git_index_free(index);
}
void test_status_renames__both_one(void) void test_status_renames__both_one(void)
{ {
git_index *index; git_index *index;
...@@ -274,6 +359,50 @@ void test_status_renames__both_two(void) ...@@ -274,6 +359,50 @@ void test_status_renames__both_two(void)
git_index_free(index); git_index_free(index);
} }
void test_status_renames__both_rename_from_rewrite(void)
{
git_index *index;
git_status_list *statuslist;
git_status_options opts = GIT_STATUS_OPTIONS_INIT;
struct status_entry expected[] = {
{ GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED,
"songof7cities.txt", "ikeepsix.txt" },
{ GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED,
"ikeepsix.txt", "sixserving.txt" },
{ GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED,
"sixserving.txt", "songof7cities.txt" },
};
opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES;
cl_git_pass(git_repository_index(&index, g_repo));
rename_file(g_repo, "ikeepsix.txt", "_temp_.txt");
rename_file(g_repo, "sixserving.txt", "ikeepsix.txt");
rename_file(g_repo, "songof7cities.txt", "sixserving.txt");
rename_file(g_repo, "_temp_.txt", "songof7cities.txt");
cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt"));
cl_git_pass(git_index_add_bypath(index, "sixserving.txt"));
cl_git_pass(git_index_add_bypath(index, "songof7cities.txt"));
cl_git_pass(git_index_write(index));
rename_file(g_repo, "songof7cities.txt", "_temp_.txt");
rename_file(g_repo, "ikeepsix.txt", "songof7cities.txt");
rename_file(g_repo, "sixserving.txt", "ikeepsix.txt");
rename_file(g_repo, "_temp_.txt", "sixserving.txt");
cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
test_status(statuslist, expected, 3);
git_status_list_free(statuslist);
git_index_free(index);
}
void test_status_renames__both_casechange_one(void) void test_status_renames__both_casechange_one(void)
{ {
git_index *index; git_index *index;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment