Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merge pull request #744 from arrbee/fix-filemodes

Fix filemode comparison in diffs
  • Loading branch information...
commit e0b110edb1d0863ad454bfce0b0c3ac1ceef3392 2 parents 80c0375 + ac971ec
Russell Belfer arrbee authored

Showing 33 changed files with 587 additions and 108 deletions. Show diff stats Hide diff stats

  1. +1 3 examples/diff.c
  2. +69 22 include/git2/diff.h
  3. +1 1  include/git2/status.h
  4. +67 37 src/diff.c
  5. +4 1 src/diff.h
  6. +16 8 src/diff_output.c
  7. +7 3 src/path.c
  8. +24 0 tests-clar/clar_helpers.c
  9. +3 0  tests-clar/clar_libgit2.h
  10. +6 1 tests-clar/diff/diff_helpers.c
  11. +1 1  tests-clar/diff/tree.c
  12. +273 31 tests-clar/diff/workdir.c
  13. +1 0  tests-clar/resources/filemodes/.gitted/HEAD
  14. +6 0 tests-clar/resources/filemodes/.gitted/config
  15. +1 0  tests-clar/resources/filemodes/.gitted/description
  16. +24 0 tests-clar/resources/filemodes/.gitted/hooks/commit-msg.sample
  17. BIN  tests-clar/resources/filemodes/.gitted/index
  18. +6 0 tests-clar/resources/filemodes/.gitted/info/exclude
  19. +1 0  tests-clar/resources/filemodes/.gitted/logs/HEAD
  20. +1 0  tests-clar/resources/filemodes/.gitted/logs/refs/heads/master
  21. BIN  tests-clar/resources/filemodes/.gitted/objects/99/62c8453ba6f0cf8dac7c5dcc2fa2897fa9964a
  22. BIN  tests-clar/resources/filemodes/.gitted/objects/a5/c5dd0fc6c313159a69b1d19d7f61a9f978e8f1
  23. BIN  tests-clar/resources/filemodes/.gitted/objects/e7/48d196331bcb20267eaaee4ff3326cb73b8182
  24. +1 0  tests-clar/resources/filemodes/.gitted/refs/heads/master
  25. +1 0  tests-clar/resources/filemodes/exec_off
  26. +1 0  tests-clar/resources/filemodes/exec_off2on_staged
  27. +1 0  tests-clar/resources/filemodes/exec_off2on_workdir
  28. +1 0  tests-clar/resources/filemodes/exec_off_untracked
  29. +1 0  tests-clar/resources/filemodes/exec_on
  30. +1 0  tests-clar/resources/filemodes/exec_on2off_staged
  31. +1 0  tests-clar/resources/filemodes/exec_on2off_workdir
  32. +1 0  tests-clar/resources/filemodes/exec_on_untracked
  33. +66 0 tests-clar/status/worktree.c
4 examples/diff.c
@@ -185,9 +185,7 @@ int main(int argc, char *argv[])
185 185
186 186 /* open repo */
187 187
188   - check(git_repository_discover(path, sizeof(path), dir, 0, "/"),
189   - "Could not discover repository");
190   - check(git_repository_open(&repo, path),
  188 + check(git_repository_open_ext(&repo, dir, 0, NULL),
191 189 "Could not open repository");
192 190
193 191 if (treeish1)
91 include/git2/diff.h
@@ -29,6 +29,10 @@
29 29 */
30 30 GIT_BEGIN_DECL
31 31
  32 +/**
  33 + * Flags for diff options. A combination of these flags can be passed
  34 + * in via the `flags` value in the `git_diff_options`.
  35 + */
32 36 enum {
33 37 GIT_DIFF_NORMAL = 0,
34 38 GIT_DIFF_REVERSE = (1 << 0),
@@ -160,15 +164,16 @@ typedef int (*git_diff_hunk_fn)(
160 164 * the file or hunk headers.
161 165 */
162 166 enum {
163   - /* these values will be sent to `git_diff_data_fn` along with the line */
  167 + /* These values will be sent to `git_diff_data_fn` along with the line */
164 168 GIT_DIFF_LINE_CONTEXT = ' ',
165 169 GIT_DIFF_LINE_ADDITION = '+',
166 170 GIT_DIFF_LINE_DELETION = '-',
167   - GIT_DIFF_LINE_ADD_EOFNL = '\n', /**< LF was added at end of file */
  171 + GIT_DIFF_LINE_ADD_EOFNL = '\n', /**< DEPRECATED */
168 172 GIT_DIFF_LINE_DEL_EOFNL = '\0', /**< LF was removed at end of file */
169   - /* these values will only be sent to a `git_diff_data_fn` when the content
170   - * of a diff is being formatted (eg. through git_diff_print_patch() or
171   - * git_diff_print_compact(), for instance).
  173 +
  174 + /* The following values will only be sent to a `git_diff_data_fn` when
  175 + * the content of a diff is being formatted (eg. through
  176 + * git_diff_print_patch() or git_diff_print_compact(), for instance).
172 177 */
173 178 GIT_DIFF_LINE_FILE_HDR = 'F',
174 179 GIT_DIFF_LINE_HUNK_HDR = 'H',
@@ -206,6 +211,8 @@ GIT_EXTERN(void) git_diff_list_free(git_diff_list *diff);
206 211 /**
207 212 * Compute a difference between two tree objects.
208 213 *
  214 + * This is equivalent to `git diff <treeish> <treeish>`
  215 + *
209 216 * @param repo The repository containing the trees.
210 217 * @param opts Structure with options to influence diff or NULL for defaults.
211 218 * @param old_tree A git_tree object to diff from.
@@ -222,6 +229,9 @@ GIT_EXTERN(int) git_diff_tree_to_tree(
222 229 /**
223 230 * Compute a difference between a tree and the index.
224 231 *
  232 + * This is equivalent to `git diff --cached <treeish>` or if you pass
  233 + * the HEAD tree, then like `git diff --cached`.
  234 + *
225 235 * @param repo The repository containing the tree and index.
226 236 * @param opts Structure with options to influence diff or NULL for defaults.
227 237 * @param old_tree A git_tree object to diff from.
@@ -236,6 +246,11 @@ GIT_EXTERN(int) git_diff_index_to_tree(
236 246 /**
237 247 * Compute a difference between the working directory and the index.
238 248 *
  249 + * This matches the `git diff` command. See the note below on
  250 + * `git_diff_workdir_to_tree` for a discussion of the difference between
  251 + * `git diff` and `git diff HEAD` and how to emulate a `git diff <treeish>`
  252 + * using libgit2.
  253 + *
239 254 * @param repo The repository.
240 255 * @param opts Structure with options to influence diff or NULL for defaults.
241 256 * @param diff A pointer to a git_diff_list pointer that will be allocated.
@@ -248,14 +263,24 @@ GIT_EXTERN(int) git_diff_workdir_to_index(
248 263 /**
249 264 * Compute a difference between the working directory and a tree.
250 265 *
251   - * This returns strictly the differences between the tree and the
252   - * files contained in the working directory, regardless of the state
253   - * of files in the index. There is no direct equivalent in C git.
  266 + * This is *NOT* the same as `git diff <treeish>`. Running `git diff HEAD`
  267 + * or the like actually uses information from the index, along with the tree
  268 + * and workdir dir info.
254 269 *
255   - * This is *NOT* the same as 'git diff HEAD' or 'git diff <SHA>'. Those
256   - * commands diff the tree, the index, and the workdir. To emulate those
257   - * functions, call `git_diff_index_to_tree` and `git_diff_workdir_to_index`,
258   - * then call `git_diff_merge` on the results.
  270 + * This function returns strictly the differences between the tree and the
  271 + * files contained in the working directory, regardless of the state of
  272 + * files in the index. It may come as a surprise, but there is no direct
  273 + * equivalent in core git.
  274 + *
  275 + * To emulate `git diff <treeish>`, you should call both
  276 + * `git_diff_index_to_tree` and `git_diff_workdir_to_index`, then call
  277 + * `git_diff_merge` on the results. That will yield a `git_diff_list` that
  278 + * matches the git output.
  279 + *
  280 + * If this seems confusing, take the case of a file with a staged deletion
  281 + * where the file has then been put back into the working dir and modified.
  282 + * The tree-to-workdir diff for that file is 'modified', but core git would
  283 + * show status 'deleted' since there is a pending deletion in the index.
259 284 *
260 285 * @param repo The repository containing the tree.
261 286 * @param opts Structure with options to influence diff or NULL for defaults.
@@ -298,10 +323,23 @@ GIT_EXTERN(int) git_diff_merge(
298 323 /**
299 324 * Iterate over a diff list issuing callbacks.
300 325 *
301   - * If the hunk and/or line callbacks are not NULL, then this will calculate
302   - * text diffs for all files it thinks are not binary. If those are both
303   - * NULL, then this will not bother with the text diffs, so it can be
304   - * efficient.
  326 + * This will iterate through all of the files described in a diff. You
  327 + * should provide a file callback to learn about each file.
  328 + *
  329 + * The "hunk" and "line" callbacks are optional, and the text diff of the
  330 + * files will only be calculated if they are not NULL. Of course, these
  331 + * callbacks will not be invoked for binary files on the diff list or for
  332 + * files whose only changed is a file mode change.
  333 + *
  334 + * @param diff A git_diff_list generated by one of the above functions.
  335 + * @param cb_data Reference pointer that will be passed to your callbacks.
  336 + * @param file_cb Callback function to make per file in the diff.
  337 + * @param hunk_cb Optional callback to make per hunk of text diff. This
  338 + * callback is called to describe a range of lines in the
  339 + * diff. It will not be issued for binary files.
  340 + * @param line_cb Optional callback to make per line of diff text. This
  341 + * same callback will be made for context lines, added, and
  342 + * removed lines, and even for a deleted trailing newline.
305 343 */
306 344 GIT_EXTERN(int) git_diff_foreach(
307 345 git_diff_list *diff,
@@ -322,6 +360,14 @@ GIT_EXTERN(int) git_diff_print_compact(
322 360 * Iterate over a diff generating text output like "git diff".
323 361 *
324 362 * This is a super easy way to generate a patch from a diff.
  363 + *
  364 + * @param diff A git_diff_list generated by one of the above functions.
  365 + * @param cb_data Reference pointer that will be passed to your callbacks.
  366 + * @param print_cb Callback function to output lines of the diff. This
  367 + * same function will be called for file headers, hunk
  368 + * headers, and diff lines. Fortunately, you can probably
  369 + * use various GIT_DIFF_LINE constants to determine what
  370 + * text you are given.
325 371 */
326 372 GIT_EXTERN(int) git_diff_print_patch(
327 373 git_diff_list *diff,
@@ -338,13 +384,14 @@ GIT_EXTERN(int) git_diff_print_patch(
338 384 /**
339 385 * Directly run a text diff on two blobs.
340 386 *
341   - * Compared to a file, a blob lacks some contextual information. As such, the
342   - * `git_diff_file` parameters of the callbacks will be filled accordingly to the following:
343   - * `mode` will be set to 0, `path` will be set to NULL. When dealing with a NULL blob, `oid`
344   - * will be set to 0.
  387 + * Compared to a file, a blob lacks some contextual information. As such,
  388 + * the `git_diff_file` parameters of the callbacks will be filled
  389 + * accordingly to the following: `mode` will be set to 0, `path` will be set
  390 + * to NULL. When dealing with a NULL blob, `oid` will be set to 0.
345 391 *
346   - * When at least one of the blobs being dealt with is binary, the `git_diff_delta` binary
347   - * attribute will be set to 1 and no call to the hunk_cb nor line_cb will be made.
  392 + * When at least one of the blobs being dealt with is binary, the
  393 + * `git_diff_delta` binary attribute will be set to 1 and no call to the
  394 + * hunk_cb nor line_cb will be made.
348 395 */
349 396 GIT_EXTERN(int) git_diff_blobs(
350 397 git_blob *old_blob,
2  include/git2/status.h
@@ -102,7 +102,7 @@ enum {
102 102 GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1 << 0),
103 103 GIT_STATUS_OPT_INCLUDE_IGNORED = (1 << 1),
104 104 GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1 << 2),
105   - GIT_STATUS_OPT_EXCLUDE_SUBMODULED = (1 << 3),
  105 + GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1 << 3),
106 106 GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1 << 4),
107 107 };
108 108
104 src/diff.c
@@ -130,37 +130,50 @@ static git_diff_delta *diff_delta__dup(
130 130 static git_diff_delta *diff_delta__merge_like_cgit(
131 131 const git_diff_delta *a, const git_diff_delta *b, git_pool *pool)
132 132 {
133   - git_diff_delta *dup = diff_delta__dup(a, pool);
134   - if (!dup)
135   - return NULL;
136   -
137   - if (git_oid_cmp(&dup->new_file.oid, &b->new_file.oid) == 0)
138   - return dup;
139   -
140   - git_oid_cpy(&dup->new_file.oid, &b->new_file.oid);
141   -
142   - dup->new_file.mode = b->new_file.mode;
143   - dup->new_file.size = b->new_file.size;
144   - dup->new_file.flags = b->new_file.flags;
  133 + git_diff_delta *dup;
145 134
146 135 /* Emulate C git for merging two diffs (a la 'git diff <sha>').
147 136 *
148 137 * When C git does a diff between the work dir and a tree, it actually
149 138 * diffs with the index but uses the workdir contents. This emulates
150 139 * those choices so we can emulate the type of diff.
  140 + *
  141 + * We have three file descriptions here, let's call them:
  142 + * f1 = a->old_file
  143 + * f2 = a->new_file AND b->old_file
  144 + * f3 = b->new_file
151 145 */
152   - if (git_oid_cmp(&dup->old_file.oid, &dup->new_file.oid) == 0) {
153   - if (dup->status == GIT_DELTA_DELETED)
154   - /* preserve pending delete info */;
155   - else if (b->status == GIT_DELTA_UNTRACKED ||
156   - b->status == GIT_DELTA_IGNORED)
157   - dup->status = b->status;
158   - else
  146 +
  147 + /* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */
  148 + if (b->status == GIT_DELTA_UNMODIFIED || a->status == GIT_DELTA_DELETED)
  149 + return diff_delta__dup(a, pool);
  150 +
  151 + /* otherwise, base this diff on the 'b' diff */
  152 + if ((dup = diff_delta__dup(b, pool)) == NULL)
  153 + return NULL;
  154 +
  155 + /* If 'a' status is uninteresting, then we're done */
  156 + if (a->status == GIT_DELTA_UNMODIFIED)
  157 + return dup;
  158 +
  159 + assert(a->status != GIT_DELTA_UNMODIFIED);
  160 + assert(b->status != GIT_DELTA_UNMODIFIED);
  161 +
  162 + /* A cgit exception is that the diff of a file that is only in the
  163 + * index (i.e. not in HEAD nor workdir) is given as empty.
  164 + */
  165 + if (dup->status == GIT_DELTA_DELETED) {
  166 + if (a->status == GIT_DELTA_ADDED)
159 167 dup->status = GIT_DELTA_UNMODIFIED;
  168 + /* else don't overwrite DELETE status */
  169 + } else {
  170 + dup->status = a->status;
160 171 }
161   - else if (dup->status == GIT_DELTA_UNMODIFIED ||
162   - b->status == GIT_DELTA_DELETED)
163   - dup->status = b->status;
  172 +
  173 + git_oid_cpy(&dup->old_file.oid, &a->old_file.oid);
  174 + dup->old_file.mode = a->old_file.mode;
  175 + dup->old_file.size = a->old_file.size;
  176 + dup->old_file.flags = a->old_file.flags;
164 177
165 178 return dup;
166 179 }
@@ -214,7 +227,9 @@ static int diff_delta__from_two(
214 227 git_diff_list *diff,
215 228 git_delta_t status,
216 229 const git_index_entry *old_entry,
  230 + uint32_t old_mode,
217 231 const git_index_entry *new_entry,
  232 + uint32_t new_mode,
218 233 git_oid *new_oid)
219 234 {
220 235 git_diff_delta *delta;
@@ -224,19 +239,22 @@ static int diff_delta__from_two(
224 239 return 0;
225 240
226 241 if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) {
227   - const git_index_entry *temp = old_entry;
  242 + uint32_t temp_mode = old_mode;
  243 + const git_index_entry *temp_entry = old_entry;
228 244 old_entry = new_entry;
229   - new_entry = temp;
  245 + new_entry = temp_entry;
  246 + old_mode = new_mode;
  247 + new_mode = temp_mode;
230 248 }
231 249
232 250 delta = diff_delta__alloc(diff, status, old_entry->path);
233 251 GITERR_CHECK_ALLOC(delta);
234 252
235   - delta->old_file.mode = old_entry->mode;
  253 + delta->old_file.mode = old_mode;
236 254 git_oid_cpy(&delta->old_file.oid, &old_entry->oid);
237 255 delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID;
238 256
239   - delta->new_file.mode = new_entry->mode;
  257 + delta->new_file.mode = new_mode;
240 258 git_oid_cpy(&delta->new_file.oid, new_oid ? new_oid : &new_entry->oid);
241 259 if (new_oid || !git_oid_iszero(&new_entry->oid))
242 260 delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID;
@@ -300,7 +318,7 @@ static git_diff_list *git_diff_list_alloc(
300 318 if (config_bool(cfg, "core.ignorestat", 0))
301 319 diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_ASSUME_UNCHANGED;
302 320 if (config_bool(cfg, "core.filemode", 1))
303   - diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_EXEC_BIT;
  321 + diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_MODE_BITS;
304 322 if (config_bool(cfg, "core.trustctime", 1))
305 323 diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME;
306 324 /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */
@@ -419,7 +437,7 @@ static int oid_for_workdir_item(
419 437 return result;
420 438 }
421 439
422   -#define EXEC_BIT_MASK 0000111
  440 +#define MODE_BITS_MASK 0000777
423 441
424 442 static int maybe_modified(
425 443 git_iterator *old_iter,
@@ -443,13 +461,13 @@ static int maybe_modified(
443 461 !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
444 462 nmode = GIT_MODE_TYPE(omode) | (nmode & GIT_MODE_PERMS_MASK);
445 463
446   - /* on platforms with no execmode, clear exec bit from comparisons */
447   - if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_EXEC_BIT)) {
448   - omode = omode & ~EXEC_BIT_MASK;
449   - nmode = nmode & ~EXEC_BIT_MASK;
450   - }
  464 + /* on platforms with no execmode, just preserve old mode */
  465 + if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) &&
  466 + (nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) &&
  467 + new_iter->type == GIT_ITERATOR_WORKDIR)
  468 + nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK);
451 469
452   - /* support "assume unchanged" (badly, b/c we still stat everything) */
  470 + /* support "assume unchanged" (poorly, b/c we still stat everything) */
453 471 if ((diff->diffcaps & GIT_DIFFCAPS_ASSUME_UNCHANGED) != 0)
454 472 status = (oitem->flags_extended & GIT_IDXENTRY_INTENT_TO_ADD) ?
455 473 GIT_DELTA_MODIFIED : GIT_DELTA_UNMODIFIED;
@@ -471,8 +489,13 @@ static int maybe_modified(
471 489 omode == nmode)
472 490 status = GIT_DELTA_UNMODIFIED;
473 491
474   - /* if we have a workdir item with an unknown oid, check deeper */
475   - else if (git_oid_iszero(&nitem->oid) && new_iter->type == GIT_ITERATOR_WORKDIR) {
  492 + /* if modes match and we have an unknown OID and a workdir iterator,
  493 + * then check deeper for matching
  494 + */
  495 + else if (omode == nmode &&
  496 + git_oid_iszero(&nitem->oid) &&
  497 + new_iter->type == GIT_ITERATOR_WORKDIR)
  498 + {
476 499 /* TODO: add check against index file st_mtime to avoid racy-git */
477 500
478 501 /* if they files look exactly alike, then we'll assume the same */
@@ -517,7 +540,8 @@ static int maybe_modified(
517 540 use_noid = &noid;
518 541 }
519 542
520   - return diff_delta__from_two(diff, status, oitem, nitem, use_noid);
  543 + return diff_delta__from_two(
  544 + diff, status, oitem, omode, nitem, nmode, use_noid);
521 545 }
522 546
523 547 static int diff_from_iterators(
@@ -772,6 +796,12 @@ int git_diff_merge(
772 796 git_vector_swap(&onto->deltas, &onto_new);
773 797 git_pool_swap(&onto->pool, &onto_pool);
774 798 onto->new_src = from->new_src;
  799 +
  800 + /* prefix strings also come from old pool, so recreate those.*/
  801 + onto->opts.old_prefix =
  802 + git_pool_strdup(&onto->pool, onto->opts.old_prefix);
  803 + onto->opts.new_prefix =
  804 + git_pool_strdup(&onto->pool, onto->opts.new_prefix);
775 805 }
776 806
777 807 git_vector_foreach(&onto_new, i, delta)
5 src/diff.h
@@ -20,7 +20,7 @@
20 20 enum {
21 21 GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */
22 22 GIT_DIFFCAPS_ASSUME_UNCHANGED = (1 << 1), /* use stat? */
23   - GIT_DIFFCAPS_TRUST_EXEC_BIT = (1 << 2), /* use st_mode exec bit? */
  23 + GIT_DIFFCAPS_TRUST_MODE_BITS = (1 << 2), /* use st_mode? */
24 24 GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */
25 25 GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */
26 26 };
@@ -36,5 +36,8 @@ struct git_diff_list {
36 36 uint32_t diffcaps;
37 37 };
38 38
  39 +extern void git_diff__cleanup_modes(
  40 + uint32_t diffcaps, uint32_t *omode, uint32_t *nmode);
  41 +
39 42 #endif
40 43
24 src/diff_output.c
@@ -83,12 +83,13 @@ static int diff_output_cb(void *priv, mmbuffer_t *bufs, int len)
83 83 info->cb_data, info->delta, &info->range, origin, bufs[1].ptr, bufs[1].size) < 0)
84 84 return -1;
85 85
86   - /* deal with adding and removing newline at EOF */
  86 + /* This should only happen if we are adding a line that does not
  87 + * have a newline at the end and the old code did. In that case,
  88 + * we have a ADD with a DEL_EOFNL as a pair.
  89 + */
87 90 if (len == 3) {
88   - if (origin == GIT_DIFF_LINE_ADDITION)
89   - origin = GIT_DIFF_LINE_ADD_EOFNL;
90   - else
91   - origin = GIT_DIFF_LINE_DEL_EOFNL;
  91 + origin = (origin == GIT_DIFF_LINE_ADDITION) ?
  92 + GIT_DIFF_LINE_DEL_EOFNL : GIT_DIFF_LINE_ADD_EOFNL;
92 93
93 94 return info->line_cb(
94 95 info->cb_data, info->delta, &info->range, origin, bufs[2].ptr, bufs[2].size);
@@ -359,7 +360,7 @@ int git_diff_foreach(
359 360
360 361 /* map files */
361 362 if (delta->binary != 1 &&
362   - (hunk_cb || line_cb) &&
  363 + (hunk_cb || line_cb || git_oid_iszero(&delta->old_file.oid)) &&
363 364 (delta->status == GIT_DELTA_DELETED ||
364 365 delta->status == GIT_DELTA_MODIFIED))
365 366 {
@@ -397,7 +398,9 @@ int git_diff_foreach(
397 398 /* since we did not have the definitive oid, we may have
398 399 * incorrect status and need to skip this item.
399 400 */
400   - if (git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid) == 0) {
  401 + if (delta->old_file.mode == delta->new_file.mode &&
  402 + !git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid))
  403 + {
401 404 delta->status = GIT_DELTA_UNMODIFIED;
402 405 if ((diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
403 406 goto cleanup;
@@ -420,7 +423,8 @@ int git_diff_foreach(
420 423 */
421 424
422 425 if (file_cb != NULL) {
423   - error = file_cb(data, delta, (float)info.index / diff->deltas.length);
  426 + error = file_cb(
  427 + data, delta, (float)info.index / diff->deltas.length);
424 428 if (error < 0)
425 429 goto cleanup;
426 430 }
@@ -433,6 +437,10 @@ int git_diff_foreach(
433 437 if (!old_data.len && !new_data.len)
434 438 goto cleanup;
435 439
  440 + /* nothing to do if only diff was a mode change */
  441 + if (!git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid))
  442 + goto cleanup;
  443 +
436 444 assert(hunk_cb || line_cb);
437 445
438 446 info.delta = delta;
10 src/path.c
@@ -17,6 +17,10 @@
17 17 #include <stdio.h>
18 18 #include <ctype.h>
19 19
  20 +#ifdef GIT_WIN32
  21 +#define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':')
  22 +#endif
  23 +
20 24 /*
21 25 * Based on the Android implementation, BSD licensed.
22 26 * Check http://android.git.kernel.org/
@@ -105,7 +109,7 @@ int git_path_dirname_r(git_buf *buffer, const char *path)
105 109 /* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return
106 110 'C:/' here */
107 111
108   - if (len == 2 && isalpha(path[0]) && path[1] == ':') {
  112 + if (len == 2 && LOOKS_LIKE_DRIVE_PREFIX(path)) {
109 113 len = 3;
110 114 goto Exit;
111 115 }
@@ -170,7 +174,7 @@ int git_path_root(const char *path)
170 174
171 175 #ifdef GIT_WIN32
172 176 /* Does the root of the path look like a windows drive ? */
173   - if (isalpha(path[0]) && (path[1] == ':'))
  177 + if (LOOKS_LIKE_DRIVE_PREFIX(path))
174 178 offset += 2;
175 179
176 180 /* Are we dealing with a windows network path? */
@@ -210,7 +214,7 @@ int git_path_prettify(git_buf *path_out, const char *path, const char *base)
210 214 giterr_set(GITERR_OS, "Failed to resolve path '%s'", path);
211 215
212 216 git_buf_clear(path_out);
213   -
  217 +
214 218 return error;
215 219 }
216 220
24 tests-clar/clar_helpers.c
@@ -155,3 +155,27 @@ void cl_git_sandbox_cleanup(void)
155 155 }
156 156 }
157 157
  158 +bool cl_toggle_filemode(const char *filename)
  159 +{
  160 + struct stat st1, st2;
  161 +
  162 + cl_must_pass(p_stat(filename, &st1));
  163 + cl_must_pass(p_chmod(filename, st1.st_mode ^ 0100));
  164 + cl_must_pass(p_stat(filename, &st2));
  165 +
  166 + return (st1.st_mode != st2.st_mode);
  167 +}
  168 +
  169 +bool cl_is_chmod_supported(void)
  170 +{
  171 + static int _is_supported = -1;
  172 +
  173 + if (_is_supported < 0) {
  174 + cl_git_mkfile("filemode.t", "Test if filemode can be modified");
  175 + _is_supported = cl_toggle_filemode("filemode.t");
  176 + cl_must_pass(p_unlink("filemode.t"));
  177 + }
  178 +
  179 + return _is_supported;
  180 +}
  181 +
3  tests-clar/clar_libgit2.h
@@ -40,6 +40,9 @@ void cl_git_append2file(const char *filename, const char *new_content);
40 40 void cl_git_rewritefile(const char *filename, const char *new_content);
41 41 void cl_git_write2file(const char *filename, const char *new_content, int mode);
42 42
  43 +bool cl_toggle_filemode(const char *filename);
  44 +bool cl_is_chmod_supported(void);
  45 +
43 46 /* Environment wrappers */
44 47 char *cl_getenv(const char *name);
45 48 int cl_setenv(const char *name, const char *value);
7 tests-clar/diff/diff_helpers.c
@@ -85,11 +85,16 @@ int diff_line_fn(
85 85 e->line_ctxt++;
86 86 break;
87 87 case GIT_DIFF_LINE_ADDITION:
88   - case GIT_DIFF_LINE_ADD_EOFNL:
89 88 e->line_adds++;
90 89 break;
  90 + case GIT_DIFF_LINE_ADD_EOFNL:
  91 + assert(0);
  92 + break;
91 93 case GIT_DIFF_LINE_DELETION:
  94 + e->line_dels++;
  95 + break;
92 96 case GIT_DIFF_LINE_DEL_EOFNL:
  97 + /* technically not a line delete, but we'll count it as such */
93 98 e->line_dels++;
94 99 break;
95 100 default:
2  tests-clar/diff/tree.c
@@ -116,7 +116,7 @@ void test_diff_tree__options(void)
116 116 { 5, 3, 0, 2, 0, 0, 0, 4, 0, 0, 51, 2, 46, 3 },
117 117 { 5, 3, 0, 2, 0, 0, 0, 4, 0, 0, 53, 4, 46, 3 },
118 118 { 5, 0, 3, 2, 0, 0, 0, 4, 0, 0, 52, 3, 3, 46 },
119   - { 5, 3, 0, 2, 0, 0, 0, 5, 0, 0, 54, 3, 48, 3 },
  119 + { 5, 3, 0, 2, 0, 0, 0, 5, 0, 0, 54, 3, 47, 4 },
120 120 /* c vs d tests */
121 121 { 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 22, 9, 10, 3 },
122 122 { 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 19, 12, 7, 0 },
304 tests-clar/diff/workdir.c
@@ -5,7 +5,6 @@ static git_repository *g_repo = NULL;
5 5
6 6 void test_diff_workdir__initialize(void)
7 7 {
8   - g_repo = cl_git_sandbox_init("status");
9 8 }
10 9
11 10 void test_diff_workdir__cleanup(void)
@@ -19,6 +18,8 @@ void test_diff_workdir__to_index(void)
19 18 git_diff_list *diff = NULL;
20 19 diff_expects exp;
21 20
  21 + g_repo = cl_git_sandbox_init("status");
  22 +
22 23 opts.context_lines = 3;
23 24 opts.interhunk_lines = 1;
24 25 opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
@@ -59,13 +60,17 @@ void test_diff_workdir__to_tree(void)
59 60 /* grabbed a couple of commit oids from the history of the attr repo */
60 61 const char *a_commit = "26a125ee1bf"; /* the current HEAD */
61 62 const char *b_commit = "0017bd4ab1ec3"; /* the start */
62   - git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit);
63   - git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit);
  63 + git_tree *a, *b;
64 64 git_diff_options opts = {0};
65 65 git_diff_list *diff = NULL;
66 66 git_diff_list *diff2 = NULL;
67 67 diff_expects exp;
68 68
  69 + g_repo = cl_git_sandbox_init("status");
  70 +
  71 + a = resolve_commit_oid_to_tree(g_repo, a_commit);
  72 + b = resolve_commit_oid_to_tree(g_repo, b_commit);
  73 +
69 74 opts.context_lines = 3;
70 75 opts.interhunk_lines = 1;
71 76 opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
@@ -87,12 +92,12 @@ void test_diff_workdir__to_tree(void)
87 92 cl_git_pass(git_diff_foreach(
88 93 diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
89 94
90   - cl_assert(exp.files == 14);
91   - cl_assert(exp.file_adds == 0);
92   - cl_assert(exp.file_dels == 4);
93   - cl_assert(exp.file_mods == 4);
94   - cl_assert(exp.file_ignored == 1);
95   - cl_assert(exp.file_untracked == 5);
  95 + cl_assert_equal_i(14, exp.files);
  96 + cl_assert_equal_i(0, exp.file_adds);
  97 + cl_assert_equal_i(4, exp.file_dels);
  98 + cl_assert_equal_i(4, exp.file_mods);
  99 + cl_assert_equal_i(1, exp.file_ignored);
  100 + cl_assert_equal_i(5, exp.file_untracked);
96 101
97 102 /* Since there is no git diff equivalent, let's just assume that the
98 103 * text diffs produced by git_diff_foreach are accurate here. We will
@@ -115,19 +120,19 @@ void test_diff_workdir__to_tree(void)
115 120 cl_git_pass(git_diff_foreach(
116 121 diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
117 122
118   - cl_assert(exp.files == 15);
119   - cl_assert(exp.file_adds == 2);
120   - cl_assert(exp.file_dels == 5);
121   - cl_assert(exp.file_mods == 4);
122   - cl_assert(exp.file_ignored == 1);
123   - cl_assert(exp.file_untracked == 3);
  123 + cl_assert_equal_i(15, exp.files);
  124 + cl_assert_equal_i(2, exp.file_adds);
  125 + cl_assert_equal_i(5, exp.file_dels);
  126 + cl_assert_equal_i(4, exp.file_mods);
  127 + cl_assert_equal_i(1, exp.file_ignored);
  128 + cl_assert_equal_i(3, exp.file_untracked);
124 129
125   - cl_assert(exp.hunks == 11);
  130 + cl_assert_equal_i(11, exp.hunks);
126 131
127   - cl_assert(exp.lines == 17);
128   - cl_assert(exp.line_ctxt == 4);
129   - cl_assert(exp.line_adds == 8);
130   - cl_assert(exp.line_dels == 5);
  132 + cl_assert_equal_i(17, exp.lines);
  133 + cl_assert_equal_i(4, exp.line_ctxt);
  134 + cl_assert_equal_i(8, exp.line_adds);
  135 + cl_assert_equal_i(5, exp.line_dels);
131 136
132 137 git_diff_list_free(diff);
133 138 diff = NULL;
@@ -144,19 +149,19 @@ void test_diff_workdir__to_tree(void)
144 149 cl_git_pass(git_diff_foreach(
145 150 diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
146 151
147   - cl_assert(exp.files == 16);
148   - cl_assert(exp.file_adds == 5);
149   - cl_assert(exp.file_dels == 4);
150   - cl_assert(exp.file_mods == 3);
151   - cl_assert(exp.file_ignored == 1);
152   - cl_assert(exp.file_untracked == 3);
  152 + cl_assert_equal_i(16, exp.files);
  153 + cl_assert_equal_i(5, exp.file_adds);
  154 + cl_assert_equal_i(4, exp.file_dels);
  155 + cl_assert_equal_i(3, exp.file_mods);
  156 + cl_assert_equal_i(1, exp.file_ignored);
  157 + cl_assert_equal_i(3, exp.file_untracked);
153 158
154   - cl_assert(exp.hunks == 12);
  159 + cl_assert_equal_i(12, exp.hunks);
155 160
156   - cl_assert(exp.lines == 19);
157   - cl_assert(exp.line_ctxt == 3);
158   - cl_assert(exp.line_adds == 12);
159   - cl_assert(exp.line_dels == 4);
  161 + cl_assert_equal_i(19, exp.lines);
  162 + cl_assert_equal_i(3, exp.line_ctxt);
  163 + cl_assert_equal_i(12, exp.line_adds);
  164 + cl_assert_equal_i(4, exp.line_dels);
160 165
161 166 git_diff_list_free(diff);
162 167
@@ -171,6 +176,8 @@ void test_diff_workdir__to_index_with_pathspec(void)
171 176 diff_expects exp;
172 177 char *pathspec = NULL;
173 178
  179 + g_repo = cl_git_sandbox_init("status");
  180 +
174 181 opts.context_lines = 3;
175 182 opts.interhunk_lines = 1;
176 183 opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
@@ -237,6 +244,241 @@ void test_diff_workdir__to_index_with_pathspec(void)
237 244 git_diff_list_free(diff);
238 245 }
239 246
  247 +void test_diff_workdir__filemode_changes(void)
  248 +{
  249 + git_config *cfg;
  250 + git_diff_list *diff = NULL;
  251 + diff_expects exp;
  252 +
  253 + if (!cl_is_chmod_supported())
  254 + return;
  255 +
  256 + g_repo = cl_git_sandbox_init("issue_592");
  257 +
  258 + cl_git_pass(git_repository_config(&cfg, g_repo));
  259 + cl_git_pass(git_config_set_bool(cfg, "core.filemode", true));
  260 +
  261 + /* test once with no mods */
  262 +
  263 + cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff));
  264 +
  265 + memset(&exp, 0, sizeof(exp));
  266 + cl_git_pass(git_diff_foreach(
  267 + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
  268 +
  269 + cl_assert_equal_i(0, exp.files);
  270 + cl_assert_equal_i(0, exp.file_mods);
  271 + cl_assert_equal_i(0, exp.hunks);
  272 +
  273 + git_diff_list_free(diff);
  274 +
  275 + /* chmod file and test again */
  276 +
  277 + cl_assert(cl_toggle_filemode("issue_592/a.txt"));
  278 +
  279 + cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff));
  280 +
  281 + memset(&exp, 0, sizeof(exp));
  282 + cl_git_pass(git_diff_foreach(
  283 + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
  284 +
  285 + cl_assert_equal_i(1, exp.files);
  286 + cl_assert_equal_i(1, exp.file_mods);
  287 + cl_assert_equal_i(0, exp.hunks);
  288 +
  289 + git_diff_list_free(diff);
  290 +
  291 + cl_assert(cl_toggle_filemode("issue_592/a.txt"));
  292 + git_config_free(cfg);
  293 +}
  294 +
  295 +void test_diff_workdir__filemode_changes_with_filemode_false(void)
  296 +{
  297 + git_config *cfg;
  298 + git_diff_list *diff = NULL;
  299 + diff_expects exp;
  300 +
  301 + if (!cl_is_chmod_supported())
  302 + return;
  303 +
  304 + g_repo = cl_git_sandbox_init("issue_592");
  305 +
  306 + cl_git_pass(git_repository_config(&cfg, g_repo));
  307 + cl_git_pass(git_config_set_bool(cfg, "core.filemode", false));
  308 +
  309 + /* test once with no mods */
  310 +
  311 + cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff));
  312 +
  313 + memset(&exp, 0, sizeof(exp));
  314 + cl_git_pass(git_diff_foreach(
  315 + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
  316 +
  317 + cl_assert_equal_i(0, exp.files);
  318 + cl_assert_equal_i(0, exp.file_mods);
  319 + cl_assert_equal_i(0, exp.hunks);
  320 +
  321 + git_diff_list_free(diff);
  322 +
  323 + /* chmod file and test again */
  324 +
  325 + cl_assert(cl_toggle_filemode("issue_592/a.txt"));
  326 +
  327 + cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff));
  328 +
  329 + memset(&exp, 0, sizeof(exp));
  330 + cl_git_pass(git_diff_foreach(
  331 + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
  332 +
  333 + cl_assert_equal_i(0, exp.files);
  334 + cl_assert_equal_i(0, exp.file_mods);
  335 + cl_assert_equal_i(0, exp.hunks);
  336 +
  337 + git_diff_list_free(diff);
  338 +
  339 + cl_assert(cl_toggle_filemode("issue_592/a.txt"));
  340 + git_config_free(cfg);
  341 +}
  342 +
  343 +void test_diff_workdir__head_index_and_workdir_all_differ(void)
  344 +{
  345 + git_diff_options opts = {0};
  346 + git_diff_list *diff_i2t = NULL, *diff_w2i = NULL;
  347 + diff_expects exp;
  348 + char *pathspec = "staged_changes_modified_file";
  349 + git_tree *tree;
  350 +
  351 + /* For this file,
  352 + * - head->index diff has 1 line of context, 1 line of diff
  353 + * - index->workdir diff has 2 lines of context, 1 line of diff
  354 + * but
  355 + * - head->workdir diff has 1 line of context, 2 lines of diff
  356 + * Let's make sure the right one is returned from each fn.
  357 + */
  358 +
  359 + g_repo = cl_git_sandbox_init("status");
  360 +
  361 + tree = resolve_commit_oid_to_tree(g_repo, "26a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f");
  362 +
  363 + opts.pathspec.strings = &pathspec;
  364 + opts.pathspec.count = 1;
  365 +
  366 + cl_git_pass(git_diff_index_to_tree(g_repo, &opts, tree, &diff_i2t));
  367 + cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff_w2i));
  368 +
  369 + memset(&exp, 0, sizeof(exp));
  370 + cl_git_pass(git_diff_foreach(
  371 + diff_i2t, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
  372 + cl_assert_equal_i(1, exp.files);
  373 + cl_assert_equal_i(0, exp.file_adds);
  374 + cl_assert_equal_i(0, exp.file_dels);
  375 + cl_assert_equal_i(1, exp.file_mods);
  376 + cl_assert_equal_i(1, exp.hunks);
  377 + cl_assert_equal_i(2, exp.lines);
  378 + cl_assert_equal_i(1, exp.line_ctxt);
  379 + cl_assert_equal_i(1, exp.line_adds);
  380 + cl_assert_equal_i(0, exp.line_dels);
  381 +
  382 + memset(&exp, 0, sizeof(exp));
  383 + cl_git_pass(git_diff_foreach(
  384 + diff_w2i, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
  385 + cl_assert_equal_i(1, exp.files);
  386 + cl_assert_equal_i(0, exp.file_adds);
  387 + cl_assert_equal_i(0, exp.file_dels);
  388 + cl_assert_equal_i(1, exp.file_mods);
  389 + cl_assert_equal_i(1, exp.hunks);
  390 + cl_assert_equal_i(3, exp.lines);
  391 + cl_assert_equal_i(2, exp.line_ctxt);
  392 + cl_assert_equal_i(1, exp.line_adds);
  393 + cl_assert_equal_i(0, exp.line_dels);
  394 +
  395 + cl_git_pass(git_diff_merge(diff_i2t, diff_w2i));
  396 +
  397 + memset(&exp, 0, sizeof(exp));
  398 + cl_git_pass(git_diff_foreach(
  399 + diff_i2t, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
  400 + cl_assert_equal_i(1, exp.files);
  401 + cl_assert_equal_i(0, exp.file_adds);
  402 + cl_assert_equal_i(0, exp.file_dels);
  403 + cl_assert_equal_i(1, exp.file_mods);
  404 + cl_assert_equal_i(1, exp.hunks);
  405 + cl_assert_equal_i(3, exp.lines);
  406 + cl_assert_equal_i(1, exp.line_ctxt);
  407 + cl_assert_equal_i(2, exp.line_adds);
  408 + cl_assert_equal_i(0, exp.line_dels);
  409 +
  410 + git_diff_list_free(diff_i2t);
  411 + git_diff_list_free(diff_w2i);
  412 +}
  413 +
  414 +void test_diff_workdir__eof_newline_changes(void)
  415 +{
  416 + git_diff_options opts = {0};
  417 + git_diff_list *diff = NULL;
  418 + diff_expects exp;
  419 + char *pathspec = "current_file";
  420 +
  421 + g_repo = cl_git_sandbox_init("status");
  422 +
  423 + opts.pathspec.strings = &pathspec;
  424 + opts.pathspec.count = 1;
  425 +
  426 + cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
  427 +
  428 + memset(&exp, 0, sizeof(exp));
  429 + cl_git_pass(git_diff_foreach(
  430 + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
  431 + cl_assert_equal_i(0, exp.files);
  432 + cl_assert_equal_i(0, exp.file_adds);
  433 + cl_assert_equal_i(0, exp.file_dels);
  434 + cl_assert_equal_i(0, exp.file_mods);
  435 + cl_assert_equal_i(0, exp.hunks);
  436 + cl_assert_equal_i(0, exp.lines);
  437 + cl_assert_equal_i(0, exp.line_ctxt);
  438 + cl_assert_equal_i(0, exp.line_adds);
  439 + cl_assert_equal_i(0, exp.line_dels);
  440 +
  441 + git_diff_list_free(diff);
  442 +
  443 + cl_git_append2file("status/current_file", "\n");
  444 +
  445 + cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
  446 +
  447 + memset(&exp, 0, sizeof(exp));
  448 + cl_git_pass(git_diff_foreach(
  449 + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
  450 + cl_assert_equal_i(1, exp.files);
  451 + cl_assert_equal_i(0, exp.file_adds);
  452 + cl_assert_equal_i(0, exp.file_dels);
  453 + cl_assert_equal_i(1, exp.file_mods);
  454 + cl_assert_equal_i(1, exp.hunks);
  455 + cl_assert_equal_i(2, exp.lines);
  456 + cl_assert_equal_i(1, exp.line_ctxt);
  457 + cl_assert_equal_i(1, exp.line_adds);
  458 + cl_assert_equal_i(0, exp.line_dels);
  459 +
  460 + git_diff_list_free(diff);
  461 +
  462 + cl_git_rewritefile("status/current_file", "current_file");
  463 +
  464 + cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
  465 +
  466 + memset(&exp, 0, sizeof(exp));
  467 + cl_git_pass(git_diff_foreach(
  468 + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
  469 + cl_assert_equal_i(1, exp.files);
  470 + cl_assert_equal_i(0, exp.file_adds);
  471 + cl_assert_equal_i(0, exp.file_dels);
  472 + cl_assert_equal_i(1, exp.file_mods);
  473 + cl_assert_equal_i(1, exp.hunks);
  474 + cl_assert_equal_i(3, exp.lines);
  475 + cl_assert_equal_i(0, exp.line_ctxt);
  476 + cl_assert_equal_i(1, exp.line_adds);
  477 + cl_assert_equal_i(2, exp.line_dels);
  478 +
  479 + git_diff_list_free(diff);
  480 +}
  481 +
240 482 /* PREPARATION OF TEST DATA
241 483 *
242 484 * Since there is no command line equivalent of git_diff_workdir_to_tree,
1  tests-clar/resources/filemodes/.gitted/HEAD
... ... @@ -0,0 +1 @@
  1 +ref: refs/heads/master
6 tests-clar/resources/filemodes/.gitted/config
... ... @@ -0,0 +1,6 @@
  1 +[core]
  2 + repositoryformatversion = 0
  3 + filemode = true
  4 + bare = false
  5 + logallrefupdates = true
  6 + ignorecase = true
1  tests-clar/resources/filemodes/.gitted/description
... ... @@ -0,0 +1 @@
  1 +Unnamed repository; edit this file 'description' to name the repository.
24 tests-clar/resources/filemodes/.gitted/hooks/commit-msg.sample
... ... @@ -0,0 +1,24 @@
  1 +#!/bin/sh
  2 +#
  3 +# An example hook script to check the commit log message.
  4 +# Called by "git commit" with one argument, the name of the file
  5 +# that has the commit message. The hook should exit with non-zero
  6 +# status after issuing an appropriate message if it wants to stop the
  7 +# commit. The hook is allowed to edit the commit message file.
  8 +#
  9 +# To enable this hook, rename this file to "commit-msg".
  10 +
  11 +# Uncomment the below to add a Signed-off-by line to the message.
  12 +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
  13 +# hook is more suited to it.
  14 +#
  15 +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
  16 +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
  17 +
  18 +# This example catches duplicate Signed-off-by lines.
  19 +
  20 +test "" = "$(grep '^Signed-off-by: ' "$1" |
  21 + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
  22 + echo >&2 Duplicate Signed-off-by lines.
  23 + exit 1
  24 +}
BIN  tests-clar/resources/filemodes/.gitted/index
Binary file not shown
6 tests-clar/resources/filemodes/.gitted/info/exclude
... ... @@ -0,0 +1,6 @@
  1 +# git ls-files --others --exclude-from=.git/info/exclude
  2 +# Lines that start with '#' are comments.
  3 +# For a project mostly in C, the following would be a good set of
  4 +# exclude patterns (uncomment them if you want to use them):
  5 +# *.[oa]
  6 +# *~
1  tests-clar/resources/filemodes/.gitted/logs/HEAD
... ... @@ -0,0 +1 @@
  1 +0000000000000000000000000000000000000000 9962c8453ba6f0cf8dac7c5dcc2fa2897fa9964a Russell Belfer <rb@github.com> 1338847682 -0700 commit (initial): Initial commit of test data
1  tests-clar/resources/filemodes/.gitted/logs/refs/heads/master
... ... @@ -0,0 +1 @@
  1 +0000000000000000000000000000000000000000 9962c8453ba6f0cf8dac7c5dcc2fa2897fa9964a Russell Belfer <rb@github.com> 1338847682 -0700 commit (initial): Initial commit of test data
BIN  tests-clar/resources/filemodes/.gitted/objects/99/62c8453ba6f0cf8dac7c5dcc2fa2897fa9964a
Binary file not shown
BIN  tests-clar/resources/filemodes/.gitted/objects/a5/c5dd0fc6c313159a69b1d19d7f61a9f978e8f1
Binary file not shown
BIN  tests-clar/resources/filemodes/.gitted/objects/e7/48d196331bcb20267eaaee4ff3326cb73b8182
Binary file not shown
1  tests-clar/resources/filemodes/.gitted/refs/heads/master
... ... @@ -0,0 +1 @@
  1 +9962c8453ba6f0cf8dac7c5dcc2fa2897fa9964a
1  tests-clar/resources/filemodes/exec_off
... ... @@ -0,0 +1 @@
  1 +Howdy
1  tests-clar/resources/filemodes/exec_off2on_staged
... ... @@ -0,0 +1 @@
  1 +Howdy
1  tests-clar/resources/filemodes/exec_off2on_workdir
... ... @@ -0,0 +1 @@
  1 +Howdy
1  tests-clar/resources/filemodes/exec_off_untracked
... ... @@ -0,0 +1 @@
  1 +Howdy
1  tests-clar/resources/filemodes/exec_on
... ... @@ -0,0 +1 @@
  1 +Howdy
1  tests-clar/resources/filemodes/exec_on2off_staged
... ... @@ -0,0 +1 @@
  1 +Howdy
1  tests-clar/resources/filemodes/exec_on2off_workdir
... ... @@ -0,0 +1 @@
  1 +Howdy
1  tests-clar/resources/filemodes/exec_on_untracked
... ... @@ -0,0 +1 @@
  1 +Howdy
66 tests-clar/status/worktree.c
@@ -581,3 +581,69 @@ void test_status_worktree__space_in_filename(void)
581 581 git_index_free(index);
582 582 git_repository_free(repo);
583 583 }
  584 +
  585 +static const char *filemode_paths[] = {
  586 + "exec_off",
  587 + "exec_off2on_staged",
  588 + "exec_off2on_workdir",
  589 + "exec_off_untracked",
  590 + "exec_on",
  591 + "exec_on2off_staged",
  592 + "exec_on2off_workdir",
  593 + "exec_on_untracked",
  594 +};
  595 +
  596 +static unsigned int filemode_statuses[] = {
  597 + GIT_STATUS_CURRENT,
  598 + GIT_STATUS_INDEX_MODIFIED,
  599 + GIT_STATUS_WT_MODIFIED,
  600 + GIT_STATUS_WT_NEW,
  601 + GIT_STATUS_CURRENT,
  602 + GIT_STATUS_INDEX_MODIFIED,
  603 + GIT_STATUS_WT_MODIFIED,
  604 + GIT_STATUS_WT_NEW
  605 +};
  606 +
  607 +static const size_t filemode_count = 8;
  608 +
  609 +void test_status_worktree__filemode_changes(void)
  610 +{
  611 + git_repository *repo = cl_git_sandbox_init("filemodes");
  612 + status_entry_counts counts;
  613 + git_status_options opts;
  614 + git_config *cfg;
  615 +
  616 + /* overwrite stored filemode with platform appropriate value */
  617 + cl_git_pass(git_repository_config(&cfg, repo));
  618 + if (cl_is_chmod_supported())
  619 + cl_git_pass(git_config_set_bool(cfg, "core.filemode", true));
  620 + else {
  621 + unsigned int i;
  622 + cl_git_pass(git_config_set_bool(cfg, "core.filemode", false));
  623 +
  624 + /* won't trust filesystem mode diffs, so these will appear unchanged */
  625 + for (i = 0; i < filemode_count; ++i)
  626 + if (filemode_statuses[i] == GIT_STATUS_WT_MODIFIED)
  627 + filemode_statuses[i] = GIT_STATUS_CURRENT;
  628 + }
  629 +
  630 + memset(&opts, 0, sizeof(opts));
  631 + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
  632 + GIT_STATUS_OPT_INCLUDE_IGNORED |
  633 + GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
  634 +
  635 + memset(&counts, 0, sizeof(counts));
  636 + counts.expected_entry_count = filemode_count;
  637 + counts.expected_paths = filemode_paths;
  638 + counts.expected_statuses = filemode_statuses;
  639 +
  640 + cl_git_pass(
  641 + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
  642 + );
  643 +
  644 + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
  645 + cl_assert_equal_i(0, counts.wrong_status_flags_count);
  646 + cl_assert_equal_i(0, counts.wrong_sorted_path);
  647 +
  648 + git_config_free(cfg);
  649 +}

0 comments on commit e0b110e

Please sign in to comment.
Something went wrong with that request. Please try again.