Fix bugs in new diff patch code This fixes all the bugs in the new diff patch code. The only really interesting one is that when we merge two diffs, we now have to actually exclude diff delta records that are not supposed to be tracked, as opposed to before where they could be included because they would be skipped silently by `git_diff_foreach()`. Other than that, there are just minor errors.
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 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
diff --git a/include/git2/diff.h b/include/git2/diff.h
index d216c13..1542645 100644
--- a/include/git2/diff.h
+++ b/include/git2/diff.h
@@ -401,6 +401,20 @@ GIT_EXTERN(int) git_diff_print_compact(
git_diff_data_fn print_cb);
/**
+ * Look up the single character abbreviation for a delta status code.
+ *
+ * When you call `git_diff_print_compact` it prints single letter codes into
+ * the output such as 'A' for added, 'D' for deleted, 'M' for modified, etc.
+ * It is sometimes convenient to convert a git_delta_t value into these
+ * letters for your own purposes. This function does just that. By the
+ * way, unmodified will return a space (i.e. ' ').
+ *
+ * @param delta_t The git_delta_t value to look up
+ * @return The single character label for that code
+ */
+GIT_EXTERN(char) git_diff_status_char(git_delta_t status);
+
+/**
* Iterate over a diff generating text output like "git diff".
*
* This is a super easy way to generate a patch from a diff.
@@ -453,9 +467,9 @@ GIT_EXTERN(size_t) git_diff_num_deltas_of_type(
* done with it. You can use the patch object to loop over all the hunks
* and lines in the diff of the one delta.
*
- * For a binary file, no `git_diff_patch` will be created, the output will
- * be set to NULL, and the `binary` flag will be set true in the
- * `git_diff_delta` structure.
+ * For an unchanged file or a binary file, no `git_diff_patch` will be
+ * created, the output will be set to NULL, and the `binary` flag will be
+ * set true in the `git_diff_delta` structure.
*
* The `git_diff_delta` pointer points to internal data and you do not have
* to release it when you are done with it. It will go away when the
diff --git a/src/diff.c b/src/diff.c
index 7c8e2a9..7ee919b 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -807,6 +807,28 @@ on_error:
return -1;
}
+
+bool git_diff_delta__should_skip(
+ git_diff_options *opts, git_diff_delta *delta)
+{
+ uint32_t flags = opts ? opts->flags : 0;
+
+ if (delta->status == GIT_DELTA_UNMODIFIED &&
+ (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
+ return true;
+
+ if (delta->status == GIT_DELTA_IGNORED &&
+ (flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
+ return true;
+
+ if (delta->status == GIT_DELTA_UNTRACKED &&
+ (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
+ return true;
+
+ return false;
+}
+
+
int git_diff_merge(
git_diff_list *onto,
const git_diff_list *from)
@@ -843,6 +865,14 @@ int git_diff_merge(
j++;
}
+ /* the ignore rules for the target may not match the source
+ * or the result of a merged delta could be skippable...
+ */
+ if (git_diff_delta__should_skip(&onto->opts, delta)) {
+ git__free(delta);
+ continue;
+ }
+
if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0)
break;
}
diff --git a/src/diff.h b/src/diff.h
index 862c33c..64fe009 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -50,5 +50,8 @@ extern void git_diff__cleanup_modes(
extern void git_diff_list_addref(git_diff_list *diff);
+extern bool git_diff_delta__should_skip(
+ git_diff_options *opts, git_diff_delta *delta);
+
#endif
diff --git a/src/diff_output.c b/src/diff_output.c
index b84b0c2..1418597 100644
--- a/src/diff_output.c
+++ b/src/diff_output.c
@@ -51,26 +51,6 @@ static int parse_hunk_header(git_diff_range *range, const char *header)
return 0;
}
-static bool diff_delta_should_skip(
- diff_context *ctxt, git_diff_delta *delta)
-{
- uint32_t flags = ctxt->opts ? ctxt->opts->flags : 0;
-
- if (delta->status == GIT_DELTA_UNMODIFIED &&
- (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
- return true;
-
- if (delta->status == GIT_DELTA_IGNORED &&
- (flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
- return true;
-
- if (delta->status == GIT_DELTA_UNTRACKED &&
- (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
- return true;
-
- return false;
-}
-
#define KNOWN_BINARY_FLAGS (GIT_DIFF_FILE_BINARY|GIT_DIFF_FILE_NOT_BINARY)
#define NOT_BINARY_FLAGS (GIT_DIFF_FILE_NOT_BINARY|GIT_DIFF_FILE_NO_DATA)
@@ -411,7 +391,7 @@ static void diff_context_init(
setup_xdiff_options(ctxt->opts, &ctxt->xdiff_config, &ctxt->xdiff_params);
}
-static bool diff_delta_file_callback(
+static int diff_delta_file_callback(
diff_context *ctxt, git_diff_delta *delta, size_t idx)
{
float progress;
@@ -419,18 +399,18 @@ static bool diff_delta_file_callback(
if (!ctxt->file_cb)
return 0;
- progress = (float)idx / ctxt->diff->deltas.length;
+ progress = ctxt->diff ? ((float)idx / ctxt->diff->deltas.length) : 1.0f;
if (ctxt->file_cb(ctxt->cb_data, delta, progress) != 0)
- return GIT_EUSER;
+ ctxt->cb_error = GIT_EUSER;
- return 0;
+ return ctxt->cb_error;
}
static void diff_patch_init(
diff_context *ctxt, git_diff_patch *patch)
{
- memset(patch, 0, sizeof(*patch));
+ memset(patch, 0, sizeof(git_diff_patch));
patch->diff = ctxt->diff;
patch->ctxt = ctxt;
@@ -454,7 +434,7 @@ static git_diff_patch *diff_patch_alloc(
git_diff_list_addref(patch->diff);
- GIT_REFCOUNT_INC(&patch);
+ GIT_REFCOUNT_INC(patch);
patch->delta = delta;
patch->flags = GIT_DIFF_PATCH_ALLOCATED;
@@ -836,6 +816,8 @@ static int diff_patch_line_cb(
}
}
+ hunk->line_count++;
+
return 0;
}
@@ -847,7 +829,7 @@ int git_diff_foreach(
git_diff_hunk_fn hunk_cb,
git_diff_data_fn data_cb)
{
- int error;
+ int error = 0;
diff_context ctxt;
size_t idx;
git_diff_patch patch;
@@ -861,14 +843,20 @@ int git_diff_foreach(
git_vector_foreach(&diff->deltas, idx, patch.delta) {
/* check flags against patch status */
- if (diff_delta_should_skip(&ctxt, patch.delta))
+ if (git_diff_delta__should_skip(ctxt.opts, patch.delta))
continue;
- if (!(error = diff_patch_load(&ctxt, &patch)) &&
- !(error = diff_delta_file_callback(&ctxt, patch.delta, idx)))
- error = diff_patch_generate(&ctxt, &patch);
+ if (!(error = diff_patch_load(&ctxt, &patch))) {
- diff_patch_unload(&patch);
+ /* invoke file callback */
+ error = diff_delta_file_callback(&ctxt, patch.delta, idx);
+
+ /* generate diffs and invoke hunk and line callbacks */
+ if (!error)
+ error = diff_patch_generate(&ctxt, &patch);
+
+ diff_patch_unload(&patch);
+ }
if (error < 0)
break;
@@ -899,25 +887,32 @@ static char pick_suffix(int mode)
return ' ';
}
+char git_diff_status_char(git_delta_t status)
+{
+ char code;
+
+ switch (status) {
+ case GIT_DELTA_ADDED: code = 'A'; break;
+ case GIT_DELTA_DELETED: code = 'D'; break;
+ case GIT_DELTA_MODIFIED: code = 'M'; break;
+ case GIT_DELTA_RENAMED: code = 'R'; break;
+ case GIT_DELTA_COPIED: code = 'C'; break;
+ case GIT_DELTA_IGNORED: code = 'I'; break;
+ case GIT_DELTA_UNTRACKED: code = '?'; break;
+ default: code = ' '; break;
+ }
+
+ return code;
+}
+
static int print_compact(void *data, git_diff_delta *delta, float progress)
{
diff_print_info *pi = data;
- char code, old_suffix, new_suffix;
+ char old_suffix, new_suffix, code = git_diff_status_char(delta->status);
GIT_UNUSED(progress);
- switch (delta->status) {
- case GIT_DELTA_ADDED: code = 'A'; break;
- case GIT_DELTA_DELETED: code = 'D'; break;
- case GIT_DELTA_MODIFIED: code = 'M'; break;
- case GIT_DELTA_RENAMED: code = 'R'; break;
- case GIT_DELTA_COPIED: code = 'C'; break;
- case GIT_DELTA_IGNORED: code = 'I'; break;
- case GIT_DELTA_UNTRACKED: code = '?'; break;
- default: code = 0;
- }
-
- if (!code)
+ if (code == ' ')
return 0;
old_suffix = pick_suffix(delta->old_file.mode);
@@ -1288,7 +1283,7 @@ int git_diff_get_patch(
&ctxt, diff, diff->repo, &diff->opts,
NULL, NULL, diff_patch_hunk_cb, diff_patch_line_cb);
- if (diff_delta_should_skip(&ctxt, delta))
+ if (git_diff_delta__should_skip(ctxt.opts, delta))
return 0;
patch = diff_patch_alloc(&ctxt, delta);
diff --git a/tests-clar/diff/diff_helpers.c b/tests-clar/diff/diff_helpers.c
index 1c04359..0c47218 100644
--- a/tests-clar/diff/diff_helpers.c
+++ b/tests-clar/diff/diff_helpers.c
@@ -120,7 +120,7 @@ int diff_foreach_via_iterator(
size_t h, num_h;
cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d));
- cl_assert(delta && patch);
+ cl_assert(delta);
/* call file_cb for this file */
if (file_cb != NULL && file_cb(data, delta, (float)d / num_d) != 0) {
@@ -128,6 +128,12 @@ int diff_foreach_via_iterator(
goto abort;
}
+ /* if there are no changes, then the patch will be NULL */
+ if (!patch) {
+ cl_assert(delta->status == GIT_DELTA_UNMODIFIED || delta->binary == 1);
+ continue;
+ }
+
if (!hunk_cb && !line_cb) {
git_diff_patch_free(patch);
continue;
diff --git a/tests-clar/diff/diffiter.c b/tests-clar/diff/diffiter.c
index 9e33d91..4273b16 100644
--- a/tests-clar/diff/diffiter.c
+++ b/tests-clar/diff/diffiter.c
@@ -256,21 +256,23 @@ void test_diff_diffiter__iterate_all(void)
cl_assert_equal_i(13, exp.files);
cl_assert_equal_i(8, exp.hunks);
- cl_assert_equal_i(13, exp.lines);
+ cl_assert_equal_i(14, exp.lines);
git_diff_list_free(diff);
}
static void iterate_over_patch(git_diff_patch *patch, diff_expects *exp)
{
- size_t h, num_h = git_diff_patch_num_hunks(patch);
+ size_t h, num_h = git_diff_patch_num_hunks(patch), num_l;
exp->files++;
exp->hunks += num_h;
/* let's iterate in reverse, just because we can! */
- for (h = 1; h <= num_h; ++h)
- exp->lines += git_diff_patch_num_lines_in_hunk(patch, num_h - h);
+ for (h = 1, num_l = 0; h <= num_h; ++h)
+ num_l += git_diff_patch_num_lines_in_hunk(patch, num_h - h);
+
+ exp->lines += num_l;
}
#define PATCH_CACHE 5
@@ -338,5 +340,5 @@ void test_diff_diffiter__iterate_randomly_while_saving_state(void)
/* hopefully it all still added up right */
cl_assert_equal_i(13, exp.files);
cl_assert_equal_i(8, exp.hunks);
- cl_assert_equal_i(13, exp.lines);
+ cl_assert_equal_i(14, exp.lines);
}