]> git.proxmox.com Git - libgit2.git/blobdiff - tests/diff/workdir.c
New upstream version 1.1.0+dfsg.1
[libgit2.git] / tests / diff / workdir.c
index 65005f927fec25da085097dac76a384fc8cb544e..f2404958eb51497c10aede7e87aadf80aefcf05d 100644 (file)
@@ -2,6 +2,7 @@
 #include "diff_helpers.h"
 #include "repository.h"
 #include "git2/sys/diff.h"
+#include "../checkout/checkout_helpers.h"
 
 static git_repository *g_repo = NULL;
 
@@ -30,10 +31,10 @@ void test_diff_workdir__to_index(void)
 
                if (use_iterator)
                        cl_git_pass(diff_foreach_via_iterator(
-                               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
                else
                        cl_git_pass(git_diff_foreach(
-                               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
                /* to generate these values:
                 * - cd to tests/resources/status,
@@ -84,9 +85,11 @@ void test_diff_workdir__to_index_with_conflicts(void)
        /* Adding an entry that represents a rename gets two files in conflict */
        our_entry.path = "subdir/modified_file";
        our_entry.mode = 0100644;
+       git_oid_fromstr(&our_entry.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf");
 
        their_entry.path = "subdir/rename_conflict";
        their_entry.mode = 0100644;
+       git_oid_fromstr(&their_entry.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863");
 
        cl_git_pass(git_repository_index(&index, g_repo));
        cl_git_pass(git_index_conflict_add(index, NULL, &our_entry, &their_entry));
@@ -94,7 +97,7 @@ void test_diff_workdir__to_index_with_conflicts(void)
        cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &opts));
 
        cl_git_pass(diff_foreach_via_iterator(
-               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
        cl_assert_equal_i(9, exp.files);
        cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -129,7 +132,7 @@ void test_diff_workdir__to_index_with_assume_unchanged(void)
        cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
        memset(&exp, 0, sizeof(exp));
        cl_git_pass(git_diff_foreach(
-               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
        cl_assert_equal_i(8, exp.files);
        cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
        cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]);
@@ -142,12 +145,12 @@ void test_diff_workdir__to_index_with_assume_unchanged(void)
 
        cl_assert((iep = git_index_get_bypath(idx, "modified_file", 0)) != NULL);
        memcpy(&ie, iep, sizeof(ie));
-       ie.flags |= GIT_IDXENTRY_VALID;
+       ie.flags |= GIT_INDEX_ENTRY_VALID;
        cl_git_pass(git_index_add(idx, &ie));
 
        cl_assert((iep = git_index_get_bypath(idx, "file_deleted", 0)) != NULL);
        memcpy(&ie, iep, sizeof(ie));
-       ie.flags |= GIT_IDXENTRY_VALID;
+       ie.flags |= GIT_INDEX_ENTRY_VALID;
        cl_git_pass(git_index_add(idx, &ie));
 
        cl_git_pass(git_index_write(idx));
@@ -158,7 +161,7 @@ void test_diff_workdir__to_index_with_assume_unchanged(void)
        cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
        memset(&exp, 0, sizeof(exp));
        cl_git_pass(git_diff_foreach(
-               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
        cl_assert_equal_i(6, exp.files);
        cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
        cl_assert_equal_i(3, exp.file_status[GIT_DELTA_DELETED]);
@@ -205,10 +208,10 @@ void test_diff_workdir__to_tree(void)
 
                if (use_iterator)
                        cl_git_pass(diff_foreach_via_iterator(
-                               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
                else
                        cl_git_pass(git_diff_foreach(
-                               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
                cl_assert_equal_i(14, exp.files);
                cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -241,10 +244,10 @@ void test_diff_workdir__to_tree(void)
 
                if (use_iterator)
                        cl_git_pass(diff_foreach_via_iterator(
-                               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
                else
                        cl_git_pass(git_diff_foreach(
-                               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
                cl_assert_equal_i(15, exp.files);
                cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]);
@@ -278,10 +281,10 @@ void test_diff_workdir__to_tree(void)
 
                if (use_iterator)
                        cl_git_pass(diff_foreach_via_iterator(
-                               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
                else
                        cl_git_pass(git_diff_foreach(
-                               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
                cl_assert_equal_i(16, exp.files);
                cl_assert_equal_i(5, exp.file_status[GIT_DELTA_ADDED]);
@@ -312,7 +315,7 @@ void test_diff_workdir__to_tree(void)
        memset(&exp, 0, sizeof(exp));
 
        cl_git_pass(git_diff_foreach(
-               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
        cl_assert_equal_i(16, exp.files);
        cl_assert_equal_i(5, exp.file_status[GIT_DELTA_DELETED]);
@@ -359,9 +362,9 @@ void test_diff_workdir__to_index_with_pathspec(void)
 
                if (use_iterator)
                        cl_git_pass(diff_foreach_via_iterator(
-                               diff, diff_file_cb, NULL, NULL, &exp));
+                               diff, diff_file_cb, NULL, NULL, NULL, &exp));
                else
-                       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+                       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp));
 
                cl_assert_equal_i(13, exp.files);
                cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -382,9 +385,9 @@ void test_diff_workdir__to_index_with_pathspec(void)
 
                if (use_iterator)
                        cl_git_pass(diff_foreach_via_iterator(
-                               diff, diff_file_cb, NULL, NULL, &exp));
+                               diff, diff_file_cb, NULL, NULL, NULL, &exp));
                else
-                       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+                       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp));
 
                cl_assert_equal_i(1, exp.files);
                cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -405,9 +408,9 @@ void test_diff_workdir__to_index_with_pathspec(void)
 
                if (use_iterator)
                        cl_git_pass(diff_foreach_via_iterator(
-                               diff, diff_file_cb, NULL, NULL, &exp));
+                               diff, diff_file_cb, NULL, NULL, NULL, &exp));
                else
-                       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+                       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp));
 
                cl_assert_equal_i(3, exp.files);
                cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -428,9 +431,9 @@ void test_diff_workdir__to_index_with_pathspec(void)
 
                if (use_iterator)
                        cl_git_pass(diff_foreach_via_iterator(
-                               diff, diff_file_cb, NULL, NULL, &exp));
+                               diff, diff_file_cb, NULL, NULL, NULL, &exp));
                else
-                       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+                       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp));
 
                cl_assert_equal_i(2, exp.files);
                cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -443,6 +446,216 @@ void test_diff_workdir__to_index_with_pathspec(void)
        git_diff_free(diff);
 }
 
+void test_diff_workdir__to_index_with_pathlist_disabling_fnmatch(void)
+{
+       git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+       git_diff *diff = NULL;
+       diff_expects exp;
+       char *pathspec = NULL;
+       int use_iterator;
+
+       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 |
+               GIT_DIFF_DISABLE_PATHSPEC_MATCH;
+       opts.pathspec.strings = &pathspec;
+       opts.pathspec.count   = 0;
+
+       /* ensure that an empty pathspec list is ignored */
+       cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+       for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+               memset(&exp, 0, sizeof(exp));
+
+               if (use_iterator)
+                       cl_git_pass(diff_foreach_via_iterator(
+                               diff, diff_file_cb, NULL, NULL, NULL, &exp));
+               else
+                       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp));
+
+               cl_assert_equal_i(13, exp.files);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+               cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]);
+               cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]);
+               cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
+               cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]);
+       }
+
+       git_diff_free(diff);
+
+       /* ensure that a single NULL pathspec is filtered out (like when using
+        * fnmatch filtering)
+        */
+
+       opts.pathspec.count   = 1;
+
+       cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+       for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+               memset(&exp, 0, sizeof(exp));
+
+               if (use_iterator)
+                       cl_git_pass(diff_foreach_via_iterator(
+                               diff, diff_file_cb, NULL, NULL, NULL, &exp));
+               else
+                       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp));
+
+               cl_assert_equal_i(13, exp.files);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+               cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]);
+               cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]);
+               cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
+               cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]);
+       }
+
+       git_diff_free(diff);
+
+       pathspec = "modified_file";
+
+       cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+       for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+               memset(&exp, 0, sizeof(exp));
+
+               if (use_iterator)
+                       cl_git_pass(diff_foreach_via_iterator(
+                               diff, diff_file_cb, NULL, NULL, NULL, &exp));
+               else
+                       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp));
+
+               cl_assert_equal_i(1, exp.files);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
+               cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]);
+       }
+
+       git_diff_free(diff);
+
+       /* ensure that subdirs can be specified */
+       pathspec = "subdir";
+
+       cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+       for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+               memset(&exp, 0, sizeof(exp));
+
+               if (use_iterator)
+                       cl_git_pass(diff_foreach_via_iterator(
+                               diff, diff_file_cb, NULL, NULL, NULL, &exp));
+               else
+                       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp));
+
+               cl_assert_equal_i(3, exp.files);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+               cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+               cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]);
+               cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]);
+       }
+
+       git_diff_free(diff);
+
+       /* ensure that subdirs can be specified with a trailing slash */
+       pathspec = "subdir/";
+
+       cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+       for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+               memset(&exp, 0, sizeof(exp));
+
+               if (use_iterator)
+                       cl_git_pass(diff_foreach_via_iterator(
+                               diff, diff_file_cb, NULL, NULL, NULL, &exp));
+               else
+                       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp));
+
+               cl_assert_equal_i(3, exp.files);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+               cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]);
+               cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]);
+               cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]);
+       }
+
+       git_diff_free(diff);
+
+       /* ensure that fnmatching is completely disabled */
+       pathspec = "subdir/*";
+
+       cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+       for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+               memset(&exp, 0, sizeof(exp));
+
+               if (use_iterator)
+                       cl_git_pass(diff_foreach_via_iterator(
+                               diff, diff_file_cb, NULL, NULL, NULL, &exp));
+               else
+                       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp));
+
+               cl_assert_equal_i(0, exp.files);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]);
+       }
+
+       git_diff_free(diff);
+
+       /* ensure that the prefix matching isn't completely braindead */
+       pathspec = "subdi";
+
+       cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+       for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+               memset(&exp, 0, sizeof(exp));
+
+               if (use_iterator)
+                       cl_git_pass(diff_foreach_via_iterator(
+                               diff, diff_file_cb, NULL, NULL, NULL, &exp));
+               else
+                       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp));
+
+               cl_assert_equal_i(0, exp.files);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]);
+       }
+
+       git_diff_free(diff);
+
+       /* ensure that fnmatching isn't working at all */
+       pathspec = "*_deleted";
+
+       cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+
+       for (use_iterator = 0; use_iterator <= 1; use_iterator++) {
+               memset(&exp, 0, sizeof(exp));
+
+               if (use_iterator)
+                       cl_git_pass(diff_foreach_via_iterator(
+                               diff, diff_file_cb, NULL, NULL, NULL, &exp));
+               else
+                       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp));
+
+               cl_assert_equal_i(0, exp.files);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]);
+               cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]);
+       }
+
+       git_diff_free(diff);
+}
+
 void test_diff_workdir__filemode_changes(void)
 {
        git_diff *diff = NULL;
@@ -465,10 +678,10 @@ void test_diff_workdir__filemode_changes(void)
 
                if (use_iterator)
                        cl_git_pass(diff_foreach_via_iterator(
-                               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
                else
                        cl_git_pass(git_diff_foreach(
-                               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
                cl_assert_equal_i(0, exp.files);
                cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]);
@@ -488,10 +701,10 @@ void test_diff_workdir__filemode_changes(void)
 
                if (use_iterator)
                        cl_git_pass(diff_foreach_via_iterator(
-                               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
                else
                        cl_git_pass(git_diff_foreach(
-                               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
                cl_assert_equal_i(1, exp.files);
                cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
@@ -521,7 +734,7 @@ void test_diff_workdir__filemode_changes_with_filemode_false(void)
 
        memset(&exp, 0, sizeof(exp));
        cl_git_pass(git_diff_foreach(
-               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
        cl_assert_equal_i(0, exp.files);
        cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]);
@@ -536,8 +749,8 @@ void test_diff_workdir__filemode_changes_with_filemode_false(void)
        cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL));
 
        memset(&exp, 0, sizeof(exp));
-       cl_git_pass(git_diff_foreach(
-               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+       cl_git_pass(git_diff_foreach(diff,
+               diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
        cl_assert_equal_i(0, exp.files);
        cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]);
@@ -580,10 +793,10 @@ void test_diff_workdir__head_index_and_workdir_all_differ(void)
 
                if (use_iterator)
                        cl_git_pass(diff_foreach_via_iterator(
-                               diff_i2t, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff_i2t, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
                else
                        cl_git_pass(git_diff_foreach(
-                               diff_i2t, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff_i2t, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
                cl_assert_equal_i(1, exp.files);
                cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -601,10 +814,10 @@ void test_diff_workdir__head_index_and_workdir_all_differ(void)
 
                if (use_iterator)
                        cl_git_pass(diff_foreach_via_iterator(
-                               diff_w2i, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff_w2i, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
                else
                        cl_git_pass(git_diff_foreach(
-                               diff_w2i, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff_w2i, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
                cl_assert_equal_i(1, exp.files);
                cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -624,10 +837,10 @@ void test_diff_workdir__head_index_and_workdir_all_differ(void)
 
                if (use_iterator)
                        cl_git_pass(diff_foreach_via_iterator(
-                               diff_i2t, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff_i2t, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
                else
                        cl_git_pass(git_diff_foreach(
-                               diff_i2t, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff_i2t, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
                cl_assert_equal_i(1, exp.files);
                cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -666,10 +879,10 @@ void test_diff_workdir__eof_newline_changes(void)
 
                if (use_iterator)
                        cl_git_pass(diff_foreach_via_iterator(
-                               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
                else
                        cl_git_pass(git_diff_foreach(
-                               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
                cl_assert_equal_i(0, exp.files);
                cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -693,10 +906,10 @@ void test_diff_workdir__eof_newline_changes(void)
 
                if (use_iterator)
                        cl_git_pass(diff_foreach_via_iterator(
-                               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
                else
                        cl_git_pass(git_diff_foreach(
-                               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
                cl_assert_equal_i(1, exp.files);
                cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -720,10 +933,10 @@ void test_diff_workdir__eof_newline_changes(void)
 
                if (use_iterator)
                        cl_git_pass(diff_foreach_via_iterator(
-                               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
                else
                        cl_git_pass(git_diff_foreach(
-                               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+                               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
                cl_assert_equal_i(1, exp.files);
                cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -913,7 +1126,7 @@ void test_diff_workdir__submodules(void)
        memset(&exp, 0, sizeof(exp));
 
        cl_git_pass(git_diff_foreach(
-               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
        /* so "git diff 873585" returns:
         *  M   .gitmodules
@@ -991,7 +1204,7 @@ void test_diff_workdir__to_null_tree(void)
        memset(&exp, 0, sizeof(exp));
 
        cl_git_pass(git_diff_foreach(
-               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
        cl_assert_equal_i(exp.files, exp.file_status[GIT_DELTA_UNTRACKED]);
 
@@ -1008,14 +1221,14 @@ void test_diff_workdir__checks_options_version(void)
 
        opts.version = 0;
        cl_git_fail(git_diff_tree_to_workdir(&diff, g_repo, NULL, &opts));
-       err = giterr_last();
-       cl_assert_equal_i(GITERR_INVALID, err->klass);
+       err = git_error_last();
+       cl_assert_equal_i(GIT_ERROR_INVALID, err->klass);
 
-       giterr_clear();
+       git_error_clear();
        opts.version = 1024;
        cl_git_fail(git_diff_tree_to_workdir(&diff, g_repo, NULL, &opts));
-       err = giterr_last();
-       cl_assert_equal_i(GITERR_INVALID, err->klass);
+       err = git_error_last();
+       cl_assert_equal_i(GIT_ERROR_INVALID, err->klass);
 }
 
 void test_diff_workdir__can_diff_empty_file(void)
@@ -1080,7 +1293,7 @@ void test_diff_workdir__to_index_issue_1397(void)
 
        memset(&exp, 0, sizeof(exp));
        cl_git_pass(git_diff_foreach(
-               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
        cl_assert_equal_i(0, exp.files);
        cl_assert_equal_i(0, exp.hunks);
@@ -1096,7 +1309,7 @@ void test_diff_workdir__to_index_issue_1397(void)
 
        memset(&exp, 0, sizeof(exp));
        cl_git_pass(git_diff_foreach(
-               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
        cl_assert_equal_i(1, exp.files);
        cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]);
@@ -1133,7 +1346,7 @@ void test_diff_workdir__to_tree_issue_1397(void)
 
        memset(&exp, 0, sizeof(exp));
        cl_git_pass(git_diff_foreach(
-               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
        cl_assert_equal_i(0, exp.files);
        cl_assert_equal_i(0, exp.hunks);
@@ -1149,7 +1362,7 @@ void test_diff_workdir__to_tree_issue_1397(void)
 
        memset(&exp, 0, sizeof(exp));
        cl_git_pass(git_diff_foreach(
-               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
        cl_assert_equal_i(0, exp.files);
        cl_assert_equal_i(0, exp.hunks);
@@ -1203,7 +1416,7 @@ void test_diff_workdir__untracked_directory_scenarios(void)
 
        cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
 
-       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp));
 
        cl_assert_equal_i(3, exp.files);
        cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -1223,7 +1436,7 @@ void test_diff_workdir__untracked_directory_scenarios(void)
 
        cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
 
-       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp));
 
        cl_assert_equal_i(4, exp.files);
        cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -1243,7 +1456,7 @@ void test_diff_workdir__untracked_directory_scenarios(void)
 
        cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
 
-       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp));
 
        cl_assert_equal_i(4, exp.files);
        cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -1267,7 +1480,7 @@ void test_diff_workdir__untracked_directory_scenarios(void)
 
        cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
 
-       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp));
 
        cl_assert_equal_i(4, exp.files);
        cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -1290,7 +1503,7 @@ void test_diff_workdir__untracked_directory_scenarios(void)
 
        cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
 
-       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp));
 
        cl_assert_equal_i(4, exp.files);
        cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -1310,7 +1523,7 @@ void test_diff_workdir__untracked_directory_scenarios(void)
 
        cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
 
-       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp));
 
        cl_assert_equal_i(4, exp.files);
        cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -1333,7 +1546,7 @@ void test_diff_workdir__untracked_directory_scenarios(void)
 
        cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
 
-       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp));
 
        cl_assert_equal_i(4, exp.files);
        cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -1354,7 +1567,7 @@ void test_diff_workdir__untracked_directory_scenarios(void)
 
        cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
 
-       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
+       cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp));
 
        cl_assert_equal_i(4, exp.files);
        cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -1464,7 +1677,7 @@ void test_diff_workdir__patience_diff(void)
        cl_assert_equal_s(expected_patience, buf.ptr);
        git_buf_clear(&buf);
 
-       git_buf_free(&buf);
+       git_buf_dispose(&buf);
        git_patch_free(patch);
        git_diff_free(diff);
 }
@@ -1505,7 +1718,7 @@ void test_diff_workdir__with_stale_index(void)
        memset(&exp, 0, sizeof(exp));
 
        cl_git_pass(git_diff_foreach(
-               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
        cl_assert_equal_i(17, exp.files);
        cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -1527,7 +1740,7 @@ void test_diff_workdir__with_stale_index(void)
        memset(&exp, 0, sizeof(exp));
 
        cl_git_pass(git_diff_foreach(
-               diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
        git_diff_free(diff);
 
@@ -1543,19 +1756,21 @@ void test_diff_workdir__with_stale_index(void)
 
 static int touch_file(void *payload, git_buf *path)
 {
-       int fd;
-       char b;
+       struct stat st;
+       struct p_timeval times[2];
 
        GIT_UNUSED(payload);
        if (git_path_isdir(path->ptr))
                return 0;
 
-       cl_assert((fd = p_open(path->ptr, O_RDWR)) >= 0);
-       cl_assert_equal_i(1, p_read(fd, &b, 1));
-       cl_must_pass(p_lseek(fd, 0, SEEK_SET));
-       cl_must_pass(p_write(fd, &b, 1));
-       cl_must_pass(p_close(fd));
+       cl_must_pass(p_stat(path->ptr, &st));
+
+       times[0].tv_sec = st.st_mtime + 3;
+       times[0].tv_usec = 0;
+       times[1].tv_sec = st.st_mtime + 3;
+       times[1].tv_usec = 0;
 
+       cl_must_pass(p_utimes(path->ptr, times));
        return 0;
 }
 
@@ -1568,7 +1783,7 @@ static void basic_diff_status(git_diff **out, const git_diff_options *opts)
        memset(&exp, 0, sizeof(exp));
 
        cl_git_pass(git_diff_foreach(
-               *out, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp));
+               *out, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
 
        cl_assert_equal_i(13, exp.files);
        cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]);
@@ -1583,6 +1798,7 @@ void test_diff_workdir__can_update_index(void)
        git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
        git_diff *diff = NULL;
        git_diff_perfdata perf = GIT_DIFF_PERFDATA_INIT;
+       git_index *index;
 
        g_repo = cl_git_sandbox_init("status");
 
@@ -1591,7 +1807,7 @@ void test_diff_workdir__can_update_index(void)
                git_buf path = GIT_BUF_INIT;
                cl_git_pass(git_buf_sets(&path, "status"));
                cl_git_pass(git_path_direach(&path, 0, touch_file, NULL));
-               git_buf_free(&path);
+               git_buf_dispose(&path);
        }
 
        opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
@@ -1607,6 +1823,10 @@ void test_diff_workdir__can_update_index(void)
        /* now allow diff to update stat cache */
        opts.flags |= GIT_DIFF_UPDATE_INDEX;
 
+       /* advance a tick for the index so we don't re-calculate racily-clean entries */
+       cl_git_pass(git_repository_index__weakptr(&index, g_repo));
+       tick_index(index);
+
        basic_diff_status(&diff, &opts);
 
        cl_git_pass(git_diff_get_perfdata(&perf, diff));
@@ -1617,6 +1837,8 @@ void test_diff_workdir__can_update_index(void)
 
        /* now if we do it again, we should see fewer OID calculations */
 
+       /* tick again as the index updating from the previous diff might have reset the timestamp */
+       tick_index(index);
        basic_diff_status(&diff, &opts);
 
        cl_git_pass(git_diff_get_perfdata(&perf, diff));
@@ -1654,9 +1876,9 @@ void test_diff_workdir__binary_detection(void)
        git_buf b = GIT_BUF_INIT;
        int i;
        git_buf data[10] = {
-               { "1234567890", 0, 0 },         /* 0 - all ascii text control */
-               { "\xC3\x85\xC3\xBC\xE2\x80\xA0\x48\xC3\xB8\xCF\x80\xCE\xA9", 0, 0 },            /* 1 - UTF-8 multibyte text */
-               { "\xEF\xBB\xBF\xC3\x9C\xE2\xA4\x92\xC6\x92\x38\xC2\xA3\xE2\x82\xAC", 0, 0 }, /* 2 - UTF-8 with BOM */
+               { "1234567890", 0, 10 },         /* 0 - all ascii text control */
+               { "\xC3\x85\xC3\xBC\xE2\x80\xA0\x48\xC3\xB8\xCF\x80\xCE\xA9", 0, 14 },            /* 1 - UTF-8 multibyte text */
+               { "\xEF\xBB\xBF\xC3\x9C\xE2\xA4\x92\xC6\x92\x38\xC2\xA3\xE2\x82\xAC", 0, 16 }, /* 2 - UTF-8 with BOM */
                { STR999Z, 0, 1000 },           /* 3 - ASCII with NUL at 1000 */
                { STR3999Z, 0, 4000 },          /* 4 - ASCII with NUL at 4000 */
                { STR4000 STR3999Z "x", 0, 8001 }, /* 5 - ASCII with NUL at 8000 */
@@ -1688,7 +1910,7 @@ void test_diff_workdir__binary_detection(void)
                cl_git_write2file(
                        b.ptr, data[i].ptr, data[i].size, O_WRONLY|O_TRUNC, 0664);
        }
-       git_index_write(idx);
+       cl_git_pass(git_index_write(idx));
 
        cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL));
 
@@ -1716,7 +1938,7 @@ void test_diff_workdir__binary_detection(void)
 
                cl_git_write2file(b.ptr, "baseline\n", 9, O_WRONLY|O_TRUNC, 0664);
        }
-       git_index_write(idx);
+       cl_git_pass(git_index_write(idx));
 
        cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL));
 
@@ -1737,5 +1959,247 @@ void test_diff_workdir__binary_detection(void)
        git_diff_free(diff);
 
        git_index_free(idx);
-       git_buf_free(&b);
+       git_buf_dispose(&b);
+}
+
+void test_diff_workdir__to_index_conflicted(void) {
+       const char *a_commit = "26a125ee1bf"; /* the current HEAD */
+       git_index_entry ancestor = {{0}}, ours = {{0}}, theirs = {{0}};
+       git_tree *a;
+       git_index *index;
+       git_diff *diff1, *diff2;
+       const git_diff_delta *delta;
+
+       g_repo = cl_git_sandbox_init("status");
+       a = resolve_commit_oid_to_tree(g_repo, a_commit);
+
+       cl_git_pass(git_repository_index(&index, g_repo));
+
+       ancestor.path = ours.path = theirs.path = "_file";
+       ancestor.mode = ours.mode = theirs.mode = 0100644;
+       git_oid_fromstr(&ancestor.id, "d427e0b2e138501a3d15cc376077a3631e15bd46");
+       git_oid_fromstr(&ours.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf");
+       git_oid_fromstr(&theirs.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863");
+       cl_git_pass(git_index_conflict_add(index, &ancestor, &ours, &theirs));
+
+       cl_git_pass(git_diff_tree_to_index(&diff1, g_repo, a, index, NULL));
+       cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, index, NULL));
+       cl_git_pass(git_diff_merge(diff1, diff2));
+
+       cl_assert_equal_i(git_diff_num_deltas(diff1), 12);
+       delta = git_diff_get_delta(diff1, 0);
+       cl_assert_equal_s(delta->old_file.path, "_file");
+       cl_assert_equal_i(delta->nfiles, 1);
+       cl_assert_equal_i(delta->status, GIT_DELTA_CONFLICTED);
+
+       git_diff_free(diff2);
+       git_diff_free(diff1);
+       git_index_free(index);
+       git_tree_free(a);
+}
+
+void test_diff_workdir__only_writes_index_when_necessary(void)
+{
+       git_index *index;
+       git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+       git_diff *diff = NULL;
+       git_reference *head;
+       git_object *head_object;
+       git_oid initial, first, second;
+       git_buf path = GIT_BUF_INIT;
+       struct stat st;
+       struct p_timeval times[2];
+
+       opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_UPDATE_INDEX;
+
+       g_repo = cl_git_sandbox_init("status");
+
+       cl_git_pass(git_repository_index(&index, g_repo));
+       cl_git_pass(git_repository_head(&head, g_repo));
+       cl_git_pass(git_reference_peel(&head_object, head, GIT_OBJECT_COMMIT));
+
+       cl_git_pass(git_reset(g_repo, head_object, GIT_RESET_HARD, NULL));
+
+       git_oid_cpy(&initial, git_index_checksum(index));
+
+       /* update the index timestamp to avoid raciness */
+       cl_must_pass(p_stat("status/.git/index", &st));
+
+       times[0].tv_sec = st.st_mtime + 5;
+       times[0].tv_usec = 0;
+       times[1].tv_sec = st.st_mtime + 5;
+       times[1].tv_usec = 0;
+
+       cl_must_pass(p_utimes("status/.git/index", times));
+
+       /* ensure diff doesn't touch the index */
+       cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+       git_diff_free(diff);
+
+       git_oid_cpy(&first, git_index_checksum(index));
+       cl_assert(!git_oid_equal(&initial, &first));
+
+       /* touch all the files so stat times are different */
+       cl_git_pass(git_buf_sets(&path, "status"));
+       cl_git_pass(git_path_direach(&path, 0, touch_file, NULL));
+
+       cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
+       git_diff_free(diff);
+
+       /* ensure the second diff did update the index */
+       git_oid_cpy(&second, git_index_checksum(index));
+       cl_assert(!git_oid_equal(&first, &second));
+
+       git_buf_dispose(&path);
+       git_object_free(head_object);
+       git_reference_free(head);
+       git_index_free(index);
+}
+
+void test_diff_workdir__to_index_pathlist(void)
+{
+       git_index *index;
+       git_diff *diff;
+       git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+       git_vector pathlist = GIT_VECTOR_INIT;
+
+       git_vector_insert(&pathlist, "foobar/asdf");
+       git_vector_insert(&pathlist, "subdir/asdf");
+       git_vector_insert(&pathlist, "ignored/asdf");
+
+       g_repo = cl_git_sandbox_init("status");
+
+       cl_git_mkfile("status/.gitignore", ".gitignore\n" "ignored/\n");
+
+       cl_must_pass(p_mkdir("status/foobar", 0777));
+       cl_git_mkfile("status/foobar/one", "one\n");
+
+       cl_must_pass(p_mkdir("status/ignored", 0777));
+       cl_git_mkfile("status/ignored/one", "one\n");
+       cl_git_mkfile("status/ignored/two", "two\n");
+       cl_git_mkfile("status/ignored/three", "three\n");
+
+       cl_git_pass(git_repository_index(&index, g_repo));
+
+       opts.flags = GIT_DIFF_INCLUDE_IGNORED;
+       opts.pathspec.strings = (char **)pathlist.contents;
+       opts.pathspec.count = pathlist.length;
+
+       cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &opts));
+       cl_assert_equal_i(0, git_diff_num_deltas(diff));
+       git_diff_free(diff);
+
+       opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH;
+
+       cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &opts));
+       cl_assert_equal_i(0, git_diff_num_deltas(diff));
+       git_diff_free(diff);
+
+       git_index_free(index);
+       git_vector_free(&pathlist);
+}
+
+void test_diff_workdir__symlink_changed_on_non_symlink_platform(void)
+{
+       git_tree *tree;
+       git_diff *diff;
+       diff_expects exp = {0};
+       const git_diff_delta *delta;
+       const char *commit = "7fccd7";
+       git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+       git_vector pathlist = GIT_VECTOR_INIT;
+       int symlinks;
+
+       g_repo = cl_git_sandbox_init("unsymlinked.git");
+
+       cl_git_pass(git_repository__configmap_lookup(&symlinks, g_repo, GIT_CONFIGMAP_SYMLINKS));
+
+       if (symlinks)
+               cl_skip();
+
+       cl_git_pass(git_vector_insert(&pathlist, "include/Nu/Nu.h"));
+
+       opts.pathspec.strings = (char **)pathlist.contents;
+       opts.pathspec.count = pathlist.length;
+
+       cl_must_pass(p_mkdir("symlink", 0777));
+       cl_git_pass(git_repository_set_workdir(g_repo, "symlink", false));
+
+       cl_assert((tree = resolve_commit_oid_to_tree(g_repo, commit)) != NULL);
+
+       /* first, do the diff with the original contents */
+
+       cl_git_pass(git_futils_mkpath2file("symlink/include/Nu/Nu.h", 0755));
+       cl_git_mkfile("symlink/include/Nu/Nu.h", "../../objc/Nu.h");
+
+       cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts));
+       cl_assert_equal_i(0, git_diff_num_deltas(diff));
+       git_diff_free(diff);
+
+       /* now update the contents and expect a difference, but that the file
+        * mode has persisted as a symbolic link.
+        */
+
+       cl_git_rewritefile("symlink/include/Nu/Nu.h", "awesome content\n");
+
+       cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts));
+
+       cl_git_pass(git_diff_foreach(
+               diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
+       cl_assert_equal_i(1, exp.files);
+
+       cl_assert_equal_i(1, git_diff_num_deltas(diff));
+       delta = git_diff_get_delta(diff, 0);
+       cl_assert_equal_i(GIT_FILEMODE_LINK, delta->old_file.mode);
+       cl_assert_equal_i(GIT_FILEMODE_LINK, delta->new_file.mode);
+
+       git_diff_free(diff);
+
+       cl_git_pass(git_futils_rmdir_r("symlink", NULL, GIT_RMDIR_REMOVE_FILES));
+
+       git_tree_free(tree);
+       git_vector_free(&pathlist);
+}
+
+void test_diff_workdir__order(void)
+{
+       git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+       git_buf patch = GIT_BUF_INIT;
+       git_oid tree_oid, blob_oid;
+       git_treebuilder *builder;
+       git_tree *tree;
+       git_diff *diff;
+
+       g_repo = cl_git_sandbox_init("empty_standard_repo");
+
+       /* Build tree with a single file "abc.txt" */
+       cl_git_pass(git_blob_create_from_buffer(&blob_oid, g_repo, "foo\n", 4));
+       cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL));
+       cl_git_pass(git_treebuilder_insert(NULL, builder, "abc.txt", &blob_oid, GIT_FILEMODE_BLOB));
+       cl_git_pass(git_treebuilder_write(&tree_oid, builder));
+       cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_oid));
+
+       /* Create a directory that sorts before and one that sorts after "abc.txt" */
+       cl_git_mkfile("empty_standard_repo/abc.txt", "bar\n");
+       cl_must_pass(p_mkdir("empty_standard_repo/abb", 0777));
+       cl_must_pass(p_mkdir("empty_standard_repo/abd", 0777));
+
+       opts.flags = GIT_DIFF_INCLUDE_UNTRACKED;
+       cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, tree, &opts));
+
+       cl_assert_equal_i(1, git_diff_num_deltas(diff));
+       cl_git_pass(git_diff_to_buf(&patch, diff, GIT_DIFF_FORMAT_PATCH));
+       cl_assert_equal_s(patch.ptr,
+               "diff --git a/abc.txt b/abc.txt\n"
+               "index 257cc56..5716ca5 100644\n"
+               "--- a/abc.txt\n"
+               "+++ b/abc.txt\n"
+               "@@ -1 +1 @@\n"
+               "-foo\n"
+               "+bar\n");
+
+       git_treebuilder_free(builder);
+       git_buf_dispose(&patch);
+       git_diff_free(diff);
+       git_tree_free(tree);
 }