Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Stash apply: stage new files even when not updating the index #3259

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 26, 2015
Merged
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
1 change: 1 addition & 0 deletions include/git2/errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
88 changes: 88 additions & 0 deletions src/iterator.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
15 changes: 15 additions & 0 deletions src/iterator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
116 changes: 30 additions & 86 deletions src/merge.c
Original file line number Diff line number Diff line change
Expand Up @@ -1449,100 +1449,44 @@ static int merge_diff_list_insert_unmodified(
return error;
}

struct merge_diff_find_data {
git_merge_diff_list *diff_list;
struct merge_diff_df_data df_data;
};

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 (!entries[0] || !entries[1] || !entries[2]) {
item_modified = true;
} else {
for (i = 1; i < 3; i++) {
if (index_entry_cmp(entries[0], entries[i]) != 0) {
item_modified = true;
break;
}
}
}

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);
}

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;

/* 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;
}

if (best_cur_item == NULL) {
best_cur_item = items[i];
cur_items[i] = items[i];
} 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]);
}
}
}

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;
struct merge_diff_find_data find_data = { diff_list };

/* 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;
}
}

done:
if (error == GIT_ITEROVER)
error = 0;

return error;
return git_iterator_walk(iterators, 3, queue_difference, &find_data);
}

git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo)
Expand Down
104 changes: 104 additions & 0 deletions src/stash.c
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,31 @@ static int retrieve_stash_trees(
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,
Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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))) {
Expand All @@ -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);
Expand Down Expand Up @@ -848,6 +951,7 @@ int git_stash_apply(
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);
Expand Down
Loading