From 1ee2ef87ec4c2c63b7c89849eb9daad9e46e6fe7 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 21 May 2013 11:05:21 -0500 Subject: [PATCH 01/16] status access by index, providing more details to callers --- include/git2/status.h | 63 ++++++++++++ include/git2/types.h | 3 + src/status.c | 232 ++++++++++++++++++++++++++++++------------ src/status.h | 23 +++++ 4 files changed, 255 insertions(+), 66 deletions(-) create mode 100644 src/status.h diff --git a/include/git2/status.h b/include/git2/status.h index 38b6fa5bd..ce1d44a06 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -174,9 +174,72 @@ typedef struct { git_strarray pathspec; } git_status_options; +/** + * A status entry, providing the differences between the file as it exists + * in HEAD and the index, and providing the differences between the index + * and the working directory. + * + * The `status` value provides the status flags for this file. + * + * The `head_to_index` value provides detailed information about the + * differences between the file in HEAD and the file in the index. + * + * The `index_to_workdir` value provides detailed information about the + * differences between the file in the index and the file in the + * working directory. + */ +typedef struct { + git_status_t status; + git_diff_delta *head_to_index; + git_diff_delta *index_to_workdir; +} git_status_entry; + #define GIT_STATUS_OPTIONS_VERSION 1 #define GIT_STATUS_OPTIONS_INIT {GIT_STATUS_OPTIONS_VERSION} +/** + * Gather file status information and populate the `git_status_list`. + * + * @param out Pointer to store the status results in + * @param repo Repository object + * @param opts Status options structure + * @return 0 on success or error code + */ +GIT_EXTERN(int) git_status_list_new( + git_status_list **out, + git_repository *repo, + const git_status_options *opts); + +/** + * Gets the count of status entries in this list. + * + * @param statuslist Existing status list object + * @return the number of status entries + */ +GIT_EXTERN(size_t) git_status_list_entrycount( + git_status_list *statuslist); + +/** + * Get a pointer to one of the entries in the status list. + * + * The entry is not modifiable and should not be freed. + * + * @param statuslist Existing status list object + * @param idx Position of the entry + * @return Pointer to the entry; NULL if out of bounds + */ +GIT_EXTERN(const git_status_entry *) git_status_byindex( + git_status_list *statuslist, + size_t idx); + +/** + * Free an existing status list + * + * @param statuslist Existing status list object + */ +GIT_EXTERN(void) git_status_list_free( + git_status_list *statuslist); + /** * Gather file status information and run callbacks as requested. * diff --git a/include/git2/types.h b/include/git2/types.h index 1bfa73be6..dc344075c 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -174,6 +174,9 @@ typedef struct git_reference_iterator git_reference_iterator; /** Merge heads, the input to merge */ typedef struct git_merge_head git_merge_head; +/** Representation of a status collection */ +typedef struct git_status_list git_status_list; + /** Basic type of any Git reference. */ typedef enum { diff --git a/src/status.c b/src/status.c index 712e0d515..33e67efac 100644 --- a/src/status.c +++ b/src/status.c @@ -11,6 +11,7 @@ #include "hash.h" #include "vector.h" #include "tree.h" +#include "status.h" #include "git2/status.h" #include "repository.h" #include "ignore.h" @@ -77,71 +78,105 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status) return st; } -typedef struct { - git_status_cb cb; - void *payload; - const git_status_options *opts; -} status_user_callback; - -static int status_invoke_cb( - git_diff_delta *h2i, git_diff_delta *i2w, void *payload) +static bool status_is_included( + git_status_list *statuslist, + git_diff_delta *head2idx, + git_diff_delta *idx2wd) { - status_user_callback *usercb = payload; - const char *path = NULL; - unsigned int status = 0; - - if (i2w) { - path = i2w->old_file.path; - status |= workdir_delta2status(i2w->status); - } - if (h2i) { - path = h2i->old_file.path; - status |= index_delta2status(h2i->status); - } - /* if excluding submodules and this is a submodule everywhere */ - if (usercb->opts && - (usercb->opts->flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) - { - bool in_tree = (h2i && h2i->status != GIT_DELTA_ADDED); - bool in_index = (h2i && h2i->status != GIT_DELTA_DELETED); - bool in_wd = (i2w && i2w->status != GIT_DELTA_DELETED); + if ((statuslist->opts.flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) { + bool in_tree = (head2idx && head2idx->status != GIT_DELTA_ADDED); + bool in_index = (head2idx && head2idx->status != GIT_DELTA_DELETED); + bool in_wd = (idx2wd && idx2wd->status != GIT_DELTA_DELETED); - if ((!in_tree || h2i->old_file.mode == GIT_FILEMODE_COMMIT) && - (!in_index || h2i->new_file.mode == GIT_FILEMODE_COMMIT) && - (!in_wd || i2w->new_file.mode == GIT_FILEMODE_COMMIT)) + if ((!in_tree || head2idx->old_file.mode == GIT_FILEMODE_COMMIT) && + (!in_index || head2idx->new_file.mode == GIT_FILEMODE_COMMIT) && + (!in_wd || idx2wd->new_file.mode == GIT_FILEMODE_COMMIT)) return 0; } - return usercb->cb(path, status, usercb->payload); + return 1; } -int git_status_foreach_ext( - git_repository *repo, - const git_status_options *opts, - git_status_cb cb, +static git_status_t status_compute( + git_diff_delta *head2idx, + git_diff_delta *idx2wd) +{ + git_status_t status = 0; + + if (head2idx) + status |= index_delta2status(head2idx->status); + + if (idx2wd) + status |= workdir_delta2status(idx2wd->status); + + return status; +} + +static int status_collect( + git_diff_delta *head2idx, + git_diff_delta *idx2wd, void *payload) { - int err = 0; + git_status_list *statuslist = payload; + git_status_entry *status_entry; + + if (!status_is_included(statuslist, head2idx, idx2wd)) + return 0; + + status_entry = git__malloc(sizeof(git_status_entry)); + GITERR_CHECK_ALLOC(status_entry); + + status_entry->status = status_compute(head2idx, idx2wd); + status_entry->head_to_index = head2idx; + status_entry->index_to_workdir = idx2wd; + + git_vector_insert(&statuslist->paired, status_entry); + + return 0; +} + +git_status_list *git_status_list_alloc(void) +{ + git_status_list *statuslist = NULL; + + if ((statuslist = git__calloc(1, sizeof(git_status_list))) == NULL || + git_vector_init(&statuslist->paired, 0, NULL) < 0) + return NULL; + + return statuslist; +} + +int git_status_list_new( + git_status_list **out, + git_repository *repo, + const git_status_options *opts) +{ + git_status_list *statuslist = NULL; git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT; - git_diff_list *head2idx = NULL, *idx2wd = NULL; git_tree *head = NULL; git_status_show_t show = opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR; - status_user_callback usercb; + int error = 0; assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR); + *out = NULL; + GITERR_CHECK_VERSION(opts, GIT_STATUS_OPTIONS_VERSION, "git_status_options"); - if (show != GIT_STATUS_SHOW_INDEX_ONLY && - (err = git_repository__ensure_not_bare(repo, "status")) < 0) - return err; + if ((error = git_repository__ensure_not_bare(repo, "status")) < 0) + return error; /* if there is no HEAD, that's okay - we'll make an empty iterator */ - if (((err = git_repository_head_tree(&head, repo)) < 0) && - !(err == GIT_ENOTFOUND || err == GIT_EORPHANEDHEAD)) - return err; + if (((error = git_repository_head_tree(&head, repo)) < 0) && + !(error == GIT_ENOTFOUND || error == GIT_EORPHANEDHEAD)) + return error; + + statuslist = git_status_list_alloc(); + GITERR_CHECK_ALLOC(statuslist); + + memcpy(&statuslist->opts, opts, sizeof(git_status_options)); memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec)); @@ -163,41 +198,106 @@ int git_status_foreach_ext( diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES; if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) { - err = git_diff_tree_to_index(&head2idx, repo, head, NULL, &diffopt); - if (err < 0) - goto cleanup; + error = git_diff_tree_to_index(&statuslist->head2idx, repo, head, NULL, &diffopt); + + if (error < 0) + goto on_error; } if (show != GIT_STATUS_SHOW_INDEX_ONLY) { - err = git_diff_index_to_workdir(&idx2wd, repo, NULL, &diffopt); - if (err < 0) - goto cleanup; - } + error = git_diff_index_to_workdir(&statuslist->idx2wd, repo, NULL, &diffopt); - usercb.cb = cb; - usercb.payload = payload; - usercb.opts = opts; + if (error < 0) + goto on_error; + } if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) { - if ((err = git_diff__paired_foreach( - head2idx, NULL, status_invoke_cb, &usercb)) < 0) - goto cleanup; + if ((error = git_diff__paired_foreach(statuslist->head2idx, NULL, status_collect, statuslist)) < 0) + goto on_error; - git_diff_list_free(head2idx); - head2idx = NULL; + git_diff_list_free(statuslist->head2idx); + statuslist->head2idx = NULL; } - err = git_diff__paired_foreach(head2idx, idx2wd, status_invoke_cb, &usercb); + if ((error = git_diff__paired_foreach(statuslist->head2idx, statuslist->idx2wd, status_collect, statuslist)) < 0) + goto on_error; -cleanup: + *out = statuslist; + goto done; + +on_error: + git_status_list_free(statuslist); + +done: git_tree_free(head); - git_diff_list_free(head2idx); - git_diff_list_free(idx2wd); - if (err == GIT_EUSER) - giterr_clear(); + return error; +} - return err; +size_t git_status_list_entrycount(git_status_list *statuslist) +{ + assert(statuslist); + + return statuslist->paired.length; +} + +const git_status_entry *git_status_byindex( + git_status_list *statuslist, + size_t i) +{ + assert(statuslist); + + return git_vector_get(&statuslist->paired, i); +} + +void git_status_list_free(git_status_list *statuslist) +{ + git_status_entry *status_entry; + size_t i; + + if (statuslist == NULL) + return; + + git_diff_list_free(statuslist->head2idx); + git_diff_list_free(statuslist->idx2wd); + + git_vector_foreach(&statuslist->paired, i, status_entry) + git__free(status_entry); + + git_vector_free(&statuslist->paired); + + git__free(statuslist); +} + +int git_status_foreach_ext( + git_repository *repo, + const git_status_options *opts, + git_status_cb cb, + void *payload) +{ + git_status_list *statuslist; + const git_status_entry *status_entry; + size_t i; + int error = 0; + + if ((error = git_status_list_new(&statuslist, repo, opts)) < 0) + return error; + + git_vector_foreach(&statuslist->paired, i, status_entry) { + const char *path = status_entry->head_to_index ? + status_entry->head_to_index->old_file.path : + status_entry->index_to_workdir->old_file.path; + + if (cb(path, status_entry->status, payload) != 0) { + error = GIT_EUSER; + giterr_clear(); + break; + } + } + + git_status_list_free(statuslist); + + return error; } int git_status_foreach( diff --git a/src/status.h b/src/status.h new file mode 100644 index 000000000..b58e0ebd6 --- /dev/null +++ b/src/status.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_status_h__ +#define INCLUDE_status_h__ + +#include "diff.h" +#include "git2/status.h" +#include "git2/diff.h" + +struct git_status_list { + git_status_options opts; + + git_diff_list *head2idx; + git_diff_list *idx2wd; + + git_vector paired; +}; + +#endif From dfe8c8df3707b2773e376633c5908dc612e59d6a Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 22 May 2013 23:19:40 -0500 Subject: [PATCH 02/16] handle renames in status computation --- include/git2/status.h | 21 ++- src/status.c | 108 +++++++++++++-- tests-clar/status/renames.c | 265 ++++++++++++++++++++++++++++++++++++ 3 files changed, 372 insertions(+), 22 deletions(-) create mode 100644 tests-clar/status/renames.c diff --git a/include/git2/status.h b/include/git2/status.h index ce1d44a06..64ce6756c 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -42,6 +42,7 @@ typedef enum { GIT_STATUS_WT_MODIFIED = (1u << 8), GIT_STATUS_WT_DELETED = (1u << 9), GIT_STATUS_WT_TYPECHANGE = (1u << 10), + GIT_STATUS_WT_RENAMED = (1u << 11), GIT_STATUS_IGNORED = (1u << 14), } git_status_t; @@ -130,6 +131,10 @@ typedef enum { * - GIT_STATUS_OPT_RECURSE_IGNORED_DIRS indicates that the contents of * ignored directories should be included in the status. This is like * doing `git ls-files -o -i --exclude-standard` with core git. + * - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX indicates that items that are + * renamed in the index will be reported as renames. + * - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates that items that + * are renamed in the working directory will be reported as renames. * * Calling `git_status_foreach()` is like calling the extended version * with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED, @@ -137,13 +142,15 @@ typedef enum { * together as `GIT_STATUS_OPT_DEFAULTS` if you want them as a baseline. */ typedef enum { - GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1u << 0), - GIT_STATUS_OPT_INCLUDE_IGNORED = (1u << 1), - GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1u << 2), - GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1u << 3), - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1u << 4), - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH = (1u << 5), - GIT_STATUS_OPT_RECURSE_IGNORED_DIRS = (1u << 6), + GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1u << 0), + GIT_STATUS_OPT_INCLUDE_IGNORED = (1u << 1), + GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1u << 2), + GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1u << 3), + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1u << 4), + GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH = (1u << 5), + GIT_STATUS_OPT_RECURSE_IGNORED_DIRS = (1u << 6), + GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX = (1u << 7), + GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR = (1u << 8), } git_status_opt_t; #define GIT_STATUS_OPT_DEFAULTS \ diff --git a/src/status.c b/src/status.c index 33e67efac..20e45b75f 100644 --- a/src/status.c +++ b/src/status.c @@ -54,7 +54,6 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status) switch (workdir_status) { case GIT_DELTA_ADDED: - case GIT_DELTA_RENAMED: case GIT_DELTA_COPIED: case GIT_DELTA_UNTRACKED: st = GIT_STATUS_WT_NEW; @@ -68,6 +67,9 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status) case GIT_DELTA_IGNORED: st = GIT_STATUS_IGNORED; break; + case GIT_DELTA_RENAMED: + st = GIT_STATUS_WT_RENAMED; + break; case GIT_DELTA_TYPECHANGE: st = GIT_STATUS_WT_TYPECHANGE; break; @@ -85,9 +87,9 @@ static bool status_is_included( { /* if excluding submodules and this is a submodule everywhere */ if ((statuslist->opts.flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) { - bool in_tree = (head2idx && head2idx->status != GIT_DELTA_ADDED); + bool in_tree = (head2idx && head2idx->status != GIT_DELTA_ADDED); bool in_index = (head2idx && head2idx->status != GIT_DELTA_DELETED); - bool in_wd = (idx2wd && idx2wd->status != GIT_DELTA_DELETED); + bool in_wd = (idx2wd && idx2wd->status != GIT_DELTA_DELETED); if ((!in_tree || head2idx->old_file.mode == GIT_FILEMODE_COMMIT) && (!in_index || head2idx->new_file.mode == GIT_FILEMODE_COMMIT) && @@ -136,24 +138,79 @@ static int status_collect( return 0; } -git_status_list *git_status_list_alloc(void) +GIT_INLINE(int) status_entry_cmp_base( + const void *a, + const void *b, + int (*strcomp)(const char *a, const char *b)) +{ + const git_status_entry *entry_a = a; + const git_status_entry *entry_b = b; + const git_diff_delta *delta_a, *delta_b; + + delta_a = entry_a->index_to_workdir ? entry_a->index_to_workdir : + entry_a->head_to_index; + delta_b = entry_b->index_to_workdir ? entry_b->index_to_workdir : + entry_b->head_to_index; + + if (!delta_a && delta_b) + return -1; + if (delta_a && !delta_b) + return 1; + if (!delta_a && !delta_b) + return 0; + + return strcomp(delta_a->new_file.path, delta_b->new_file.path); +} + +static int status_entry_icmp(const void *a, const void *b) +{ + return status_entry_cmp_base(a, b, git__strcasecmp); +} + +static int status_entry_cmp(const void *a, const void *b) +{ + return status_entry_cmp_base(a, b, git__strcmp); +} + +static git_status_list *git_status_list_alloc(git_index *index) { git_status_list *statuslist = NULL; + int (*entrycmp)(const void *a, const void *b); + + entrycmp = index->ignore_case ? status_entry_icmp : status_entry_cmp; if ((statuslist = git__calloc(1, sizeof(git_status_list))) == NULL || - git_vector_init(&statuslist->paired, 0, NULL) < 0) + git_vector_init(&statuslist->paired, 0, entrycmp) < 0) return NULL; return statuslist; } +static int newfile_cmp(const void *a, const void *b) +{ + const git_diff_delta *delta_a = a; + const git_diff_delta *delta_b = b; + + return git__strcmp(delta_a->new_file.path, delta_b->new_file.path); +} + +static int newfile_casecmp(const void *a, const void *b) +{ + const git_diff_delta *delta_a = a; + const git_diff_delta *delta_b = b; + + return git__strcasecmp(delta_a->new_file.path, delta_b->new_file.path); +} + int git_status_list_new( git_status_list **out, git_repository *repo, const git_status_options *opts) { + git_index *index = NULL; git_status_list *statuslist = NULL; git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options findopts_i2w = GIT_DIFF_FIND_OPTIONS_INIT; git_tree *head = NULL; git_status_show_t show = opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR; @@ -165,7 +222,8 @@ int git_status_list_new( GITERR_CHECK_VERSION(opts, GIT_STATUS_OPTIONS_VERSION, "git_status_options"); - if ((error = git_repository__ensure_not_bare(repo, "status")) < 0) + if ((error = git_repository__ensure_not_bare(repo, "status")) < 0 || + (error = git_repository_index(&index, repo)) < 0) return error; /* if there is no HEAD, that's okay - we'll make an empty iterator */ @@ -173,7 +231,7 @@ int git_status_list_new( !(error == GIT_ENOTFOUND || error == GIT_EORPHANEDHEAD)) return error; - statuslist = git_status_list_alloc(); + statuslist = git_status_list_alloc(index); GITERR_CHECK_ALLOC(statuslist); memcpy(&statuslist->opts, opts, sizeof(git_status_options)); @@ -197,17 +255,23 @@ int git_status_list_new( if ((opts->flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES; - if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) { - error = git_diff_tree_to_index(&statuslist->head2idx, repo, head, NULL, &diffopt); + findopts_i2w.flags |= GIT_DIFF_FIND_FOR_UNTRACKED; - if (error < 0) + if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) { + if ((error = git_diff_tree_to_index(&statuslist->head2idx, repo, head, NULL, &diffopt)) < 0) + goto on_error; + + if ((opts->flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 && + (error = git_diff_find_similar(statuslist->head2idx, NULL)) < 0) goto on_error; } if (show != GIT_STATUS_SHOW_INDEX_ONLY) { - error = git_diff_index_to_workdir(&statuslist->idx2wd, repo, NULL, &diffopt); + if ((error = git_diff_index_to_workdir(&statuslist->idx2wd, repo, NULL, &diffopt)) < 0) + goto on_error; - if (error < 0) + if ((opts->flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 && + (error = git_diff_find_similar(statuslist->idx2wd, &findopts_i2w)) < 0) goto on_error; } @@ -219,9 +283,22 @@ int git_status_list_new( statuslist->head2idx = NULL; } - if ((error = git_diff__paired_foreach(statuslist->head2idx, statuslist->idx2wd, status_collect, statuslist)) < 0) + if ((opts->flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0) { + statuslist->head2idx->deltas._cmp = + (statuslist->head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 ? + newfile_casecmp : newfile_cmp; + + git_vector_sort(&statuslist->head2idx->deltas); + } + + if ((error = git_diff__paired_foreach(statuslist->head2idx, statuslist->idx2wd, + status_collect, statuslist)) < 0) goto on_error; + if ((opts->flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 || + (opts->flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0) + git_vector_sort(&statuslist->paired); + *out = statuslist; goto done; @@ -230,6 +307,7 @@ on_error: done: git_tree_free(head); + git_index_free(index); return error; } @@ -307,7 +385,7 @@ int git_status_foreach( { git_status_options opts = GIT_STATUS_OPTIONS_INIT; - opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED | GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; @@ -364,7 +442,7 @@ int git_status_file( if (index->ignore_case) sfi.fnm_flags = FNM_CASEFOLD; - opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED | GIT_STATUS_OPT_RECURSE_IGNORED_DIRS | GIT_STATUS_OPT_INCLUDE_UNTRACKED | diff --git a/tests-clar/status/renames.c b/tests-clar/status/renames.c new file mode 100644 index 000000000..d29c7bfe8 --- /dev/null +++ b/tests-clar/status/renames.c @@ -0,0 +1,265 @@ +#include "clar_libgit2.h" +#include "buffer.h" +#include "path.h" +#include "posix.h" +#include "status_helpers.h" +#include "util.h" +#include "status.h" + +static git_repository *g_repo = NULL; + +void test_status_renames__initialize(void) +{ + g_repo = cl_git_sandbox_init("renames"); +} + +void test_status_renames__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void rename_file(git_repository *repo, const char *oldname, const char *newname) +{ + git_buf oldpath = GIT_BUF_INIT, newpath = GIT_BUF_INIT; + + git_buf_joinpath(&oldpath, git_repository_workdir(repo), oldname); + git_buf_joinpath(&newpath, git_repository_workdir(repo), newname); + + cl_git_pass(p_rename(oldpath.ptr, newpath.ptr)); + + git_buf_free(&oldpath); + git_buf_free(&newpath); +} + +static void rename_and_edit_file(git_repository *repo, const char *oldname, const char *newname) +{ + git_buf oldpath = GIT_BUF_INIT, newpath = GIT_BUF_INIT; + + git_buf_joinpath(&oldpath, git_repository_workdir(repo), oldname); + git_buf_joinpath(&newpath, git_repository_workdir(repo), newname); + + cl_git_pass(p_rename(oldpath.ptr, newpath.ptr)); + cl_git_append2file(newpath.ptr, "Added at the end to keep similarity!"); + + git_buf_free(&oldpath); + git_buf_free(&newpath); +} + +struct status_entry { + git_status_t status; + const char *oldname; + const char *newname; +}; + +static void test_status( + git_status_list *status_list, + struct status_entry *expected_list, + size_t expected_len) +{ + const git_status_entry *actual; + const struct status_entry *expected; + const char *oldname, *newname; + size_t i; + + cl_assert(expected_len == git_status_list_entrycount(status_list)); + + for (i = 0; i < expected_len; i++) { + actual = git_status_byindex(status_list, i); + expected = &expected_list[i]; + + cl_assert(actual->status == expected->status); + + oldname = actual->head_to_index ? actual->head_to_index->old_file.path : + actual->index_to_workdir ? actual->index_to_workdir->old_file.path : NULL; + + newname = actual->index_to_workdir ? actual->index_to_workdir->new_file.path : + actual->head_to_index ? actual->head_to_index->new_file.path : NULL; + + if (oldname) + cl_assert(git__strcmp(oldname, expected->oldname) == 0); + else + cl_assert(expected->oldname == NULL); + + if (newname) + cl_assert(git__strcmp(newname, expected->newname) == 0); + else + cl_assert(expected->newname == NULL); + } +} + +void test_status_renames__head2index_one(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", "newname.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_file(g_repo, "ikeepsix.txt", "newname.txt"); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "newname.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 1); + git_status_list_free(statuslist); + + git_index_free(index); +} + +void test_status_renames__head2index_two(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_INDEX_RENAMED, "sixserving.txt", "aaa.txt" }, + { GIT_STATUS_INDEX_RENAMED, "untimely.txt", "bbb.txt" }, + { GIT_STATUS_INDEX_RENAMED, "songof7cities.txt", "ccc.txt" }, + { GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", "ddd.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_file(g_repo, "ikeepsix.txt", "ddd.txt"); + rename_and_edit_file(g_repo, "sixserving.txt", "aaa.txt"); + rename_file(g_repo, "songof7cities.txt", "ccc.txt"); + rename_and_edit_file(g_repo, "untimely.txt", "bbb.txt"); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_remove_bypath(index, "sixserving.txt")); + cl_git_pass(git_index_remove_bypath(index, "songof7cities.txt")); + cl_git_pass(git_index_remove_bypath(index, "untimely.txt")); + cl_git_pass(git_index_add_bypath(index, "ddd.txt")); + cl_git_pass(git_index_add_bypath(index, "aaa.txt")); + cl_git_pass(git_index_add_bypath(index, "ccc.txt")); + cl_git_pass(git_index_add_bypath(index, "bbb.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 4); + git_status_list_free(statuslist); + + git_index_free(index); +} + +void test_status_renames__index2workdir_one(void) +{ + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "newname.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + rename_file(g_repo, "ikeepsix.txt", "newname.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 1); + git_status_list_free(statuslist); +} + +void test_status_renames__index2workdir_two(void) +{ + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_WT_RENAMED, "sixserving.txt", "aaa.txt" }, + { GIT_STATUS_WT_RENAMED, "untimely.txt", "bbb.txt" }, + { GIT_STATUS_WT_RENAMED, "songof7cities.txt", "ccc.txt" }, + { GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "ddd.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + rename_file(g_repo, "ikeepsix.txt", "ddd.txt"); + rename_and_edit_file(g_repo, "sixserving.txt", "aaa.txt"); + rename_file(g_repo, "songof7cities.txt", "ccc.txt"); + rename_and_edit_file(g_repo, "untimely.txt", "bbb.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 4); + git_status_list_free(statuslist); +} + +void test_status_renames__both_one(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "newname-workdir.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_file(g_repo, "ikeepsix.txt", "newname-index.txt"); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "newname-index.txt")); + cl_git_pass(git_index_write(index)); + + rename_file(g_repo, "newname-index.txt", "newname-workdir.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 1); + git_status_list_free(statuslist); + + git_index_free(index); +} + +void test_status_renames__both_two(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + struct status_entry expected[] = { + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "ikeepsix-both.txt" }, + { GIT_STATUS_INDEX_RENAMED, "sixserving.txt", "sixserving-index.txt" }, + { GIT_STATUS_WT_RENAMED, "songof7cities.txt", "songof7cities-workdir.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, "untimely.txt", "untimely-both.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + cl_git_pass(git_repository_index(&index, g_repo)); + + rename_and_edit_file(g_repo, "ikeepsix.txt", "ikeepsix-index.txt"); + rename_and_edit_file(g_repo, "sixserving.txt", "sixserving-index.txt"); + rename_file(g_repo, "untimely.txt", "untimely-index.txt"); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_remove_bypath(index, "sixserving.txt")); + cl_git_pass(git_index_remove_bypath(index, "untimely.txt")); + cl_git_pass(git_index_add_bypath(index, "ikeepsix-index.txt")); + cl_git_pass(git_index_add_bypath(index, "sixserving-index.txt")); + cl_git_pass(git_index_add_bypath(index, "untimely-index.txt")); + cl_git_pass(git_index_write(index)); + + rename_and_edit_file(g_repo, "ikeepsix-index.txt", "ikeepsix-both.txt"); + rename_and_edit_file(g_repo, "songof7cities.txt", "songof7cities-workdir.txt"); + rename_file(g_repo, "untimely-index.txt", "untimely-both.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + test_status(statuslist, expected, 4); + git_status_list_free(statuslist); + + git_index_free(index); +} From e3b4a47c1ebd55931cb25bf5c2af821df9b0bffa Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 31 May 2013 16:30:09 -0500 Subject: [PATCH 03/16] git__strcasesort_cmp: strcasecmp sorting rules but requires strict equality --- src/util.c | 25 +++++++++++++++++++++++++ src/util.h | 2 ++ tests-clar/core/string.c | 13 +++++++++++++ 3 files changed, 40 insertions(+) diff --git a/src/util.c b/src/util.c index da15a039d..8536c9513 100644 --- a/src/util.c +++ b/src/util.c @@ -279,6 +279,31 @@ int git__strcasecmp(const char *a, const char *b) return (tolower(*a) - tolower(*b)); } +int git__strcasesort_cmp(const char *a, const char *b) +{ + int cmp = 0; + + const char *orig_a = a; + const char *orig_b = b; + + while (*a && *b) { + if (*a == *b) + ; + else if (tolower(*a) == tolower(*b)) { + if (!cmp) + cmp = (int)(*(const unsigned char *)a) - (int)(*(const unsigned char *)b); + } else + break; + + ++a, ++b; + } + + if (*a || *b) + return tolower(*a) - tolower(*b); + + return cmp; +} + int git__strncmp(const char *a, const char *b, size_t sz) { while (sz && *a && *b && *a == *b) diff --git a/src/util.h b/src/util.h index 1ef9e65b5..e0088399c 100644 --- a/src/util.h +++ b/src/util.h @@ -194,6 +194,8 @@ extern int git__strcasecmp(const char *a, const char *b); extern int git__strncmp(const char *a, const char *b, size_t sz); extern int git__strncasecmp(const char *a, const char *b, size_t sz); +extern int git__strcasesort_cmp(const char *a, const char *b); + #include "thread-utils.h" typedef struct { diff --git a/tests-clar/core/string.c b/tests-clar/core/string.c index bf6ec0a80..ec9575685 100644 --- a/tests-clar/core/string.c +++ b/tests-clar/core/string.c @@ -26,3 +26,16 @@ void test_core_string__1(void) cl_assert(git__suffixcmp("zaz", "ac") > 0); } +/* compare icase sorting with case equality */ +void test_core_string__2(void) +{ + cl_assert(git__strcasesort_cmp("", "") == 0); + cl_assert(git__strcasesort_cmp("foo", "foo") == 0); + cl_assert(git__strcasesort_cmp("foo", "bar") > 0); + cl_assert(git__strcasesort_cmp("bar", "foo") < 0); + cl_assert(git__strcasesort_cmp("foo", "FOO") > 0); + cl_assert(git__strcasesort_cmp("FOO", "foo") < 0); + cl_assert(git__strcasesort_cmp("foo", "BAR") > 0); + cl_assert(git__strcasesort_cmp("BAR", "foo") < 0); + cl_assert(git__strcasesort_cmp("fooBar", "foobar") < 0); +} From c9b18018fd537566e76308fd2fec67e483b1d201 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 13 Jun 2013 15:26:56 -0700 Subject: [PATCH 04/16] Fix some warnings --- src/util.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/util.c b/src/util.c index 8536c9513..c543a3d21 100644 --- a/src/util.c +++ b/src/util.c @@ -283,17 +283,14 @@ int git__strcasesort_cmp(const char *a, const char *b) { int cmp = 0; - const char *orig_a = a; - const char *orig_b = b; - while (*a && *b) { - if (*a == *b) - ; - else if (tolower(*a) == tolower(*b)) { + if (*a != *b) { + if (tolower(*a) != tolower(*b)) + break; + /* use case in sort order even if not in equivalence */ if (!cmp) - cmp = (int)(*(const unsigned char *)a) - (int)(*(const unsigned char *)b); - } else - break; + cmp = (int)(*(const uint8_t *)a) - (int)(*(const uint8_t *)b); + } ++a, ++b; } From 4e28e638ea016f5a5b35ba952d9f06a1108d6b5e Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 13 Jun 2013 15:27:30 -0700 Subject: [PATCH 05/16] Clarify some docs and minor reordering This simplifies some documentation and hopefully makes a couple of things easier to read. Also, this rearrages the order in this branch so that the overall diff against the trunk will hopefully be a bit cleaner. --- include/git2/status.h | 183 +++++++++++++++++++++--------------------- 1 file changed, 90 insertions(+), 93 deletions(-) diff --git a/include/git2/status.h b/include/git2/status.h index 64ce6756c..282b606cb 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -59,44 +59,20 @@ typedef enum { typedef int (*git_status_cb)( const char *path, unsigned int status_flags, void *payload); -/** - * Gather file statuses and run a callback for each one. - * - * The callback is passed the path of the file, the status (a combination of - * the `git_status_t` values above) and the `payload` data pointer passed - * into this function. - * - * If the callback returns a non-zero value, this function will stop looping - * and return GIT_EUSER. - * - * @param repo A repository object - * @param callback The function to call on each file - * @param payload Pointer to pass through to callback function - * @return 0 on success, GIT_EUSER on non-zero callback, or error code - */ -GIT_EXTERN(int) git_status_foreach( - git_repository *repo, - git_status_cb callback, - void *payload); - /** * For extended status, select the files on which to report status. * - * - GIT_STATUS_SHOW_INDEX_AND_WORKDIR is the default. This is the - * rough equivalent of `git status --porcelain` where each file - * will receive a callback indicating its status in the index and - * in the workdir. - * - GIT_STATUS_SHOW_INDEX_ONLY will only make callbacks for index - * side of status. The status of the index contents relative to - * the HEAD will be given. - * - GIT_STATUS_SHOW_WORKDIR_ONLY will only make callbacks for the - * workdir side of status, reporting the status of workdir content - * relative to the index. - * - GIT_STATUS_SHOW_INDEX_THEN_WORKDIR behaves like index-only - * followed by workdir-only, causing two callbacks to be issued - * per file (first index then workdir). This is slightly more - * efficient than making separate calls. This makes it easier to - * emulate the output of a plain `git status`. + * - GIT_STATUS_SHOW_INDEX_AND_WORKDIR is the default. This roughly + * matches `git status --porcelain` where each file gets a callback + * indicating its status in the index and in the working directory. + * - GIT_STATUS_SHOW_INDEX_ONLY only gives status based on HEAD to index + * comparison, not looking at working directory changes. + * - GIT_STATUS_SHOW_WORKDIR_ONLY only gives status based on index to + * working directory comparison, not comparing the index to the HEAD. + * - GIT_STATUS_SHOW_INDEX_THEN_WORKDIR runs index-only then workdir-only, + * issuing (up to) two callbacks per file (first index, then workdir). + * This is slightly more efficient than separate calls and can make it + * easier to emulate plain `git status` text output. */ typedef enum { GIT_STATUS_SHOW_INDEX_AND_WORKDIR = 0, @@ -111,30 +87,30 @@ typedef enum { * - GIT_STATUS_OPT_INCLUDE_UNTRACKED says that callbacks should be made * on untracked files. These will only be made if the workdir files are * included in the status "show" option. - * - GIT_STATUS_OPT_INCLUDE_IGNORED says that ignored files should get - * callbacks. Again, these callbacks will only be made if the workdir - * files are included in the status "show" option. Right now, there is - * no option to include all files in directories that are ignored - * completely. + * - GIT_STATUS_OPT_INCLUDE_IGNORED says that ignored files get callbacks. + * Again, these callbacks will only be made if the workdir files are + * included in the status "show" option. * - GIT_STATUS_OPT_INCLUDE_UNMODIFIED indicates that callback should be * made even on unmodified files. - * - GIT_STATUS_OPT_EXCLUDE_SUBMODULES indicates that directories which - * appear to be submodules should just be skipped over. - * - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS indicates that the contents of - * untracked directories should be included in the status. Normally if - * an entire directory is new, then just the top-level directory will be - * included (with a trailing slash on the entry name). Given this flag, - * the directory itself will not be included, but all the files in it - * will. + * - GIT_STATUS_OPT_EXCLUDE_SUBMODULES indicates that submodules should be + * skipped. This only applies if there are no pending typechanges to + * the submodule (either from or to another type). + * - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS indicates that all files in + * untracked directories should be included. Normally if an entire + * directory is new, then just the top-level directory is included (with + * a trailing slash on the entry name). This flag says to include all + * of the individual files in the directory instead. * - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH indicates that the given path - * will be treated as a literal path, and not as a pathspec. + * should be treated as a literal path, and not as a pathspec pattern. * - GIT_STATUS_OPT_RECURSE_IGNORED_DIRS indicates that the contents of * ignored directories should be included in the status. This is like * doing `git ls-files -o -i --exclude-standard` with core git. - * - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX indicates that items that are - * renamed in the index will be reported as renames. - * - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates that items that - * are renamed in the working directory will be reported as renames. + * - GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX indicates that rename detection + * should be processed between the head and the index and enables + * the GIT_STATUS_INDEX_RENAMED as a possible status flag. + * - GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR indicates tha rename + * detection should be run between the index and the working directory + * and enabled GIT_STATUS_WT_RENAMED as a possible status flag. * * Calling `git_status_foreach()` is like calling the extended version * with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED, @@ -181,6 +157,9 @@ typedef struct { git_strarray pathspec; } git_status_options; +#define GIT_STATUS_OPTIONS_VERSION 1 +#define GIT_STATUS_OPTIONS_INIT {GIT_STATUS_OPTIONS_VERSION} + /** * A status entry, providing the differences between the file as it exists * in HEAD and the index, and providing the differences between the index @@ -201,8 +180,64 @@ typedef struct { git_diff_delta *index_to_workdir; } git_status_entry; -#define GIT_STATUS_OPTIONS_VERSION 1 -#define GIT_STATUS_OPTIONS_INIT {GIT_STATUS_OPTIONS_VERSION} + +/** + * Gather file statuses and run a callback for each one. + * + * The callback is passed the path of the file, the status (a combination of + * the `git_status_t` values above) and the `payload` data pointer passed + * into this function. + * + * If the callback returns a non-zero value, this function will stop looping + * and return GIT_EUSER. + * + * @param repo A repository object + * @param callback The function to call on each file + * @param payload Pointer to pass through to callback function + * @return 0 on success, GIT_EUSER on non-zero callback, or error code + */ +GIT_EXTERN(int) git_status_foreach( + git_repository *repo, + git_status_cb callback, + void *payload); + +/** + * Gather file status information and run callbacks as requested. + * + * This is an extended version of the `git_status_foreach()` API that + * allows for more granular control over which paths will be processed and + * in what order. See the `git_status_options` structure for details + * about the additional controls that this makes available. + * + * @param repo Repository object + * @param opts Status options structure + * @param callback The function to call on each file + * @param payload Pointer to pass through to callback function + * @return 0 on success, GIT_EUSER on non-zero callback, or error code + */ +GIT_EXTERN(int) git_status_foreach_ext( + git_repository *repo, + const git_status_options *opts, + git_status_cb callback, + void *payload); + +/** + * Get file status for a single file. + * + * This is not quite the same as calling `git_status_foreach_ext()` with + * the pathspec set to the specified path. + * + * @param status_flags The status value for the file + * @param repo A repository object + * @param path The file to retrieve status for, rooted at the repo's workdir + * @return 0 on success, GIT_ENOTFOUND if the file is not found in the HEAD, + * index, and work tree, GIT_EINVALIDPATH if `path` points at a folder, + * GIT_EAMBIGUOUS if "path" matches multiple files, -1 on other error. + */ +GIT_EXTERN(int) git_status_file( + unsigned int *status_flags, + git_repository *repo, + const char *path); /** * Gather file status information and populate the `git_status_list`. @@ -247,44 +282,6 @@ GIT_EXTERN(const git_status_entry *) git_status_byindex( GIT_EXTERN(void) git_status_list_free( git_status_list *statuslist); -/** - * Gather file status information and run callbacks as requested. - * - * This is an extended version of the `git_status_foreach()` API that - * allows for more granular control over which paths will be processed and - * in what order. See the `git_status_options` structure for details - * about the additional controls that this makes available. - * - * @param repo Repository object - * @param opts Status options structure - * @param callback The function to call on each file - * @param payload Pointer to pass through to callback function - * @return 0 on success, GIT_EUSER on non-zero callback, or error code - */ -GIT_EXTERN(int) git_status_foreach_ext( - git_repository *repo, - const git_status_options *opts, - git_status_cb callback, - void *payload); - -/** - * Get file status for a single file. - * - * This is not quite the same as calling `git_status_foreach_ext()` with - * the pathspec set to the specified path. - * - * @param status_flags The status value for the file - * @param repo A repository object - * @param path The file to retrieve status for, rooted at the repo's workdir - * @return 0 on success, GIT_ENOTFOUND if the file is not found in the HEAD, - * index, and work tree, GIT_EINVALIDPATH if `path` points at a folder, - * GIT_EAMBIGUOUS if "path" matches multiple files, -1 on other error. - */ -GIT_EXTERN(int) git_status_file( - unsigned int *status_flags, - git_repository *repo, - const char *path); - /** * Test if the ignore rules apply to a given file. * From 3a68d7f00289afbaa415c3b34d5eeca183dcfb52 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 13 Jun 2013 15:30:26 -0700 Subject: [PATCH 06/16] Fix broken status EXCLUDE_SUBMODULES logic The exclude submodules flag was not doing the right thing, in that a file with no diff between the head and the index and just a delete in the workdir could be excluded if submodules were excluded. --- src/status.c | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/status.c b/src/status.c index 20e45b75f..ba4eef895 100644 --- a/src/status.c +++ b/src/status.c @@ -81,23 +81,33 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status) } static bool status_is_included( - git_status_list *statuslist, + git_status_list *status, git_diff_delta *head2idx, git_diff_delta *idx2wd) { - /* if excluding submodules and this is a submodule everywhere */ - if ((statuslist->opts.flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) { - bool in_tree = (head2idx && head2idx->status != GIT_DELTA_ADDED); - bool in_index = (head2idx && head2idx->status != GIT_DELTA_DELETED); - bool in_wd = (idx2wd && idx2wd->status != GIT_DELTA_DELETED); + if (!(status->opts.flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES)) + return 1; - if ((!in_tree || head2idx->old_file.mode == GIT_FILEMODE_COMMIT) && - (!in_index || head2idx->new_file.mode == GIT_FILEMODE_COMMIT) && - (!in_wd || idx2wd->new_file.mode == GIT_FILEMODE_COMMIT)) - return 0; + /* if excluding submodules and this is a submodule everywhere */ + if (head2idx) { + if (head2idx->status != GIT_DELTA_ADDED && + head2idx->old_file.mode != GIT_FILEMODE_COMMIT) + return 1; + if (head2idx->status != GIT_DELTA_DELETED && + head2idx->new_file.mode != GIT_FILEMODE_COMMIT) + return 1; + } + if (idx2wd) { + if (idx2wd->status != GIT_DELTA_ADDED && + idx2wd->old_file.mode != GIT_FILEMODE_COMMIT) + return 1; + if (idx2wd->status != GIT_DELTA_DELETED && + idx2wd->new_file.mode != GIT_FILEMODE_COMMIT) + return 1; } - return 1; + /* only get here if every valid mode is GIT_FILEMODE_COMMIT */ + return 0; } static git_status_t status_compute( From a3e8dbb40b87f66e1f5f4d1730f9989805822db0 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 13 Jun 2013 15:32:09 -0700 Subject: [PATCH 07/16] Be more careful about the path with diffs This makes diff more careful about picking the canonical path when generating a delta so that it won't accidentally pick up a case-mismatched path on a case-insensitive file system. This should make sure we use the "most accurate" case correct version of the path (i.e. from the tree if possible, or the index if need be). --- src/diff.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/diff.c b/src/diff.c index 3bfe149e3..feb77b59b 100644 --- a/src/diff.c +++ b/src/diff.c @@ -134,6 +134,7 @@ static int diff_delta__from_two( { git_diff_delta *delta; int notify_res; + const char *canonical_path = old_entry->path; if (status == GIT_DELTA_UNMODIFIED && DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED)) @@ -153,7 +154,7 @@ static int diff_delta__from_two( new_mode = temp_mode; } - delta = diff_delta__alloc(diff, status, old_entry->path); + delta = diff_delta__alloc(diff, status, canonical_path); GITERR_CHECK_ALLOC(delta); git_oid_cpy(&delta->old_file.oid, &old_entry->oid); From 351888cf3dc3c3525faccb010adcf5c130ac5b16 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 13 Jun 2013 15:37:06 -0700 Subject: [PATCH 08/16] Improve case handling in git_diff__paired_foreach This commit reinstates some changes to git_diff__paired_foreach that were discarded during the rebase (because the diff_output.c file had gone away), and also adjusts the case insensitively logic slightly to hopefully deal with either mismatched icase diffs and other case insensitivity scenarios. --- src/diff.c | 78 ++++++++++----- src/diff.h | 1 + src/status.c | 173 ++++++++++++++++------------------ tests-clar/status/worktree.c | 48 ++++++++++ tests-clar/submodule/status.c | 7 +- 5 files changed, 193 insertions(+), 114 deletions(-) diff --git a/src/diff.c b/src/diff.c index feb77b59b..87189a49c 100644 --- a/src/diff.c +++ b/src/diff.c @@ -254,6 +254,13 @@ int git_diff_delta__cmp(const void *a, const void *b) return val ? val : ((int)da->status - (int)db->status); } +int git_diff_delta__casecmp(const void *a, const void *b) +{ + const git_diff_delta *da = a, *db = b; + int val = strcasecmp(diff_delta__path(da), diff_delta__path(db)); + return val ? val : ((int)da->status - (int)db->status); +} + bool git_diff_delta__should_skip( const git_diff_options *opts, const git_diff_delta *delta) { @@ -1197,51 +1204,78 @@ size_t git_diff_num_deltas_of_type(git_diff_list *diff, git_delta_t type) } int git_diff__paired_foreach( - git_diff_list *idx2head, - git_diff_list *wd2idx, - int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload), + git_diff_list *head2idx, + git_diff_list *idx2wd, + int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload), void *payload) { int cmp; - git_diff_delta *i2h, *w2i; + git_diff_delta *h2i, *i2w; size_t i, j, i_max, j_max; - int (*strcomp)(const char *, const char *); + int (*strcomp)(const char *, const char *) = git__strcmp; + bool icase_mismatch; - i_max = idx2head ? idx2head->deltas.length : 0; - j_max = wd2idx ? wd2idx->deltas.length : 0; + i_max = head2idx ? head2idx->deltas.length : 0; + j_max = idx2wd ? idx2wd->deltas.length : 0; - /* Get appropriate strcmp function */ - strcomp = idx2head ? idx2head->strcomp : wd2idx ? wd2idx->strcomp : NULL; + /* At some point, tree-to-index diffs will probably never ignore case, + * even if that isn't true now. Index-to-workdir diffs may or may not + * ignore case, but the index filename for the idx2wd diff should + * still be using the canonical case-preserving name. + * + * Therefore the main thing we need to do here is make sure the diffs + * are traversed in a compatible order. To do this, we temporarily + * resort a mismatched diff to get the order correct. + */ + icase_mismatch = + (head2idx != NULL && idx2wd != NULL && + ((head2idx->opts.flags ^ idx2wd->opts.flags) & GIT_DIFF_DELTAS_ARE_ICASE)); - /* Assert both iterators use matching ignore-case. If this function ever - * supports merging diffs that are not sorted by the same function, then - * it will need to spool and sort on one of the results before merging - */ - if (idx2head && wd2idx) { - assert(idx2head->strcomp == wd2idx->strcomp); + /* force case-sensitive delta sort */ + if (icase_mismatch) { + if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) { + head2idx->deltas._cmp = git_diff_delta__cmp; + git_vector_sort(&head2idx->deltas); + } else { + idx2wd->deltas._cmp = git_diff_delta__cmp; + git_vector_sort(&idx2wd->deltas); + } } + else if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) + strcomp = git__strcasecmp; for (i = 0, j = 0; i < i_max || j < j_max; ) { - i2h = idx2head ? GIT_VECTOR_GET(&idx2head->deltas,i) : NULL; - w2i = wd2idx ? GIT_VECTOR_GET(&wd2idx->deltas,j) : NULL; + h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL; + i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL; - cmp = !w2i ? -1 : !i2h ? 1 : - strcomp(i2h->old_file.path, w2i->old_file.path); + cmp = !i2w ? -1 : !h2i ? 1 : + strcomp(h2i->new_file.path, i2w->old_file.path); if (cmp < 0) { - if (cb(i2h, NULL, payload)) + if (cb(h2i, NULL, payload)) return GIT_EUSER; i++; } else if (cmp > 0) { - if (cb(NULL, w2i, payload)) + if (cb(NULL, i2w, payload)) return GIT_EUSER; j++; } else { - if (cb(i2h, w2i, payload)) + if (cb(h2i, i2w, payload)) return GIT_EUSER; i++; j++; } } + /* restore case-insensitive delta sort */ + if (icase_mismatch) { + if (head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) { + head2idx->deltas._cmp = git_diff_delta__casecmp; + git_vector_sort(&head2idx->deltas); + } else { + idx2wd->deltas._cmp = git_diff_delta__casecmp; + git_vector_sort(&idx2wd->deltas); + } + } + return 0; } diff --git a/src/diff.h b/src/diff.h index ad12e7731..1536f79d7 100644 --- a/src/diff.h +++ b/src/diff.h @@ -74,6 +74,7 @@ extern void git_diff__cleanup_modes( extern void git_diff_list_addref(git_diff_list *diff); extern int git_diff_delta__cmp(const void *a, const void *b); +extern int git_diff_delta__casecmp(const void *a, const void *b); extern bool git_diff_delta__should_skip( const git_diff_options *opts, const git_diff_delta *delta); diff --git a/src/status.c b/src/status.c index ba4eef895..9c4aead74 100644 --- a/src/status.c +++ b/src/status.c @@ -130,12 +130,12 @@ static int status_collect( git_diff_delta *idx2wd, void *payload) { - git_status_list *statuslist = payload; + git_status_list *status = payload; git_status_entry *status_entry; - - if (!status_is_included(statuslist, head2idx, idx2wd)) + + if (!status_is_included(status, head2idx, idx2wd)) return 0; - + status_entry = git__malloc(sizeof(git_status_entry)); GITERR_CHECK_ALLOC(status_entry); @@ -143,9 +143,7 @@ static int status_collect( status_entry->head_to_index = head2idx; status_entry->index_to_workdir = idx2wd; - git_vector_insert(&statuslist->paired, status_entry); - - return 0; + return git_vector_insert(&status->paired, status_entry); } GIT_INLINE(int) status_entry_cmp_base( @@ -184,18 +182,23 @@ static int status_entry_cmp(const void *a, const void *b) static git_status_list *git_status_list_alloc(git_index *index) { - git_status_list *statuslist = NULL; + git_status_list *status = NULL; int (*entrycmp)(const void *a, const void *b); + if (!(status = git__calloc(1, sizeof(git_status_list)))) + return NULL; + entrycmp = index->ignore_case ? status_entry_icmp : status_entry_cmp; - if ((statuslist = git__calloc(1, sizeof(git_status_list))) == NULL || - git_vector_init(&statuslist->paired, 0, entrycmp) < 0) + if (git_vector_init(&status->paired, 0, entrycmp) < 0) { + git__free(status); return NULL; + } - return statuslist; + return status; } +/* static int newfile_cmp(const void *a, const void *b) { const git_diff_delta *delta_a = a; @@ -211,6 +214,7 @@ static int newfile_casecmp(const void *a, const void *b) return git__strcasecmp(delta_a->new_file.path, delta_b->new_file.path); } +*/ int git_status_list_new( git_status_list **out, @@ -218,13 +222,14 @@ int git_status_list_new( const git_status_options *opts) { git_index *index = NULL; - git_status_list *statuslist = NULL; + git_status_list *status = NULL; git_diff_options diffopt = GIT_DIFF_OPTIONS_INIT; git_diff_find_options findopts_i2w = GIT_DIFF_FIND_OPTIONS_INIT; git_tree *head = NULL; git_status_show_t show = opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR; int error = 0; + unsigned int flags = opts ? opts->flags : GIT_STATUS_OPT_DEFAULTS; assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR); @@ -238,123 +243,121 @@ int git_status_list_new( /* if there is no HEAD, that's okay - we'll make an empty iterator */ if (((error = git_repository_head_tree(&head, repo)) < 0) && - !(error == GIT_ENOTFOUND || error == GIT_EORPHANEDHEAD)) + error != GIT_ENOTFOUND && error != GIT_EORPHANEDHEAD) { + git_index_free(index); /* release index */ return error; + } - statuslist = git_status_list_alloc(index); - GITERR_CHECK_ALLOC(statuslist); + status = git_status_list_alloc(index); + GITERR_CHECK_ALLOC(status); - memcpy(&statuslist->opts, opts, sizeof(git_status_options)); - - memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec)); + if (opts) { + memcpy(&status->opts, opts, sizeof(git_status_options)); + memcpy(&diffopt.pathspec, &opts->pathspec, sizeof(diffopt.pathspec)); + } diffopt.flags = GIT_DIFF_INCLUDE_TYPECHANGE; - if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0) + if ((flags & GIT_STATUS_OPT_INCLUDE_UNTRACKED) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNTRACKED; - if ((opts->flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0) + if ((flags & GIT_STATUS_OPT_INCLUDE_IGNORED) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_IGNORED; - if ((opts->flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0) + if ((flags & GIT_STATUS_OPT_INCLUDE_UNMODIFIED) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED; - if ((opts->flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0) + if ((flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS; - if ((opts->flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0) + if ((flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH; - if ((opts->flags & GIT_STATUS_OPT_RECURSE_IGNORED_DIRS) != 0) + if ((flags & GIT_STATUS_OPT_RECURSE_IGNORED_DIRS) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_IGNORED_DIRS; - if ((opts->flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) + if ((flags & GIT_STATUS_OPT_EXCLUDE_SUBMODULES) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_IGNORE_SUBMODULES; findopts_i2w.flags |= GIT_DIFF_FIND_FOR_UNTRACKED; if (show != GIT_STATUS_SHOW_WORKDIR_ONLY) { - if ((error = git_diff_tree_to_index(&statuslist->head2idx, repo, head, NULL, &diffopt)) < 0) - goto on_error; + if ((error = git_diff_tree_to_index( + &status->head2idx, repo, head, NULL, &diffopt)) < 0) + goto done; - if ((opts->flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 && - (error = git_diff_find_similar(statuslist->head2idx, NULL)) < 0) - goto on_error; + if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 && + (error = git_diff_find_similar(status->head2idx, NULL)) < 0) + goto done; } if (show != GIT_STATUS_SHOW_INDEX_ONLY) { - if ((error = git_diff_index_to_workdir(&statuslist->idx2wd, repo, NULL, &diffopt)) < 0) - goto on_error; + if ((error = git_diff_index_to_workdir( + &status->idx2wd, repo, NULL, &diffopt)) < 0) + goto done; - if ((opts->flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 && - (error = git_diff_find_similar(statuslist->idx2wd, &findopts_i2w)) < 0) - goto on_error; + if ((flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0 && + (error = git_diff_find_similar(status->idx2wd, &findopts_i2w)) < 0) + goto done; } if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) { - if ((error = git_diff__paired_foreach(statuslist->head2idx, NULL, status_collect, statuslist)) < 0) - goto on_error; + if ((error = git_diff__paired_foreach( + status->head2idx, NULL, status_collect, status)) < 0) + goto done; - git_diff_list_free(statuslist->head2idx); - statuslist->head2idx = NULL; + git_diff_list_free(status->head2idx); + status->head2idx = NULL; } - if ((opts->flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0) { - statuslist->head2idx->deltas._cmp = - (statuslist->head2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 ? - newfile_casecmp : newfile_cmp; + if ((error = git_diff__paired_foreach( + status->head2idx, status->idx2wd, status_collect, status)) < 0) + goto done; - git_vector_sort(&statuslist->head2idx->deltas); - } - - if ((error = git_diff__paired_foreach(statuslist->head2idx, statuslist->idx2wd, - status_collect, statuslist)) < 0) - goto on_error; - - if ((opts->flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 || - (opts->flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0) - git_vector_sort(&statuslist->paired); - - *out = statuslist; - goto done; - -on_error: - git_status_list_free(statuslist); + if ((flags & GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX) != 0 || + (flags & GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR) != 0) + git_vector_sort(&status->paired); done: + if (error < 0) { + git_status_list_free(status); + status = NULL; + } + + *out = status; + git_tree_free(head); git_index_free(index); return error; } -size_t git_status_list_entrycount(git_status_list *statuslist) +size_t git_status_list_entrycount(git_status_list *status) { - assert(statuslist); + assert(status); - return statuslist->paired.length; + return status->paired.length; } -const git_status_entry *git_status_byindex( - git_status_list *statuslist, - size_t i) +const git_status_entry *git_status_byindex(git_status_list *status, size_t i) { - assert(statuslist); + assert(status); - return git_vector_get(&statuslist->paired, i); + return git_vector_get(&status->paired, i); } -void git_status_list_free(git_status_list *statuslist) +void git_status_list_free(git_status_list *status) { git_status_entry *status_entry; size_t i; - if (statuslist == NULL) + if (status == NULL) return; - git_diff_list_free(statuslist->head2idx); - git_diff_list_free(statuslist->idx2wd); + git_diff_list_free(status->head2idx); + git_diff_list_free(status->idx2wd); - git_vector_foreach(&statuslist->paired, i, status_entry) + git_vector_foreach(&status->paired, i, status_entry) git__free(status_entry); - git_vector_free(&statuslist->paired); + git_vector_free(&status->paired); - git__free(statuslist); + git__memzero(status, sizeof(*status)); + git__free(status); } int git_status_foreach_ext( @@ -363,15 +366,15 @@ int git_status_foreach_ext( git_status_cb cb, void *payload) { - git_status_list *statuslist; + git_status_list *status; const git_status_entry *status_entry; size_t i; int error = 0; - if ((error = git_status_list_new(&statuslist, repo, opts)) < 0) + if ((error = git_status_list_new(&status, repo, opts)) < 0) return error; - git_vector_foreach(&statuslist->paired, i, status_entry) { + git_vector_foreach(&status->paired, i, status_entry) { const char *path = status_entry->head_to_index ? status_entry->head_to_index->old_file.path : status_entry->index_to_workdir->old_file.path; @@ -383,24 +386,14 @@ int git_status_foreach_ext( } } - git_status_list_free(statuslist); + git_status_list_free(status); return error; } -int git_status_foreach( - git_repository *repo, - git_status_cb callback, - void *payload) +int git_status_foreach(git_repository *repo, git_status_cb cb, void *payload) { - git_status_options opts = GIT_STATUS_OPTIONS_INIT; - - opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; - opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED | - GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; - - return git_status_foreach_ext(repo, &opts, callback, payload); + return git_status_foreach_ext(repo, NULL, cb, payload); } struct status_file_info { diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index 13335843b..7c27ee588 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -695,3 +695,51 @@ void test_status_worktree__file_status_honors_case_ignorecase_regarding_untracke /* Actually returns GIT_STATUS_IGNORED on Windows */ cl_git_fail_with(git_status_file(&status, repo, "NEW_FILE"), GIT_ENOTFOUND); } + +void test_status_worktree__simple_delete(void) +{ + git_repository *repo = cl_git_sandbox_init("renames"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + int count; + + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH | + GIT_STATUS_OPT_EXCLUDE_SUBMODULES | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + count = 0; + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__count, &count) ); + cl_assert_equal_i(0, count); + + cl_must_pass(p_unlink("renames/untimely.txt")); + + count = 0; + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__count, &count) ); + cl_assert_equal_i(1, count); +} + +void test_status_worktree__simple_delete_indexed(void) +{ + git_repository *repo = cl_git_sandbox_init("renames"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + git_status_list *status; + + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH | + GIT_STATUS_OPT_EXCLUDE_SUBMODULES | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + cl_git_pass(git_status_list_new(&status, repo, &opts)); + cl_assert_equal_sz(0, git_status_list_entrycount(status)); + git_status_list_free(status); + + cl_must_pass(p_unlink("renames/untimely.txt")); + + cl_git_pass(git_status_list_new(&status, repo, &opts)); + cl_assert_equal_sz(1, git_status_list_entrycount(status)); + cl_assert_equal_i( + GIT_STATUS_WT_DELETED, git_status_byindex(status, 0)->status); + git_status_list_free(status); +} diff --git a/tests-clar/submodule/status.c b/tests-clar/submodule/status.c index 39c83a0b7..68110bdd5 100644 --- a/tests-clar/submodule/status.c +++ b/tests-clar/submodule/status.c @@ -376,9 +376,12 @@ void test_submodule_status__iterator(void) git_iterator_free(iter); - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_INCLUDE_UNMODIFIED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_INCLUDE_UNMODIFIED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; - cl_git_pass(git_status_foreach_ext(g_repo, &opts, confirm_submodule_status, &exp)); + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, confirm_submodule_status, &exp)); } void test_submodule_status__untracked_dirs_containing_ignored_files(void) From 1540b19990fa5c566d607785c7b3651756e706ff Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 31 May 2013 18:12:49 -0500 Subject: [PATCH 09/16] some simple case-sensitive index tests --- tests-clar/diff/rename.c | 32 ++++++++++++++++++++++++++++++++ tests-clar/index/tests.c | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index 224945e60..6227a54e8 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -906,3 +906,35 @@ void test_diff_rename__rejected_match_can_match_others(void) git_buf_free(&one); git_buf_free(&two); } + +void test_diff_rename__case_changes_are_split(void) +{ + git_index *index; + git_tree *tree; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff_list *diff = NULL; + diff_expects exp; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/IKEEPSIX.txt")); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "IKEEPSIX.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, NULL)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(2, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); + + git_index_free(index); +} + diff --git a/tests-clar/index/tests.c b/tests-clar/index/tests.c index 88e374e6e..53d45a84e 100644 --- a/tests-clar/index/tests.c +++ b/tests-clar/index/tests.c @@ -416,3 +416,35 @@ void test_index_tests__remove_directory(void) git_repository_free(repo); cl_fixture_cleanup("index_test"); } + +void test_index_tests__preserves_case(void) +{ + git_repository *repo; + git_index *index; + const git_index_entry *entry; + + cl_set_cleanup(&cleanup_myrepo, NULL); + + cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); + cl_git_pass(git_repository_index(&index, repo)); + + cl_git_rewritefile("myrepo/test.txt", "hey there\n"); + cl_git_pass(git_index_add_bypath(index, "test.txt")); + + cl_git_pass(p_rename("myrepo/test.txt", "myrepo/TEST.txt")); + cl_git_rewritefile("myrepo/TEST.txt", "hello again\n"); + cl_git_pass(git_index_add_bypath(index, "TEST.txt")); + + cl_assert(git_index_entrycount(index) == 1); + + /* Test access by path instead of index */ + cl_assert((entry = git_index_get_bypath(index, "test.txt", 0)) != NULL); + cl_assert((entry = git_index_get_bypath(index, "TEST.txt", 0)) != NULL); + + /* The path should *not* have changed without an explicit remove */ + cl_assert(git__strcmp(entry->path, "test.txt") == 0); + + git_index_free(index); + git_repository_free(repo); +} + From 6ea999bb88e1c5d0be17d823c23728d11adfed47 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 13 Jun 2013 15:52:12 -0700 Subject: [PATCH 10/16] Make index_insert keep existing case In a case insensitive index, if you attempt to add a file from disk with a different case pattern, the old case pattern in the index should be preserved. This fixes that (and a couple of minor warnings). --- src/index.c | 5 +++-- tests-clar/diff/rename.c | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.c b/src/index.c index 4f0c70135..560a257e7 100644 --- a/src/index.c +++ b/src/index.c @@ -734,8 +734,9 @@ static int index_insert(git_index *index, git_index_entry *entry, int replace) if (!replace || !existing) return git_vector_insert(&index->entries, entry); - /* exists, replace it */ - git__free((*existing)->path); + /* exists, replace it (preserving name from existing entry) */ + git__free(entry->path); + entry->path = (*existing)->path; git__free(*existing); *existing = entry; diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index 6227a54e8..c4b722314 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -911,7 +911,6 @@ void test_diff_rename__case_changes_are_split(void) { git_index *index; git_tree *tree; - git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_diff_list *diff = NULL; diff_expects exp; From eefef642c8c0d9d527633294acdf9d7a0c9e94c0 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 13 Jun 2013 16:09:53 -0700 Subject: [PATCH 11/16] Always do tree to index diffs case sensitively Trees are always case sensitive. The index is always case preserving and will be case sensitive when it is turned into a tree. Therefore the tree and the index can and should always be compared to one another case sensitively. This will restore the index to case insensitive order after the diff has been generated. Consider this a short-term fix. The long term fix is to have the index always stored both case sensitively and case insensitively (at least on platforms that sometimes require case insensitivity). --- src/diff.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/diff.c b/src/diff.c index 87189a49c..fa2c5c71d 100644 --- a/src/diff.c +++ b/src/diff.c @@ -364,6 +364,8 @@ static git_diff_list *diff_list_alloc( diff->strncomp = git__strncasecmp; diff->pfxcomp = git__prefixcmp_icase; diff->entrycomp = git_index_entry__cmp_icase; + + diff->deltas._cmp = git_diff_delta__casecmp; } return diff; @@ -1127,17 +1129,40 @@ int git_diff_tree_to_index( const git_diff_options *opts) { int error = 0; + bool reset_index_ignore_case = false; assert(diff && repo); if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0) return error; + if (index->ignore_case) { + git_index__set_ignore_case(index, false); + reset_index_ignore_case = true; + } + DIFF_FROM_ITERATORS( git_iterator_for_tree(&a, old_tree, 0, pfx, pfx), git_iterator_for_index(&b, index, 0, pfx, pfx) ); + if (reset_index_ignore_case) { + git_index__set_ignore_case(index, true); + + if (!error) { + git_diff_list *d = *diff; + + d->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE; + d->strcomp = git__strcasecmp; + d->strncomp = git__strncasecmp; + d->pfxcomp = git__prefixcmp_icase; + d->entrycomp = git_index_entry__cmp_icase; + + d->deltas._cmp = git_diff_delta__casecmp; + git_vector_sort(&d->deltas); + } + } + return error; } From fb03a223189f418d0767d7d04ff7509dfcfe8394 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 13 Jun 2013 16:31:11 -0700 Subject: [PATCH 12/16] Test has to work on case sensitive systems --- tests-clar/index/tests.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/tests-clar/index/tests.c b/tests-clar/index/tests.c index 53d45a84e..1bc5e6a07 100644 --- a/tests-clar/index/tests.c +++ b/tests-clar/index/tests.c @@ -422,12 +422,15 @@ void test_index_tests__preserves_case(void) git_repository *repo; git_index *index; const git_index_entry *entry; + int index_caps; cl_set_cleanup(&cleanup_myrepo, NULL); cl_git_pass(git_repository_init(&repo, "./myrepo", 0)); cl_git_pass(git_repository_index(&index, repo)); + index_caps = git_index_caps(index); + cl_git_rewritefile("myrepo/test.txt", "hey there\n"); cl_git_pass(git_index_add_bypath(index, "test.txt")); @@ -435,15 +438,23 @@ void test_index_tests__preserves_case(void) cl_git_rewritefile("myrepo/TEST.txt", "hello again\n"); cl_git_pass(git_index_add_bypath(index, "TEST.txt")); - cl_assert(git_index_entrycount(index) == 1); + if (index_caps & GIT_INDEXCAP_IGNORE_CASE) + cl_assert_equal_i(1, (int)git_index_entrycount(index)); + else + cl_assert_equal_i(2, (int)git_index_entrycount(index)); /* Test access by path instead of index */ cl_assert((entry = git_index_get_bypath(index, "test.txt", 0)) != NULL); - cl_assert((entry = git_index_get_bypath(index, "TEST.txt", 0)) != NULL); - /* The path should *not* have changed without an explicit remove */ cl_assert(git__strcmp(entry->path, "test.txt") == 0); + cl_assert((entry = git_index_get_bypath(index, "TEST.txt", 0)) != NULL); + if (index_caps & GIT_INDEXCAP_IGNORE_CASE) + /* The path should *not* have changed without an explicit remove */ + cl_assert(git__strcmp(entry->path, "test.txt") == 0); + else + cl_assert(git__strcmp(entry->path, "TEST.txt") == 0); + git_index_free(index); git_repository_free(repo); } From a1683f28ce2709e615490939e4e244046654d0e5 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 14 Jun 2013 16:18:04 -0700 Subject: [PATCH 13/16] More tests and bug fixes for status with rename This changes the behavior of the status RENAMED flags so that they will be combined with the MODIFIED flags if appropriate. If a file is modified in the index and also renamed, then the status code will have both the GIT_STATUS_INDEX_MODIFIED and INDEX_RENAMED bits set. If it is renamed but the OID has not changed, then just the GIT_STATUS_INDEX_RENAMED bit will be set. Similarly, the flags GIT_STATUS_WT_MODIFIED and GIT_STATUS_WT_RENAMED can both be set independently of one another. This fixes a serious bug where the check for unmodified files that was done at data load time could end up erasing the RENAMED state of a file that was renamed with no changes. Lastly, this contains a bunch of new tests for status with renames, including tests where the only rename changes are case changes. The expected results of these tests have to vary by whether the platform uses a case sensitive filesystem or not, so the expected data covers those platform differences separately. --- src/diff.h | 9 +-- src/diff_patch.c | 6 +- src/diff_print.c | 109 ++++++++++++++------------- src/diff_tform.c | 2 +- src/status.c | 49 ++++++++++--- tests-clar/status/renames.c | 142 +++++++++++++++++++++++++++++++++--- 6 files changed, 237 insertions(+), 80 deletions(-) diff --git a/src/diff.h b/src/diff.h index 1536f79d7..6ef03ee7c 100644 --- a/src/diff.h +++ b/src/diff.h @@ -95,17 +95,16 @@ extern int git_diff__paired_foreach( int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload), void *payload); -int git_diff_find_similar__hashsig_for_file( +extern int git_diff_find_similar__hashsig_for_file( void **out, const git_diff_file *f, const char *path, void *p); -int git_diff_find_similar__hashsig_for_buf( +extern int git_diff_find_similar__hashsig_for_buf( void **out, const git_diff_file *f, const char *buf, size_t len, void *p); -void git_diff_find_similar__hashsig_free(void *sig, void *payload); +extern void git_diff_find_similar__hashsig_free(void *sig, void *payload); -int git_diff_find_similar__calc_similarity( +extern int git_diff_find_similar__calc_similarity( int *score, void *siga, void *sigb, void *payload); - #endif diff --git a/src/diff_patch.c b/src/diff_patch.c index a1e1fe84c..40cb3371a 100644 --- a/src/diff_patch.c +++ b/src/diff_patch.c @@ -175,10 +175,11 @@ static int diff_patch_load(git_diff_patch *patch, git_diff_output *output) goto cleanup; } - /* if we were previously missing an oid, reassess UNMODIFIED state */ + /* if we were previously missing an oid, update MODIFIED->UNMODIFIED */ if (incomplete_data && patch->ofile.file.mode == patch->nfile.file.mode && - git_oid_equal(&patch->ofile.file.oid, &patch->nfile.file.oid)) + git_oid_equal(&patch->ofile.file.oid, &patch->nfile.file.oid) && + patch->delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */ patch->delta->status = GIT_DELTA_UNMODIFIED; cleanup: @@ -284,6 +285,7 @@ int git_diff_foreach( git_xdiff_init(&xo, &diff->opts); git_vector_foreach(&diff->deltas, idx, patch.delta) { + /* check flags against patch status */ if (git_diff_delta__should_skip(&diff->opts, patch.delta)) continue; diff --git a/src/diff_print.c b/src/diff_print.c index 244aa6e1d..6fc7425eb 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -41,7 +41,7 @@ static int diff_print_info_init( return 0; } -static char pick_suffix(int mode) +static char diff_pick_suffix(int mode) { if (S_ISDIR(mode)) return '/'; @@ -76,10 +76,11 @@ static int callback_error(void) return GIT_EUSER; } -static int print_compact( +static int diff_print_one_compact( const git_diff_delta *delta, float progress, void *data) { diff_print_info *pi = data; + git_buf *out = pi->buf; char old_suffix, new_suffix, code = git_diff_status_char(delta->status); GIT_UNUSED(progress); @@ -87,34 +88,35 @@ static int print_compact( if (code == ' ') return 0; - old_suffix = pick_suffix(delta->old_file.mode); - new_suffix = pick_suffix(delta->new_file.mode); + old_suffix = diff_pick_suffix(delta->old_file.mode); + new_suffix = diff_pick_suffix(delta->new_file.mode); - git_buf_clear(pi->buf); + git_buf_clear(out); if (delta->old_file.path != delta->new_file.path && pi->diff->strcomp(delta->old_file.path,delta->new_file.path) != 0) - git_buf_printf(pi->buf, "%c\t%s%c -> %s%c\n", code, + git_buf_printf(out, "%c\t%s%c -> %s%c\n", code, delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); else if (delta->old_file.mode != delta->new_file.mode && delta->old_file.mode != 0 && delta->new_file.mode != 0) - git_buf_printf(pi->buf, "%c\t%s%c (%o -> %o)\n", code, + git_buf_printf(out, "%c\t%s%c (%o -> %o)\n", code, delta->old_file.path, new_suffix, delta->old_file.mode, delta->new_file.mode); else if (old_suffix != ' ') - git_buf_printf(pi->buf, "%c\t%s%c\n", code, delta->old_file.path, old_suffix); + git_buf_printf(out, "%c\t%s%c\n", code, delta->old_file.path, old_suffix); else - git_buf_printf(pi->buf, "%c\t%s\n", code, delta->old_file.path); + git_buf_printf(out, "%c\t%s\n", code, delta->old_file.path); - if (git_buf_oom(pi->buf)) + if (git_buf_oom(out)) return -1; if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR, - git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) + git_buf_cstr(out), git_buf_len(out), pi->payload)) return callback_error(); return 0; } +/* print a git_diff_list to a print callback in compact format */ int git_diff_print_compact( git_diff_list *diff, git_diff_data_cb print_cb, @@ -125,17 +127,18 @@ int git_diff_print_compact( diff_print_info pi; if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload))) - error = git_diff_foreach(diff, print_compact, NULL, NULL, &pi); + error = git_diff_foreach(diff, diff_print_one_compact, NULL, NULL, &pi); git_buf_free(&buf); return error; } -static int print_raw( +static int diff_print_one_raw( const git_diff_delta *delta, float progress, void *data) { diff_print_info *pi = data; + git_buf *out = pi->buf; char code = git_diff_status_char(delta->status); char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; @@ -144,36 +147,37 @@ static int print_raw( if (code == ' ') return 0; - git_buf_clear(pi->buf); + git_buf_clear(out); git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid); git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.oid); git_buf_printf( - pi->buf, ":%06o %06o %s... %s... %c", + out, ":%06o %06o %s... %s... %c", delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code); if (delta->similarity > 0) - git_buf_printf(pi->buf, "%03u", delta->similarity); + git_buf_printf(out, "%03u", delta->similarity); if (delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_COPIED) git_buf_printf( - pi->buf, "\t%s %s\n", delta->old_file.path, delta->new_file.path); + out, "\t%s %s\n", delta->old_file.path, delta->new_file.path); else git_buf_printf( - pi->buf, "\t%s\n", delta->old_file.path ? + out, "\t%s\n", delta->old_file.path ? delta->old_file.path : delta->new_file.path); - if (git_buf_oom(pi->buf)) + if (git_buf_oom(out)) return -1; if (pi->print_cb(delta, NULL, GIT_DIFF_LINE_FILE_HDR, - git_buf_cstr(pi->buf), git_buf_len(pi->buf), pi->payload)) + git_buf_cstr(out), git_buf_len(out), pi->payload)) return callback_error(); return 0; } +/* print a git_diff_list to a print callback in raw output format */ int git_diff_print_raw( git_diff_list *diff, git_diff_data_cb print_cb, @@ -184,15 +188,16 @@ int git_diff_print_raw( diff_print_info pi; if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload))) - error = git_diff_foreach(diff, print_raw, NULL, NULL, &pi); + error = git_diff_foreach(diff, diff_print_one_raw, NULL, NULL, &pi); git_buf_free(&buf); return error; } -static int print_oid_range(diff_print_info *pi, const git_diff_delta *delta) +static int diff_print_oid_range(diff_print_info *pi, const git_diff_delta *delta) { + git_buf *out = pi->buf; char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.oid); @@ -200,27 +205,27 @@ static int print_oid_range(diff_print_info *pi, const git_diff_delta *delta) /* TODO: Match git diff more closely */ if (delta->old_file.mode == delta->new_file.mode) { - git_buf_printf(pi->buf, "index %s..%s %o\n", + git_buf_printf(out, "index %s..%s %o\n", start_oid, end_oid, delta->old_file.mode); } else { if (delta->old_file.mode == 0) { - git_buf_printf(pi->buf, "new file mode %o\n", delta->new_file.mode); + git_buf_printf(out, "new file mode %o\n", delta->new_file.mode); } else if (delta->new_file.mode == 0) { - git_buf_printf(pi->buf, "deleted file mode %o\n", delta->old_file.mode); + git_buf_printf(out, "deleted file mode %o\n", delta->old_file.mode); } else { - git_buf_printf(pi->buf, "old mode %o\n", delta->old_file.mode); - git_buf_printf(pi->buf, "new mode %o\n", delta->new_file.mode); + git_buf_printf(out, "old mode %o\n", delta->old_file.mode); + git_buf_printf(out, "new mode %o\n", delta->new_file.mode); } - git_buf_printf(pi->buf, "index %s..%s\n", start_oid, end_oid); + git_buf_printf(out, "index %s..%s\n", start_oid, end_oid); } - if (git_buf_oom(pi->buf)) + if (git_buf_oom(out)) return -1; return 0; } -static int print_patch_file( +static int diff_print_patch_file( const git_diff_delta *delta, float progress, void *data) { diff_print_info *pi = data; @@ -247,7 +252,7 @@ static int print_patch_file( git_buf_clear(pi->buf); git_buf_printf(pi->buf, "diff --git %s%s %s%s\n", oldpfx, delta->old_file.path, newpfx, delta->new_file.path); - if (print_oid_range(pi, delta) < 0) + if (diff_print_oid_range(pi, delta) < 0) return -1; if (git_oid_iszero(&delta->old_file.oid)) { @@ -288,7 +293,7 @@ static int print_patch_file( return 0; } -static int print_patch_hunk( +static int diff_print_patch_hunk( const git_diff_delta *d, const git_diff_range *r, const char *header, @@ -311,7 +316,7 @@ static int print_patch_hunk( return 0; } -static int print_patch_line( +static int diff_print_patch_line( const git_diff_delta *delta, const git_diff_range *range, char line_origin, /* GIT_DIFF_LINE value from above */ @@ -343,6 +348,7 @@ static int print_patch_line( return 0; } +/* print a git_diff_list to an output callback in patch format */ int git_diff_print_patch( git_diff_list *diff, git_diff_data_cb print_cb, @@ -354,27 +360,15 @@ int git_diff_print_patch( if (!(error = diff_print_info_init(&pi, &buf, diff, print_cb, payload))) error = git_diff_foreach( - diff, print_patch_file, print_patch_hunk, print_patch_line, &pi); + diff, diff_print_patch_file, diff_print_patch_hunk, + diff_print_patch_line, &pi); git_buf_free(&buf); return error; } - -static int print_to_buffer_cb( - const git_diff_delta *delta, - const git_diff_range *range, - char line_origin, - const char *content, - size_t content_len, - void *payload) -{ - git_buf *output = payload; - GIT_UNUSED(delta); GIT_UNUSED(range); GIT_UNUSED(line_origin); - return git_buf_put(output, content, content_len); -} - +/* print a git_diff_patch to an output callback */ int git_diff_patch_print( git_diff_patch *patch, git_diff_data_cb print_cb, @@ -389,13 +383,28 @@ int git_diff_patch_print( if (!(error = diff_print_info_init( &pi, &temp, git_diff_patch__diff(patch), print_cb, payload))) error = git_diff_patch__invoke_callbacks( - patch, print_patch_file, print_patch_hunk, print_patch_line, &pi); + patch, diff_print_patch_file, diff_print_patch_hunk, + diff_print_patch_line, &pi); git_buf_free(&temp); return error; } +static int diff_print_to_buffer_cb( + const git_diff_delta *delta, + const git_diff_range *range, + char line_origin, + const char *content, + size_t content_len, + void *payload) +{ + git_buf *output = payload; + GIT_UNUSED(delta); GIT_UNUSED(range); GIT_UNUSED(line_origin); + return git_buf_put(output, content, content_len); +} + +/* print a git_diff_patch to a string buffer */ int git_diff_patch_to_str( char **string, git_diff_patch *patch) @@ -403,7 +412,7 @@ int git_diff_patch_to_str( int error; git_buf output = GIT_BUF_INIT; - error = git_diff_patch_print(patch, print_to_buffer_cb, &output); + error = git_diff_patch_print(patch, diff_print_to_buffer_cb, &output); /* GIT_EUSER means git_buf_put in print_to_buffer_cb returned -1, * meaning a memory allocation failure, so just map to -1... diff --git a/src/diff_tform.c b/src/diff_tform.c index 94fa035f2..64746e7dd 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -483,7 +483,7 @@ static int similarity_measure( if (GIT_MODE_TYPE(a_file->mode) != GIT_MODE_TYPE(b_file->mode)) return 0; - /* if exact match is requested, force calculation of missing OIDs */ + /* if exact match is requested, force calculation of missing OIDs now */ if (exact_match) { if (git_oid_iszero(&a_file->oid) && diff->old_src == GIT_ITERATOR_TYPE_WORKDIR && diff --git a/src/status.c b/src/status.c index 9c4aead74..375100a89 100644 --- a/src/status.c +++ b/src/status.c @@ -20,11 +20,11 @@ #include "git2/diff.h" #include "diff.h" -static unsigned int index_delta2status(git_delta_t index_status) +static unsigned int index_delta2status(const git_diff_delta *head2idx) { - unsigned int st = GIT_STATUS_CURRENT; + git_status_t st = GIT_STATUS_CURRENT; - switch (index_status) { + switch (head2idx->status) { case GIT_DELTA_ADDED: case GIT_DELTA_COPIED: st = GIT_STATUS_INDEX_NEW; @@ -37,6 +37,9 @@ static unsigned int index_delta2status(git_delta_t index_status) break; case GIT_DELTA_RENAMED: st = GIT_STATUS_INDEX_RENAMED; + + if (!git_oid_equal(&head2idx->old_file.oid, &head2idx->new_file.oid)) + st |= GIT_STATUS_INDEX_MODIFIED; break; case GIT_DELTA_TYPECHANGE: st = GIT_STATUS_INDEX_TYPECHANGE; @@ -48,11 +51,12 @@ static unsigned int index_delta2status(git_delta_t index_status) return st; } -static unsigned int workdir_delta2status(git_delta_t workdir_status) +static unsigned int workdir_delta2status( + git_diff_list *diff, git_diff_delta *idx2wd) { - unsigned int st = GIT_STATUS_CURRENT; + git_status_t st = GIT_STATUS_CURRENT; - switch (workdir_status) { + switch (idx2wd->status) { case GIT_DELTA_ADDED: case GIT_DELTA_COPIED: case GIT_DELTA_UNTRACKED: @@ -69,6 +73,28 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status) break; case GIT_DELTA_RENAMED: st = GIT_STATUS_WT_RENAMED; + + if (!git_oid_equal(&idx2wd->old_file.oid, &idx2wd->new_file.oid)) { + /* if OIDs don't match, we might need to calculate them now to + * discern between RENAMED vs RENAMED+MODIFED + */ + if (git_oid_iszero(&idx2wd->old_file.oid) && + diff->old_src == GIT_ITERATOR_TYPE_WORKDIR && + !git_diff__oid_for_file( + diff->repo, idx2wd->old_file.path, idx2wd->old_file.mode, + idx2wd->old_file.size, &idx2wd->old_file.oid)) + idx2wd->old_file.flags |= GIT_DIFF_FLAG_VALID_OID; + + if (git_oid_iszero(&idx2wd->new_file.oid) && + diff->new_src == GIT_ITERATOR_TYPE_WORKDIR && + !git_diff__oid_for_file( + diff->repo, idx2wd->new_file.path, idx2wd->new_file.mode, + idx2wd->new_file.size, &idx2wd->new_file.oid)) + idx2wd->new_file.flags |= GIT_DIFF_FLAG_VALID_OID; + + if (!git_oid_equal(&idx2wd->old_file.oid, &idx2wd->new_file.oid)) + st |= GIT_STATUS_WT_MODIFIED; + } break; case GIT_DELTA_TYPECHANGE: st = GIT_STATUS_WT_TYPECHANGE; @@ -111,18 +137,19 @@ static bool status_is_included( } static git_status_t status_compute( + git_status_list *status, git_diff_delta *head2idx, git_diff_delta *idx2wd) { - git_status_t status = 0; + git_status_t st = GIT_STATUS_CURRENT; if (head2idx) - status |= index_delta2status(head2idx->status); + st |= index_delta2status(head2idx); if (idx2wd) - status |= workdir_delta2status(idx2wd->status); + st |= workdir_delta2status(status->idx2wd, idx2wd); - return status; + return st; } static int status_collect( @@ -139,7 +166,7 @@ static int status_collect( status_entry = git__malloc(sizeof(git_status_entry)); GITERR_CHECK_ALLOC(status_entry); - status_entry->status = status_compute(head2idx, idx2wd); + status_entry->status = status_compute(status, head2idx, idx2wd); status_entry->head_to_index = head2idx; status_entry->index_to_workdir = idx2wd; diff --git a/tests-clar/status/renames.c b/tests-clar/status/renames.c index d29c7bfe8..80ff26020 100644 --- a/tests-clar/status/renames.c +++ b/tests-clar/status/renames.c @@ -61,13 +61,13 @@ static void test_status( const char *oldname, *newname; size_t i; - cl_assert(expected_len == git_status_list_entrycount(status_list)); + cl_assert_equal_sz(expected_len, git_status_list_entrycount(status_list)); for (i = 0; i < expected_len; i++) { actual = git_status_byindex(status_list, i); expected = &expected_list[i]; - cl_assert(actual->status == expected->status); + cl_assert_equal_i((int)expected->status, (int)actual->status); oldname = actual->head_to_index ? actual->head_to_index->old_file.path : actual->index_to_workdir ? actual->index_to_workdir->old_file.path : NULL; @@ -119,8 +119,10 @@ void test_status_renames__head2index_two(void) git_status_list *statuslist; git_status_options opts = GIT_STATUS_OPTIONS_INIT; struct status_entry expected[] = { - { GIT_STATUS_INDEX_RENAMED, "sixserving.txt", "aaa.txt" }, - { GIT_STATUS_INDEX_RENAMED, "untimely.txt", "bbb.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED, + "sixserving.txt", "aaa.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED, + "untimely.txt", "bbb.txt" }, { GIT_STATUS_INDEX_RENAMED, "songof7cities.txt", "ccc.txt" }, { GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", "ddd.txt" }, }; @@ -174,8 +176,10 @@ void test_status_renames__index2workdir_two(void) git_status_list *statuslist; git_status_options opts = GIT_STATUS_OPTIONS_INIT; struct status_entry expected[] = { - { GIT_STATUS_WT_RENAMED, "sixserving.txt", "aaa.txt" }, - { GIT_STATUS_WT_RENAMED, "untimely.txt", "bbb.txt" }, + { GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, + "sixserving.txt", "aaa.txt" }, + { GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, + "untimely.txt", "bbb.txt" }, { GIT_STATUS_WT_RENAMED, "songof7cities.txt", "ccc.txt" }, { GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "ddd.txt" }, }; @@ -199,7 +203,8 @@ void test_status_renames__both_one(void) git_status_list *statuslist; git_status_options opts = GIT_STATUS_OPTIONS_INIT; struct status_entry expected[] = { - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "newname-workdir.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "ikeepsix.txt", "newname-workdir.txt" }, }; opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; @@ -229,10 +234,15 @@ void test_status_renames__both_two(void) git_status_list *statuslist; git_status_options opts = GIT_STATUS_OPTIONS_INIT; struct status_entry expected[] = { - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "ikeepsix-both.txt" }, - { GIT_STATUS_INDEX_RENAMED, "sixserving.txt", "sixserving-index.txt" }, - { GIT_STATUS_WT_RENAMED, "songof7cities.txt", "songof7cities-workdir.txt" }, - { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, "untimely.txt", "untimely-both.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED | + GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, + "ikeepsix.txt", "ikeepsix-both.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED, + "sixserving.txt", "sixserving-index.txt" }, + { GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, + "songof7cities.txt", "songof7cities-workdir.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "untimely.txt", "untimely-both.txt" }, }; opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; @@ -263,3 +273,113 @@ void test_status_renames__both_two(void) git_index_free(index); } + +void test_status_renames__both_casechange_one(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + int index_caps; + struct status_entry expected_icase[] = { + { GIT_STATUS_INDEX_RENAMED, + "ikeepsix.txt", "IKeepSix.txt" }, + }; + struct status_entry expected_case[] = { + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "ikeepsix.txt", "IKEEPSIX.txt" }, + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + cl_git_pass(git_repository_index(&index, g_repo)); + index_caps = git_index_caps(index); + + rename_file(g_repo, "ikeepsix.txt", "IKeepSix.txt"); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "IKeepSix.txt")); + cl_git_pass(git_index_write(index)); + + /* on a case-insensitive file system, this change won't matter. + * on a case-sensitive one, it will. + */ + rename_file(g_repo, "IKeepSix.txt", "IKEEPSIX.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + + test_status(statuslist, (index_caps & GIT_INDEXCAP_IGNORE_CASE) ? + expected_icase : expected_case, 1); + + git_status_list_free(statuslist); + + git_index_free(index); +} + +void test_status_renames__both_casechange_two(void) +{ + git_index *index; + git_status_list *statuslist; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + int index_caps; + struct status_entry expected_icase[] = { + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED | + GIT_STATUS_WT_MODIFIED, + "ikeepsix.txt", "IKeepSix.txt" }, + { GIT_STATUS_INDEX_MODIFIED, + "sixserving.txt", "sixserving.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_MODIFIED, + "songof7cities.txt", "songof7.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "untimely.txt", "untimeliest.txt" } + }; + struct status_entry expected_case[] = { + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED | + GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED, + "ikeepsix.txt", "ikeepsix.txt" }, + { GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_RENAMED, + "sixserving.txt", "SixServing.txt" }, + { GIT_STATUS_INDEX_RENAMED | + GIT_STATUS_WT_MODIFIED | GIT_STATUS_WT_RENAMED, + "songof7cities.txt", "SONGOF7.txt" }, + { GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, + "untimely.txt", "untimeliest.txt" } + }; + + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED; + opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR; + + cl_git_pass(git_repository_index(&index, g_repo)); + index_caps = git_index_caps(index); + + rename_and_edit_file(g_repo, "ikeepsix.txt", "IKeepSix.txt"); + rename_and_edit_file(g_repo, "sixserving.txt", "sixserving.txt"); + rename_file(g_repo, "songof7cities.txt", "songof7.txt"); + rename_file(g_repo, "untimely.txt", "untimelier.txt"); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_remove_bypath(index, "sixserving.txt")); + cl_git_pass(git_index_remove_bypath(index, "songof7cities.txt")); + cl_git_pass(git_index_remove_bypath(index, "untimely.txt")); + cl_git_pass(git_index_add_bypath(index, "IKeepSix.txt")); + cl_git_pass(git_index_add_bypath(index, "sixserving.txt")); + cl_git_pass(git_index_add_bypath(index, "songof7.txt")); + cl_git_pass(git_index_add_bypath(index, "untimelier.txt")); + cl_git_pass(git_index_write(index)); + + rename_and_edit_file(g_repo, "IKeepSix.txt", "ikeepsix.txt"); + rename_file(g_repo, "sixserving.txt", "SixServing.txt"); + rename_and_edit_file(g_repo, "songof7.txt", "SONGOF7.txt"); + rename_file(g_repo, "untimelier.txt", "untimeliest.txt"); + + cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts)); + + test_status(statuslist, (index_caps & GIT_INDEXCAP_IGNORE_CASE) ? + expected_icase : expected_case, 4); + + git_status_list_free(statuslist); + + git_index_free(index); +} From f3b5bc835ae4345a7a03834ffaf54a0aca92387d Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Sun, 16 Jun 2013 21:51:43 -0700 Subject: [PATCH 14/16] Add test of rename with no changes A tree to index rename with no changes was getting erased by the iteration routine (if the routine actually loaded the data for the unmodified file). This invokes the code path that was previously messing up the diff and iterates twice to make sure that the iteration process itself doesn't modify the data. --- tests-clar/diff/rename.c | 57 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index c4b722314..8a08da3ef 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -899,6 +899,7 @@ void test_diff_rename__rejected_match_can_match_others(void) cl_git_pass( git_diff_foreach(diff, test_names_expected, NULL, NULL, &expect)); + git_diff_list_free(diff); git_tree_free(tree); git_index_free(index); git_reference_free(head); @@ -913,6 +914,7 @@ void test_diff_rename__case_changes_are_split(void) git_tree *tree; git_diff_list *diff = NULL; diff_expects exp; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; cl_git_pass(git_repository_index(&index, g_repo)); @@ -934,6 +936,61 @@ void test_diff_rename__case_changes_are_split(void) cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_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_RENAMED]); + + git_diff_list_free(diff); git_index_free(index); } +void test_diff_rename__unmodified_can_be_renamed(void) +{ + git_index *index; + git_tree *tree; + git_diff_list *diff = NULL; + diff_expects exp; + git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options opts = GIT_DIFF_FIND_OPTIONS_INIT; + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass( + git_revparse_single((git_object **)&tree, g_repo, "HEAD^{tree}")); + + cl_git_pass(p_rename("renames/ikeepsix.txt", "renames/ikeepsix2.txt")); + + cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt")); + cl_git_pass(git_index_add_bypath(index, "ikeepsix2.txt")); + cl_git_pass(git_index_write(index)); + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, tree, index, &diffopts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + cl_assert_equal_i(2, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); + + opts.flags = GIT_DIFF_FIND_ALL; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_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_RENAMED]); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_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_RENAMED]); + + git_diff_list_free(diff); +} From de0555a347b4be48f80d0b3bf26ddd81f5ef38aa Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 17 Jun 2013 09:55:29 -0700 Subject: [PATCH 15/16] Fix memory leaks in diff rename tests This fixes a couple objects I forgot to free, and also updates the valgrind suppressions file on the Mac to cover a few more cases that had crept in. --- tests-clar/diff/rename.c | 3 +++ tests-clar/valgrind-supp-mac.txt | 14 ++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c index 8a08da3ef..2600bd872 100644 --- a/tests-clar/diff/rename.c +++ b/tests-clar/diff/rename.c @@ -947,6 +947,7 @@ void test_diff_rename__case_changes_are_split(void) git_diff_list_free(diff); git_index_free(index); + git_tree_free(tree); } void test_diff_rename__unmodified_can_be_renamed(void) @@ -993,4 +994,6 @@ void test_diff_rename__unmodified_can_be_renamed(void) cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); git_diff_list_free(diff); + git_index_free(index); + git_tree_free(tree); } diff --git a/tests-clar/valgrind-supp-mac.txt b/tests-clar/valgrind-supp-mac.txt index 297b11e62..fcc7ede86 100644 --- a/tests-clar/valgrind-supp-mac.txt +++ b/tests-clar/valgrind-supp-mac.txt @@ -113,24 +113,18 @@ { mac-ssl-leak-1 Memcheck:Leak - fun:malloc - fun:CRYPTO_malloc ... fun:ERR_load_strings } { mac-ssl-leak-2 Memcheck:Leak - fun:malloc - fun:CRYPTO_malloc ... fun:SSL_library_init } { mac-ssl-leak-3 Memcheck:Leak - fun:malloc - fun:strdup ... fun:si_module_with_name fun:getaddrinfo @@ -143,6 +137,14 @@ ... fun:ssl3_get_server_certificate } +{ + mac-ssl-leak-5 + Memcheck:Leak + fun:malloc + fun:CRYPTO_malloc + ... + fun:ERR_put_error +} { clar-printf-buf Memcheck:Leak From f4183347607c85d3fbe02e8591d9393a011ecdf2 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 17 Jun 2013 10:23:53 -0700 Subject: [PATCH 16/16] Update clar to latest version --- tests-clar/clar.c | 48 +++++++++++++++++++++++---------------- tests-clar/clar/sandbox.h | 12 ++++++---- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/tests-clar/clar.c b/tests-clar/clar.c index 0eae81bf5..fb10dd397 100644 --- a/tests-clar/clar.c +++ b/tests-clar/clar.c @@ -183,10 +183,10 @@ clar_run_test( } static void -clar_run_suite(const struct clar_suite *suite, const char *name) +clar_run_suite(const struct clar_suite *suite, const char *filter) { const struct clar_func *test = suite->tests; - size_t i, namelen; + size_t i, matchlen; if (!suite->enabled) return; @@ -200,21 +200,21 @@ clar_run_suite(const struct clar_suite *suite, const char *name) _clar.active_suite = suite->name; _clar.suite_errors = 0; - if (name) { + if (filter) { size_t suitelen = strlen(suite->name); - namelen = strlen(name); - if (namelen <= suitelen) { - name = NULL; + matchlen = strlen(filter); + if (matchlen <= suitelen) { + filter = NULL; } else { - name += suitelen; - while (*name == ':') - ++name; - namelen = strlen(name); + filter += suitelen; + while (*filter == ':') + ++filter; + matchlen = strlen(filter); } } for (i = 0; i < suite->test_count; ++i) { - if (name && strncmp(test[i].name, name, namelen)) + if (filter && strncmp(test[i].name, filter, matchlen)) continue; _clar.active_test = test[i].name; @@ -230,7 +230,7 @@ clar_usage(const char *arg) { printf("Usage: %s [options]\n\n", arg); printf("Options:\n"); - printf(" -sname\tRun only the suite with `name`\n"); + printf(" -sname\tRun only the suite with `name` (can go to individual test name)\n"); printf(" -iname\tInclude the suite with `name`\n"); printf(" -xname\tExclude the suite with `name`\n"); printf(" -q \tOnly report tests that had an error\n"); @@ -256,21 +256,20 @@ clar_parse_args(int argc, char **argv) case 'x': { /* given suite name */ int offset = (argument[2] == '=') ? 3 : 2, found = 0; char action = argument[1]; - size_t j, len, cmplen; + size_t j, arglen, suitelen, cmplen; argument += offset; - len = strlen(argument); + arglen = strlen(argument); - if (len == 0) + if (arglen == 0) clar_usage(argv[0]); for (j = 0; j < _clar_suite_count; ++j) { - cmplen = strlen(_clar_suites[j].name); - if (cmplen > len) - cmplen = len; + suitelen = strlen(_clar_suites[j].name); + cmplen = (arglen < suitelen) ? arglen : suitelen; if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) { - int exact = !strcmp(argument, _clar_suites[j].name); + int exact = (arglen >= suitelen); ++found; @@ -419,7 +418,16 @@ void clar__assert_equal_s( if (!match) { char buf[4096]; - snprint_eq(buf, sizeof(buf), "'%s' != '%s'", s1, s2); + + if (s1 && s2) { + int pos; + for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos) + /* find differing byte offset */; + snprint_eq(buf, sizeof(buf), "'%s' != '%s' (at byte %d)", s1, s2, pos); + } else { + snprint_eq(buf, sizeof(buf), "'%s' != '%s'", s1, s2); + } + clar__fail(file, line, err, buf, should_abort); } } diff --git a/tests-clar/clar/sandbox.h b/tests-clar/clar/sandbox.h index bed3011fe..1ca6fcae8 100644 --- a/tests-clar/clar/sandbox.h +++ b/tests-clar/clar/sandbox.h @@ -18,9 +18,9 @@ static int find_tmp_path(char *buffer, size_t length) { #ifndef _WIN32 - static const size_t var_count = 4; + static const size_t var_count = 5; static const char *env_vars[] = { - "TMPDIR", "TMP", "TEMP", "USERPROFILE" + "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE" }; size_t i; @@ -43,6 +43,12 @@ find_tmp_path(char *buffer, size_t length) } #else + DWORD env_len; + + if ((env_len = GetEnvironmentVariable("CLAR_TMP", buffer, length)) > 0 && + env_len < length) + return 0; + if (GetTempPath((DWORD)length, buffer)) return 0; #endif @@ -61,9 +67,7 @@ static void clar_unsandbox(void) if (_clar_path[0] == '\0') return; -#ifdef _WIN32 chdir(".."); -#endif fs_rm(_clar_path); }