]> git.proxmox.com Git - libgit2.git/commitdiff
Merge pull request #744 from arrbee/fix-filemodes
authorRussell Belfer <rb@github.com>
Fri, 8 Jun 2012 22:17:41 +0000 (15:17 -0700)
committerRussell Belfer <rb@github.com>
Fri, 8 Jun 2012 22:17:41 +0000 (15:17 -0700)
Fix filemode comparison in diffs

33 files changed:
examples/diff.c
include/git2/diff.h
include/git2/status.h
src/diff.c
src/diff.h
src/diff_output.c
src/path.c
tests-clar/clar_helpers.c
tests-clar/clar_libgit2.h
tests-clar/diff/diff_helpers.c
tests-clar/diff/tree.c
tests-clar/diff/workdir.c
tests-clar/resources/filemodes/.gitted/HEAD [new file with mode: 0644]
tests-clar/resources/filemodes/.gitted/config [new file with mode: 0644]
tests-clar/resources/filemodes/.gitted/description [new file with mode: 0644]
tests-clar/resources/filemodes/.gitted/hooks/commit-msg.sample [new file with mode: 0755]
tests-clar/resources/filemodes/.gitted/index [new file with mode: 0644]
tests-clar/resources/filemodes/.gitted/info/exclude [new file with mode: 0644]
tests-clar/resources/filemodes/.gitted/logs/HEAD [new file with mode: 0644]
tests-clar/resources/filemodes/.gitted/logs/refs/heads/master [new file with mode: 0644]
tests-clar/resources/filemodes/.gitted/objects/99/62c8453ba6f0cf8dac7c5dcc2fa2897fa9964a [new file with mode: 0644]
tests-clar/resources/filemodes/.gitted/objects/a5/c5dd0fc6c313159a69b1d19d7f61a9f978e8f1 [new file with mode: 0644]
tests-clar/resources/filemodes/.gitted/objects/e7/48d196331bcb20267eaaee4ff3326cb73b8182 [new file with mode: 0644]
tests-clar/resources/filemodes/.gitted/refs/heads/master [new file with mode: 0644]
tests-clar/resources/filemodes/exec_off [new file with mode: 0644]
tests-clar/resources/filemodes/exec_off2on_staged [new file with mode: 0755]
tests-clar/resources/filemodes/exec_off2on_workdir [new file with mode: 0755]
tests-clar/resources/filemodes/exec_off_untracked [new file with mode: 0644]
tests-clar/resources/filemodes/exec_on [new file with mode: 0755]
tests-clar/resources/filemodes/exec_on2off_staged [new file with mode: 0644]
tests-clar/resources/filemodes/exec_on2off_workdir [new file with mode: 0644]
tests-clar/resources/filemodes/exec_on_untracked [new file with mode: 0755]
tests-clar/status/worktree.c

index 1b4ab549b55c7438d5150878e1b71cbe03fcce06..b72a75e1c9365887ece68ff5ed8adcb940a5c049 100644 (file)
@@ -185,9 +185,7 @@ int main(int argc, char *argv[])
 
        /* open repo */
 
-       check(git_repository_discover(path, sizeof(path), dir, 0, "/"),
-               "Could not discover repository");
-       check(git_repository_open(&repo, path),
+       check(git_repository_open_ext(&repo, dir, 0, NULL),
                "Could not open repository");
 
        if (treeish1)
index 46b80d872c5ab6bb40e85ef0170f5aef33d28ed2..d4d0eac474c4325124999804c441a0a9fbde14e1 100644 (file)
  */
 GIT_BEGIN_DECL
 
+/**
+ * Flags for diff options.  A combination of these flags can be passed
+ * in via the `flags` value in the `git_diff_options`.
+ */
 enum {
        GIT_DIFF_NORMAL = 0,
        GIT_DIFF_REVERSE = (1 << 0),
@@ -160,15 +164,16 @@ typedef int (*git_diff_hunk_fn)(
  * the file or hunk headers.
  */
 enum {
-       /* these values will be sent to `git_diff_data_fn` along with the line */
+       /* These values will be sent to `git_diff_data_fn` along with the line */
        GIT_DIFF_LINE_CONTEXT   = ' ',
        GIT_DIFF_LINE_ADDITION  = '+',
        GIT_DIFF_LINE_DELETION  = '-',
-       GIT_DIFF_LINE_ADD_EOFNL = '\n', /**< LF was added at end of file */
+       GIT_DIFF_LINE_ADD_EOFNL = '\n', /**< DEPRECATED */
        GIT_DIFF_LINE_DEL_EOFNL = '\0', /**< LF was removed at end of file */
-       /* these values will only be sent to a `git_diff_data_fn` when the content
-        * of a diff is being formatted (eg. through git_diff_print_patch() or
-        * git_diff_print_compact(), for instance).
+
+       /* The following values will only be sent to a `git_diff_data_fn` when
+        * the content of a diff is being formatted (eg. through
+        * git_diff_print_patch() or git_diff_print_compact(), for instance).
         */
        GIT_DIFF_LINE_FILE_HDR  = 'F',
        GIT_DIFF_LINE_HUNK_HDR  = 'H',
@@ -206,6 +211,8 @@ GIT_EXTERN(void) git_diff_list_free(git_diff_list *diff);
 /**
  * Compute a difference between two tree objects.
  *
+ * This is equivalent to `git diff <treeish> <treeish>`
+ *
  * @param repo The repository containing the trees.
  * @param opts Structure with options to influence diff or NULL for defaults.
  * @param old_tree A git_tree object to diff from.
@@ -222,6 +229,9 @@ GIT_EXTERN(int) git_diff_tree_to_tree(
 /**
  * Compute a difference between a tree and the index.
  *
+ * This is equivalent to `git diff --cached <treeish>` or if you pass
+ * the HEAD tree, then like `git diff --cached`.
+ *
  * @param repo The repository containing the tree and index.
  * @param opts Structure with options to influence diff or NULL for defaults.
  * @param old_tree A git_tree object to diff from.
@@ -236,6 +246,11 @@ GIT_EXTERN(int) git_diff_index_to_tree(
 /**
  * Compute a difference between the working directory and the index.
  *
+ * This matches the `git diff` command.  See the note below on
+ * `git_diff_workdir_to_tree` for a discussion of the difference between
+ * `git diff` and `git diff HEAD` and how to emulate a `git diff <treeish>`
+ * using libgit2.
+ *
  * @param repo The repository.
  * @param opts Structure with options to influence diff or NULL for defaults.
  * @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(
 /**
  * Compute a difference between the working directory and a tree.
  *
- * This returns strictly the differences between the tree and the
- * files contained in the working directory, regardless of the state
- * of files in the index.  There is no direct equivalent in C git.
+ * This is *NOT* the same as `git diff <treeish>`.  Running `git diff HEAD`
+ * or the like actually uses information from the index, along with the tree
+ * and workdir dir info.
  *
- * This is *NOT* the same as 'git diff HEAD' or 'git diff <SHA>'.  Those
- * commands diff the tree, the index, and the workdir.  To emulate those
- * functions, call `git_diff_index_to_tree` and `git_diff_workdir_to_index`,
- * then call `git_diff_merge` on the results.
+ * This function returns strictly the differences between the tree and the
+ * files contained in the working directory, regardless of the state of
+ * files in the index.  It may come as a surprise, but there is no direct
+ * equivalent in core git.
+ *
+ * To emulate `git diff <treeish>`, you should call both
+ * `git_diff_index_to_tree` and `git_diff_workdir_to_index`, then call
+ * `git_diff_merge` on the results.  That will yield a `git_diff_list` that
+ * matches the git output.
+ *
+ * If this seems confusing, take the case of a file with a staged deletion
+ * where the file has then been put back into the working dir and modified.
+ * The tree-to-workdir diff for that file is 'modified', but core git would
+ * show status 'deleted' since there is a pending deletion in the index.
  *
  * @param repo The repository containing the tree.
  * @param opts Structure with options to influence diff or NULL for defaults.
@@ -298,10 +323,23 @@ GIT_EXTERN(int) git_diff_merge(
 /**
  * Iterate over a diff list issuing callbacks.
  *
- * If the hunk and/or line callbacks are not NULL, then this will calculate
- * text diffs for all files it thinks are not binary.  If those are both
- * NULL, then this will not bother with the text diffs, so it can be
- * efficient.
+ * This will iterate through all of the files described in a diff.  You
+ * should provide a file callback to learn about each file.
+ *
+ * The "hunk" and "line" callbacks are optional, and the text diff of the
+ * files will only be calculated if they are not NULL.  Of course, these
+ * callbacks will not be invoked for binary files on the diff list or for
+ * files whose only changed is a file mode change.
+ *
+ * @param diff A git_diff_list generated by one of the above functions.
+ * @param cb_data Reference pointer that will be passed to your callbacks.
+ * @param file_cb Callback function to make per file in the diff.
+ * @param hunk_cb Optional callback to make per hunk of text diff.  This
+ *                callback is called to describe a range of lines in the
+ *                diff.  It will not be issued for binary files.
+ * @param line_cb Optional callback to make per line of diff text.  This
+ *                same callback will be made for context lines, added, and
+ *                removed lines, and even for a deleted trailing newline.
  */
 GIT_EXTERN(int) git_diff_foreach(
        git_diff_list *diff,
@@ -322,6 +360,14 @@ GIT_EXTERN(int) git_diff_print_compact(
  * Iterate over a diff generating text output like "git diff".
  *
  * This is a super easy way to generate a patch from a diff.
+ *
+ * @param diff A git_diff_list generated by one of the above functions.
+ * @param cb_data Reference pointer that will be passed to your callbacks.
+ * @param print_cb Callback function to output lines of the diff.  This
+ *                 same function will be called for file headers, hunk
+ *                 headers, and diff lines.  Fortunately, you can probably
+ *                 use various GIT_DIFF_LINE constants to determine what
+ *                 text you are given.
  */
 GIT_EXTERN(int) git_diff_print_patch(
        git_diff_list *diff,
@@ -338,13 +384,14 @@ GIT_EXTERN(int) git_diff_print_patch(
 /**
  * Directly run a text diff on two blobs.
  *
- * Compared to a file, a blob lacks some contextual information. As such, the
- * `git_diff_file` parameters of the callbacks will be filled accordingly to the following:
- * `mode` will be set to 0, `path` will be set to NULL. When dealing with a NULL blob, `oid`
- * will be set to 0.
+ * Compared to a file, a blob lacks some contextual information. As such,
+ * the `git_diff_file` parameters of the callbacks will be filled
+ * accordingly to the following: `mode` will be set to 0, `path` will be set
+ * to NULL. When dealing with a NULL blob, `oid` will be set to 0.
  *
- * When at least one of the blobs being dealt with is binary, the `git_diff_delta` binary
- * attribute will be set to 1 and no call to the hunk_cb nor line_cb will be made.
+ * When at least one of the blobs being dealt with is binary, the
+ * `git_diff_delta` binary attribute will be set to 1 and no call to the
+ * hunk_cb nor line_cb will be made.
  */
 GIT_EXTERN(int) git_diff_blobs(
        git_blob *old_blob,
index 6a424dfd61e1716b2d83b96b17a6043d205813ab..69b6e47e07eecd8f7c23a495a424ff052ffe3fd0 100644 (file)
@@ -102,7 +102,7 @@ enum {
        GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1 << 0),
        GIT_STATUS_OPT_INCLUDE_IGNORED = (1 << 1),
        GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1 << 2),
-       GIT_STATUS_OPT_EXCLUDE_SUBMODULED = (1 << 3),
+       GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1 << 3),
        GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1 << 4),
 };
 
index 90baa9588344ff47b315b7146056dbf33ffc159a..02b89b46ed4cb5db56dadf481daa565892a67494 100644 (file)
@@ -130,37 +130,50 @@ fail:
 static git_diff_delta *diff_delta__merge_like_cgit(
        const git_diff_delta *a, const git_diff_delta *b, git_pool *pool)
 {
-       git_diff_delta *dup = diff_delta__dup(a, pool);
-       if (!dup)
-               return NULL;
-
-       if (git_oid_cmp(&dup->new_file.oid, &b->new_file.oid) == 0)
-               return dup;
-
-       git_oid_cpy(&dup->new_file.oid, &b->new_file.oid);
-
-       dup->new_file.mode = b->new_file.mode;
-       dup->new_file.size = b->new_file.size;
-       dup->new_file.flags = b->new_file.flags;
+       git_diff_delta *dup;
 
        /* Emulate C git for merging two diffs (a la 'git diff <sha>').
         *
         * When C git does a diff between the work dir and a tree, it actually
         * diffs with the index but uses the workdir contents.  This emulates
         * those choices so we can emulate the type of diff.
+        *
+        * We have three file descriptions here, let's call them:
+        *  f1 = a->old_file
+        *  f2 = a->new_file AND b->old_file
+        *  f3 = b->new_file
         */
-       if (git_oid_cmp(&dup->old_file.oid, &dup->new_file.oid) == 0) {
-               if (dup->status == GIT_DELTA_DELETED)
-                       /* preserve pending delete info */;
-               else if (b->status == GIT_DELTA_UNTRACKED ||
-                                b->status == GIT_DELTA_IGNORED)
-                       dup->status = b->status;
-               else
+
+       /* 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);
+
+       /* otherwise, base this diff on the 'b' diff */
+       if ((dup = diff_delta__dup(b, pool)) == NULL)
+               return NULL;
+
+       /* If 'a' status is uninteresting, then we're done */
+       if (a->status == GIT_DELTA_UNMODIFIED)
+               return dup;
+
+       assert(a->status != GIT_DELTA_UNMODIFIED);
+       assert(b->status != GIT_DELTA_UNMODIFIED);
+
+       /* A cgit exception is that the diff of a file that is only in the
+        * index (i.e. not in HEAD nor workdir) is given as empty.
+        */
+       if (dup->status == GIT_DELTA_DELETED) {
+               if (a->status == GIT_DELTA_ADDED)
                        dup->status = GIT_DELTA_UNMODIFIED;
+               /* else don't overwrite DELETE status */
+       } else {
+               dup->status = a->status;
        }
-       else if (dup->status == GIT_DELTA_UNMODIFIED ||
-                        b->status == GIT_DELTA_DELETED)
-               dup->status = b->status;
+
+       git_oid_cpy(&dup->old_file.oid, &a->old_file.oid);
+       dup->old_file.mode  = a->old_file.mode;
+       dup->old_file.size  = a->old_file.size;
+       dup->old_file.flags = a->old_file.flags;
 
        return dup;
 }
@@ -214,7 +227,9 @@ static int diff_delta__from_two(
        git_diff_list *diff,
        git_delta_t   status,
        const git_index_entry *old_entry,
+       uint32_t old_mode,
        const git_index_entry *new_entry,
+       uint32_t new_mode,
        git_oid *new_oid)
 {
        git_diff_delta *delta;
@@ -224,19 +239,22 @@ static int diff_delta__from_two(
                return 0;
 
        if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0) {
-               const git_index_entry *temp = old_entry;
+               uint32_t temp_mode = old_mode;
+               const git_index_entry *temp_entry = old_entry;
                old_entry = new_entry;
-               new_entry = temp;
+               new_entry = temp_entry;
+               old_mode = new_mode;
+               new_mode = temp_mode;
        }
 
        delta = diff_delta__alloc(diff, status, old_entry->path);
        GITERR_CHECK_ALLOC(delta);
 
-       delta->old_file.mode = old_entry->mode;
+       delta->old_file.mode = old_mode;
        git_oid_cpy(&delta->old_file.oid, &old_entry->oid);
        delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID;
 
-       delta->new_file.mode = new_entry->mode;
+       delta->new_file.mode = new_mode;
        git_oid_cpy(&delta->new_file.oid, new_oid ? new_oid : &new_entry->oid);
        if (new_oid || !git_oid_iszero(&new_entry->oid))
                delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID;
@@ -300,7 +318,7 @@ static git_diff_list *git_diff_list_alloc(
        if (config_bool(cfg, "core.ignorestat", 0))
                diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_ASSUME_UNCHANGED;
        if (config_bool(cfg, "core.filemode", 1))
-               diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_EXEC_BIT;
+               diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_MODE_BITS;
        if (config_bool(cfg, "core.trustctime", 1))
                diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME;
        /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */
@@ -419,7 +437,7 @@ static int oid_for_workdir_item(
        return result;
 }
 
-#define EXEC_BIT_MASK 0000111
+#define MODE_BITS_MASK 0000777
 
 static int maybe_modified(
        git_iterator *old_iter,
@@ -443,13 +461,13 @@ static int maybe_modified(
                !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
                nmode = GIT_MODE_TYPE(omode) | (nmode & GIT_MODE_PERMS_MASK);
 
-       /* on platforms with no execmode, clear exec bit from comparisons */
-       if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_EXEC_BIT)) {
-               omode = omode & ~EXEC_BIT_MASK;
-               nmode = nmode & ~EXEC_BIT_MASK;
-       }
+       /* on platforms with no execmode, just preserve old mode */
+       if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) &&
+               (nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) &&
+               new_iter->type == GIT_ITERATOR_WORKDIR)
+               nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK);
 
-       /* support "assume unchanged" (badly, b/c we still stat everything) */
+       /* support "assume unchanged" (poorly, b/c we still stat everything) */
        if ((diff->diffcaps & GIT_DIFFCAPS_ASSUME_UNCHANGED) != 0)
                status = (oitem->flags_extended & GIT_IDXENTRY_INTENT_TO_ADD) ?
                        GIT_DELTA_MODIFIED : GIT_DELTA_UNMODIFIED;
@@ -471,8 +489,13 @@ static int maybe_modified(
                         omode == nmode)
                status = GIT_DELTA_UNMODIFIED;
 
-       /* if we have a workdir item with an unknown oid, check deeper */
-       else if (git_oid_iszero(&nitem->oid) && new_iter->type == GIT_ITERATOR_WORKDIR) {
+       /* if modes match and we have an unknown OID and a workdir iterator,
+        * then check deeper for matching
+        */
+       else if (omode == nmode &&
+               git_oid_iszero(&nitem->oid) &&
+               new_iter->type == GIT_ITERATOR_WORKDIR)
+       {
                /* TODO: add check against index file st_mtime to avoid racy-git */
 
                /* if they files look exactly alike, then we'll assume the same */
@@ -517,7 +540,8 @@ static int maybe_modified(
                use_noid = &noid;
        }
 
-       return diff_delta__from_two(diff, status, oitem, nitem, use_noid);
+       return diff_delta__from_two(
+               diff, status, oitem, omode, nitem, nmode, use_noid);
 }
 
 static int diff_from_iterators(
@@ -772,6 +796,12 @@ int git_diff_merge(
                git_vector_swap(&onto->deltas, &onto_new);
                git_pool_swap(&onto->pool, &onto_pool);
                onto->new_src = from->new_src;
+
+               /* prefix strings also come from old pool, so recreate those.*/
+               onto->opts.old_prefix =
+                       git_pool_strdup(&onto->pool, onto->opts.old_prefix);
+               onto->opts.new_prefix =
+                       git_pool_strdup(&onto->pool, onto->opts.new_prefix);
        }
 
        git_vector_foreach(&onto_new, i, delta)
index ac24579563be90871b7c6ad32e9c7934f9e3a0ee..6cc854fbd601b7360a84ac14335caead8c236785 100644 (file)
@@ -20,7 +20,7 @@
 enum {
        GIT_DIFFCAPS_HAS_SYMLINKS     = (1 << 0), /* symlinks on platform? */
        GIT_DIFFCAPS_ASSUME_UNCHANGED = (1 << 1), /* use stat? */
-       GIT_DIFFCAPS_TRUST_EXEC_BIT   = (1 << 2), /* use st_mode exec bit? */
+       GIT_DIFFCAPS_TRUST_MODE_BITS  = (1 << 2), /* use st_mode? */
        GIT_DIFFCAPS_TRUST_CTIME      = (1 << 3), /* use st_ctime? */
        GIT_DIFFCAPS_USE_DEV          = (1 << 4), /* use st_dev? */
 };
@@ -36,5 +36,8 @@ struct git_diff_list {
        uint32_t diffcaps;
 };
 
+extern void git_diff__cleanup_modes(
+       uint32_t diffcaps, uint32_t *omode, uint32_t *nmode);
+
 #endif
 
index 1c65e1bb8b0af399d4008a6d07d60a77c03f57a5..92f7f8f2f70c8c560d28a476b946f0dd26428b8d 100644 (file)
@@ -83,12 +83,13 @@ static int diff_output_cb(void *priv, mmbuffer_t *bufs, int len)
                        info->cb_data, info->delta, &info->range, origin, bufs[1].ptr, bufs[1].size) < 0)
                        return -1;
 
-               /* deal with adding and removing newline at EOF */
+               /* This should only happen if we are adding a line that does not
+                * have a newline at the end and the old code did.  In that case,
+                * we have a ADD with a DEL_EOFNL as a pair.
+                */
                if (len == 3) {
-                       if (origin == GIT_DIFF_LINE_ADDITION)
-                               origin = GIT_DIFF_LINE_ADD_EOFNL;
-                       else
-                               origin = GIT_DIFF_LINE_DEL_EOFNL;
+                       origin = (origin == GIT_DIFF_LINE_ADDITION) ?
+                               GIT_DIFF_LINE_DEL_EOFNL : GIT_DIFF_LINE_ADD_EOFNL;
 
                        return info->line_cb(
                                info->cb_data, info->delta, &info->range, origin, bufs[2].ptr, bufs[2].size);
@@ -359,7 +360,7 @@ int git_diff_foreach(
 
                /* map files */
                if (delta->binary != 1 &&
-                       (hunk_cb || line_cb) &&
+                       (hunk_cb || line_cb || git_oid_iszero(&delta->old_file.oid)) &&
                        (delta->status == GIT_DELTA_DELETED ||
                         delta->status == GIT_DELTA_MODIFIED))
                {
@@ -397,7 +398,9 @@ int git_diff_foreach(
                                /* since we did not have the definitive oid, we may have
                                 * incorrect status and need to skip this item.
                                 */
-                               if (git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid) == 0) {
+                               if (delta->old_file.mode == delta->new_file.mode &&
+                                       !git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid))
+                               {
                                        delta->status = GIT_DELTA_UNMODIFIED;
                                        if ((diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
                                                goto cleanup;
@@ -420,7 +423,8 @@ int git_diff_foreach(
                 */
 
                if (file_cb != NULL) {
-                       error = file_cb(data, delta, (float)info.index / diff->deltas.length);
+                       error = file_cb(
+                               data, delta, (float)info.index / diff->deltas.length);
                        if (error < 0)
                                goto cleanup;
                }
@@ -433,6 +437,10 @@ int git_diff_foreach(
                if (!old_data.len && !new_data.len)
                        goto cleanup;
 
+               /* nothing to do if only diff was a mode change */
+               if (!git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid))
+                       goto cleanup;
+
                assert(hunk_cb || line_cb);
 
                info.delta = delta;
index 84edf6d890df127526d7eeccc4a56c691c1c9264..1d85559a9f5269be73ca70cfc33544b512081cff 100644 (file)
 #include <stdio.h>
 #include <ctype.h>
 
+#ifdef GIT_WIN32
+#define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':')
+#endif
+
 /*
  * Based on the Android implementation, BSD licensed.
  * Check http://android.git.kernel.org/
@@ -105,7 +109,7 @@ int git_path_dirname_r(git_buf *buffer, const char *path)
        /* Mimic unix behavior where '/.git' returns '/': 'C:/.git' will return
                'C:/' here */
 
-       if (len == 2 && isalpha(path[0]) && path[1] == ':') {
+       if (len == 2 && LOOKS_LIKE_DRIVE_PREFIX(path)) {
                len = 3;
                goto Exit;
        }
@@ -170,7 +174,7 @@ int git_path_root(const char *path)
 
 #ifdef GIT_WIN32
        /* Does the root of the path look like a windows drive ? */
-       if (isalpha(path[0]) && (path[1] == ':'))
+       if (LOOKS_LIKE_DRIVE_PREFIX(path))
                offset += 2;
 
        /* 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)
                giterr_set(GITERR_OS, "Failed to resolve path '%s'", path);
 
                git_buf_clear(path_out);
-               
+
                return error;
        }
 
index 23765d9e5dc6dc2a4c74bc77e76a26a98eb69622..1275d16208ec0409c219aae2a30d8a3dc481963e 100644 (file)
@@ -155,3 +155,27 @@ void cl_git_sandbox_cleanup(void)
        }
 }
 
+bool cl_toggle_filemode(const char *filename)
+{
+       struct stat st1, st2;
+
+       cl_must_pass(p_stat(filename, &st1));
+       cl_must_pass(p_chmod(filename, st1.st_mode ^ 0100));
+       cl_must_pass(p_stat(filename, &st2));
+
+       return (st1.st_mode != st2.st_mode);
+}
+
+bool cl_is_chmod_supported(void)
+{
+       static int _is_supported = -1;
+
+       if (_is_supported < 0) {
+               cl_git_mkfile("filemode.t", "Test if filemode can be modified");
+               _is_supported = cl_toggle_filemode("filemode.t");
+               cl_must_pass(p_unlink("filemode.t"));
+       }
+
+       return _is_supported;
+}
+
index aa613b2c409f803322fdf3661db48ad5d11d6d96..a3b03bbb32bd664e5552ec5364afa75e62ded516 100644 (file)
@@ -40,6 +40,9 @@ void cl_git_append2file(const char *filename, const char *new_content);
 void cl_git_rewritefile(const char *filename, const char *new_content);
 void cl_git_write2file(const char *filename, const char *new_content, int mode);
 
+bool cl_toggle_filemode(const char *filename);
+bool cl_is_chmod_supported(void);
+
 /* Environment wrappers */
 char *cl_getenv(const char *name);
 int cl_setenv(const char *name, const char *value);
index 8587be9b1100eb0f2d59bdbca9d6dd7d7672c451..1d9f6121c1e0a608b8bf1b5c04b69c85e3d0a771 100644 (file)
@@ -85,11 +85,16 @@ int diff_line_fn(
                e->line_ctxt++;
                break;
        case GIT_DIFF_LINE_ADDITION:
-       case GIT_DIFF_LINE_ADD_EOFNL:
                e->line_adds++;
                break;
+       case GIT_DIFF_LINE_ADD_EOFNL:
+               assert(0);
+               break;
        case GIT_DIFF_LINE_DELETION:
+               e->line_dels++;
+               break;
        case GIT_DIFF_LINE_DEL_EOFNL:
+               /* technically not a line delete, but we'll count it as such */
                e->line_dels++;
                break;
        default:
index b932fa10e07a01f4655bef166ee7f16495553c2e..4201ad2a7371c277a5d7c6815b4042f0f71d37f6 100644 (file)
@@ -116,7 +116,7 @@ void test_diff_tree__options(void)
                { 5, 3, 0, 2, 0, 0, 0, 4, 0, 0, 51, 2, 46, 3 },
                { 5, 3, 0, 2, 0, 0, 0, 4, 0, 0, 53, 4, 46, 3 },
                { 5, 0, 3, 2, 0, 0, 0, 4, 0, 0, 52, 3, 3, 46 },
-               { 5, 3, 0, 2, 0, 0, 0, 5, 0, 0, 54, 3, 48, 3 },
+               { 5, 3, 0, 2, 0, 0, 0, 5, 0, 0, 54, 3, 47, 4 },
                /* c vs d tests */
                { 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 22, 9, 10, 3 },
                { 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 19, 12, 7, 0 },
index 42152f1ad60bf8847a0943c9c87d13f867b35e92..0c17eeb4ac48a075034cabbd316c40a08432383a 100644 (file)
@@ -5,7 +5,6 @@ static git_repository *g_repo = NULL;
 
 void test_diff_workdir__initialize(void)
 {
-       g_repo = cl_git_sandbox_init("status");
 }
 
 void test_diff_workdir__cleanup(void)
@@ -19,6 +18,8 @@ void test_diff_workdir__to_index(void)
        git_diff_list *diff = NULL;
        diff_expects exp;
 
+       g_repo = cl_git_sandbox_init("status");
+
        opts.context_lines = 3;
        opts.interhunk_lines = 1;
        opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
@@ -59,13 +60,17 @@ void test_diff_workdir__to_tree(void)
        /* grabbed a couple of commit oids from the history of the attr repo */
        const char *a_commit = "26a125ee1bf"; /* the current HEAD */
        const char *b_commit = "0017bd4ab1ec3"; /* the start */
-       git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit);
-       git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit);
+       git_tree *a, *b;
        git_diff_options opts = {0};
        git_diff_list *diff = NULL;
        git_diff_list *diff2 = NULL;
        diff_expects exp;
 
+       g_repo = cl_git_sandbox_init("status");
+
+       a = resolve_commit_oid_to_tree(g_repo, a_commit);
+       b = resolve_commit_oid_to_tree(g_repo, b_commit);
+
        opts.context_lines = 3;
        opts.interhunk_lines = 1;
        opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
@@ -87,12 +92,12 @@ void test_diff_workdir__to_tree(void)
        cl_git_pass(git_diff_foreach(
                diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
 
-       cl_assert(exp.files == 14);
-       cl_assert(exp.file_adds == 0);
-       cl_assert(exp.file_dels == 4);
-       cl_assert(exp.file_mods == 4);
-       cl_assert(exp.file_ignored == 1);
-       cl_assert(exp.file_untracked == 5);
+       cl_assert_equal_i(14, exp.files);
+       cl_assert_equal_i(0, exp.file_adds);
+       cl_assert_equal_i(4, exp.file_dels);
+       cl_assert_equal_i(4, exp.file_mods);
+       cl_assert_equal_i(1, exp.file_ignored);
+       cl_assert_equal_i(5, exp.file_untracked);
 
        /* Since there is no git diff equivalent, let's just assume that the
         * text diffs produced by git_diff_foreach are accurate here.  We will
@@ -115,19 +120,19 @@ void test_diff_workdir__to_tree(void)
        cl_git_pass(git_diff_foreach(
                diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
 
-       cl_assert(exp.files == 15);
-       cl_assert(exp.file_adds == 2);
-       cl_assert(exp.file_dels == 5);
-       cl_assert(exp.file_mods == 4);
-       cl_assert(exp.file_ignored == 1);
-       cl_assert(exp.file_untracked == 3);
+       cl_assert_equal_i(15, exp.files);
+       cl_assert_equal_i(2, exp.file_adds);
+       cl_assert_equal_i(5, exp.file_dels);
+       cl_assert_equal_i(4, exp.file_mods);
+       cl_assert_equal_i(1, exp.file_ignored);
+       cl_assert_equal_i(3, exp.file_untracked);
 
-       cl_assert(exp.hunks == 11);
+       cl_assert_equal_i(11, exp.hunks);
 
-       cl_assert(exp.lines == 17);
-       cl_assert(exp.line_ctxt == 4);
-       cl_assert(exp.line_adds == 8);
-       cl_assert(exp.line_dels == 5);
+       cl_assert_equal_i(17, exp.lines);
+       cl_assert_equal_i(4, exp.line_ctxt);
+       cl_assert_equal_i(8, exp.line_adds);
+       cl_assert_equal_i(5, exp.line_dels);
 
        git_diff_list_free(diff);
        diff = NULL;
@@ -144,19 +149,19 @@ void test_diff_workdir__to_tree(void)
        cl_git_pass(git_diff_foreach(
                diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
 
-       cl_assert(exp.files == 16);
-       cl_assert(exp.file_adds == 5);
-       cl_assert(exp.file_dels == 4);
-       cl_assert(exp.file_mods == 3);
-       cl_assert(exp.file_ignored == 1);
-       cl_assert(exp.file_untracked == 3);
+       cl_assert_equal_i(16, exp.files);
+       cl_assert_equal_i(5, exp.file_adds);
+       cl_assert_equal_i(4, exp.file_dels);
+       cl_assert_equal_i(3, exp.file_mods);
+       cl_assert_equal_i(1, exp.file_ignored);
+       cl_assert_equal_i(3, exp.file_untracked);
 
-       cl_assert(exp.hunks == 12);
+       cl_assert_equal_i(12, exp.hunks);
 
-       cl_assert(exp.lines == 19);
-       cl_assert(exp.line_ctxt == 3);
-       cl_assert(exp.line_adds == 12);
-       cl_assert(exp.line_dels == 4);
+       cl_assert_equal_i(19, exp.lines);
+       cl_assert_equal_i(3, exp.line_ctxt);
+       cl_assert_equal_i(12, exp.line_adds);
+       cl_assert_equal_i(4, exp.line_dels);
 
        git_diff_list_free(diff);
 
@@ -171,6 +176,8 @@ void test_diff_workdir__to_index_with_pathspec(void)
        diff_expects exp;
        char *pathspec = NULL;
 
+       g_repo = cl_git_sandbox_init("status");
+
        opts.context_lines = 3;
        opts.interhunk_lines = 1;
        opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
@@ -237,6 +244,241 @@ void test_diff_workdir__to_index_with_pathspec(void)
        git_diff_list_free(diff);
 }
 
+void test_diff_workdir__filemode_changes(void)
+{
+       git_config *cfg;
+       git_diff_list *diff = NULL;
+       diff_expects exp;
+
+       if (!cl_is_chmod_supported())
+               return;
+
+       g_repo = cl_git_sandbox_init("issue_592");
+
+       cl_git_pass(git_repository_config(&cfg, g_repo));
+       cl_git_pass(git_config_set_bool(cfg, "core.filemode", true));
+
+       /* test once with no mods */
+
+       cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff));
+
+       memset(&exp, 0, sizeof(exp));
+       cl_git_pass(git_diff_foreach(
+               diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+
+       cl_assert_equal_i(0, exp.files);
+       cl_assert_equal_i(0, exp.file_mods);
+       cl_assert_equal_i(0, exp.hunks);
+
+       git_diff_list_free(diff);
+
+       /* chmod file and test again */
+
+       cl_assert(cl_toggle_filemode("issue_592/a.txt"));
+
+       cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff));
+
+       memset(&exp, 0, sizeof(exp));
+       cl_git_pass(git_diff_foreach(
+               diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+
+       cl_assert_equal_i(1, exp.files);
+       cl_assert_equal_i(1, exp.file_mods);
+       cl_assert_equal_i(0, exp.hunks);
+
+       git_diff_list_free(diff);
+
+       cl_assert(cl_toggle_filemode("issue_592/a.txt"));
+       git_config_free(cfg);
+}
+
+void test_diff_workdir__filemode_changes_with_filemode_false(void)
+{
+       git_config *cfg;
+       git_diff_list *diff = NULL;
+       diff_expects exp;
+
+       if (!cl_is_chmod_supported())
+               return;
+
+       g_repo = cl_git_sandbox_init("issue_592");
+
+       cl_git_pass(git_repository_config(&cfg, g_repo));
+       cl_git_pass(git_config_set_bool(cfg, "core.filemode", false));
+
+       /* test once with no mods */
+
+       cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff));
+
+       memset(&exp, 0, sizeof(exp));
+       cl_git_pass(git_diff_foreach(
+               diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+
+       cl_assert_equal_i(0, exp.files);
+       cl_assert_equal_i(0, exp.file_mods);
+       cl_assert_equal_i(0, exp.hunks);
+
+       git_diff_list_free(diff);
+
+       /* chmod file and test again */
+
+       cl_assert(cl_toggle_filemode("issue_592/a.txt"));
+
+       cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff));
+
+       memset(&exp, 0, sizeof(exp));
+       cl_git_pass(git_diff_foreach(
+               diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+
+       cl_assert_equal_i(0, exp.files);
+       cl_assert_equal_i(0, exp.file_mods);
+       cl_assert_equal_i(0, exp.hunks);
+
+       git_diff_list_free(diff);
+
+       cl_assert(cl_toggle_filemode("issue_592/a.txt"));
+       git_config_free(cfg);
+}
+
+void test_diff_workdir__head_index_and_workdir_all_differ(void)
+{
+       git_diff_options opts = {0};
+       git_diff_list *diff_i2t = NULL, *diff_w2i = NULL;
+       diff_expects exp;
+       char *pathspec = "staged_changes_modified_file";
+       git_tree *tree;
+
+       /* For this file,
+        * - head->index diff has 1 line of context, 1 line of diff
+        * - index->workdir diff has 2 lines of context, 1 line of diff
+        * but
+        * - head->workdir diff has 1 line of context, 2 lines of diff
+        * Let's make sure the right one is returned from each fn.
+        */
+
+       g_repo = cl_git_sandbox_init("status");
+
+       tree = resolve_commit_oid_to_tree(g_repo, "26a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f");
+
+       opts.pathspec.strings = &pathspec;
+       opts.pathspec.count   = 1;
+
+       cl_git_pass(git_diff_index_to_tree(g_repo, &opts, tree, &diff_i2t));
+       cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff_w2i));
+
+       memset(&exp, 0, sizeof(exp));
+       cl_git_pass(git_diff_foreach(
+               diff_i2t, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+       cl_assert_equal_i(1, exp.files);
+       cl_assert_equal_i(0, exp.file_adds);
+       cl_assert_equal_i(0, exp.file_dels);
+       cl_assert_equal_i(1, exp.file_mods);
+       cl_assert_equal_i(1, exp.hunks);
+       cl_assert_equal_i(2, exp.lines);
+       cl_assert_equal_i(1, exp.line_ctxt);
+       cl_assert_equal_i(1, exp.line_adds);
+       cl_assert_equal_i(0, exp.line_dels);
+
+       memset(&exp, 0, sizeof(exp));
+       cl_git_pass(git_diff_foreach(
+               diff_w2i, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+       cl_assert_equal_i(1, exp.files);
+       cl_assert_equal_i(0, exp.file_adds);
+       cl_assert_equal_i(0, exp.file_dels);
+       cl_assert_equal_i(1, exp.file_mods);
+       cl_assert_equal_i(1, exp.hunks);
+       cl_assert_equal_i(3, exp.lines);
+       cl_assert_equal_i(2, exp.line_ctxt);
+       cl_assert_equal_i(1, exp.line_adds);
+       cl_assert_equal_i(0, exp.line_dels);
+
+       cl_git_pass(git_diff_merge(diff_i2t, diff_w2i));
+
+       memset(&exp, 0, sizeof(exp));
+       cl_git_pass(git_diff_foreach(
+               diff_i2t, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+       cl_assert_equal_i(1, exp.files);
+       cl_assert_equal_i(0, exp.file_adds);
+       cl_assert_equal_i(0, exp.file_dels);
+       cl_assert_equal_i(1, exp.file_mods);
+       cl_assert_equal_i(1, exp.hunks);
+       cl_assert_equal_i(3, exp.lines);
+       cl_assert_equal_i(1, exp.line_ctxt);
+       cl_assert_equal_i(2, exp.line_adds);
+       cl_assert_equal_i(0, exp.line_dels);
+
+       git_diff_list_free(diff_i2t);
+       git_diff_list_free(diff_w2i);
+}
+
+void test_diff_workdir__eof_newline_changes(void)
+{
+       git_diff_options opts = {0};
+       git_diff_list *diff = NULL;
+       diff_expects exp;
+       char *pathspec = "current_file";
+
+       g_repo = cl_git_sandbox_init("status");
+
+       opts.pathspec.strings = &pathspec;
+       opts.pathspec.count   = 1;
+
+       cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
+
+       memset(&exp, 0, sizeof(exp));
+       cl_git_pass(git_diff_foreach(
+               diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+       cl_assert_equal_i(0, exp.files);
+       cl_assert_equal_i(0, exp.file_adds);
+       cl_assert_equal_i(0, exp.file_dels);
+       cl_assert_equal_i(0, exp.file_mods);
+       cl_assert_equal_i(0, exp.hunks);
+       cl_assert_equal_i(0, exp.lines);
+       cl_assert_equal_i(0, exp.line_ctxt);
+       cl_assert_equal_i(0, exp.line_adds);
+       cl_assert_equal_i(0, exp.line_dels);
+
+       git_diff_list_free(diff);
+
+       cl_git_append2file("status/current_file", "\n");
+
+       cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
+
+       memset(&exp, 0, sizeof(exp));
+       cl_git_pass(git_diff_foreach(
+               diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+       cl_assert_equal_i(1, exp.files);
+       cl_assert_equal_i(0, exp.file_adds);
+       cl_assert_equal_i(0, exp.file_dels);
+       cl_assert_equal_i(1, exp.file_mods);
+       cl_assert_equal_i(1, exp.hunks);
+       cl_assert_equal_i(2, exp.lines);
+       cl_assert_equal_i(1, exp.line_ctxt);
+       cl_assert_equal_i(1, exp.line_adds);
+       cl_assert_equal_i(0, exp.line_dels);
+
+       git_diff_list_free(diff);
+
+       cl_git_rewritefile("status/current_file", "current_file");
+
+       cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff));
+
+       memset(&exp, 0, sizeof(exp));
+       cl_git_pass(git_diff_foreach(
+               diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn));
+       cl_assert_equal_i(1, exp.files);
+       cl_assert_equal_i(0, exp.file_adds);
+       cl_assert_equal_i(0, exp.file_dels);
+       cl_assert_equal_i(1, exp.file_mods);
+       cl_assert_equal_i(1, exp.hunks);
+       cl_assert_equal_i(3, exp.lines);
+       cl_assert_equal_i(0, exp.line_ctxt);
+       cl_assert_equal_i(1, exp.line_adds);
+       cl_assert_equal_i(2, exp.line_dels);
+
+       git_diff_list_free(diff);
+}
+
 /* PREPARATION OF TEST DATA
  *
  * Since there is no command line equivalent of git_diff_workdir_to_tree,
diff --git a/tests-clar/resources/filemodes/.gitted/HEAD b/tests-clar/resources/filemodes/.gitted/HEAD
new file mode 100644 (file)
index 0000000..cb089cd
--- /dev/null
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests-clar/resources/filemodes/.gitted/config b/tests-clar/resources/filemodes/.gitted/config
new file mode 100644 (file)
index 0000000..af10792
--- /dev/null
@@ -0,0 +1,6 @@
+[core]
+       repositoryformatversion = 0
+       filemode = true
+       bare = false
+       logallrefupdates = true
+       ignorecase = true
diff --git a/tests-clar/resources/filemodes/.gitted/description b/tests-clar/resources/filemodes/.gitted/description
new file mode 100644 (file)
index 0000000..498b267
--- /dev/null
@@ -0,0 +1 @@
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/tests-clar/resources/filemodes/.gitted/hooks/commit-msg.sample b/tests-clar/resources/filemodes/.gitted/hooks/commit-msg.sample
new file mode 100755 (executable)
index 0000000..b58d118
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/sh
+#
+# An example hook script to check the commit log message.
+# Called by "git commit" with one argument, the name of the file
+# that has the commit message.  The hook should exit with non-zero
+# status after issuing an appropriate message if it wants to stop the
+# commit.  The hook is allowed to edit the commit message file.
+#
+# To enable this hook, rename this file to "commit-msg".
+
+# Uncomment the below to add a Signed-off-by line to the message.
+# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
+# hook is more suited to it.
+#
+# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
+# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
+
+# This example catches duplicate Signed-off-by lines.
+
+test "" = "$(grep '^Signed-off-by: ' "$1" |
+        sort | uniq -c | sed -e '/^[   ]*1[    ]/d')" || {
+       echo >&2 Duplicate Signed-off-by lines.
+       exit 1
+}
diff --git a/tests-clar/resources/filemodes/.gitted/index b/tests-clar/resources/filemodes/.gitted/index
new file mode 100644 (file)
index 0000000..b1b175a
Binary files /dev/null and b/tests-clar/resources/filemodes/.gitted/index differ
diff --git a/tests-clar/resources/filemodes/.gitted/info/exclude b/tests-clar/resources/filemodes/.gitted/info/exclude
new file mode 100644 (file)
index 0000000..a5196d1
--- /dev/null
@@ -0,0 +1,6 @@
+# git ls-files --others --exclude-from=.git/info/exclude
+# Lines that start with '#' are comments.
+# For a project mostly in C, the following would be a good set of
+# exclude patterns (uncomment them if you want to use them):
+# *.[oa]
+# *~
diff --git a/tests-clar/resources/filemodes/.gitted/logs/HEAD b/tests-clar/resources/filemodes/.gitted/logs/HEAD
new file mode 100644 (file)
index 0000000..1cb6a84
--- /dev/null
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 9962c8453ba6f0cf8dac7c5dcc2fa2897fa9964a Russell Belfer <rb@github.com> 1338847682 -0700      commit (initial): Initial commit of test data
diff --git a/tests-clar/resources/filemodes/.gitted/logs/refs/heads/master b/tests-clar/resources/filemodes/.gitted/logs/refs/heads/master
new file mode 100644 (file)
index 0000000..1cb6a84
--- /dev/null
@@ -0,0 +1 @@
+0000000000000000000000000000000000000000 9962c8453ba6f0cf8dac7c5dcc2fa2897fa9964a Russell Belfer <rb@github.com> 1338847682 -0700      commit (initial): Initial commit of test data
diff --git a/tests-clar/resources/filemodes/.gitted/objects/99/62c8453ba6f0cf8dac7c5dcc2fa2897fa9964a b/tests-clar/resources/filemodes/.gitted/objects/99/62c8453ba6f0cf8dac7c5dcc2fa2897fa9964a
new file mode 100644 (file)
index 0000000..cbd2b55
Binary files /dev/null and b/tests-clar/resources/filemodes/.gitted/objects/99/62c8453ba6f0cf8dac7c5dcc2fa2897fa9964a differ
diff --git a/tests-clar/resources/filemodes/.gitted/objects/a5/c5dd0fc6c313159a69b1d19d7f61a9f978e8f1 b/tests-clar/resources/filemodes/.gitted/objects/a5/c5dd0fc6c313159a69b1d19d7f61a9f978e8f1
new file mode 100644 (file)
index 0000000..a9eaf2c
Binary files /dev/null and b/tests-clar/resources/filemodes/.gitted/objects/a5/c5dd0fc6c313159a69b1d19d7f61a9f978e8f1 differ
diff --git a/tests-clar/resources/filemodes/.gitted/objects/e7/48d196331bcb20267eaaee4ff3326cb73b8182 b/tests-clar/resources/filemodes/.gitted/objects/e7/48d196331bcb20267eaaee4ff3326cb73b8182
new file mode 100644 (file)
index 0000000..9806602
Binary files /dev/null and b/tests-clar/resources/filemodes/.gitted/objects/e7/48d196331bcb20267eaaee4ff3326cb73b8182 differ
diff --git a/tests-clar/resources/filemodes/.gitted/refs/heads/master b/tests-clar/resources/filemodes/.gitted/refs/heads/master
new file mode 100644 (file)
index 0000000..9822d2d
--- /dev/null
@@ -0,0 +1 @@
+9962c8453ba6f0cf8dac7c5dcc2fa2897fa9964a
diff --git a/tests-clar/resources/filemodes/exec_off b/tests-clar/resources/filemodes/exec_off
new file mode 100644 (file)
index 0000000..a5c5dd0
--- /dev/null
@@ -0,0 +1 @@
+Howdy
diff --git a/tests-clar/resources/filemodes/exec_off2on_staged b/tests-clar/resources/filemodes/exec_off2on_staged
new file mode 100755 (executable)
index 0000000..a5c5dd0
--- /dev/null
@@ -0,0 +1 @@
+Howdy
diff --git a/tests-clar/resources/filemodes/exec_off2on_workdir b/tests-clar/resources/filemodes/exec_off2on_workdir
new file mode 100755 (executable)
index 0000000..a5c5dd0
--- /dev/null
@@ -0,0 +1 @@
+Howdy
diff --git a/tests-clar/resources/filemodes/exec_off_untracked b/tests-clar/resources/filemodes/exec_off_untracked
new file mode 100644 (file)
index 0000000..a5c5dd0
--- /dev/null
@@ -0,0 +1 @@
+Howdy
diff --git a/tests-clar/resources/filemodes/exec_on b/tests-clar/resources/filemodes/exec_on
new file mode 100755 (executable)
index 0000000..a5c5dd0
--- /dev/null
@@ -0,0 +1 @@
+Howdy
diff --git a/tests-clar/resources/filemodes/exec_on2off_staged b/tests-clar/resources/filemodes/exec_on2off_staged
new file mode 100644 (file)
index 0000000..a5c5dd0
--- /dev/null
@@ -0,0 +1 @@
+Howdy
diff --git a/tests-clar/resources/filemodes/exec_on2off_workdir b/tests-clar/resources/filemodes/exec_on2off_workdir
new file mode 100644 (file)
index 0000000..a5c5dd0
--- /dev/null
@@ -0,0 +1 @@
+Howdy
diff --git a/tests-clar/resources/filemodes/exec_on_untracked b/tests-clar/resources/filemodes/exec_on_untracked
new file mode 100755 (executable)
index 0000000..a5c5dd0
--- /dev/null
@@ -0,0 +1 @@
+Howdy
index b3ebdb78121967a996cae8e127febbde0457cc17..3670b72a87efa5a95298feda3448163dae2e4a2a 100644 (file)
@@ -581,3 +581,69 @@ void test_status_worktree__space_in_filename(void)
        git_index_free(index);
        git_repository_free(repo);
 }
+
+static const char *filemode_paths[] = {
+       "exec_off",
+       "exec_off2on_staged",
+       "exec_off2on_workdir",
+       "exec_off_untracked",
+       "exec_on",
+       "exec_on2off_staged",
+       "exec_on2off_workdir",
+       "exec_on_untracked",
+};
+
+static unsigned int filemode_statuses[] = {
+       GIT_STATUS_CURRENT,
+       GIT_STATUS_INDEX_MODIFIED,
+       GIT_STATUS_WT_MODIFIED,
+       GIT_STATUS_WT_NEW,
+       GIT_STATUS_CURRENT,
+       GIT_STATUS_INDEX_MODIFIED,
+       GIT_STATUS_WT_MODIFIED,
+       GIT_STATUS_WT_NEW
+};
+
+static const size_t filemode_count = 8;
+
+void test_status_worktree__filemode_changes(void)
+{
+       git_repository *repo = cl_git_sandbox_init("filemodes");
+       status_entry_counts counts;
+       git_status_options opts;
+       git_config *cfg;
+
+       /* overwrite stored filemode with platform appropriate value */
+       cl_git_pass(git_repository_config(&cfg, repo));
+       if (cl_is_chmod_supported())
+               cl_git_pass(git_config_set_bool(cfg, "core.filemode", true));
+       else {
+               unsigned int i;
+               cl_git_pass(git_config_set_bool(cfg, "core.filemode", false));
+
+               /* won't trust filesystem mode diffs, so these will appear unchanged */
+               for (i = 0; i < filemode_count; ++i)
+                       if (filemode_statuses[i] == GIT_STATUS_WT_MODIFIED)
+                               filemode_statuses[i] = GIT_STATUS_CURRENT;
+       }
+
+       memset(&opts, 0, sizeof(opts));
+       opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+               GIT_STATUS_OPT_INCLUDE_IGNORED |
+               GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
+
+       memset(&counts, 0, sizeof(counts));
+       counts.expected_entry_count = filemode_count;
+       counts.expected_paths = filemode_paths;
+       counts.expected_statuses = filemode_statuses;
+
+       cl_git_pass(
+               git_status_foreach_ext(repo, &opts, cb_status__normal, &counts)
+       );
+
+       cl_assert_equal_i(counts.expected_entry_count, counts.entry_count);
+       cl_assert_equal_i(0, counts.wrong_status_flags_count);
+       cl_assert_equal_i(0, counts.wrong_sorted_path);
+
+       git_config_free(cfg);
+}