]> git.proxmox.com Git - libgit2.git/commitdiff
Add complex checkout test and then fix checkout
authorRussell Belfer <rb@github.com>
Fri, 5 Oct 2012 22:56:57 +0000 (15:56 -0700)
committerRussell Belfer <rb@github.com>
Tue, 9 Oct 2012 18:59:34 +0000 (11:59 -0700)
This started as a complex new test for checkout going through the
"typechanges" test repository, but that revealed numerous issues
with checkout, including:

* complete failure with submodules
* failure to create blobs with exec bits
* problems when replacing a tree with a blob because the tree
  "example/" sorts after the blob "example" so the delete was
  being processed after the single file blob was created

This fixes most of those problems and includes a number of other
minor changes that made it easier to do that, including improving
the TYPECHANGE support in diff/status, etc.

27 files changed:
include/git2/checkout.h
include/git2/diff.h
include/git2/tree.h
src/checkout.c
src/clone.c
src/diff.c
src/diff_output.c
src/fileops.c
src/fileops.h
src/iterator.c
src/iterator.h
src/reflog.c
src/refs.c
src/status.c
src/tree.c
tests-clar/checkout/typechange.c [new file with mode: 0644]
tests-clar/core/copy.c
tests-clar/core/mkdir.c
tests-clar/core/rmdir.c
tests-clar/diff/iterator.c
tests-clar/object/blob/write.c
tests-clar/repo/discover.c
tests-clar/repo/open.c
tests-clar/resources/typechanges/.gitted/index
tests-clar/resources/typechanges/README.md
tests-clar/status/worktree.c
tests-clar/submodule/status.c

index ef3badbe994055e2d8beac18bcaaebc2333e5d0f..fb1a2303010705afe4218f9dcd35d3d1bdda2efd 100644 (file)
  */
 GIT_BEGIN_DECL
 
-enum {
+/**
+ * Checkout behavior flags
+ *
+ * These flags control what checkout does with files.  Pass in a
+ * combination of these values OR'ed together.
+ *
+ * - GIT_CHECKOUT_DEFAULT: With this value, checkout does not update
+ *   any files in the working directory.
+ * - GIT_CHECKOUT_OVERWRITE_MODIFIED: When a file exists and is modified,
+ *   replace the modifications with the new version.
+ * - GIT_CHECKOUT_CREATE_MISSING: When a file does not exist in the
+ *   working directory, create it.
+ * - GIT_CHECKOUT_REMOVE_UNTRACKED: If an untracked file in encountered
+ *   in the working directory, delete it.
+ */
+typedef enum {
        GIT_CHECKOUT_DEFAULT                    = (1 << 0),
        GIT_CHECKOUT_OVERWRITE_MODIFIED = (1 << 1),
        GIT_CHECKOUT_CREATE_MISSING             = (1 << 2),
        GIT_CHECKOUT_REMOVE_UNTRACKED   = (1 << 3),
-};
+} git_checkout_strategy_t;
 
 /* Use zeros to indicate default settings */
 typedef struct git_checkout_opts {
index 551e525ef8d64e1919a2792af6f186dc7f76bbec..1d32d9ad21c420c8fa9679d3af4358d5b7130ba1 100644 (file)
@@ -56,6 +56,9 @@ GIT_BEGIN_DECL
  * - GIT_DIFF_DONT_SPLIT_TYPECHANGE: normally, a type change between files
  *   will be converted into a DELETED record for the old file and an ADDED
  *   record for the new one; this option enabled TYPECHANGE records.
+ * - GIT_DIFF_SKIP_BINARY_CHECK: the binary flag in the delta record will
+ *   not be updated.  This is useful if iterating over a diff without hunk
+ *   and line callbacks and you want to avoid loading files completely.
  */
 enum {
        GIT_DIFF_NORMAL = 0,
@@ -73,7 +76,9 @@ enum {
        GIT_DIFF_DISABLE_PATHSPEC_MATCH = (1 << 11),
        GIT_DIFF_DELTAS_ARE_ICASE = (1 << 12),
        GIT_DIFF_INCLUDE_UNTRACKED_CONTENT = (1 << 13),
-       GIT_DIFF_DONT_SPLIT_TYPECHANGE = (1 << 14),
+       GIT_DIFF_SKIP_BINARY_CHECK = (1 << 14),
+       GIT_DIFF_INCLUDE_TYPECHANGE = (1 << 15),
+       GIT_DIFF_INCLUDE_TYPECHANGE_TREES = (1 << 16),
 };
 
 /**
index e5261417cd2f3c665f33281d82da43ba071dbe73..2ee1f4afa69461c59fc089b6c3e7e30c6a7db961 100644 (file)
@@ -100,7 +100,7 @@ GIT_EXTERN(git_tree_entry *) git_tree_entry_dup(const git_tree_entry *entry);
  * @param tree a previously loaded tree.
  * @return object identity for the tree.
  */
-GIT_EXTERN(const git_oid *) git_tree_id(git_tree *tree);
+GIT_EXTERN(const git_oid *) git_tree_id(const git_tree *tree);
 
 /**
  * Get the number of entries listed in a tree
@@ -108,7 +108,7 @@ GIT_EXTERN(const git_oid *) git_tree_id(git_tree *tree);
  * @param tree a previously loaded tree.
  * @return the number of entries in the tree
  */
-GIT_EXTERN(unsigned int) git_tree_entrycount(git_tree *tree);
+GIT_EXTERN(unsigned int) git_tree_entrycount(const git_tree *tree);
 
 /**
  * Lookup a tree entry by its filename
index ee6e043d5b6b4e0ca5f84614969dabdbdaaffad4..6f5cfffd7e6bf5c69c773020f8bae8f494310a33 100644 (file)
@@ -30,6 +30,7 @@ struct checkout_diff_data
        git_indexer_stats *stats;
        git_repository *owner;
        bool can_symlink;
+       bool create_submodules;
        int error;
 };
 
@@ -40,18 +41,27 @@ static int buffer_to_file(
        int file_open_flags,
        mode_t file_mode)
 {
-       int fd, error_write, error_close;
+       int fd, error, error_close;
 
-       if (git_futils_mkpath2file(path, dir_mode) < 0)
-               return -1;
+       if ((error = git_futils_mkpath2file(path, dir_mode)) < 0)
+               return error;
 
        if ((fd = p_open(path, file_open_flags, file_mode)) < 0)
-               return -1;
+               return fd;
+
+       error = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer));
 
-       error_write = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer));
        error_close = p_close(fd);
 
-       return error_write ? error_write : error_close;
+       if (!error)
+               error = error_close;
+
+       if (!error &&
+               (file_mode & 0100) != 0 &&
+               (error = p_chmod(path, file_mode)) < 0)
+               giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path);
+
+       return error;
 }
 
 static int blob_content_to_file(
@@ -125,107 +135,122 @@ static int blob_content_to_link(git_blob *blob, const char *path, bool can_symli
        return error;
 }
 
+static int checkout_submodule(
+       struct checkout_diff_data *data,
+       const git_diff_file *file)
+{
+       if (git_futils_mkdir(
+                       file->path, git_repository_workdir(data->owner),
+                       data->checkout_opts->dir_mode, GIT_MKDIR_PATH) < 0)
+               return -1;
+
+       /* TODO: two cases:
+        * 1 - submodule already checked out, but we need to move the HEAD
+        *     to the new OID, or
+        * 2 - submodule not checked out and we should recursively check it out
+        *
+        * Checkout will not execute a pull request on the submodule, but a
+        * clone command should probably be able to.  Do we need a submodule
+        * callback option?
+        */
+
+       return 0;
+}
+
 static int checkout_blob(
-       git_repository *repo,
-       const git_oid *blob_oid,
-       const char *path,
-       mode_t filemode,
-       bool can_symlink,
-       git_checkout_opts *opts)
+       struct checkout_diff_data *data,
+       const git_diff_file *file)
 {
        git_blob *blob;
        int error;
 
-       if ((error = git_blob_lookup(&blob, repo, blob_oid)) < 0)
-               return error; /* Add an error message */
+       git_buf_truncate(data->path, data->workdir_len);
+       if (git_buf_joinpath(data->path, git_buf_cstr(data->path), file->path) < 0)
+               return -1;
+
+       if ((error = git_blob_lookup(&blob, data->owner, &file->oid)) < 0)
+               return error;
 
-       if (S_ISLNK(filemode))
-               error = blob_content_to_link(blob, path, can_symlink);
+       if (S_ISLNK(file->mode))
+               error = blob_content_to_link(
+                       blob, git_buf_cstr(data->path), data->can_symlink);
        else
-               error = blob_content_to_file(blob, path, filemode, opts);
+               error = blob_content_to_file(
+                       blob, git_buf_cstr(data->path), file->mode, data->checkout_opts);
 
        git_blob_free(blob);
 
        return error;
 }
 
-static int checkout_diff_fn(
-       void *cb_data,
-       const git_diff_delta *delta,
-       float progress)
+static int checkout_remove_the_old(
+       void *cb_data, const git_diff_delta *delta, float progress)
 {
        struct checkout_diff_data *data = cb_data;
-       int error = 0;
        git_checkout_opts *opts = data->checkout_opts;
-       bool do_delete = false, do_checkout_blob = false;
-
-       data->stats->processed = (unsigned int)(data->stats->total * progress);
-
-       switch (delta->status) {
-       case GIT_DELTA_UNTRACKED:
-               if ((opts->checkout_strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0)
-                       do_delete = true;
-               break;
-
-       case GIT_DELTA_MODIFIED:
-       case GIT_DELTA_TYPECHANGE:
-               if (!(opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED)) {
-
-                       if (opts->skipped_notify_cb != NULL &&
-                               opts->skipped_notify_cb(
-                                       delta->new_file.path,
-                                       &delta->old_file.oid,
-                                       delta->old_file.mode,
-                                       opts->notify_payload) != 0)
-                       {
-                               giterr_clear();
-                               error = GIT_EUSER;
-                       }
-
-                       goto cleanup;
-               }
-
-               do_checkout_blob = true;
 
-               if (delta->status == GIT_DELTA_TYPECHANGE)
-                       do_delete = true;
-               break;
+       GIT_UNUSED(progress);
+       data->stats->processed++;
+
+       if ((delta->status == GIT_DELTA_UNTRACKED &&
+                (opts->checkout_strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0) ||
+               (delta->status == GIT_DELTA_TYPECHANGE &&
+                (opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED) != 0))
+       {
+               data->error = git_futils_rmdir_r(
+                       delta->new_file.path,
+                       git_repository_workdir(data->owner),
+                       GIT_DIRREMOVAL_FILES_AND_DIRS);
+       }
 
-       case GIT_DELTA_DELETED:
-               if ((opts->checkout_strategy & GIT_CHECKOUT_CREATE_MISSING) != 0)
-                       do_checkout_blob = true;
-               break;
+       return data->error;
+}
 
-       default:
-               giterr_set(GITERR_INVALID, "Unexpected status (%d) for path '%s'.",
-                       delta->status, delta->new_file.path);
-               error = -1;
-               goto cleanup;
+static int checkout_create_the_new(
+       void *cb_data, const git_diff_delta *delta, float progress)
+{
+       int error = 0;
+       struct checkout_diff_data *data = cb_data;
+       git_checkout_opts *opts = data->checkout_opts;
+       bool do_checkout = false, do_notify = false;
+
+       GIT_UNUSED(progress);
+       data->stats->processed++;
+
+       if (delta->status == GIT_DELTA_MODIFIED ||
+               delta->status == GIT_DELTA_TYPECHANGE)
+       {
+               if ((opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED) != 0)
+                       do_checkout = true;
+               else if (opts->skipped_notify_cb != NULL)
+                       do_notify = !data->create_submodules;
+       }
+       else if (delta->status == GIT_DELTA_DELETED &&
+                        (opts->checkout_strategy & GIT_CHECKOUT_CREATE_MISSING) != 0)
+               do_checkout = true;
+
+       if (do_notify) {
+               if (opts->skipped_notify_cb(
+                       delta->old_file.path, &delta->old_file.oid,
+                       delta->old_file.mode, opts->notify_payload))
+               {
+                       giterr_clear();
+                       error = GIT_EUSER;
+               }
        }
 
-       git_buf_truncate(data->path, data->workdir_len);
-
-       if ((error = git_buf_joinpath(
-                       data->path, git_buf_cstr(data->path), delta->new_file.path)) < 0)
-               goto cleanup;
+       if (do_checkout) {
+               bool is_submodule = S_ISGITLINK(delta->old_file.mode);
 
-       if (do_delete &&
-               (error = git_futils_rmdir_r(
-                       git_buf_cstr(data->path), GIT_DIRREMOVAL_FILES_AND_DIRS)) < 0)
-               goto cleanup;
+               if (!is_submodule && !data->create_submodules)
+                       error = checkout_blob(data, &delta->old_file);
 
-       if (do_checkout_blob)
-               error = checkout_blob(
-                       data->owner,
-                       &delta->old_file.oid,
-                       git_buf_cstr(data->path),
-                       delta->old_file.mode,
-                       data->can_symlink,
-                       opts);
+               else if (is_submodule && data->create_submodules)
+                       error = checkout_submodule(data, &delta->old_file);
+       }
 
-cleanup:
        if (error)
-               data->error = error; /* preserve real error */
+               data->error = error;
 
        return error;
 }
@@ -278,7 +303,6 @@ int git_checkout_index(
        git_checkout_opts *opts,
        git_indexer_stats *stats)
 {
-       git_index *index = NULL;
        git_diff_list *diff = NULL;
        git_indexer_stats dummy_stats;
 
@@ -292,11 +316,13 @@ int git_checkout_index(
 
        assert(repo);
 
-       if ((git_repository__ensure_not_bare(repo, "checkout")) < 0)
-               return GIT_EBAREREPO;
+       if ((error = git_repository__ensure_not_bare(repo, "checkout")) < 0)
+               return error;
 
-       diff_opts.flags = GIT_DIFF_INCLUDE_UNTRACKED |
-               GIT_DIFF_DONT_SPLIT_TYPECHANGE;
+       diff_opts.flags =
+               GIT_DIFF_INCLUDE_UNTRACKED |
+               GIT_DIFF_INCLUDE_TYPECHANGE |
+               GIT_DIFF_SKIP_BINARY_CHECK;
 
        if (opts && opts->paths.count > 0)
                diff_opts.pathspec = opts->paths;
@@ -313,11 +339,7 @@ int git_checkout_index(
                stats = &dummy_stats;
 
        stats->processed = 0;
-
-       if ((git_repository_index(&index, repo)) < 0)
-               goto cleanup;
-
-       stats->total = git_index_entrycount(index);
+       stats->total = (unsigned int)git_diff_num_deltas(diff) * 3 /* # passes */;
 
        memset(&data, 0, sizeof(data));
 
@@ -330,15 +352,33 @@ int git_checkout_index(
        if ((error = retrieve_symlink_capabilities(repo, &data.can_symlink)) < 0)
                goto cleanup;
 
-       error = git_diff_foreach(diff, &data, checkout_diff_fn, NULL, NULL);
+       /* Checkout is best performed with three passes through the diff.
+        *
+        * 1. First do removes, because we iterate in alphabetical order, thus
+        *    a new untracked directory will end up sorted *after* a blob that
+        *    should be checked out with the same name.
+        * 2. Then checkout all blobs.
+        * 3. Then checkout all submodules in case a new .gitmodules blob was
+        *    checked out during pass #2.
+        */
 
+       if (!(error = git_diff_foreach(
+                       diff, &data, checkout_remove_the_old, NULL, NULL)) &&
+               !(error = git_diff_foreach(
+                       diff, &data, checkout_create_the_new, NULL, NULL)))
+       {
+               data.create_submodules = true;
+               error = git_diff_foreach(
+                       diff, &data, checkout_create_the_new, NULL, NULL);
+       }
+
+cleanup:
        if (error == GIT_EUSER)
                error = (data.error != 0) ? data.error : -1;
 
-cleanup:
-       git_index_free(index);
        git_diff_list_free(diff);
        git_buf_free(&workdir);
+
        return error;
 }
 
index 00e39d3b5102721b5e3b8bfe6f137c015aea1595..d16d098433190057f03978e38b208cf8399109d8 100644 (file)
@@ -314,7 +314,7 @@ static int clone_internal(
                if ((retcode = setup_remotes_and_fetch(repo, origin_url, fetch_stats)) < 0) {
                        /* Failed to fetch; clean up */
                        git_repository_free(repo);
-                       git_futils_rmdir_r(path, GIT_DIRREMOVAL_FILES_AND_DIRS);
+                       git_futils_rmdir_r(path, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS);
                } else {
                        *out = repo;
                        retcode = 0;
index f88bda4aad89b9543077a961d3b3ff9633d6a437..7f500b8e88e0276606761d7438237a0dcc4315f3 100644 (file)
@@ -291,6 +291,36 @@ static int diff_delta__from_two(
        return 0;
 }
 
+static git_diff_delta *diff_delta__last_for_item(
+       git_diff_list *diff,
+       const git_index_entry *item)
+{
+       git_diff_delta *delta = git_vector_last(&diff->deltas);
+       if (!delta)
+               return NULL;
+
+       switch (delta->status) {
+       case GIT_DELTA_UNMODIFIED:
+       case GIT_DELTA_DELETED:
+               if (git_oid_cmp(&delta->old_file.oid, &item->oid) == 0)
+                       return delta;
+               break;
+       case GIT_DELTA_ADDED:
+               if (git_oid_cmp(&delta->new_file.oid, &item->oid) == 0)
+                       return delta;
+               break;
+       case GIT_DELTA_MODIFIED:
+               if (git_oid_cmp(&delta->old_file.oid, &item->oid) == 0 ||
+                       git_oid_cmp(&delta->new_file.oid, &item->oid) == 0)
+                       return delta;
+               break;
+       default:
+               break;
+       }
+
+       return NULL;
+}
+
 static char *diff_strdup_prefix(git_pool *pool, const char *prefix)
 {
        size_t len = strlen(prefix);
@@ -368,6 +398,10 @@ static git_diff_list *git_diff_list_alloc(
                diff->opts.new_prefix = swap;
        }
 
+       /* INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */
+       if (diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES)
+               diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE;
+
        /* only copy pathspec if it is "interesting" so we can test
         * diff->pathspec.length > 0 to know if it is worth calling
         * fnmatch as we iterate.
@@ -537,7 +571,7 @@ static int maybe_modified(
 
        /* if basic type of file changed, then split into delete and add */
        else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) {
-               if ((diff->opts.flags & GIT_DIFF_DONT_SPLIT_TYPECHANGE) != 0)
+               if ((diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE) != 0)
                        status = GIT_DELTA_TYPECHANGE;
                else {
                        if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||
@@ -590,7 +624,7 @@ static int maybe_modified(
                                /* grab OID while we are here */
                                if (git_oid_iszero(&nitem->oid)) {
                                        const git_oid *sm_oid = git_submodule_wd_oid(sub);
-                                       if (sub != NULL) {
+                                       if (sm_oid != NULL) {
                                                git_oid_cpy(&noid, sm_oid);
                                                use_noid = &noid;
                                        }
@@ -632,6 +666,24 @@ static int git_index_entry_cmp_icase(const void *a, const void *b)
        return strcasecmp(entry_a->path, entry_b->path);
 }
 
+static bool entry_is_prefixed(
+       const git_index_entry *item,
+       git_iterator *prefix_iterator,
+       const git_index_entry *prefix_item)
+{
+       size_t pathlen;
+
+       if (!prefix_item ||
+               ITERATOR_PREFIXCMP(*prefix_iterator, prefix_item->path, item->path))
+               return false;
+
+       pathlen = strlen(item->path);
+
+       return (item->path[pathlen - 1] == '/' ||
+                       prefix_item->path[pathlen] == '\0' ||
+                       prefix_item->path[pathlen] == '/');
+}
+
 static int diff_from_iterators(
        git_repository *repo,
        const git_diff_options *opts, /**< can be NULL for defaults */
@@ -681,8 +733,24 @@ static int diff_from_iterators(
 
                /* create DELETED records for old items not matched in new */
                if (oitem && (!nitem || entry_compare(oitem, nitem) < 0)) {
-                       if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0 ||
-                               git_iterator_advance(old_iter, &oitem) < 0)
+                       if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0)
+                               goto fail;
+
+                       /* if we are generating TYPECHANGE records then check for that
+                        * instead of just generating a DELETE record
+                        */
+                       if ((diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) != 0 &&
+                               entry_is_prefixed(oitem, new_iter, nitem))
+                       {
+                               /* this entry has become a tree! convert to TYPECHANGE */
+                               git_diff_delta *last = diff_delta__last_for_item(diff, oitem);
+                               if (last) {
+                                       last->status = GIT_DELTA_TYPECHANGE;
+                                       last->new_file.mode = GIT_FILEMODE_TREE;
+                               }
+                       }
+
+                       if (git_iterator_advance(old_iter, &oitem) < 0)
                                goto fail;
                }
 
@@ -704,8 +772,7 @@ static int diff_from_iterators(
                                 * directories and it is not under an ignored directory.
                                 */
                                bool contains_tracked =
-                                       (oitem &&
-                                        !ITERATOR_PREFIXCMP(*old_iter, oitem->path, nitem->path));
+                                       entry_is_prefixed(nitem, old_iter, oitem);
                                bool recurse_untracked =
                                        (delta_type == GIT_DELTA_UNTRACKED &&
                                         (diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) != 0);
@@ -761,8 +828,25 @@ static int diff_from_iterators(
                        else if (new_iter->type != GIT_ITERATOR_WORKDIR)
                                delta_type = GIT_DELTA_ADDED;
 
-                       if (diff_delta__from_one(diff, delta_type, nitem) < 0 ||
-                               git_iterator_advance(new_iter, &nitem) < 0)
+                       if (diff_delta__from_one(diff, delta_type, nitem) < 0)
+                               goto fail;
+
+                       /* if we are generating TYPECHANGE records then check for that
+                        * instead of just generating an ADD/UNTRACKED record
+                        */
+                       if (delta_type != GIT_DELTA_IGNORED &&
+                               (diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) != 0 &&
+                               entry_is_prefixed(nitem, old_iter, oitem))
+                       {
+                               /* this entry was a tree! convert to TYPECHANGE */
+                               git_diff_delta *last = diff_delta__last_for_item(diff, oitem);
+                               if (last) {
+                                       last->status = GIT_DELTA_TYPECHANGE;
+                                       last->old_file.mode = GIT_FILEMODE_TREE;
+                               }
+                       }
+
+                       if (git_iterator_advance(new_iter, &nitem) < 0)
                                goto fail;
                }
 
index 10fbd391cdbe3e5c8a4bda961dd1e357b6cc65c2..5f0d13c647279a4b3c3a1fbdd7ca9efeeadb8d5d 100644 (file)
@@ -533,6 +533,11 @@ static int diff_patch_load(
        if (delta->binary == 1)
                goto cleanup;
 
+       if (!ctxt->hunk_cb &&
+               !ctxt->data_cb &&
+               (ctxt->opts->flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0)
+               goto cleanup;
+
        switch (delta->status) {
        case GIT_DELTA_ADDED:
                delta->old_file.flags |= GIT_DIFF_FILE_NO_DATA;
@@ -698,8 +703,10 @@ static int diff_patch_generate(
        if ((patch->flags & GIT_DIFF_PATCH_DIFFABLE) == 0)
                return 0;
 
-       if (ctxt)
-               patch->ctxt = ctxt;
+       if (!ctxt->file_cb && !ctxt->hunk_cb)
+               return 0;
+
+       patch->ctxt = ctxt;
 
        memset(&xdiff_callback, 0, sizeof(xdiff_callback));
        xdiff_callback.outf = diff_patch_cb;
@@ -1360,7 +1367,9 @@ int git_diff_get_patch(
        if (delta_ptr)
                *delta_ptr = delta;
 
-       if (!patch_ptr && delta->binary != -1)
+       if (!patch_ptr &&
+               (delta->binary != -1 ||
+                (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0))
                return 0;
 
        diff_context_init(
index 8ccf063d5d69e9390752d77e86a8faf81f97f062..23bfa8e55d0ad96a70b767b63891f83a0095d8e4 100644 (file)
@@ -323,10 +323,6 @@ static int _rmdir_recurs_foreach(void *opaque, git_buf *path)
 {
        git_directory_removal_type removal_type = *(git_directory_removal_type *)opaque;
 
-       assert(removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY
-               || removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS
-               || removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS);
-
        if (git_path_isdir(path->ptr) == true) {
                if (git_path_direach(path, _rmdir_recurs_foreach, opaque) < 0)
                        return -1;
@@ -359,15 +355,24 @@ static int _rmdir_recurs_foreach(void *opaque, git_buf *path)
        return 0;
 }
 
-int git_futils_rmdir_r(const char *path, git_directory_removal_type removal_type)
+int git_futils_rmdir_r(
+       const char *path, const char *base, git_directory_removal_type removal_type)
 {
        int error;
-       git_buf p = GIT_BUF_INIT;
+       git_buf fullpath = GIT_BUF_INIT;
+
+       assert(removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY
+               || removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS
+               || removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS);
+
+       /* build path and find "root" where we should start calling mkdir */
+       if (git_path_join_unrooted(&fullpath, path, base, NULL) < 0)
+               return -1;
+
+       error = _rmdir_recurs_foreach(&removal_type, &fullpath);
+
+       git_buf_free(&fullpath);
 
-       error = git_buf_sets(&p, path);
-       if (!error)
-               error = _rmdir_recurs_foreach(&removal_type, &p);
-       git_buf_free(&p);
        return error;
 }
 
index d2944f4603bf827063d5ae580540f004c5cd58b7..19f7ffd540f7cc6807e873311d198d7debcc1795 100644 (file)
@@ -107,15 +107,17 @@ typedef enum {
  * Remove path and any files and directories beneath it.
  *
  * @param path Path to to top level directory to process.
- *
+ * @param base Root for relative path.
  * @param removal_type GIT_DIRREMOVAL_EMPTY_HIERARCHY to remove a hierarchy
- * of empty directories (will fail if any file is found), GIT_DIRREMOVAL_FILES_AND_DIRS
- * to remove a hierarchy of files and folders, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS to only remove
- * empty directories (no failure on file encounter).
+ *                     of empty directories (will fail if any file is found),
+ *                     GIT_DIRREMOVAL_FILES_AND_DIRS to remove a hierarchy of
+ *                     files and folders,
+ *                     GIT_DIRREMOVAL_ONLY_EMPTY_DIRS to only remove empty
+ *                     directories (no failure on file encounter).
  *
  * @return 0 on success; -1 on error.
  */
-extern int git_futils_rmdir_r(const char *path, git_directory_removal_type removal_type);
+extern int git_futils_rmdir_r(const char *path, const char *base, git_directory_removal_type removal_type);
 
 /**
  * Create and open a temporary file with a `_git2_` suffix.
index 267687e01d8581386a293ec8b71148bc1218519c..df6da9a87a8b5d7600054b0c0cfabe3e82732ac5 100644 (file)
@@ -82,7 +82,7 @@ int git_iterator_for_nothing(git_iterator **iter)
 
 typedef struct tree_iterator_frame tree_iterator_frame;
 struct tree_iterator_frame {
-       tree_iterator_frame *next;
+       tree_iterator_frame *next, *prev;
        git_tree *tree;
        char *start;
        unsigned int index;
@@ -91,7 +91,7 @@ struct tree_iterator_frame {
 typedef struct {
        git_iterator base;
        git_repository *repo;
-       tree_iterator_frame *stack;
+       tree_iterator_frame *stack, *tail;
        git_index_entry entry;
        git_buf path;
        bool path_has_filename;
@@ -119,8 +119,10 @@ static void tree_iterator__pop_frame(tree_iterator *ti)
 {
        tree_iterator_frame *tf = ti->stack;
        ti->stack = tf->next;
-       if (ti->stack != NULL) /* don't free the initial tree */
-               git_tree_free(tf->tree);
+       if (ti->stack != NULL) {
+               git_tree_free(tf->tree); /* don't free the initial tree */
+               ti->stack->prev = NULL;  /* disconnect prev */
+       }
        git__free(tf);
 }
 
@@ -221,6 +223,7 @@ static int tree_iterator__expand_tree(tree_iterator *ti)
 
                tf->next  = ti->stack;
                ti->stack = tf;
+               tf->next->prev = tf;
 
                te = tree_iterator__tree_entry(ti);
        }
@@ -312,7 +315,7 @@ int git_iterator_for_tree_range(
        ITERATOR_BASE_INIT(ti, tree, TREE);
 
        ti->repo  = repo;
-       ti->stack = tree_iterator__alloc_frame(tree, ti->base.start);
+       ti->stack = ti->tail = tree_iterator__alloc_frame(tree, ti->base.start);
 
        if ((error = tree_iterator__expand_tree(ti)) < 0)
                git_iterator_free((git_iterator *)ti);
@@ -864,6 +867,45 @@ int git_iterator_current_tree_entry(
        return 0;
 }
 
+int git_iterator_current_parent_tree(
+       git_iterator *iter,
+       const char *parent_path,
+       const git_tree **tree_ptr)
+{
+       tree_iterator *ti = (tree_iterator *)iter;
+       tree_iterator_frame *tf;
+       const char *scan = parent_path;
+
+       if (iter->type != GIT_ITERATOR_TREE || ti->stack == NULL)
+               goto notfound;
+
+       for (tf = ti->tail; tf != NULL; tf = tf->prev) {
+               const git_tree_entry *te;
+
+               if (!*scan) {
+                       *tree_ptr = tf->tree;
+                       return 0;
+               }
+
+               te = git_tree_entry_byindex(tf->tree, tf->index);
+
+               if (strncmp(scan, te->filename, te->filename_len) != 0)
+                       goto notfound;
+
+               scan += te->filename_len;
+
+               if (*scan) {
+                       if (*scan != '/')
+                               goto notfound;
+                       scan++;
+               }
+       }
+
+notfound:
+       *tree_ptr = NULL;
+       return 0;
+}
+
 int git_iterator_current_is_ignored(git_iterator *iter)
 {
        return (iter->type != GIT_ITERATOR_WORKDIR) ? 0 :
index 29c8985d405335a8a66eda8f68bfc46542f05ba7..d7df50137f05753c8e88785fd9e69fa3973dfef8 100644 (file)
@@ -142,6 +142,9 @@ GIT_INLINE(git_iterator_type_t) git_iterator_type(git_iterator *iter)
 extern int git_iterator_current_tree_entry(
        git_iterator *iter, const git_tree_entry **tree_entry);
 
+extern int git_iterator_current_parent_tree(
+       git_iterator *iter, const char *parent_path, const git_tree **tree_ptr);
+
 extern int git_iterator_current_is_ignored(git_iterator *iter);
 
 /**
index 80e40b960fd2c3728e9b6dcd37c6cfd3f38b785f..a1ea7a27d2295c42b33dfcd33d731e9c263e4d58 100644 (file)
@@ -372,7 +372,7 @@ int git_reflog_rename(git_reference *ref, const char *new_name)
                goto cleanup;
 
        if (git_path_isdir(git_buf_cstr(&new_path)) && 
-               (git_futils_rmdir_r(git_buf_cstr(&new_path), GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0))
+               (git_futils_rmdir_r(git_buf_cstr(&new_path), NULL, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0))
                goto cleanup;
 
        if (git_futils_mkpath2file(git_buf_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0)
index 9dc422e1b7e86224023279936d32adbdc8fdabd9..9249391e10fc7fb359dad93733ac1942410597f5 100644 (file)
@@ -262,14 +262,15 @@ static int loose_write(git_reference *ref)
        if (git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name) < 0)
                return -1;
 
-       /* Remove a possibly existing empty directory hierarchy 
+       /* Remove a possibly existing empty directory hierarchy
         * which name would collide with the reference name
         */
-       if (git_path_isdir(git_buf_cstr(&ref_path)) && 
-               (git_futils_rmdir_r(git_buf_cstr(&ref_path), GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0)) {
-                       git_buf_free(&ref_path);
-                       return -1;
-               }
+       if (git_path_isdir(git_buf_cstr(&ref_path)) &&
+               git_futils_rmdir_r(git_buf_cstr(&ref_path), NULL,
+                       GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0) {
+               git_buf_free(&ref_path);
+               return -1;
+       }
 
        if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) {
                git_buf_free(&ref_path);
index e39006e938d0d6405e56404e8172381cabe7a8d4..50ac19d3b44bbe0635709a4925f4e45fb344a56d 100644 (file)
@@ -100,7 +100,7 @@ int git_status_foreach_ext(
        memset(&diffopt, 0, sizeof(diffopt));
        memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec));
 
-       diffopt.flags = GIT_DIFF_DONT_SPLIT_TYPECHANGE;
+       diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE;
 
        if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0)
                diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED;
index 83aa303d444debe9d47b425fd5e57ae2317cad42..8d3f2665c273b98f2cc3a954772c520b8707d07f 100644 (file)
@@ -180,9 +180,9 @@ void git_tree__free(git_tree *tree)
        git__free(tree);
 }
 
-const git_oid *git_tree_id(git_tree *c)
+const git_oid *git_tree_id(const git_tree *c)
 {
-       return git_object_id((git_object *)c);
+       return git_object_id((const git_object *)c);
 }
 
 git_filemode_t git_tree_entry_filemode(const git_tree_entry *entry)
@@ -286,7 +286,7 @@ int git_tree__prefix_position(git_tree *tree, const char *path)
        return at_pos;
 }
 
-unsigned int git_tree_entrycount(git_tree *tree)
+unsigned int git_tree_entrycount(const git_tree *tree)
 {
        assert(tree);
        return (unsigned int)tree->entries.length;
diff --git a/tests-clar/checkout/typechange.c b/tests-clar/checkout/typechange.c
new file mode 100644 (file)
index 0000000..f013617
--- /dev/null
@@ -0,0 +1,72 @@
+#include "clar_libgit2.h"
+#include "git2/checkout.h"
+#include "path.h"
+#include "posix.h"
+
+static git_repository *g_repo = NULL;
+
+static const char *g_typechange_oids[] = {
+       "79b9f23e85f55ea36a472a902e875bc1121a94cb",
+       "9bdb75b73836a99e3dbeea640a81de81031fdc29",
+       "0e7ed140b514b8cae23254cb8656fe1674403aff",
+       "9d0235c7a7edc0889a18f97a42ee6db9fe688447",
+       "9b19edf33a03a0c59cdfc113bfa5c06179bf9b1a",
+       "1b63caae4a5ca96f78e8dfefc376c6a39a142475",
+       "6eae26c90e8ccc4d16208972119c40635489c6f0",
+       NULL
+};
+
+static bool g_typechange_empty[] = {
+       true, false, false, false, false, false, true, true
+};
+
+void test_checkout_typechange__initialize(void)
+{
+       g_repo = cl_git_sandbox_init("typechanges");
+
+       cl_fixture_sandbox("submod2_target");
+       p_rename("submod2_target/.gitted", "submod2_target/.git");
+}
+
+void test_checkout_typechange__cleanup(void)
+{
+       cl_git_sandbox_cleanup();
+       cl_fixture_cleanup("submod2_target");
+}
+
+void test_checkout_typechange__checkout_typechanges(void)
+{
+       int i;
+       git_object *obj;
+       git_checkout_opts opts = {0};
+
+       opts.checkout_strategy =
+               GIT_CHECKOUT_REMOVE_UNTRACKED |
+               GIT_CHECKOUT_CREATE_MISSING |
+               GIT_CHECKOUT_OVERWRITE_MODIFIED;
+
+       for (i = 0; g_typechange_oids[i] != NULL; ++i) {
+               cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i]));
+               /* fprintf(stderr, "checking out '%s'\n", g_typechange_oids[i]); */
+
+               cl_git_pass(git_checkout_tree(g_repo, obj, &opts, NULL));
+
+               git_object_free(obj);
+
+               if (!g_typechange_empty[i]) {
+                       cl_assert(git_path_isdir("typechanges"));
+                       cl_assert(git_path_exists("typechanges/a"));
+                       cl_assert(git_path_exists("typechanges/b"));
+                       cl_assert(git_path_exists("typechanges/c"));
+                       cl_assert(git_path_exists("typechanges/d"));
+                       cl_assert(git_path_exists("typechanges/e"));
+               } else {
+                       cl_assert(git_path_isdir("typechanges"));
+                       cl_assert(!git_path_exists("typechanges/a"));
+                       cl_assert(!git_path_exists("typechanges/b"));
+                       cl_assert(!git_path_exists("typechanges/c"));
+                       cl_assert(!git_path_exists("typechanges/d"));
+                       cl_assert(!git_path_exists("typechanges/e"));
+               }
+       }
+}
index 2fdfed863f0268b18833b9031ddb9044cda6ea4d..d0b21f6ec8cfd5e035143cf4c0e0042754b2f2d4 100644 (file)
@@ -41,7 +41,7 @@ void test_core_copy__file_in_dir(void)
        cl_assert(S_ISREG(st.st_mode));
        cl_assert(strlen(content) == (size_t)st.st_size);
 
-       cl_git_pass(git_futils_rmdir_r("an_dir", GIT_DIRREMOVAL_FILES_AND_DIRS));
+       cl_git_pass(git_futils_rmdir_r("an_dir", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
        cl_assert(!git_path_isdir("an_dir"));
 }
 
@@ -95,7 +95,7 @@ void test_core_copy__tree(void)
        cl_assert(S_ISLNK(st.st_mode));
 #endif
 
-       cl_git_pass(git_futils_rmdir_r("t1", GIT_DIRREMOVAL_FILES_AND_DIRS));
+       cl_git_pass(git_futils_rmdir_r("t1", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
        cl_assert(!git_path_isdir("t1"));
 
        /* copy with empty dirs, no links, yes dotfiles, no overwrite */
@@ -119,8 +119,8 @@ void test_core_copy__tree(void)
        cl_git_fail(git_path_lstat("t2/c/d/l1", &st));
 #endif
 
-       cl_git_pass(git_futils_rmdir_r("t2", GIT_DIRREMOVAL_FILES_AND_DIRS));
+       cl_git_pass(git_futils_rmdir_r("t2", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
        cl_assert(!git_path_isdir("t2"));
 
-       cl_git_pass(git_futils_rmdir_r("src", GIT_DIRREMOVAL_FILES_AND_DIRS));
+       cl_git_pass(git_futils_rmdir_r("src", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
 }
index 08ba2419e67dccad0d5079180cf3eb1b09401d2b..e5dc6654bbb24ab21abdbf97c7b286d3dcbe22b6 100644 (file)
@@ -6,11 +6,11 @@
 static void cleanup_basic_dirs(void *ref)
 {
        GIT_UNUSED(ref);
-       git_futils_rmdir_r("d0", GIT_DIRREMOVAL_EMPTY_HIERARCHY);
-       git_futils_rmdir_r("d1", GIT_DIRREMOVAL_EMPTY_HIERARCHY);
-       git_futils_rmdir_r("d2", GIT_DIRREMOVAL_EMPTY_HIERARCHY);
-       git_futils_rmdir_r("d3", GIT_DIRREMOVAL_EMPTY_HIERARCHY);
-       git_futils_rmdir_r("d4", GIT_DIRREMOVAL_EMPTY_HIERARCHY);
+       git_futils_rmdir_r("d0", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY);
+       git_futils_rmdir_r("d1", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY);
+       git_futils_rmdir_r("d2", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY);
+       git_futils_rmdir_r("d3", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY);
+       git_futils_rmdir_r("d4", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY);
 }
 
 void test_core_mkdir__basic(void)
@@ -56,7 +56,7 @@ void test_core_mkdir__basic(void)
 static void cleanup_basedir(void *ref)
 {
        GIT_UNUSED(ref);
-       git_futils_rmdir_r("base", GIT_DIRREMOVAL_EMPTY_HIERARCHY);
+       git_futils_rmdir_r("base", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY);
 }
 
 void test_core_mkdir__with_base(void)
@@ -108,7 +108,7 @@ static void cleanup_chmod_root(void *ref)
                git__free(mode);
        }
 
-       git_futils_rmdir_r("r", GIT_DIRREMOVAL_EMPTY_HIERARCHY);
+       git_futils_rmdir_r("r", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY);
 }
 
 static void check_mode(mode_t expected, mode_t actual)
index 530f1f9089a4213826022d3a077dd39d1de05d5c..9ada8f42651f195b943a66f685d79f7eab367fb7 100644 (file)
@@ -30,7 +30,7 @@ void test_core_rmdir__initialize(void)
 /* make sure empty dir can be deleted recusively */
 void test_core_rmdir__delete_recursive(void)
 {
-       cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_EMPTY_HIERARCHY));
+       cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY));
 }
 
 /* make sure non-empty dir cannot be deleted recusively */
@@ -42,10 +42,10 @@ void test_core_rmdir__fail_to_delete_non_empty_dir(void)
 
        cl_git_mkfile(git_buf_cstr(&file), "dummy");
 
-       cl_git_fail(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_EMPTY_HIERARCHY));
+       cl_git_fail(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY));
 
        cl_must_pass(p_unlink(file.ptr));
-       cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_EMPTY_HIERARCHY));
+       cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY));
 
        git_buf_free(&file);
 }
@@ -58,10 +58,10 @@ void test_core_rmdir__can_skip__non_empty_dir(void)
 
        cl_git_mkfile(git_buf_cstr(&file), "dummy");
 
-       cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS));
+       cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS));
        cl_assert(git_path_exists(git_buf_cstr(&file)) == true);
 
-       cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, GIT_DIRREMOVAL_FILES_AND_DIRS));
+       cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
        cl_assert(git_path_exists(empty_tmp_dir) == false);
 
        git_buf_free(&file);
index c27d3fa6cf2fea6139ab2f2ab8ba647c2c0d7631..023bc465af75b50dbd77b3296a4131af0ec01d18 100644 (file)
@@ -1,6 +1,7 @@
 #include "clar_libgit2.h"
 #include "diff_helpers.h"
 #include "iterator.h"
+#include "tree.h"
 
 void test_diff_iterator__initialize(void)
 {
@@ -237,6 +238,103 @@ void test_diff_iterator__tree_range_empty_2(void)
                NULL, ".aaa_empty_before", 0, NULL);
 }
 
+static void check_tree_entry(
+       git_iterator *i,
+       const char *oid,
+       const char *oid_p,
+       const char *oid_pp,
+       const char *oid_ppp)
+{
+       const git_index_entry *ie;
+       const git_tree_entry *te;
+       const git_tree *tree;
+       git_buf path = GIT_BUF_INIT;
+
+       cl_git_pass(git_iterator_current_tree_entry(i, &te));
+       cl_assert(te);
+       cl_assert(git_oid_streq(&te->oid, oid) == 0);
+
+       cl_git_pass(git_iterator_current(i, &ie));
+       cl_git_pass(git_buf_sets(&path, ie->path));
+
+       if (oid_p) {
+               git_buf_rtruncate_at_char(&path, '/');
+               cl_git_pass(git_iterator_current_parent_tree(i, path.ptr, &tree));
+               cl_assert(tree);
+               cl_assert(git_oid_streq(git_tree_id(tree), oid_p) == 0);
+       }
+
+       if (oid_pp) {
+               git_buf_rtruncate_at_char(&path, '/');
+               cl_git_pass(git_iterator_current_parent_tree(i, path.ptr, &tree));
+               cl_assert(tree);
+               cl_assert(git_oid_streq(git_tree_id(tree), oid_pp) == 0);
+       }
+
+       if (oid_ppp) {
+               git_buf_rtruncate_at_char(&path, '/');
+               cl_git_pass(git_iterator_current_parent_tree(i, path.ptr, &tree));
+               cl_assert(tree);
+               cl_assert(git_oid_streq(git_tree_id(tree), oid_ppp) == 0);
+       }
+
+       git_buf_free(&path);
+}
+
+void test_diff_iterator__tree_special_functions(void)
+{
+       git_tree *t;
+       git_iterator *i;
+       const git_index_entry *entry;
+       git_repository *repo = cl_git_sandbox_init("attr");
+       int cases = 0;
+       const char *rootoid = "ce39a97a7fb1fa90bcf5e711249c1e507476ae0e";
+
+       t = resolve_commit_oid_to_tree(
+               repo, "24fa9a9fc4e202313e24b648087495441dab432b");
+       cl_assert(t != NULL);
+
+       cl_git_pass(git_iterator_for_tree_range(&i, repo, t, NULL, NULL));
+       cl_git_pass(git_iterator_current(i, &entry));
+
+       while (entry != NULL) {
+               if (strcmp(entry->path, "sub/file") == 0) {
+                       cases++;
+                       check_tree_entry(
+                               i, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057",
+                               "ecb97df2a174987475ac816e3847fc8e9f6c596b",
+                               rootoid, NULL);
+               }
+               else if (strcmp(entry->path, "sub/sub/subsub.txt") == 0) {
+                       cases++;
+                       check_tree_entry(
+                               i, "9e5bdc47d6a80f2be0ea3049ad74231b94609242",
+                               "4e49ba8c5b6c32ff28cd9dcb60be34df50fcc485",
+                               "ecb97df2a174987475ac816e3847fc8e9f6c596b", rootoid);
+               }
+               else if (strcmp(entry->path, "subdir/.gitattributes") == 0) {
+                       cases++;
+                       check_tree_entry(
+                               i, "99eae476896f4907224978b88e5ecaa6c5bb67a9",
+                               "9fb40b6675dde60b5697afceae91b66d908c02d9",
+                               rootoid, NULL);
+               }
+               else if (strcmp(entry->path, "subdir2/subdir2_test1") == 0) {
+                       cases++;
+                       check_tree_entry(
+                               i, "dccada462d3df8ac6de596fb8c896aba9344f941",
+                               "2929de282ce999e95183aedac6451d3384559c4b",
+                               rootoid, NULL);
+               }
+
+               cl_git_pass(git_iterator_advance(i, &entry));
+       }
+
+       cl_assert_equal_i(4, cases);
+       git_iterator_free(i);
+       git_tree_free(t);
+}
+
 /* -- INDEX ITERATOR TESTS -- */
 
 static void index_iterator_test(
index 722c7b956e3164c0c1cf6d11e37303f66ea3cffc..87a9e20725959c0029a8d844f1a47895dbe240ae 100644 (file)
@@ -49,7 +49,7 @@ void test_object_blob_write__can_create_a_blob_in_a_standard_repo_from_a_absolut
        assert_blob_creation(ELSEWHERE "/test.txt", git_buf_cstr(&full_path), &git_blob_create_fromdisk);
 
        git_buf_free(&full_path);
-       cl_must_pass(git_futils_rmdir_r(ELSEWHERE, GIT_DIRREMOVAL_FILES_AND_DIRS));
+       cl_must_pass(git_futils_rmdir_r(ELSEWHERE, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
 }
 
 void test_object_blob_write__can_create_a_blob_in_a_bare_repo_from_a_absolute_filepath(void)
@@ -65,5 +65,5 @@ void test_object_blob_write__can_create_a_blob_in_a_bare_repo_from_a_absolute_fi
        assert_blob_creation(ELSEWHERE "/test.txt", git_buf_cstr(&full_path), &git_blob_create_fromdisk);
 
        git_buf_free(&full_path);
-       cl_must_pass(git_futils_rmdir_r(ELSEWHERE, GIT_DIRREMOVAL_FILES_AND_DIRS));
+       cl_must_pass(git_futils_rmdir_r(ELSEWHERE, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
 }
index b3d639bd16fbe452545d04a31e38ebe53890f7fb..b5afab75ac4b2f055556a99a97be7c5991b1f0d4 100644 (file)
@@ -135,7 +135,7 @@ void test_repo_discover__0(void)
        ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs, sub_repository_path);
        ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs, repository_path);
 
-       cl_git_pass(git_futils_rmdir_r(TEMP_REPO_FOLDER, GIT_DIRREMOVAL_FILES_AND_DIRS));
+       cl_git_pass(git_futils_rmdir_r(TEMP_REPO_FOLDER, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
        git_repository_free(repo);
        git_buf_free(&ceiling_dirs_buf);
 }
index c70ec83a9dad892f2d95049388f38d83e51af091..ef912fa0e636c15f2f0fe7143961d79689fffe0a 100644 (file)
@@ -7,7 +7,7 @@ void test_repo_open__cleanup(void)
        cl_git_sandbox_cleanup();
 
        if (git_path_isdir("alternate"))
-               git_futils_rmdir_r("alternate", GIT_DIRREMOVAL_FILES_AND_DIRS);
+               git_futils_rmdir_r("alternate", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS);
 }
 
 void test_repo_open__bare_empty_repo(void)
@@ -202,8 +202,8 @@ void test_repo_open__bad_gitlinks(void)
                cl_git_fail(git_repository_open_ext(&repo, "alternate", 0, NULL));
        }
 
-       git_futils_rmdir_r("invalid", GIT_DIRREMOVAL_FILES_AND_DIRS);
-       git_futils_rmdir_r("invalid2", GIT_DIRREMOVAL_FILES_AND_DIRS);
+       git_futils_rmdir_r("invalid", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS);
+       git_futils_rmdir_r("invalid2", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS);
 }
 
 #ifdef GIT_WIN32
index 2f4c6d752fc0cea3022ed17ea6f31d4f038d0005..4f6d12a3b9e8c3ea13c0ef5fb2adae059a268558 100644 (file)
Binary files a/tests-clar/resources/typechanges/.gitted/index and b/tests-clar/resources/typechanges/.gitted/index differ
index 99e8bab9ece009f0fba7eb41f850f4c12bedb9b7..1f5a95a9fa86ccd90e916792dd05d8c2b4a8e256 100644 (file)
@@ -1,32 +1,43 @@
 This is a test repo for libgit2 where tree entries have type changes
 
+Types
+-----
+
 The key types that could be found in tree entries are:
 
-1 - GIT_FILEMODE_NEW             = 0000000
-2 - GIT_FILEMODE_TREE            = 0040000
-3 - GIT_FILEMODE_BLOB            = 0100644
-4 - GIT_FILEMODE_BLOB_EXECUTABLE = 0100755
-5 - GIT_FILEMODE_LINK            = 0120000
-6 - GIT_FILEMODE_COMMIT          = 0160000
+1. GIT_FILEMODE_NEW             = 0000000 (i.e. file does not exist)
+2. GIT_FILEMODE_TREE            = 0040000
+3. GIT_FILEMODE_BLOB            = 0100644
+4. GIT_FILEMODE_BLOB_EXECUTABLE = 0100755
+5. GIT_FILEMODE_LINK            = 0120000
+6. GIT_FILEMODE_COMMIT          = 0160000
 
 I will try to have every type of transition somewhere in the history
 of this repo.
 
 Commits
 -------
-Initial commit - a(1)    b(1)    c(1)    d(1)    e(1)
-  79b9f23e85f55ea36a472a902e875bc1121a94cb
-Create content - a(1->2) b(1->3) c(1->4) d(1->5) e(1->6)
-  9bdb75b73836a99e3dbeea640a81de81031fdc29
-Changes #1     - a(2->3) b(3->4) c(4->5) d(5->6) e(6->2)
-  0e7ed140b514b8cae23254cb8656fe1674403aff
-Changes #2     - a(3->5) b(4->6) c(5->2) d(6->3) e(2->4)
-  9d0235c7a7edc0889a18f97a42ee6db9fe688447
-Changes #3     - a(5->3) b(6->4) c(2->5) d(3->6) e(4->2)
-  9b19edf33a03a0c59cdfc113bfa5c06179bf9b1a
-Changes #4     - a(3->2) b(4->3) c(5->4) d(6->5) e(2->6)
-  1b63caae4a5ca96f78e8dfefc376c6a39a142475
-  Matches "Changes #1" except README.md
-Changes #5     - a(2->1) b(3->1) c(4->1) d(5->1) e(6->1)
-  6eae26c90e8ccc4d16208972119c40635489c6f0
-  Matches "Initial commit" except README.md and .gitmodules
+
+* `a(1--1) b(1--1) c(1--1) d(1--1) e(1--1)`
+  **Initial commit**<br>
+  `79b9f23e85f55ea36a472a902e875bc1121a94cb`
+* `a(1->2) b(1->3) c(1->4) d(1->5) e(1->6)`
+  **Create content**<br>
+  `9bdb75b73836a99e3dbeea640a81de81031fdc29`
+* `a(2->3) b(3->4) c(4->5) d(5->6) e(6->2)`
+  **Changes #1**<br>
+  `0e7ed140b514b8cae23254cb8656fe1674403aff`
+* `a(3->5) b(4->6) c(5->2) d(6->3) e(2->4)`
+  **Changes #2**<br>
+  `9d0235c7a7edc0889a18f97a42ee6db9fe688447`
+* `a(5->3) b(6->4) c(2->5) d(3->6) e(4->2)`
+  **Changes #3**<br>
+  `9b19edf33a03a0c59cdfc113bfa5c06179bf9b1a`
+* `a(3->2) b(4->3) c(5->4) d(6->5) e(2->6)`
+  **Changes #4**<br>
+  `1b63caae4a5ca96f78e8dfefc376c6a39a142475`<br>
+  Matches **Changes #1** except README.md
+* `a(2->1) b(3->1) c(4->1) d(5->1) e(6->1)`
+  **Changes #5**<br>
+  `6eae26c90e8ccc4d16208972119c40635489c6f0`<br>
+  Matches **Initial commit** except README.md and .gitmodules
index 0ceba7f164f3470bcb68c13a4ed11e8302d98ed9..4f03643c07b4b024d01766029357ccc82a9a4c31 100644 (file)
@@ -71,7 +71,7 @@ static int remove_file_cb(void *data, git_buf *file)
                return 0;
 
        if (git_path_isdir(filename))
-               cl_git_pass(git_futils_rmdir_r(filename, GIT_DIRREMOVAL_FILES_AND_DIRS));
+               cl_git_pass(git_futils_rmdir_r(filename, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
        else
                cl_git_pass(p_unlink(git_buf_cstr(file)));
 
@@ -314,7 +314,7 @@ void test_status_worktree__issue_592_3(void)
        repo = cl_git_sandbox_init("issue_592");
 
        cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "c"));
-       cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS));
+       cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
 
        cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt"));
 
@@ -344,7 +344,7 @@ void test_status_worktree__issue_592_5(void)
        repo = cl_git_sandbox_init("issue_592");
 
        cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "t"));
-       cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS));
+       cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
        cl_git_pass(p_mkdir(git_buf_cstr(&path), 0777));
 
        cl_git_pass(git_status_foreach(repo, cb_status__check_592, NULL));
index d3a39235a1c9c799998af3a18a8458019f9e11dd..63073ceca1a923c1f39b09eebc2359c8a365d138 100644 (file)
@@ -50,7 +50,7 @@ void test_submodule_status__ignore_none(void)
        git_buf path = GIT_BUF_INIT;
 
        cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "sm_unchanged"));
-       cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS));
+       cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
 
        cl_git_fail(git_submodule_lookup(&sm, g_repo, "not_submodule"));
 
@@ -135,7 +135,7 @@ void test_submodule_status__ignore_untracked(void)
        git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_UNTRACKED;
 
        cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "sm_unchanged"));
-       cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS));
+       cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
 
        cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign));
 
@@ -195,7 +195,7 @@ void test_submodule_status__ignore_dirty(void)
        git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_DIRTY;
 
        cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "sm_unchanged"));
-       cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS));
+       cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
 
        cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign));
 
@@ -255,7 +255,7 @@ void test_submodule_status__ignore_all(void)
        git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_ALL;
 
        cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "sm_unchanged"));
-       cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), GIT_DIRREMOVAL_FILES_AND_DIRS));
+       cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS));
 
        cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign));