Commit e38f0d69 by Edward Thomson

Add rename from rewrites to status

In git_diff_paired_foreach, temporarily resort the
index->workdir diff list by index path so that we can
track a rename in the workdir from head->index->workdir.
parent 9b7d02ff
......@@ -107,7 +107,7 @@ typedef enum {
* - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX indicates that rename detection
* should be processed between the head and the index and enables
* 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
* and enabled GIT_STATUS_WT_RENAMED as a possible status flag.
* - GIT_STATUS_OPT_SORT_CASE_SENSITIVELY overrides the native case
......@@ -116,6 +116,8 @@ typedef enum {
* - GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY overrides the native case
* sensitivity for the file system and forces the output to be in
* 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
* with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED,
......@@ -134,6 +136,7 @@ typedef enum {
GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR = (1u << 8),
GIT_STATUS_OPT_SORT_CASE_SENSITIVELY = (1u << 9),
GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY = (1u << 10),
GIT_STATUS_OPT_RENAMES_FROM_REWRITES = (1u << 11),
} git_status_opt_t;
#define GIT_STATUS_OPT_DEFAULTS \
......
......@@ -258,6 +258,26 @@ int git_diff_delta__casecmp(const void *a, const void *b)
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(
const git_diff_options *opts, const git_diff_delta *delta)
{
......@@ -1276,7 +1296,7 @@ int git_diff__paired_foreach(
git_diff_delta *h2i, *i2w;
size_t i, j, i_max, j_max;
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;
j_max = idx2wd ? idx2wd->deltas.length : 0;
......@@ -1291,24 +1311,35 @@ int git_diff__paired_foreach(
* 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
* 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 =
(head2idx != NULL && idx2wd != NULL &&
((head2idx->opts.flags ^ idx2wd->opts.flags) & GIT_DIFF_DELTAS_ARE_ICASE));
(head2idx != NULL && idx2wd != NULL && h2i_icase != i2w_icase);
/* force case-sensitive delta sort */
if (icase_mismatch) {
if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) {
if (icase_mismatch && h2i_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;
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; ) {
h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL;
i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL;
......@@ -1332,14 +1363,16 @@ int git_diff__paired_foreach(
}
/* restore case-insensitive delta sort */
if (icase_mismatch) {
if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) {
if (icase_mismatch && h2i_icase) {
git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp);
git_vector_sort(&head2idx->deltas);
} else {
git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__casecmp);
git_vector_sort(&idx2wd->deltas);
}
/* restore idx2wd sort by new path */
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;
......
......@@ -225,24 +225,6 @@ static git_status_list *git_status_list_alloc(git_index *index)
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(
git_status_list **out,
git_repository *repo,
......@@ -251,7 +233,7 @@ int git_status_list_new(
git_index *index = NULL;
git_status_list *status = NULL;
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_status_show_t show =
opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
......@@ -284,6 +266,7 @@ int git_status_list_new(
}
diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE;
findopt.flags = GIT_DIFF_FIND_FOR_UNTRACKED;
if ((flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0)
diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED;
......@@ -300,7 +283,9 @@ int git_status_list_new(
if ((flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0)
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 ((error = git_diff_tree_to_index(
......@@ -308,7 +293,7 @@ int git_status_list_new(
goto done;
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;
}
......@@ -318,7 +303,7 @@ int git_status_list_new(
goto done;
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;
}
......
......@@ -153,6 +153,65 @@ void test_status_renames__head2index_two(void)
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)
{
git_status_list *statuslist;
......@@ -197,6 +256,32 @@ void test_status_renames__index2workdir_two(void)
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)
{
git_index *index;
......@@ -274,6 +359,50 @@ void test_status_renames__both_two(void)
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)
{
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