stash: save the workdir file when deleted in index When stashing the workdir tree, examine the index as well. Using a mechanism similar to `git_diff_tree_to_workdir_with_index` allows us to determine that a file was added in the index and subsequently modified in the working directory. Without examining the index, we would erroneously believe that this file was untracked and fail to include it in the working directory tree. Use a slightly modified `git_diff_tree_to_workdir_with_index` in order to avoid some of the behavior custom to `git diff`. In particular, be sure to include the working directory side of a file when it was deleted in the index.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
diff --git a/src/diff.h b/src/diff.h
index 39d15fa..a202a08 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -133,6 +133,15 @@ typedef git_diff_delta *(*git_diff__merge_cb)(
extern int git_diff__merge(
git_diff *onto, const git_diff *from, git_diff__merge_cb cb);
+extern git_diff_delta *git_diff__merge_like_cgit(
+ const git_diff_delta *a,
+ const git_diff_delta *b,
+ git_pool *pool);
+
+/* Duplicate a `git_diff_delta` out of the `git_pool` */
+extern git_diff_delta *git_diff__delta_dup(
+ const git_diff_delta *d, git_pool *pool);
+
/*
* Sometimes a git_diff_file will have a zero size; this attempts to
* fill in the size without loading the blob if possible. If that is
diff --git a/src/diff_tform.c b/src/diff_tform.c
index 52a7b52..92647e3 100644
--- a/src/diff_tform.c
+++ b/src/diff_tform.c
@@ -15,7 +15,7 @@
#include "fileops.h"
#include "config.h"
-static git_diff_delta *diff_delta__dup(
+git_diff_delta *git_diff__delta_dup(
const git_diff_delta *d, git_pool *pool)
{
git_diff_delta *delta = git__malloc(sizeof(git_diff_delta));
@@ -46,7 +46,7 @@ fail:
return NULL;
}
-static git_diff_delta *diff_delta__merge_like_cgit(
+git_diff_delta *git_diff__merge_like_cgit(
const git_diff_delta *a,
const git_diff_delta *b,
git_pool *pool)
@@ -67,16 +67,16 @@ static git_diff_delta *diff_delta__merge_like_cgit(
/* If one of the diffs is a conflict, just dup it */
if (b->status == GIT_DELTA_CONFLICTED)
- return diff_delta__dup(b, pool);
+ return git_diff__delta_dup(b, pool);
if (a->status == GIT_DELTA_CONFLICTED)
- return diff_delta__dup(a, pool);
+ return git_diff__delta_dup(a, pool);
/* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */
if (b->status == GIT_DELTA_UNMODIFIED || a->status == GIT_DELTA_DELETED)
- return diff_delta__dup(a, pool);
+ return git_diff__delta_dup(a, pool);
/* otherwise, base this diff on the 'b' diff */
- if ((dup = diff_delta__dup(b, pool)) == NULL)
+ if ((dup = git_diff__delta_dup(b, pool)) == NULL)
return NULL;
/* If 'a' status is uninteresting, then we're done */
@@ -109,7 +109,7 @@ static git_diff_delta *diff_delta__merge_like_cgit(
return dup;
}
-int git_diff__merge_deltas(
+int git_diff__merge(
git_diff *onto, const git_diff *from, git_diff__merge_cb cb)
{
int error = 0;
@@ -146,10 +146,10 @@ int git_diff__merge_deltas(
STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path);
if (cmp < 0) {
- delta = diff_delta__dup(o, &onto_pool);
+ delta = git_diff__delta_dup(o, &onto_pool);
i++;
} else if (cmp > 0) {
- delta = diff_delta__dup(f, &onto_pool);
+ delta = git_diff__delta_dup(f, &onto_pool);
j++;
} else {
const git_diff_delta *left = reversed ? f : o;
@@ -196,7 +196,7 @@ int git_diff__merge_deltas(
int git_diff_merge(git_diff *onto, const git_diff *from)
{
- return git_diff__merge_deltas(onto, from, diff_delta__merge_like_cgit);
+ return git_diff__merge(onto, from, git_diff__merge_like_cgit);
}
int git_diff_find_similar__hashsig_for_file(
@@ -347,7 +347,7 @@ static int insert_delete_side_of_split(
git_diff *diff, git_vector *onto, const git_diff_delta *delta)
{
/* make new record for DELETED side of split */
- git_diff_delta *deleted = diff_delta__dup(delta, &diff->pool);
+ git_diff_delta *deleted = git_diff__delta_dup(delta, &diff->pool);
GITERR_CHECK_ALLOC(deleted);
deleted->status = GIT_DELTA_DELETED;
diff --git a/src/stash.c b/src/stash.c
index 8f512d4..9010c47 100644
--- a/src/stash.c
+++ b/src/stash.c
@@ -22,6 +22,7 @@
#include "signature.h"
#include "iterator.h"
#include "merge.h"
+#include "diff.h"
static int create_error(int error, const char *msg)
{
@@ -292,6 +293,25 @@ cleanup:
return error;
}
+static git_diff_delta *stash_delta_merge(
+ const git_diff_delta *a,
+ const git_diff_delta *b,
+ git_pool *pool)
+{
+ /* Special case for stash: if a file is deleted in the index, but exists
+ * in the working tree, we need to stash the workdir copy for the workdir.
+ */
+ if (a->status == GIT_DELTA_DELETED && b->status == GIT_DELTA_UNTRACKED) {
+ git_diff_delta *dup = git_diff__delta_dup(b, pool);
+
+ if (dup)
+ dup->status = GIT_DELTA_MODIFIED;
+ return dup;
+ }
+
+ return git_diff__merge_like_cgit(a, b, pool);
+}
+
static int build_workdir_tree(
git_tree **tree_out,
git_index *index,
@@ -299,17 +319,19 @@ static int build_workdir_tree(
{
git_repository *repo = git_index_owner(index);
git_tree *b_tree = NULL;
- git_diff *diff = NULL;
+ git_diff *diff = NULL, *idx_to_wd = NULL;
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
struct stash_update_rules data = {0};
int error;
- opts.flags = GIT_DIFF_IGNORE_SUBMODULES;
+ opts.flags = GIT_DIFF_IGNORE_SUBMODULES | GIT_DIFF_INCLUDE_UNTRACKED;
if ((error = git_commit_tree(&b_tree, b_commit)) < 0)
goto cleanup;
- if ((error = git_diff_tree_to_workdir(&diff, repo, b_tree, &opts)) < 0)
+ if ((error = git_diff_tree_to_index(&diff, repo, b_tree, index, &opts)) < 0 ||
+ (error = git_diff_index_to_workdir(&idx_to_wd, repo, index, &opts)) < 0 ||
+ (error = git_diff__merge(diff, idx_to_wd, stash_delta_merge)) < 0)
goto cleanup;
data.include_changed = true;
@@ -320,6 +342,7 @@ static int build_workdir_tree(
error = build_tree_from_index(tree_out, index);
cleanup:
+ git_diff_free(idx_to_wd);
git_diff_free(diff);
git_tree_free(b_tree);