Commit 354268ca by Carlos Martín Nieto

Merge pull request #3259 from ethomson/stash_apply_argh

Stash apply: stage new files even when not updating the index
parents 3c7a4697 b7f5cb8d
......@@ -47,6 +47,7 @@ typedef enum {
GIT_EPEEL = -19, /**< The requested peel operation is not possible */
GIT_EEOF = -20, /**< Unexpected EOF */
GIT_EINVALID = -21, /**< Invalid operation or input */
GIT_EUNCOMMITTED = -22, /**< Uncommitted changes in index prevented operation */
GIT_PASSTHROUGH = -30, /**< Internal only */
GIT_ITEROVER = -31, /**< Signals end of iteration with iterator */
......
......@@ -1843,3 +1843,91 @@ int git_iterator_advance_over_with_status(
return error;
}
int git_iterator_walk(
git_iterator **iterators,
size_t cnt,
git_iterator_walk_cb cb,
void *data)
{
const git_index_entry **iterator_item; /* next in each iterator */
const git_index_entry **cur_items; /* current path in each iter */
const git_index_entry *first_match;
int cur_item_modified;
size_t i, j;
int error = 0;
iterator_item = git__calloc(cnt, sizeof(git_index_entry *));
cur_items = git__calloc(cnt, sizeof(git_index_entry *));
GITERR_CHECK_ALLOC(iterator_item);
GITERR_CHECK_ALLOC(cur_items);
/* Set up the iterators */
for (i = 0; i < cnt; i++) {
error = git_iterator_current(&iterator_item[i], iterators[i]);
if (error < 0 && error != GIT_ITEROVER)
goto done;
}
while (true) {
for (i = 0; i < cnt; i++)
cur_items[i] = NULL;
first_match = NULL;
cur_item_modified = 0;
/* Find the next path(s) to consume from each iterator */
for (i = 0; i < cnt; i++) {
if (iterator_item[i] == NULL)
continue;
if (first_match == NULL) {
first_match = iterator_item[i];
cur_items[i] = iterator_item[i];
} else {
int path_diff = git_index_entry_cmp(iterator_item[i], first_match);
if (path_diff < 0) {
/* Found an index entry that sorts before the one we're
* looking at. Forget that we've seen the other and
* look at the other iterators for this path.
*/
for (j = 0; j < i; j++)
cur_items[j] = NULL;
first_match = iterator_item[i];
cur_items[i] = iterator_item[i];
} else if (path_diff > 0) {
/* No entry for the current item, this is modified */
cur_item_modified = 1;
} else if (path_diff == 0) {
cur_items[i] = iterator_item[i];
}
}
}
if (first_match == NULL)
break;
if ((error = cb(cur_items, data)) != 0)
goto done;
/* Advance each iterator that participated */
for (i = 0; i < cnt; i++) {
if (cur_items[i] == NULL)
continue;
error = git_iterator_advance(&iterator_item[i], iterators[i]);
if (error < 0 && error != GIT_ITEROVER)
goto done;
}
}
done:
if (error == GIT_ITEROVER)
error = 0;
return error;
}
......@@ -294,4 +294,19 @@ extern int git_iterator_advance_over_with_status(
*/
extern int git_iterator_index(git_index **out, git_iterator *iter);
typedef int (*git_iterator_walk_cb)(
const git_index_entry **entries,
void *data);
/**
* Walk the given iterators in lock-step. The given callback will be
* called for each unique path, with the index entry in each iterator
* (or NULL if the given iterator does not contain that path).
*/
extern int git_iterator_walk(
git_iterator **iterators,
size_t cnt,
git_iterator_walk_cb cb,
void *data);
#endif
......@@ -1449,100 +1449,44 @@ static int merge_diff_list_insert_unmodified(
return error;
}
int git_merge_diff_list__find_differences(
git_merge_diff_list *diff_list,
git_iterator *ancestor_iter,
git_iterator *our_iter,
git_iterator *their_iter)
{
git_iterator *iterators[3] = { ancestor_iter, our_iter, their_iter };
const git_index_entry *items[3] = {0}, *best_cur_item, *cur_items[3];
git_vector_cmp entry_compare = git_index_entry_cmp;
struct merge_diff_df_data df_data = {0};
int cur_item_modified;
size_t i, j;
int error = 0;
assert(diff_list && (our_iter || their_iter));
/* Set up the iterators */
for (i = 0; i < 3; i++) {
error = git_iterator_current(&items[i], iterators[i]);
if (error < 0 && error != GIT_ITEROVER)
goto done;
}
while (true) {
for (i = 0; i < 3; i++)
cur_items[i] = NULL;
best_cur_item = NULL;
cur_item_modified = 0;
struct merge_diff_find_data {
git_merge_diff_list *diff_list;
struct merge_diff_df_data df_data;
};
/* Find the next path(s) to consume from each iterator */
for (i = 0; i < 3; i++) {
if (items[i] == NULL) {
cur_item_modified = 1;
continue;
}
static int queue_difference(const git_index_entry **entries, void *data)
{
struct merge_diff_find_data *find_data = data;
bool item_modified = false;
size_t i;
if (best_cur_item == NULL) {
best_cur_item = items[i];
cur_items[i] = items[i];
if (!entries[0] || !entries[1] || !entries[2]) {
item_modified = true;
} else {
int path_diff = entry_compare(items[i], best_cur_item);
if (path_diff < 0) {
/*
* Found an item that sorts before our current item, make
* our current item this one.
*/
for (j = 0; j < i; j++)
cur_items[j] = NULL;
cur_item_modified = 1;
best_cur_item = items[i];
cur_items[i] = items[i];
} else if (path_diff > 0) {
/* No entry for the current item, this is modified */
cur_item_modified = 1;
} else if (path_diff == 0) {
cur_items[i] = items[i];
if (!cur_item_modified)
cur_item_modified = index_entry_cmp(best_cur_item, items[i]);
for (i = 1; i < 3; i++) {
if (index_entry_cmp(entries[0], entries[i]) != 0) {
item_modified = true;
break;
}
}
}
if (best_cur_item == NULL)
break;
if (cur_item_modified)
error = merge_diff_list_insert_conflict(diff_list, &df_data, cur_items);
else
error = merge_diff_list_insert_unmodified(diff_list, cur_items);
if (error < 0)
goto done;
/* Advance each iterator that participated */
for (i = 0; i < 3; i++) {
if (cur_items[i] == NULL)
continue;
error = git_iterator_advance(&items[i], iterators[i]);
if (error < 0 && error != GIT_ITEROVER)
goto done;
}
}
return item_modified ?
merge_diff_list_insert_conflict(
find_data->diff_list, &find_data->df_data, entries) :
merge_diff_list_insert_unmodified(find_data->diff_list, entries);
}
done:
if (error == GIT_ITEROVER)
error = 0;
int git_merge_diff_list__find_differences(
git_merge_diff_list *diff_list,
git_iterator *ancestor_iter,
git_iterator *our_iter,
git_iterator *their_iter)
{
git_iterator *iterators[3] = { ancestor_iter, our_iter, their_iter };
struct merge_diff_find_data find_data = { diff_list };
return error;
return git_iterator_walk(iterators, 3, queue_difference, &find_data);
}
git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo)
......
......@@ -671,6 +671,31 @@ cleanup:
return error;
}
static int merge_indexes(
git_index **out,
git_repository *repo,
git_tree *ancestor_tree,
git_index *ours_index,
git_index *theirs_index)
{
git_iterator *ancestor = NULL, *ours = NULL, *theirs = NULL;
const git_iterator_flag_t flags = GIT_ITERATOR_DONT_IGNORE_CASE;
int error;
if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, flags, NULL, NULL)) < 0 ||
(error = git_iterator_for_index(&ours, ours_index, flags, NULL, NULL)) < 0 ||
(error = git_iterator_for_index(&theirs, theirs_index, flags, NULL, NULL)) < 0)
goto done;
error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL);
done:
git_iterator_free(ancestor);
git_iterator_free(ours);
git_iterator_free(theirs);
return error;
}
static int merge_index_and_tree(
git_index **out,
git_repository *repo,
......@@ -733,6 +758,70 @@ int git_stash_apply_init_options(git_stash_apply_options *opts, unsigned int ver
} \
} while(false);
static int ensure_clean_index(git_repository *repo, git_index *index)
{
git_tree *head_tree = NULL;
git_diff *index_diff = NULL;
int error = 0;
if ((error = git_repository_head_tree(&head_tree, repo)) < 0 ||
(error = git_diff_tree_to_index(
&index_diff, repo, head_tree, index, NULL)) < 0)
goto done;
if (git_diff_num_deltas(index_diff) > 0) {
giterr_set(GITERR_STASH, "%d uncommitted changes exist in the index",
git_diff_num_deltas(index_diff));
error = GIT_EUNCOMMITTED;
}
done:
git_diff_free(index_diff);
git_tree_free(head_tree);
return error;
}
static int stage_new_file(const git_index_entry **entries, void *data)
{
git_index *index = data;
if(entries[0] == NULL)
return git_index_add(index, entries[1]);
else
return git_index_add(index, entries[0]);
}
static int stage_new_files(
git_index **out,
git_repository *repo,
git_tree *parent_tree,
git_tree *tree)
{
git_iterator *iterators[2] = { NULL, NULL };
git_index *index = NULL;
int error;
if ((error = git_index_new(&index)) < 0 ||
(error = git_iterator_for_tree(&iterators[0], parent_tree,
GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 ||
(error = git_iterator_for_tree(&iterators[1], tree,
GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0)
goto done;
error = git_iterator_walk(iterators, 2, stage_new_file, index);
done:
if (error < 0)
git_index_free(index);
else
*out = index;
git_iterator_free(iterators[0]);
git_iterator_free(iterators[1]);
return error;
}
int git_stash_apply(
git_repository *repo,
size_t index,
......@@ -746,6 +835,7 @@ int git_stash_apply(
git_tree *index_tree = NULL;
git_tree *index_parent_tree = NULL;
git_tree *untracked_tree = NULL;
git_index *stash_adds = NULL;
git_index *repo_index = NULL;
git_index *unstashed_index = NULL;
git_index *modified_index = NULL;
......@@ -775,6 +865,9 @@ int git_stash_apply(
NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX);
if ((error = ensure_clean_index(repo, repo_index)) < 0)
goto cleanup;
/* Restore index if required */
if ((opts.flags & GIT_STASH_APPLY_REINSTATE_INDEX) &&
git_oid_cmp(git_tree_id(stash_parent_tree), git_tree_id(index_tree))) {
......@@ -787,6 +880,16 @@ int git_stash_apply(
error = GIT_ECONFLICT;
goto cleanup;
}
/* Otherwise, stage any new files in the stash tree. (Note: their
* previously unstaged contents are staged, not the previously staged.)
*/
} else if ((opts.flags & GIT_STASH_APPLY_REINSTATE_INDEX) == 0) {
if ((error = stage_new_files(
&stash_adds, repo, stash_parent_tree, stash_tree)) < 0 ||
(error = merge_indexes(
&unstashed_index, repo, stash_parent_tree, repo_index, stash_adds)) < 0)
goto cleanup;
}
NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED);
......@@ -848,6 +951,7 @@ cleanup:
git_index_free(untracked_index);
git_index_free(modified_index);
git_index_free(unstashed_index);
git_index_free(stash_adds);
git_index_free(repo_index);
git_tree_free(untracked_tree);
git_tree_free(index_parent_tree);
......
......@@ -10,14 +10,14 @@ void test_stash_apply__initialize(void)
{
git_oid oid;
cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */
cl_git_pass(git_repository_init(&repo, "stash", 0));
repo = cl_git_sandbox_init_new("stash");
cl_git_pass(git_repository_index(&repo_index, repo));
cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */
cl_git_mkfile("stash/what", "hello\n");
cl_git_mkfile("stash/how", "small\n");
cl_git_mkfile("stash/who", "world\n");
cl_git_mkfile("stash/where", "meh\n");
cl_git_pass(git_index_add_bypath(repo_index, "what"));
cl_git_pass(git_index_add_bypath(repo_index, "how"));
......@@ -28,14 +28,23 @@ void test_stash_apply__initialize(void)
cl_git_rewritefile("stash/what", "goodbye\n");
cl_git_rewritefile("stash/who", "funky world\n");
cl_git_mkfile("stash/when", "tomorrow\n");
cl_git_mkfile("stash/why", "would anybody use stash?\n");
cl_git_mkfile("stash/where", "????\n");
cl_git_pass(git_index_add_bypath(repo_index, "who"));
cl_git_pass(git_index_add_bypath(repo_index, "why"));
cl_git_pass(git_index_add_bypath(repo_index, "where"));
git_index_write(repo_index);
cl_git_rewritefile("stash/where", "....\n");
/* Pre-stash state */
assert_status(repo, "what", GIT_STATUS_WT_MODIFIED);
assert_status(repo, "how", GIT_STATUS_CURRENT);
assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED);
assert_status(repo, "when", GIT_STATUS_WT_NEW);
assert_status(repo, "why", GIT_STATUS_INDEX_NEW);
assert_status(repo, "where", GIT_STATUS_INDEX_NEW|GIT_STATUS_WT_MODIFIED);
cl_git_pass(git_stash_save(&oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED));
......@@ -44,6 +53,8 @@ void test_stash_apply__initialize(void)
assert_status(repo, "how", GIT_STATUS_CURRENT);
assert_status(repo, "who", GIT_STATUS_CURRENT);
assert_status(repo, "when", GIT_ENOTFOUND);
assert_status(repo, "why", GIT_ENOTFOUND);
assert_status(repo, "where", GIT_ENOTFOUND);
}
void test_stash_apply__cleanup(void)
......@@ -54,15 +65,13 @@ void test_stash_apply__cleanup(void)
git_index_free(repo_index);
repo_index = NULL;
git_repository_free(repo);
repo = NULL;
cl_git_pass(git_futils_rmdir_r("stash", NULL, GIT_RMDIR_REMOVE_FILES));
cl_fixture_cleanup("sorry-it-is-a-non-bare-only-party");
cl_git_sandbox_cleanup();
}
void test_stash_apply__with_default(void)
{
git_buf where = GIT_BUF_INIT;
cl_git_pass(git_stash_apply(repo, 0, NULL));
cl_assert_equal_i(git_index_has_conflicts(repo_index), 0);
......@@ -70,10 +79,42 @@ void test_stash_apply__with_default(void)
assert_status(repo, "how", GIT_STATUS_CURRENT);
assert_status(repo, "who", GIT_STATUS_WT_MODIFIED);
assert_status(repo, "when", GIT_STATUS_WT_NEW);
assert_status(repo, "why", GIT_STATUS_INDEX_NEW);
assert_status(repo, "where", GIT_STATUS_INDEX_NEW);
cl_git_pass(git_futils_readbuffer(&where, "stash/where"));
cl_assert_equal_s("....\n", where.ptr);
git_buf_free(&where);
}
void test_stash_apply__with_existing_file(void)
{
cl_git_mkfile("stash/where", "oops!\n");
cl_git_fail(git_stash_apply(repo, 0, NULL));
}
void test_stash_apply__merges_new_file(void)
{
git_index_entry *ancestor, *our, *their;
cl_git_mkfile("stash/where", "committed before stash\n");
cl_git_pass(git_index_add_bypath(repo_index, "where"));
cl_repo_commit_from_index(NULL, repo, signature, 0, "Other commit");
cl_git_pass(git_stash_apply(repo, 0, NULL));
cl_assert_equal_i(1, git_index_has_conflicts(repo_index));
assert_status(repo, "what", GIT_STATUS_INDEX_MODIFIED);
cl_git_pass(git_index_conflict_get(&ancestor, &our, &their, repo_index, "where")); /* unmerged */
assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED);
assert_status(repo, "when", GIT_STATUS_WT_NEW);
assert_status(repo, "why", GIT_STATUS_INDEX_NEW);
}
void test_stash_apply__with_reinstate_index(void)
{
git_buf where = GIT_BUF_INIT;
git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT;
opts.flags = GIT_STASH_APPLY_REINSTATE_INDEX;
......@@ -85,6 +126,13 @@ void test_stash_apply__with_reinstate_index(void)
assert_status(repo, "how", GIT_STATUS_CURRENT);
assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED);
assert_status(repo, "when", GIT_STATUS_WT_NEW);
assert_status(repo, "why", GIT_STATUS_INDEX_NEW);
assert_status(repo, "where", GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_MODIFIED);
cl_git_pass(git_futils_readbuffer(&where, "stash/where"));
cl_assert_equal_s("....\n", where.ptr);
git_buf_free(&where);
}
void test_stash_apply__conflict_index_with_default(void)
......@@ -96,6 +144,7 @@ void test_stash_apply__conflict_index_with_default(void)
cl_git_rewritefile("stash/who", "nothing\n");
cl_git_pass(git_index_add_bypath(repo_index, "who"));
cl_git_pass(git_index_write(repo_index));
cl_repo_commit_from_index(NULL, repo, signature, 0, "Other commit");
cl_git_pass(git_stash_apply(repo, 0, NULL));
......@@ -104,6 +153,7 @@ void test_stash_apply__conflict_index_with_default(void)
assert_status(repo, "how", GIT_STATUS_CURRENT);
cl_git_pass(git_index_conflict_get(&ancestor, &our, &their, repo_index, "who")); /* unmerged */
assert_status(repo, "when", GIT_STATUS_WT_NEW);
assert_status(repo, "why", GIT_STATUS_INDEX_NEW);
}
void test_stash_apply__conflict_index_with_reinstate_index(void)
......@@ -115,14 +165,16 @@ void test_stash_apply__conflict_index_with_reinstate_index(void)
cl_git_rewritefile("stash/who", "nothing\n");
cl_git_pass(git_index_add_bypath(repo_index, "who"));
cl_git_pass(git_index_write(repo_index));
cl_repo_commit_from_index(NULL, repo, signature, 0, "Other commit");
cl_git_fail_with(git_stash_apply(repo, 0, &opts), GIT_ECONFLICT);
cl_assert_equal_i(git_index_has_conflicts(repo_index), 0);
assert_status(repo, "what", GIT_STATUS_CURRENT);
assert_status(repo, "how", GIT_STATUS_CURRENT);
assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED);
assert_status(repo, "who", GIT_STATUS_CURRENT);
assert_status(repo, "when", GIT_ENOTFOUND);
assert_status(repo, "why", GIT_ENOTFOUND);
}
void test_stash_apply__conflict_untracked_with_default(void)
......@@ -138,6 +190,7 @@ void test_stash_apply__conflict_untracked_with_default(void)
assert_status(repo, "how", GIT_STATUS_CURRENT);
assert_status(repo, "who", GIT_STATUS_CURRENT);
assert_status(repo, "when", GIT_STATUS_WT_NEW);
assert_status(repo, "why", GIT_ENOTFOUND);
}
void test_stash_apply__conflict_untracked_with_reinstate_index(void)
......@@ -155,6 +208,7 @@ void test_stash_apply__conflict_untracked_with_reinstate_index(void)
assert_status(repo, "how", GIT_STATUS_CURRENT);
assert_status(repo, "who", GIT_STATUS_CURRENT);
assert_status(repo, "when", GIT_STATUS_WT_NEW);
assert_status(repo, "why", GIT_ENOTFOUND);
}
void test_stash_apply__conflict_workdir_with_default(void)
......@@ -168,6 +222,7 @@ void test_stash_apply__conflict_workdir_with_default(void)
assert_status(repo, "how", GIT_STATUS_CURRENT);
assert_status(repo, "who", GIT_STATUS_CURRENT);
assert_status(repo, "when", GIT_STATUS_WT_NEW);
assert_status(repo, "why", GIT_ENOTFOUND);
}
void test_stash_apply__conflict_workdir_with_reinstate_index(void)
......@@ -185,6 +240,7 @@ void test_stash_apply__conflict_workdir_with_reinstate_index(void)
assert_status(repo, "how", GIT_STATUS_CURRENT);
assert_status(repo, "who", GIT_STATUS_CURRENT);
assert_status(repo, "when", GIT_STATUS_WT_NEW);
assert_status(repo, "why", GIT_ENOTFOUND);
}
void test_stash_apply__conflict_commit_with_default(void)
......@@ -204,6 +260,7 @@ void test_stash_apply__conflict_commit_with_default(void)
assert_status(repo, "how", GIT_STATUS_CURRENT);
assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED);
assert_status(repo, "when", GIT_STATUS_WT_NEW);
assert_status(repo, "why", GIT_STATUS_INDEX_NEW);
}
void test_stash_apply__conflict_commit_with_reinstate_index(void)
......@@ -226,6 +283,23 @@ void test_stash_apply__conflict_commit_with_reinstate_index(void)
assert_status(repo, "how", GIT_STATUS_CURRENT);
assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED);
assert_status(repo, "when", GIT_STATUS_WT_NEW);
assert_status(repo, "why", GIT_STATUS_INDEX_NEW);
}
void test_stash_apply__fails_with_uncommitted_changes_in_index(void)
{
cl_git_rewritefile("stash/who", "nothing\n");
cl_git_pass(git_index_add_bypath(repo_index, "who"));
cl_git_pass(git_index_write(repo_index));
cl_git_fail_with(git_stash_apply(repo, 0, NULL), GIT_EUNCOMMITTED);
cl_assert_equal_i(git_index_has_conflicts(repo_index), 0);
assert_status(repo, "what", GIT_STATUS_CURRENT);
assert_status(repo, "how", GIT_STATUS_CURRENT);
assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED);
assert_status(repo, "when", GIT_ENOTFOUND);
assert_status(repo, "why", GIT_ENOTFOUND);
}
void test_stash_apply__pop(void)
......@@ -285,6 +359,8 @@ void test_stash_apply__executes_notify_cb(void)
assert_status(repo, "how", GIT_STATUS_CURRENT);
assert_status(repo, "who", GIT_STATUS_WT_MODIFIED);
assert_status(repo, "when", GIT_STATUS_WT_NEW);
assert_status(repo, "why", GIT_STATUS_INDEX_NEW);
assert_status(repo, "where", GIT_STATUS_INDEX_NEW);
cl_assert_equal_b(true, seen_paths.what);
cl_assert_equal_b(false, seen_paths.how);
......
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