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

Skip to content

Commit 354268c

Browse files
committed
Merge pull request #3259 from ethomson/stash_apply_argh
Stash apply: stage new files even when not updating the index
2 parents 3c7a469 + b7f5cb8 commit 354268c

File tree

6 files changed

+323
-95
lines changed

6 files changed

+323
-95
lines changed

include/git2/errors.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ typedef enum {
4747
GIT_EPEEL = -19, /**< The requested peel operation is not possible */
4848
GIT_EEOF = -20, /**< Unexpected EOF */
4949
GIT_EINVALID = -21, /**< Invalid operation or input */
50+
GIT_EUNCOMMITTED = -22, /**< Uncommitted changes in index prevented operation */
5051

5152
GIT_PASSTHROUGH = -30, /**< Internal only */
5253
GIT_ITEROVER = -31, /**< Signals end of iteration with iterator */

src/iterator.c

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1843,3 +1843,91 @@ int git_iterator_advance_over_with_status(
18431843
return error;
18441844
}
18451845

1846+
int git_iterator_walk(
1847+
git_iterator **iterators,
1848+
size_t cnt,
1849+
git_iterator_walk_cb cb,
1850+
void *data)
1851+
{
1852+
const git_index_entry **iterator_item; /* next in each iterator */
1853+
const git_index_entry **cur_items; /* current path in each iter */
1854+
const git_index_entry *first_match;
1855+
int cur_item_modified;
1856+
size_t i, j;
1857+
int error = 0;
1858+
1859+
iterator_item = git__calloc(cnt, sizeof(git_index_entry *));
1860+
cur_items = git__calloc(cnt, sizeof(git_index_entry *));
1861+
1862+
GITERR_CHECK_ALLOC(iterator_item);
1863+
GITERR_CHECK_ALLOC(cur_items);
1864+
1865+
/* Set up the iterators */
1866+
for (i = 0; i < cnt; i++) {
1867+
error = git_iterator_current(&iterator_item[i], iterators[i]);
1868+
1869+
if (error < 0 && error != GIT_ITEROVER)
1870+
goto done;
1871+
}
1872+
1873+
while (true) {
1874+
for (i = 0; i < cnt; i++)
1875+
cur_items[i] = NULL;
1876+
1877+
first_match = NULL;
1878+
cur_item_modified = 0;
1879+
1880+
/* Find the next path(s) to consume from each iterator */
1881+
for (i = 0; i < cnt; i++) {
1882+
if (iterator_item[i] == NULL)
1883+
continue;
1884+
1885+
if (first_match == NULL) {
1886+
first_match = iterator_item[i];
1887+
cur_items[i] = iterator_item[i];
1888+
} else {
1889+
int path_diff = git_index_entry_cmp(iterator_item[i], first_match);
1890+
1891+
if (path_diff < 0) {
1892+
/* Found an index entry that sorts before the one we're
1893+
* looking at. Forget that we've seen the other and
1894+
* look at the other iterators for this path.
1895+
*/
1896+
for (j = 0; j < i; j++)
1897+
cur_items[j] = NULL;
1898+
1899+
first_match = iterator_item[i];
1900+
cur_items[i] = iterator_item[i];
1901+
} else if (path_diff > 0) {
1902+
/* No entry for the current item, this is modified */
1903+
cur_item_modified = 1;
1904+
} else if (path_diff == 0) {
1905+
cur_items[i] = iterator_item[i];
1906+
}
1907+
}
1908+
}
1909+
1910+
if (first_match == NULL)
1911+
break;
1912+
1913+
if ((error = cb(cur_items, data)) != 0)
1914+
goto done;
1915+
1916+
/* Advance each iterator that participated */
1917+
for (i = 0; i < cnt; i++) {
1918+
if (cur_items[i] == NULL)
1919+
continue;
1920+
1921+
error = git_iterator_advance(&iterator_item[i], iterators[i]);
1922+
1923+
if (error < 0 && error != GIT_ITEROVER)
1924+
goto done;
1925+
}
1926+
}
1927+
1928+
done:
1929+
if (error == GIT_ITEROVER)
1930+
error = 0;
1931+
1932+
return error;
1933+
}

src/iterator.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,4 +294,19 @@ extern int git_iterator_advance_over_with_status(
294294
*/
295295
extern int git_iterator_index(git_index **out, git_iterator *iter);
296296

297+
typedef int (*git_iterator_walk_cb)(
298+
const git_index_entry **entries,
299+
void *data);
300+
301+
/**
302+
* Walk the given iterators in lock-step. The given callback will be
303+
* called for each unique path, with the index entry in each iterator
304+
* (or NULL if the given iterator does not contain that path).
305+
*/
306+
extern int git_iterator_walk(
307+
git_iterator **iterators,
308+
size_t cnt,
309+
git_iterator_walk_cb cb,
310+
void *data);
311+
297312
#endif

src/merge.c

Lines changed: 30 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1449,100 +1449,44 @@ static int merge_diff_list_insert_unmodified(
14491449
return error;
14501450
}
14511451

1452+
struct merge_diff_find_data {
1453+
git_merge_diff_list *diff_list;
1454+
struct merge_diff_df_data df_data;
1455+
};
1456+
1457+
static int queue_difference(const git_index_entry **entries, void *data)
1458+
{
1459+
struct merge_diff_find_data *find_data = data;
1460+
bool item_modified = false;
1461+
size_t i;
1462+
1463+
if (!entries[0] || !entries[1] || !entries[2]) {
1464+
item_modified = true;
1465+
} else {
1466+
for (i = 1; i < 3; i++) {
1467+
if (index_entry_cmp(entries[0], entries[i]) != 0) {
1468+
item_modified = true;
1469+
break;
1470+
}
1471+
}
1472+
}
1473+
1474+
return item_modified ?
1475+
merge_diff_list_insert_conflict(
1476+
find_data->diff_list, &find_data->df_data, entries) :
1477+
merge_diff_list_insert_unmodified(find_data->diff_list, entries);
1478+
}
1479+
14521480
int git_merge_diff_list__find_differences(
14531481
git_merge_diff_list *diff_list,
14541482
git_iterator *ancestor_iter,
14551483
git_iterator *our_iter,
14561484
git_iterator *their_iter)
14571485
{
14581486
git_iterator *iterators[3] = { ancestor_iter, our_iter, their_iter };
1459-
const git_index_entry *items[3] = {0}, *best_cur_item, *cur_items[3];
1460-
git_vector_cmp entry_compare = git_index_entry_cmp;
1461-
struct merge_diff_df_data df_data = {0};
1462-
int cur_item_modified;
1463-
size_t i, j;
1464-
int error = 0;
1465-
1466-
assert(diff_list && (our_iter || their_iter));
1467-
1468-
/* Set up the iterators */
1469-
for (i = 0; i < 3; i++) {
1470-
error = git_iterator_current(&items[i], iterators[i]);
1471-
1472-
if (error < 0 && error != GIT_ITEROVER)
1473-
goto done;
1474-
}
1475-
1476-
while (true) {
1477-
for (i = 0; i < 3; i++)
1478-
cur_items[i] = NULL;
1479-
1480-
best_cur_item = NULL;
1481-
cur_item_modified = 0;
1482-
1483-
/* Find the next path(s) to consume from each iterator */
1484-
for (i = 0; i < 3; i++) {
1485-
if (items[i] == NULL) {
1486-
cur_item_modified = 1;
1487-
continue;
1488-
}
1489-
1490-
if (best_cur_item == NULL) {
1491-
best_cur_item = items[i];
1492-
cur_items[i] = items[i];
1493-
} else {
1494-
int path_diff = entry_compare(items[i], best_cur_item);
1495-
1496-
if (path_diff < 0) {
1497-
/*
1498-
* Found an item that sorts before our current item, make
1499-
* our current item this one.
1500-
*/
1501-
for (j = 0; j < i; j++)
1502-
cur_items[j] = NULL;
1503-
1504-
cur_item_modified = 1;
1505-
best_cur_item = items[i];
1506-
cur_items[i] = items[i];
1507-
} else if (path_diff > 0) {
1508-
/* No entry for the current item, this is modified */
1509-
cur_item_modified = 1;
1510-
} else if (path_diff == 0) {
1511-
cur_items[i] = items[i];
1512-
1513-
if (!cur_item_modified)
1514-
cur_item_modified = index_entry_cmp(best_cur_item, items[i]);
1515-
}
1516-
}
1517-
}
1518-
1519-
if (best_cur_item == NULL)
1520-
break;
1521-
1522-
if (cur_item_modified)
1523-
error = merge_diff_list_insert_conflict(diff_list, &df_data, cur_items);
1524-
else
1525-
error = merge_diff_list_insert_unmodified(diff_list, cur_items);
1526-
if (error < 0)
1527-
goto done;
1487+
struct merge_diff_find_data find_data = { diff_list };
15281488

1529-
/* Advance each iterator that participated */
1530-
for (i = 0; i < 3; i++) {
1531-
if (cur_items[i] == NULL)
1532-
continue;
1533-
1534-
error = git_iterator_advance(&items[i], iterators[i]);
1535-
1536-
if (error < 0 && error != GIT_ITEROVER)
1537-
goto done;
1538-
}
1539-
}
1540-
1541-
done:
1542-
if (error == GIT_ITEROVER)
1543-
error = 0;
1544-
1545-
return error;
1489+
return git_iterator_walk(iterators, 3, queue_difference, &find_data);
15461490
}
15471491

15481492
git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo)

src/stash.c

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,31 @@ static int retrieve_stash_trees(
671671
return error;
672672
}
673673

674+
static int merge_indexes(
675+
git_index **out,
676+
git_repository *repo,
677+
git_tree *ancestor_tree,
678+
git_index *ours_index,
679+
git_index *theirs_index)
680+
{
681+
git_iterator *ancestor = NULL, *ours = NULL, *theirs = NULL;
682+
const git_iterator_flag_t flags = GIT_ITERATOR_DONT_IGNORE_CASE;
683+
int error;
684+
685+
if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, flags, NULL, NULL)) < 0 ||
686+
(error = git_iterator_for_index(&ours, ours_index, flags, NULL, NULL)) < 0 ||
687+
(error = git_iterator_for_index(&theirs, theirs_index, flags, NULL, NULL)) < 0)
688+
goto done;
689+
690+
error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL);
691+
692+
done:
693+
git_iterator_free(ancestor);
694+
git_iterator_free(ours);
695+
git_iterator_free(theirs);
696+
return error;
697+
}
698+
674699
static int merge_index_and_tree(
675700
git_index **out,
676701
git_repository *repo,
@@ -733,6 +758,70 @@ int git_stash_apply_init_options(git_stash_apply_options *opts, unsigned int ver
733758
} \
734759
} while(false);
735760

761+
static int ensure_clean_index(git_repository *repo, git_index *index)
762+
{
763+
git_tree *head_tree = NULL;
764+
git_diff *index_diff = NULL;
765+
int error = 0;
766+
767+
if ((error = git_repository_head_tree(&head_tree, repo)) < 0 ||
768+
(error = git_diff_tree_to_index(
769+
&index_diff, repo, head_tree, index, NULL)) < 0)
770+
goto done;
771+
772+
if (git_diff_num_deltas(index_diff) > 0) {
773+
giterr_set(GITERR_STASH, "%d uncommitted changes exist in the index",
774+
git_diff_num_deltas(index_diff));
775+
error = GIT_EUNCOMMITTED;
776+
}
777+
778+
done:
779+
git_diff_free(index_diff);
780+
git_tree_free(head_tree);
781+
return error;
782+
}
783+
784+
static int stage_new_file(const git_index_entry **entries, void *data)
785+
{
786+
git_index *index = data;
787+
788+
if(entries[0] == NULL)
789+
return git_index_add(index, entries[1]);
790+
else
791+
return git_index_add(index, entries[0]);
792+
}
793+
794+
static int stage_new_files(
795+
git_index **out,
796+
git_repository *repo,
797+
git_tree *parent_tree,
798+
git_tree *tree)
799+
{
800+
git_iterator *iterators[2] = { NULL, NULL };
801+
git_index *index = NULL;
802+
int error;
803+
804+
if ((error = git_index_new(&index)) < 0 ||
805+
(error = git_iterator_for_tree(&iterators[0], parent_tree,
806+
GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 ||
807+
(error = git_iterator_for_tree(&iterators[1], tree,
808+
GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0)
809+
goto done;
810+
811+
error = git_iterator_walk(iterators, 2, stage_new_file, index);
812+
813+
done:
814+
if (error < 0)
815+
git_index_free(index);
816+
else
817+
*out = index;
818+
819+
git_iterator_free(iterators[0]);
820+
git_iterator_free(iterators[1]);
821+
822+
return error;
823+
}
824+
736825
int git_stash_apply(
737826
git_repository *repo,
738827
size_t index,
@@ -746,6 +835,7 @@ int git_stash_apply(
746835
git_tree *index_tree = NULL;
747836
git_tree *index_parent_tree = NULL;
748837
git_tree *untracked_tree = NULL;
838+
git_index *stash_adds = NULL;
749839
git_index *repo_index = NULL;
750840
git_index *unstashed_index = NULL;
751841
git_index *modified_index = NULL;
@@ -775,6 +865,9 @@ int git_stash_apply(
775865

776866
NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX);
777867

868+
if ((error = ensure_clean_index(repo, repo_index)) < 0)
869+
goto cleanup;
870+
778871
/* Restore index if required */
779872
if ((opts.flags & GIT_STASH_APPLY_REINSTATE_INDEX) &&
780873
git_oid_cmp(git_tree_id(stash_parent_tree), git_tree_id(index_tree))) {
@@ -787,6 +880,16 @@ int git_stash_apply(
787880
error = GIT_ECONFLICT;
788881
goto cleanup;
789882
}
883+
884+
/* Otherwise, stage any new files in the stash tree. (Note: their
885+
* previously unstaged contents are staged, not the previously staged.)
886+
*/
887+
} else if ((opts.flags & GIT_STASH_APPLY_REINSTATE_INDEX) == 0) {
888+
if ((error = stage_new_files(
889+
&stash_adds, repo, stash_parent_tree, stash_tree)) < 0 ||
890+
(error = merge_indexes(
891+
&unstashed_index, repo, stash_parent_tree, repo_index, stash_adds)) < 0)
892+
goto cleanup;
790893
}
791894

792895
NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED);
@@ -848,6 +951,7 @@ int git_stash_apply(
848951
git_index_free(untracked_index);
849952
git_index_free(modified_index);
850953
git_index_free(unstashed_index);
954+
git_index_free(stash_adds);
851955
git_index_free(repo_index);
852956
git_tree_free(untracked_tree);
853957
git_tree_free(index_parent_tree);

0 commit comments

Comments
 (0)