From 3aa443a9511f5b9848d314337b226c41ef3eef84 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Mon, 20 Aug 2012 16:56:45 +0200 Subject: [PATCH 01/14] checkout: introduce git_checkout_tree() --- include/git2/checkout.h | 29 ++- src/checkout.c | 409 +++++++++++++++++++++------------ src/clone.c | 3 +- src/filter.c | 38 +-- src/filter.h | 7 +- tests-clar/checkout/checkout.c | 206 ----------------- tests-clar/checkout/tree.c | 268 +++++++++++++++++++++ 7 files changed, 573 insertions(+), 387 deletions(-) delete mode 100644 tests-clar/checkout/checkout.c create mode 100644 tests-clar/checkout/tree.c diff --git a/include/git2/checkout.h b/include/git2/checkout.h index deb828722..21b68e3ab 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -32,10 +32,16 @@ typedef struct git_checkout_opts { int dir_mode; /* default is 0755 */ int file_mode; /* default is 0644 */ int file_open_flags; /* default is O_CREAT | O_TRUNC | O_WRONLY */ + + /* when not NULL, arrays of fnmatch pattern specifying + * which paths should be taken into account + */ + git_strarray *paths; } git_checkout_opts; /** - * Updates files in the working tree to match the commit pointed to by HEAD. + * Updates files in the index and the working tree to match the content of the + * commit pointed at by HEAD. * * @param repo repository to check out (must be non-bare) * @param opts specifies checkout options (may be NULL) @@ -49,7 +55,9 @@ GIT_EXTERN(int) git_checkout_head( git_indexer_stats *stats); /** - * Updates files in the working tree to match a commit pointed to by a ref. + * Updates files in the index and the working tree to match the content of the + * commit pointed at by the reference. + * * * @param ref reference to follow to a commit * @param opts specifies checkout options (may be NULL) @@ -62,6 +70,23 @@ GIT_EXTERN(int) git_checkout_reference( git_checkout_opts *opts, git_indexer_stats *stats); +/** + * Updates files in the index and working tree to match the content of the + * tree pointed at by the treeish. + * + * @param repo repository to check out (must be non-bare) + * @param treeish a commit, tag or tree which content will be used to update + * the working directory + * @param opts specifies checkout options (may be NULL) + * @param stats structure through which progress information is reported + * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information + * about the error) + */ +GIT_EXTERN(int) git_checkout_tree( + git_repository *repo, + git_object *treeish, + git_checkout_opts *opts, + git_indexer_stats *stats); /** @} */ GIT_END_DECL diff --git a/src/checkout.c b/src/checkout.c index d1720fcf3..663a362fd 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -11,9 +11,9 @@ #include "git2/repository.h" #include "git2/refs.h" #include "git2/tree.h" -#include "git2/commit.h" #include "git2/blob.h" #include "git2/config.h" +#include "git2/diff.h" #include "common.h" #include "refs.h" @@ -22,204 +22,321 @@ #include "filter.h" #include "blob.h" -typedef struct tree_walk_data +struct checkout_diff_data { + git_buf *path; + int workdir_len; + git_checkout_opts *checkout_opts; git_indexer_stats *stats; - git_checkout_opts *opts; - git_repository *repo; - git_odb *odb; - bool no_symlinks; -} tree_walk_data; + git_repository *owner; + bool can_symlink; +}; - -static int blob_contents_to_link(tree_walk_data *data, git_buf *fnbuf, - const git_oid *id) +static int buffer_to_file( + git_buf *buffer, + const char *path, + int dir_mode, + int file_open_flags, + mode_t file_mode) { - int retcode = GIT_ERROR; - git_blob *blob; + int fd, error_write, error_close; - /* Get the link target */ - if (!(retcode = git_blob_lookup(&blob, data->repo, id))) { - git_buf linktarget = GIT_BUF_INIT; - if (!(retcode = git_blob__getbuf(&linktarget, blob))) { - /* Create the link */ - const char *new = git_buf_cstr(&linktarget), - *old = git_buf_cstr(fnbuf); - retcode = data->no_symlinks - ? git_futils_fake_symlink(new, old) - : p_symlink(new, old); - } - git_buf_free(&linktarget); - git_blob_free(blob); - } + if (git_futils_mkpath2file(path, dir_mode) < 0) + return -1; - return retcode; + if ((fd = p_open(path, file_open_flags, file_mode)) < 0) + return -1; + + error_write = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer)); + error_close = p_close(fd); + + return error_write ? error_write : error_close; } - -static int blob_contents_to_file(git_repository *repo, git_buf *fnbuf, - const git_tree_entry *entry, tree_walk_data *data) +static int blob_content_to_file( + git_blob *blob, + const char *path, + unsigned int entry_filemode, + git_checkout_opts *opts) { - int retcode = GIT_ERROR; - int fd = -1; - git_buf contents = GIT_BUF_INIT; - const git_oid *id = git_tree_entry_id(entry); - int file_mode = data->opts->file_mode; - - /* Deal with pre-existing files */ - if (git_path_exists(git_buf_cstr(fnbuf)) && - data->opts->existing_file_action == GIT_CHECKOUT_SKIP_EXISTING) - return 0; + int retcode; + git_buf content = GIT_BUF_INIT; + int file_mode = opts->file_mode; /* Allow disabling of filters */ - if (data->opts->disable_filters) { - git_blob *blob; - if (!(retcode = git_blob_lookup(&blob, repo, id))) { - retcode = git_blob__getbuf(&contents, blob); - git_blob_free(blob); - } - } else { - retcode = git_filter_blob_contents(&contents, repo, id, git_buf_cstr(fnbuf)); - } - if (retcode < 0) goto bctf_cleanup; + if (opts->disable_filters) + retcode = git_blob__getbuf(&content, blob); + else + retcode = git_filter_blob_content(&content, blob, path); + + if (retcode < 0) + goto cleanup; /* Allow overriding of file mode */ if (!file_mode) - file_mode = git_tree_entry_filemode(entry); + file_mode = entry_filemode; - if ((retcode = git_futils_mkpath2file(git_buf_cstr(fnbuf), data->opts->dir_mode)) < 0) - goto bctf_cleanup; + retcode = buffer_to_file(&content, path, opts->dir_mode, opts->file_open_flags, file_mode); - fd = p_open(git_buf_cstr(fnbuf), data->opts->file_open_flags, file_mode); - if (fd < 0) goto bctf_cleanup; - - if (!p_write(fd, git_buf_cstr(&contents), git_buf_len(&contents))) - retcode = 0; - else - retcode = GIT_ERROR; - p_close(fd); - -bctf_cleanup: - git_buf_free(&contents); +cleanup: + git_buf_free(&content); return retcode; } -static int checkout_walker(const char *path, const git_tree_entry *entry, void *payload) +static int blob_content_to_link(git_blob *blob, const char *path, bool can_symlink) { - int retcode = 0; - tree_walk_data *data = (tree_walk_data*)payload; - int attr = git_tree_entry_filemode(entry); - git_buf fnbuf = GIT_BUF_INIT; - git_buf_join_n(&fnbuf, '/', 3, - git_repository_workdir(data->repo), - path, - git_tree_entry_name(entry)); + git_buf linktarget = GIT_BUF_INIT; + int error; - switch(git_tree_entry_type(entry)) - { - case GIT_OBJ_TREE: - /* Nothing to do; the blob handling creates necessary directories. */ + if (git_blob__getbuf(&linktarget, blob) < 0) + return -1; + + if (can_symlink) + error = p_symlink(git_buf_cstr(&linktarget), path); + else + error = git_futils_fake_symlink(git_buf_cstr(&linktarget), path); + + git_buf_free(&linktarget); + + return error; +} + +static int checkout_blob( + git_repository *repo, + git_oid *blob_oid, + const char *path, + unsigned int filemode, + bool can_symlink, + git_checkout_opts *opts) +{ + git_blob *blob; + int error; + + if (git_blob_lookup(&blob, repo, blob_oid) < 0) + return -1; /* Add an error message */ + + if (S_ISLNK(filemode)) + error = blob_content_to_link(blob, path, can_symlink); + else + error = blob_content_to_file(blob, path, filemode, opts); + + git_blob_free(blob); + + return error; +} + +static int checkout_diff_fn( + void *cb_data, + git_diff_delta *delta, + float progress) +{ + struct checkout_diff_data *data; + int error = -1; + + data = (struct checkout_diff_data *)cb_data; + + data->stats->processed = (unsigned int)(data->stats->total * progress); + + git_buf_truncate(data->path, data->workdir_len); + if (git_buf_joinpath(data->path, git_buf_cstr(data->path), delta->new_file.path) < 0) + return -1; + + switch (delta->status) { + case GIT_DELTA_UNTRACKED: + if (!git__suffixcmp(delta->new_file.path, "/")) + error = git_futils_rmdir_r(git_buf_cstr(data->path), GIT_DIRREMOVAL_FILES_AND_DIRS); + else + error = p_unlink(git_buf_cstr(data->path)); break; - case GIT_OBJ_COMMIT: - /* Submodule */ - git_futils_mkpath2file(git_buf_cstr(&fnbuf), data->opts->dir_mode); - retcode = p_mkdir(git_buf_cstr(&fnbuf), data->opts->dir_mode); - break; + case GIT_DELTA_MODIFIED: + /* Deal with pre-existing files */ + if (data->checkout_opts->existing_file_action == GIT_CHECKOUT_SKIP_EXISTING) + return 0; + + case GIT_DELTA_DELETED: + if (checkout_blob( + data->owner, + &delta->old_file.oid, + git_buf_cstr(data->path), + delta->old_file.mode, + data->can_symlink, + data->checkout_opts) < 0) + goto cleanup; - case GIT_OBJ_BLOB: - if (S_ISLNK(attr)) { - retcode = blob_contents_to_link(data, &fnbuf, - git_tree_entry_id(entry)); - } else { - retcode = blob_contents_to_file(data->repo, &fnbuf, entry, data); - } break; default: - retcode = -1; - break; + giterr_set(GITERR_INVALID, "Unexpected status (%d) for path '%s'.", delta->status, delta->new_file.path); + goto cleanup; } - git_buf_free(&fnbuf); - data->stats->processed++; - return retcode; + error = 0; + +cleanup: + return error; } - -int git_checkout_head(git_repository *repo, git_checkout_opts *opts, git_indexer_stats *stats) +static int retrieve_symlink_capabilities(git_repository *repo, bool *can_symlink) { - int retcode = GIT_ERROR; - git_indexer_stats dummy_stats; - git_checkout_opts default_opts = {0}; - git_tree *tree; - tree_walk_data payload; git_config *cfg; + int error; - assert(repo); - if (!opts) opts = &default_opts; - if (!stats) stats = &dummy_stats; + if (git_repository_config__weakptr(&cfg, repo) < 0) + return -1; + + error = git_config_get_bool((int *)can_symlink, cfg, "core.symlinks"); + + /* + * When no "core.symlinks" entry is found in any of the configuration + * store (local, global or system), default value is "true". + */ + if (error == GIT_ENOTFOUND) { + *can_symlink = true; + error = 0; + } + + return error; +} + +static void normalize_options(git_checkout_opts *normalized, git_checkout_opts *proposed) +{ + assert(normalized); + + if (!proposed) + memset(normalized, 0, sizeof(git_checkout_opts)); + else + memmove(normalized, proposed, sizeof(git_checkout_opts)); /* Default options */ - if (!opts->existing_file_action) - opts->existing_file_action = GIT_CHECKOUT_OVERWRITE_EXISTING; - /* opts->disable_filters is false by default */ - if (!opts->dir_mode) opts->dir_mode = GIT_DIR_MODE; - if (!opts->file_open_flags) - opts->file_open_flags = O_CREAT | O_TRUNC | O_WRONLY; + if (!normalized->existing_file_action) + normalized->existing_file_action = GIT_CHECKOUT_OVERWRITE_EXISTING; - if (git_repository_is_bare(repo)) { - giterr_set(GITERR_INVALID, "Checkout is not allowed for bare repositories"); + /* opts->disable_filters is false by default */ + if (!normalized->dir_mode) + normalized->dir_mode = GIT_DIR_MODE; + + if (!normalized->file_open_flags) + normalized->file_open_flags = O_CREAT | O_TRUNC | O_WRONLY; +} + +int git_checkout_tree( + git_repository *repo, + git_object *treeish, + git_checkout_opts *opts, + git_indexer_stats *stats) +{ + git_index *index = NULL; + git_tree *tree = NULL; + git_diff_list *diff = NULL; + git_indexer_stats dummy_stats; + + git_diff_options diff_opts = {0}; + git_checkout_opts checkout_opts; + + struct checkout_diff_data data; + git_buf workdir = GIT_BUF_INIT; + + int error; + + assert(repo && treeish); + + if ((git_repository__ensure_not_bare(repo, "checkout")) < 0) + return GIT_EBAREREPO; + + if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) { + giterr_set(GITERR_INVALID, "Provided treeish cannot be peeled into a tree."); return GIT_ERROR; } - memset(&payload, 0, sizeof(payload)); + if ((error = git_repository_index(&index, repo)) < 0) + goto cleanup; - /* Determine if symlinks should be handled */ - if (!git_repository_config__weakptr(&cfg, repo)) { - int temp = true; - if (!git_config_get_bool(&temp, cfg, "core.symlinks")) { - payload.no_symlinks = !temp; - } + if ((error = git_index_read_tree(index, tree, NULL)) < 0) + goto cleanup; + + if ((error = git_index_write(index)) < 0) + goto cleanup; + + diff_opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + + if (opts && opts->paths) { + diff_opts.pathspec.strings = opts->paths->strings; + diff_opts.pathspec.count = opts->paths->count; } - stats->total = stats->processed = 0; - payload.stats = stats; - payload.opts = opts; - payload.repo = repo; - if (git_repository_odb(&payload.odb, repo) < 0) return GIT_ERROR; + if ((error = git_diff_workdir_to_index(repo, &diff_opts, &diff)) < 0) + goto cleanup; - if (!git_repository_head_tree(&tree, repo)) { - git_index *idx; - if (!(retcode = git_repository_index(&idx, repo))) { - if (!(retcode = git_index_read_tree(idx, tree, stats))) { - git_index_write(idx); - retcode = git_tree_walk(tree, checkout_walker, GIT_TREEWALK_POST, &payload); - } - git_index_free(idx); - } - git_tree_free(tree); - } + if ((error = git_buf_puts(&workdir, git_repository_workdir(repo))) < 0) + goto cleanup; - git_odb_free(payload.odb); - return retcode; + normalize_options(&checkout_opts, opts); + + if (!stats) + stats = &dummy_stats; + + stats->processed = 0; + stats->total = git_index_entrycount(index); + + memset(&data, 0, sizeof(data)); + + data.path = &workdir; + data.workdir_len = git_buf_len(&workdir); + data.checkout_opts = &checkout_opts; + data.stats = stats; + data.owner = repo; + + if ((error = retrieve_symlink_capabilities(repo, &data.can_symlink)) < 0) + goto cleanup; + + error = git_diff_foreach(diff, &data, checkout_diff_fn, NULL, NULL); + +cleanup: + git_diff_list_free(diff); + git_index_free(index); + git_tree_free(tree); + git_buf_free(&workdir); + return error; } +int git_checkout_head( + git_repository *repo, + git_checkout_opts *opts, + git_indexer_stats *stats) +{ + int error; + git_tree *tree = NULL; -int git_checkout_reference(git_reference *ref, - git_checkout_opts *opts, - git_indexer_stats *stats) + assert(repo); + + if (git_repository_head_tree(&tree, repo) < 0) + return -1; + + error = git_checkout_tree(repo, (git_object *)tree, opts, stats); + + git_tree_free(tree); + + return error; +} + +int git_checkout_reference( + git_reference *ref, + git_checkout_opts *opts, + git_indexer_stats *stats) { git_repository *repo= git_reference_owner(ref); git_reference *head = NULL; - int retcode = GIT_ERROR; + int error; - if ((retcode = git_reference_create_symbolic(&head, repo, GIT_HEAD_FILE, - git_reference_name(ref), true)) < 0) - return retcode; + if ((error = git_reference_create_symbolic( + &head, repo, GIT_HEAD_FILE, git_reference_name(ref), true)) < 0) + return error; - retcode = git_checkout_head(git_reference_owner(ref), opts, stats); + error = git_checkout_head(git_reference_owner(ref), opts, stats); git_reference_free(head); - return retcode; + return error; } + + diff --git a/src/clone.c b/src/clone.c index c4f6ec97e..80a13d0f2 100644 --- a/src/clone.c +++ b/src/clone.c @@ -235,9 +235,8 @@ int git_clone(git_repository **out, assert(out && origin_url && workdir_path); - if (!(retcode = clone_internal(out, origin_url, workdir_path, fetch_stats, 0))) { + if (!(retcode = clone_internal(out, origin_url, workdir_path, fetch_stats, 0))) retcode = git_checkout_head(*out, checkout_opts, checkout_stats); - } return retcode; } diff --git a/src/filter.c b/src/filter.c index e9517a259..5b6bb286a 100644 --- a/src/filter.c +++ b/src/filter.c @@ -165,37 +165,21 @@ int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters) return 0; } -static int unfiltered_blob_contents(git_buf *out, git_repository *repo, const git_oid *blob_id) +int git_filter_blob_content(git_buf *out, git_blob *blob, const char *hintpath) { - int retcode = GIT_ERROR; - git_blob *blob; - - if (!(retcode = git_blob_lookup(&blob, repo, blob_id))) - { - retcode = git_blob__getbuf(out, blob); - git_blob_free(blob); - } - - return retcode; -} - -int git_filter_blob_contents(git_buf *out, git_repository *repo, const git_oid *oid, const char *path) -{ - int retcode = GIT_ERROR; - git_buf unfiltered = GIT_BUF_INIT; - if (!unfiltered_blob_contents(&unfiltered, repo, oid)) { - git_vector filters = GIT_VECTOR_INIT; - if (git_filters_load(&filters, - repo, path, GIT_FILTER_TO_WORKTREE) >= 0) { - git_buf_clear(out); + git_vector filters = GIT_VECTOR_INIT; + int retcode; + + retcode = git_blob__getbuf(&unfiltered, blob); + + git_buf_clear(out); + + if (git_filters_load(&filters, git_object_owner((git_object *)blob), hintpath, GIT_FILTER_TO_WORKTREE) >= 0) retcode = git_filters_apply(out, &unfiltered, &filters); - } - - git_filters_free(&filters); - } + git_filters_free(&filters); git_buf_free(&unfiltered); + return retcode; } - diff --git a/src/filter.h b/src/filter.h index 5b7a25b04..d58e173f9 100644 --- a/src/filter.h +++ b/src/filter.h @@ -124,11 +124,10 @@ extern int git_text_is_binary(git_text_stats *stats); * Get the content of a blob after all filters have been run. * * @param out buffer to receive the contents - * @param repo repository containing the blob - * @param oid object id for the blob - * @param path path to the blob's output file, relative to the workdir root + * @param hintpath path to the blob's output file, relative to the workdir root. + * Used to determine what git filters should be applied to the content. * @return 0 on success, an error code otherwise */ -extern int git_filter_blob_contents(git_buf *out, git_repository *repo, const git_oid *oid, const char *path); +extern int git_filter_blob_content(git_buf *out, git_blob *blob, const char *hintpath); #endif diff --git a/tests-clar/checkout/checkout.c b/tests-clar/checkout/checkout.c deleted file mode 100644 index ba14194c4..000000000 --- a/tests-clar/checkout/checkout.c +++ /dev/null @@ -1,206 +0,0 @@ -#include "clar_libgit2.h" - -#include "git2/checkout.h" -#include "repository.h" - - -static git_repository *g_repo; - -void test_checkout_checkout__initialize(void) -{ - const char *attributes = "* text eol=lf\n"; - - g_repo = cl_git_sandbox_init("testrepo"); - cl_git_mkfile("./testrepo/.gitattributes", attributes); -} - -void test_checkout_checkout__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - - -static void test_file_contents(const char *path, const char *expectedcontents) -{ - int fd; - char buffer[1024] = {0}; - size_t expectedlen, actuallen; - - fd = p_open(path, O_RDONLY); - cl_assert(fd >= 0); - - expectedlen = strlen(expectedcontents); - actuallen = p_read(fd, buffer, 1024); - cl_git_pass(p_close(fd)); - - cl_assert_equal_sz(actuallen, expectedlen); - cl_assert_equal_s(buffer, expectedcontents); -} - - -void test_checkout_checkout__bare(void) -{ - cl_git_sandbox_cleanup(); - g_repo = cl_git_sandbox_init("testrepo.git"); - cl_git_fail(git_checkout_head(g_repo, NULL, NULL)); -} - -void test_checkout_checkout__default(void) -{ - cl_git_pass(git_checkout_head(g_repo, NULL, NULL)); - test_file_contents("./testrepo/README", "hey there\n"); - test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); - test_file_contents("./testrepo/new.txt", "my new file\n"); -} - - -void test_checkout_checkout__crlf(void) -{ - const char *attributes = - "branch_file.txt text eol=crlf\n" - "new.txt text eol=lf\n"; - git_config *cfg; - - cl_git_pass(git_repository_config__weakptr(&cfg, g_repo)); - cl_git_pass(git_config_set_bool(cfg, "core.autocrlf", false)); - cl_git_mkfile("./testrepo/.gitattributes", attributes); - - cl_git_pass(git_checkout_head(g_repo, NULL, NULL)); - test_file_contents("./testrepo/README", "hey there\n"); - test_file_contents("./testrepo/new.txt", "my new file\n"); - test_file_contents("./testrepo/branch_file.txt", "hi\r\nbye!\r\n"); -} - - -void test_checkout_checkout__win32_autocrlf(void) -{ -#ifdef GIT_WIN32 - git_config *cfg; - const char *expected_readme_text = "hey there\r\n"; - - cl_must_pass(p_unlink("./testrepo/.gitattributes")); - cl_git_pass(git_repository_config__weakptr(&cfg, g_repo)); - cl_git_pass(git_config_set_bool(cfg, "core.autocrlf", true)); - - cl_git_pass(git_checkout_head(g_repo, NULL, NULL)); - test_file_contents("./testrepo/README", expected_readme_text); -#endif -} - - -static void enable_symlinks(bool enable) -{ - git_config *cfg; - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_git_pass(git_config_set_bool(cfg, "core.symlinks", enable)); - git_config_free(cfg); -} - -void test_checkout_checkout__symlinks(void) -{ - /* First try with symlinks forced on */ - enable_symlinks(true); - cl_git_pass(git_checkout_head(g_repo, NULL, NULL)); - -#ifdef GIT_WIN32 - test_file_contents("./testrepo/link_to_new.txt", "new.txt"); -#else - { - char link_data[1024]; - size_t link_size = 1024; - - link_size = p_readlink("./testrepo/link_to_new.txt", link_data, link_size); - link_data[link_size] = '\0'; - cl_assert_equal_i(link_size, strlen("new.txt")); - cl_assert_equal_s(link_data, "new.txt"); - test_file_contents("./testrepo/link_to_new.txt", "my new file\n"); - } -#endif - - /* Now with symlinks forced off */ - cl_git_sandbox_cleanup(); - g_repo = cl_git_sandbox_init("testrepo"); - enable_symlinks(false); - cl_git_pass(git_checkout_head(g_repo, NULL, NULL)); - - test_file_contents("./testrepo/link_to_new.txt", "new.txt"); -} - -void test_checkout_checkout__existing_file_skip(void) -{ - git_checkout_opts opts = {0}; - cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); - opts.existing_file_action = GIT_CHECKOUT_SKIP_EXISTING; - cl_git_pass(git_checkout_head(g_repo, &opts, NULL)); - test_file_contents("./testrepo/new.txt", "This isn't what's stored!"); -} - -void test_checkout_checkout__existing_file_overwrite(void) -{ - git_checkout_opts opts = {0}; - cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); - opts.existing_file_action = GIT_CHECKOUT_OVERWRITE_EXISTING; - cl_git_pass(git_checkout_head(g_repo, &opts, NULL)); - test_file_contents("./testrepo/new.txt", "my new file\n"); -} - -void test_checkout_checkout__disable_filters(void) -{ - git_checkout_opts opts = {0}; - cl_git_mkfile("./testrepo/.gitattributes", "*.txt text eol=crlf\n"); - /* TODO cl_git_pass(git_checkout_head(g_repo, &opts, NULL));*/ - /* TODO test_file_contents("./testrepo/new.txt", "my new file\r\n");*/ - opts.disable_filters = true; - cl_git_pass(git_checkout_head(g_repo, &opts, NULL)); - test_file_contents("./testrepo/new.txt", "my new file\n"); -} - -void test_checkout_checkout__dir_modes(void) -{ -#ifndef GIT_WIN32 - git_checkout_opts opts = {0}; - struct stat st; - git_reference *ref; - - cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/dir")); - - opts.dir_mode = 0701; - cl_git_pass(git_checkout_reference(ref, &opts, NULL)); - cl_git_pass(p_stat("./testrepo/a", &st)); - cl_assert_equal_i(st.st_mode & 0777, 0701); - - /* File-mode test, since we're on the 'dir' branch */ - cl_git_pass(p_stat("./testrepo/a/b.txt", &st)); - cl_assert_equal_i(st.st_mode & 0777, 0755); - - git_reference_free(ref); -#endif -} - -void test_checkout_checkout__override_file_modes(void) -{ -#ifndef GIT_WIN32 - git_checkout_opts opts = {0}; - struct stat st; - - opts.file_mode = 0700; - cl_git_pass(git_checkout_head(g_repo, &opts, NULL)); - cl_git_pass(p_stat("./testrepo/new.txt", &st)); - cl_assert_equal_i(st.st_mode & 0777, 0700); -#endif -} - -void test_checkout_checkout__open_flags(void) -{ - git_checkout_opts opts = {0}; - - cl_git_mkfile("./testrepo/new.txt", "hi\n"); - opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND; - cl_git_pass(git_checkout_head(g_repo, &opts, NULL)); - test_file_contents("./testrepo/new.txt", "hi\nmy new file\n"); -} - -void test_checkout_checkout__detached_head(void) -{ - /* TODO: write this when git_checkout_commit is implemented. */ -} diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c new file mode 100644 index 000000000..d04bba0da --- /dev/null +++ b/tests-clar/checkout/tree.c @@ -0,0 +1,268 @@ +#include "clar_libgit2.h" + +#include "git2/checkout.h" +#include "repository.h" + + +static git_repository *g_repo; +static git_object *g_treeish; +static git_checkout_opts g_opts; + +void test_checkout_tree__initialize(void) +{ + memset(&g_opts, 0, sizeof(g_opts)); + + g_repo = cl_git_sandbox_init("testrepo"); + + cl_git_rewritefile( + "./testrepo/.gitattributes", + "* text eol=lf\n"); + + cl_git_pass(git_repository_head_tree((git_tree **)&g_treeish, g_repo)); +} + +void test_checkout_tree__cleanup(void) +{ + git_object_free(g_treeish); + cl_git_sandbox_cleanup(); +} + +static void test_file_contents(const char *path, const char *expectedcontents) +{ + int fd; + char buffer[1024] = {0}; + size_t expectedlen, actuallen; + + fd = p_open(path, O_RDONLY); + cl_assert(fd >= 0); + + expectedlen = strlen(expectedcontents); + actuallen = p_read(fd, buffer, 1024); + cl_git_pass(p_close(fd)); + + cl_assert_equal_sz(actuallen, expectedlen); + cl_assert_equal_s(buffer, expectedcontents); +} + +void test_checkout_tree__cannot_checkout_a_bare_repository(void) +{ + test_checkout_tree__cleanup(); + + memset(&g_opts, 0, sizeof(g_opts)); + g_repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_head_tree((git_tree **)&g_treeish, g_repo)); + + cl_git_fail(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); +} + +void test_checkout_tree__update_the_content_of_workdir_with_missing_files(void) +{ + cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); + + cl_git_pass(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); + + test_file_contents("./testrepo/README", "hey there\n"); + test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); + test_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_tree__honor_the_specified_pathspecs(void) +{ + git_strarray paths; + char *entries[] = { "*.txt" }; + + paths.strings = entries; + paths.count = 1; + g_opts.paths = &paths; + + cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); + + cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); + + cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); + test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); + test_file_contents("./testrepo/new.txt", "my new file\n"); +} + +static void set_config_entry_to(const char *entry_name, bool value) +{ + git_config *cfg; + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_bool(cfg, entry_name, value)); + + git_config_free(cfg); +} + +static void set_core_autocrlf_to(bool value) +{ + set_config_entry_to("core.autocrlf", value); +} + +void test_checkout_tree__honor_the_gitattributes_directives(void) +{ + const char *attributes = + "branch_file.txt text eol=crlf\n" + "new.txt text eol=lf\n"; + + cl_git_mkfile("./testrepo/.gitattributes", attributes); + set_core_autocrlf_to(false); + + cl_git_pass(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); + + test_file_contents("./testrepo/README", "hey there\n"); + test_file_contents("./testrepo/new.txt", "my new file\n"); + test_file_contents("./testrepo/branch_file.txt", "hi\r\nbye!\r\n"); +} + +void test_checkout_tree__honor_coreautocrlf_setting_set_to_true(void) +{ +#ifdef GIT_WIN32 + const char *expected_readme_text = "hey there\r\n"; + + cl_git_pass(p_unlink("./testrepo/.gitattributes")); + set_core_autocrlf_to(true); + + cl_git_pass(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); + + test_file_contents("./testrepo/README", expected_readme_text); +#endif +} + +static void set_repo_symlink_handling_cap_to(bool value) +{ + set_config_entry_to("core.symlinks", value); +} + +void test_checkout_tree__honor_coresymlinks_setting_set_to_true(void) +{ + set_repo_symlink_handling_cap_to(true); + + cl_git_pass(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); + +#ifdef GIT_WIN32 + test_file_contents("./testrepo/link_to_new.txt", "new.txt"); +#else + { + char link_data[1024]; + size_t link_size = 1024; + + link_size = p_readlink("./testrepo/link_to_new.txt", link_data, link_size); + link_data[link_size] = '\0'; + cl_assert_equal_i(link_size, strlen("new.txt")); + cl_assert_equal_s(link_data, "new.txt"); + test_file_contents("./testrepo/link_to_new.txt", "my new file\n"); + } +#endif +} + +void test_checkout_tree__honor_coresymlinks_setting_set_to_false(void) +{ + set_repo_symlink_handling_cap_to(false); + + cl_git_pass(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); + + test_file_contents("./testrepo/link_to_new.txt", "new.txt"); +} + +void test_checkout_tree__options_skip_existing_file(void) +{ + cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); + g_opts.existing_file_action = GIT_CHECKOUT_SKIP_EXISTING; + + cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); + + test_file_contents("./testrepo/new.txt", "This isn't what's stored!"); +} + +void test_checkout_tree__options_overwrite_existing_file(void) +{ + cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); + g_opts.existing_file_action = GIT_CHECKOUT_OVERWRITE_EXISTING; + + cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); + + test_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_tree__options_disable_filters(void) +{ + cl_git_mkfile("./testrepo/.gitattributes", "*.txt text eol=crlf\n"); + + g_opts.disable_filters = false; + cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); + + test_file_contents("./testrepo/new.txt", "my new file\r\n"); + + p_unlink("./testrepo/new.txt"); + + g_opts.disable_filters = true; + cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); + + test_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_tree__options_dir_modes(void) +{ +#ifndef GIT_WIN32 + struct stat st; + git_oid oid; + git_commit *commit; + + cl_git_pass(git_reference_name_to_oid(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); + + g_opts.dir_mode = 0701; + cl_git_pass(git_checkout_tree(g_repo, (git_object *)commit, &g_opts, NULL)); + + cl_git_pass(p_stat("./testrepo/a", &st)); + cl_assert_equal_i(st.st_mode & 0777, 0701); + + /* File-mode test, since we're on the 'dir' branch */ + cl_git_pass(p_stat("./testrepo/a/b.txt", &st)); + cl_assert_equal_i(st.st_mode & 0777, 0755); + + git_commit_free(commit); +#endif +} + +void test_checkout_tree__options_override_file_modes(void) +{ +#ifndef GIT_WIN32 + struct stat st; + + g_opts.file_mode = 0700; + + cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); + + cl_git_pass(p_stat("./testrepo/new.txt", &st)); + cl_assert_equal_i(st.st_mode & 0777, 0700); +#endif +} + +void test_checkout_tree__options_open_flags(void) +{ + cl_git_mkfile("./testrepo/new.txt", "hi\n"); + + g_opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND; + cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); + + test_file_contents("./testrepo/new.txt", "hi\nmy new file\n"); +} + +void test_checkout_tree__cannot_checkout_a_non_treeish(void) +{ + git_oid oid; + git_blob *blob; + + cl_git_pass(git_oid_fromstr(&oid, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")); + cl_git_pass(git_blob_lookup(&blob, g_repo, &oid)); + + cl_git_fail(git_checkout_tree(g_repo, (git_object *)blob, NULL, NULL)); + + git_blob_free(blob); +} From e93af304112735f02bfeb0833b58ed8230de2371 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 24 Aug 2012 10:40:17 +0200 Subject: [PATCH 02/14] checkout: introduce git_checkout_index() --- include/git2/checkout.h | 14 ++++++++++ src/checkout.c | 60 ++++++++++++++++++++++++++++------------- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/include/git2/checkout.h b/include/git2/checkout.h index 21b68e3ab..3217ac9a0 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -70,6 +70,20 @@ GIT_EXTERN(int) git_checkout_reference( git_checkout_opts *opts, git_indexer_stats *stats); +/** + * Updates files in the working tree to match the content of the index. + * + * @param repo repository to check out (must be non-bare) + * @param opts specifies checkout options (may be NULL) + * @param stats structure through which progress information is reported + * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information + * about the error) + */ +GIT_EXTERN(int) git_checkout_index( + git_repository *repo, + git_checkout_opts *opts, + git_indexer_stats *stats); + /** * Updates files in the index and working tree to match the content of the * tree pointed at by the treeish. diff --git a/src/checkout.c b/src/checkout.c index 663a362fd..6e34e50ab 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -220,14 +220,12 @@ static void normalize_options(git_checkout_opts *normalized, git_checkout_opts * normalized->file_open_flags = O_CREAT | O_TRUNC | O_WRONLY; } -int git_checkout_tree( +int git_checkout_index( git_repository *repo, - git_object *treeish, git_checkout_opts *opts, git_indexer_stats *stats) { git_index *index = NULL; - git_tree *tree = NULL; git_diff_list *diff = NULL; git_indexer_stats dummy_stats; @@ -239,25 +237,11 @@ int git_checkout_tree( int error; - assert(repo && treeish); + assert(repo); if ((git_repository__ensure_not_bare(repo, "checkout")) < 0) return GIT_EBAREREPO; - if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) { - giterr_set(GITERR_INVALID, "Provided treeish cannot be peeled into a tree."); - return GIT_ERROR; - } - - if ((error = git_repository_index(&index, repo)) < 0) - goto cleanup; - - if ((error = git_index_read_tree(index, tree, NULL)) < 0) - goto cleanup; - - if ((error = git_index_write(index)) < 0) - goto cleanup; - diff_opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; if (opts && opts->paths) { @@ -277,6 +261,10 @@ int git_checkout_tree( stats = &dummy_stats; stats->processed = 0; + + if ((git_repository_index(&index, repo)) < 0) + goto cleanup; + stats->total = git_index_entrycount(index); memset(&data, 0, sizeof(data)); @@ -293,10 +281,44 @@ int git_checkout_tree( error = git_diff_foreach(diff, &data, checkout_diff_fn, NULL, NULL); cleanup: + git_index_free(index); git_diff_list_free(diff); + git_buf_free(&workdir); + return error; +} + +int git_checkout_tree( + git_repository *repo, + git_object *treeish, + git_checkout_opts *opts, + git_indexer_stats *stats) +{ + git_index *index = NULL; + git_tree *tree = NULL; + + int error; + + assert(repo && treeish); + + if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) { + giterr_set(GITERR_INVALID, "Provided treeish cannot be peeled into a tree."); + return GIT_ERROR; + } + + if ((error = git_repository_index(&index, repo)) < 0) + goto cleanup; + + if ((error = git_index_read_tree(index, tree, NULL)) < 0) + goto cleanup; + + if ((error = git_index_write(index)) < 0) + goto cleanup; + + error = git_checkout_index(repo, opts, stats); + +cleanup: git_index_free(index); git_tree_free(tree); - git_buf_free(&workdir); return error; } From ee8bb8ba649118c4abb09cb02bd3c02e60b98e06 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sun, 19 Aug 2012 21:24:51 +0200 Subject: [PATCH 03/14] reset: add support for GIT_RESET_HARD mode --- include/git2/reset.h | 3 +++ include/git2/types.h | 1 + src/reset.c | 15 +++++++++++++- tests-clar/reset/hard.c | 46 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 tests-clar/reset/hard.c diff --git a/include/git2/reset.h b/include/git2/reset.h index cd263fa99..cdcfb7671 100644 --- a/include/git2/reset.h +++ b/include/git2/reset.h @@ -24,6 +24,9 @@ GIT_BEGIN_DECL * Specifying a Mixed kind of reset will trigger a Soft reset and the index will * be replaced with the content of the commit tree. * + * Specifying a Hard kind of reset will trigger a Mixed reset and the working + * directory will be replaced with the content of the index. + * * TODO: Implement remaining kinds of resets. * * @param repo Repository where to perform the reset operation. diff --git a/include/git2/types.h b/include/git2/types.h index d3a905372..26e9c57e7 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -173,6 +173,7 @@ typedef enum { typedef enum { GIT_RESET_SOFT = 1, GIT_RESET_MIXED = 2, + GIT_RESET_HARD = 3, } git_reset_type; /** Valid modes for index and tree entries. */ diff --git a/src/reset.c b/src/reset.c index 5aaf94840..efe3b6be9 100644 --- a/src/reset.c +++ b/src/reset.c @@ -9,6 +9,7 @@ #include "commit.h" #include "tag.h" #include "git2/reset.h" +#include "git2/checkout.h" #define ERROR_MSG "Cannot perform reset" @@ -29,7 +30,9 @@ int git_reset( int error = -1; assert(repo && target); - assert(reset_type == GIT_RESET_SOFT || reset_type == GIT_RESET_MIXED); + assert(reset_type == GIT_RESET_SOFT + || reset_type == GIT_RESET_MIXED + || reset_type == GIT_RESET_HARD); if (git_object_owner(target) != repo) return reset_error_invalid("The given target does not belong to this repository."); @@ -73,6 +76,16 @@ int git_reset( goto cleanup; } + if (reset_type == GIT_RESET_MIXED) { + error = 0; + goto cleanup; + } + + if (git_checkout_index(repo, NULL, NULL, NULL) < 0) { + giterr_set(GITERR_INDEX, "%s - Failed to checkout the index.", ERROR_MSG); + goto cleanup; + } + error = 0; cleanup: diff --git a/tests-clar/reset/hard.c b/tests-clar/reset/hard.c new file mode 100644 index 000000000..ad3badb8a --- /dev/null +++ b/tests-clar/reset/hard.c @@ -0,0 +1,46 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "reset_helpers.h" +#include "path.h" +#include "fileops.h" + +static git_repository *repo; +static git_object *target; + +void test_reset_hard__initialize(void) +{ + repo = cl_git_sandbox_init("status"); + target = NULL; +} + +void test_reset_hard__cleanup(void) +{ + git_object_free(target); + cl_git_sandbox_cleanup(); +} + +void test_reset_hard__resetting_culls_empty_directories(void) +{ + git_buf subdir_path = GIT_BUF_INIT; + git_buf subfile_path = GIT_BUF_INIT; + git_buf newdir_path = GIT_BUF_INIT; + + cl_git_pass(git_buf_joinpath(&newdir_path, git_repository_workdir(repo), "newdir/")); + + cl_git_pass(git_buf_joinpath(&subfile_path, git_buf_cstr(&newdir_path), "with/nested/file.txt")); + cl_git_pass(git_futils_mkpath2file(git_buf_cstr(&subfile_path), 0755)); + cl_git_mkfile(git_buf_cstr(&subfile_path), "all anew...\n"); + + cl_git_pass(git_buf_joinpath(&subdir_path, git_repository_workdir(repo), "subdir/")); + cl_assert(git_path_isdir(git_buf_cstr(&subdir_path)) == true); + + retrieve_target_from_oid(&target, repo, "0017bd4ab1ec30440b17bae1680cff124ab5f1f6"); + cl_git_pass(git_reset(repo, target, GIT_RESET_HARD)); + + cl_assert(git_path_isdir(git_buf_cstr(&subdir_path)) == false); + cl_assert(git_path_isdir(git_buf_cstr(&newdir_path)) == false); + + git_buf_free(&subdir_path); + git_buf_free(&subfile_path); + git_buf_free(&newdir_path); +} From 020cda99c2bec386cb10200f6cfe1b150911ffc9 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 14 Sep 2012 16:45:24 +0200 Subject: [PATCH 04/14] checkout: separate tree from index related tests --- tests-clar/checkout/index.c | 273 ++++++++++++++++++++++++++++++++++++ tests-clar/checkout/tree.c | 239 ------------------------------- 2 files changed, 273 insertions(+), 239 deletions(-) create mode 100644 tests-clar/checkout/index.c diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c new file mode 100644 index 000000000..b81aa9170 --- /dev/null +++ b/tests-clar/checkout/index.c @@ -0,0 +1,273 @@ +#include "clar_libgit2.h" + +#include "git2/checkout.h" +#include "repository.h" + +static git_repository *g_repo; +static git_checkout_opts g_opts; + +static void reset_index_to_treeish(git_object *treeish) +{ + git_object *tree; + git_index *index; + + cl_git_pass(git_object_peel(&tree, treeish, GIT_OBJ_TREE)); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_read_tree(index, (git_tree *)tree, NULL)); + cl_git_pass(git_index_write(index)); + + git_object_free(tree); + git_index_free(index); +} + +void test_checkout_index__initialize(void) +{ + git_tree *tree; + + memset(&g_opts, 0, sizeof(g_opts)); + + g_repo = cl_git_sandbox_init("testrepo"); + + cl_git_pass(git_repository_head_tree(&tree, g_repo)); + + reset_index_to_treeish((git_object *)tree); + git_tree_free(tree); + + cl_git_rewritefile( + "./testrepo/.gitattributes", + "* text eol=lf\n"); +} + +void test_checkout_index__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void test_file_contents(const char *path, const char *expectedcontents) +{ + int fd; + char buffer[1024] = {0}; + size_t expectedlen, actuallen; + + fd = p_open(path, O_RDONLY); + cl_assert(fd >= 0); + + expectedlen = strlen(expectedcontents); + actuallen = p_read(fd, buffer, 1024); + cl_git_pass(p_close(fd)); + + cl_assert_equal_sz(actuallen, expectedlen); + cl_assert_equal_s(buffer, expectedcontents); +} + +void test_checkout_index__cannot_checkout_a_bare_repository(void) +{ + test_checkout_index__cleanup(); + + memset(&g_opts, 0, sizeof(g_opts)); + g_repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_fail(git_checkout_index(g_repo, NULL, NULL)); +} + +void test_checkout_index__update_the_content_of_workdir_with_missing_files(void) +{ + cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); + + cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); + + test_file_contents("./testrepo/README", "hey there\n"); + test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); + test_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_index__honor_the_specified_pathspecs(void) +{ + git_strarray paths; + char *entries[] = { "*.txt" }; + + paths.strings = entries; + paths.count = 1; + g_opts.paths = &paths; + + cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); + cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); + + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + + cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); + test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); + test_file_contents("./testrepo/new.txt", "my new file\n"); +} + +static void set_config_entry_to(const char *entry_name, bool value) +{ + git_config *cfg; + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_bool(cfg, entry_name, value)); + + git_config_free(cfg); +} + +static void set_core_autocrlf_to(bool value) +{ + set_config_entry_to("core.autocrlf", value); +} + +void test_checkout_index__honor_the_gitattributes_directives(void) +{ + const char *attributes = + "branch_file.txt text eol=crlf\n" + "new.txt text eol=lf\n"; + + cl_git_mkfile("./testrepo/.gitattributes", attributes); + set_core_autocrlf_to(false); + + cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); + + test_file_contents("./testrepo/README", "hey there\n"); + test_file_contents("./testrepo/new.txt", "my new file\n"); + test_file_contents("./testrepo/branch_file.txt", "hi\r\nbye!\r\n"); +} + +void test_checkout_index__honor_coreautocrlf_setting_set_to_true(void) +{ +#ifdef GIT_WIN32 + const char *expected_readme_text = "hey there\r\n"; + + cl_git_pass(p_unlink("./testrepo/.gitattributes")); + set_core_autocrlf_to(true); + + cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); + + test_file_contents("./testrepo/README", expected_readme_text); +#endif +} + +static void set_repo_symlink_handling_cap_to(bool value) +{ + set_config_entry_to("core.symlinks", value); +} + +void test_checkout_index__honor_coresymlinks_setting_set_to_true(void) +{ + set_repo_symlink_handling_cap_to(true); + + cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); + +#ifdef GIT_WIN32 + test_file_contents("./testrepo/link_to_new.txt", "new.txt"); +#else + { + char link_data[1024]; + size_t link_size = 1024; + + link_size = p_readlink("./testrepo/link_to_new.txt", link_data, link_size); + link_data[link_size] = '\0'; + cl_assert_equal_i(link_size, strlen("new.txt")); + cl_assert_equal_s(link_data, "new.txt"); + test_file_contents("./testrepo/link_to_new.txt", "my new file\n"); + } +#endif +} + +void test_checkout_index__honor_coresymlinks_setting_set_to_false(void) +{ + set_repo_symlink_handling_cap_to(false); + + cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); + + test_file_contents("./testrepo/link_to_new.txt", "new.txt"); +} + +void test_checkout_index__options_skip_existing_file(void) +{ + cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); + g_opts.existing_file_action = GIT_CHECKOUT_SKIP_EXISTING; + + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + + test_file_contents("./testrepo/new.txt", "This isn't what's stored!"); +} + +void test_checkout_index__options_overwrite_existing_file(void) +{ + cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); + g_opts.existing_file_action = GIT_CHECKOUT_OVERWRITE_EXISTING; + + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + + test_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_index__options_disable_filters(void) +{ + cl_git_mkfile("./testrepo/.gitattributes", "*.txt text eol=crlf\n"); + + g_opts.disable_filters = false; + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + + test_file_contents("./testrepo/new.txt", "my new file\r\n"); + + p_unlink("./testrepo/new.txt"); + + g_opts.disable_filters = true; + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + + test_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_index__options_dir_modes(void) +{ +#ifndef GIT_WIN32 + struct stat st; + git_oid oid; + git_commit *commit; + + cl_git_pass(git_reference_name_to_oid(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); + + reset_index_to_treeish((git_object *)commit); + + g_opts.dir_mode = 0701; + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + + cl_git_pass(p_stat("./testrepo/a", &st)); + cl_assert_equal_i(st.st_mode & 0777, 0701); + + /* File-mode test, since we're on the 'dir' branch */ + cl_git_pass(p_stat("./testrepo/a/b.txt", &st)); + cl_assert_equal_i(st.st_mode & 0777, 0755); + + git_commit_free(commit); +#endif +} + +void test_checkout_index__options_override_file_modes(void) +{ +#ifndef GIT_WIN32 + struct stat st; + + g_opts.file_mode = 0700; + + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + + cl_git_pass(p_stat("./testrepo/new.txt", &st)); + cl_assert_equal_i(st.st_mode & 0777, 0700); +#endif +} + +void test_checkout_index__options_open_flags(void) +{ + cl_git_mkfile("./testrepo/new.txt", "hi\n"); + + g_opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND; + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + + test_file_contents("./testrepo/new.txt", "hi\nmy new file\n"); +} diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c index d04bba0da..32b64e5d7 100644 --- a/tests-clar/checkout/tree.c +++ b/tests-clar/checkout/tree.c @@ -3,257 +3,18 @@ #include "git2/checkout.h" #include "repository.h" - static git_repository *g_repo; -static git_object *g_treeish; -static git_checkout_opts g_opts; void test_checkout_tree__initialize(void) { - memset(&g_opts, 0, sizeof(g_opts)); - g_repo = cl_git_sandbox_init("testrepo"); - - cl_git_rewritefile( - "./testrepo/.gitattributes", - "* text eol=lf\n"); - - cl_git_pass(git_repository_head_tree((git_tree **)&g_treeish, g_repo)); } void test_checkout_tree__cleanup(void) { - git_object_free(g_treeish); cl_git_sandbox_cleanup(); } -static void test_file_contents(const char *path, const char *expectedcontents) -{ - int fd; - char buffer[1024] = {0}; - size_t expectedlen, actuallen; - - fd = p_open(path, O_RDONLY); - cl_assert(fd >= 0); - - expectedlen = strlen(expectedcontents); - actuallen = p_read(fd, buffer, 1024); - cl_git_pass(p_close(fd)); - - cl_assert_equal_sz(actuallen, expectedlen); - cl_assert_equal_s(buffer, expectedcontents); -} - -void test_checkout_tree__cannot_checkout_a_bare_repository(void) -{ - test_checkout_tree__cleanup(); - - memset(&g_opts, 0, sizeof(g_opts)); - g_repo = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(git_repository_head_tree((git_tree **)&g_treeish, g_repo)); - - cl_git_fail(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); -} - -void test_checkout_tree__update_the_content_of_workdir_with_missing_files(void) -{ - cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); - cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); - cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); - - cl_git_pass(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); - - test_file_contents("./testrepo/README", "hey there\n"); - test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); - test_file_contents("./testrepo/new.txt", "my new file\n"); -} - -void test_checkout_tree__honor_the_specified_pathspecs(void) -{ - git_strarray paths; - char *entries[] = { "*.txt" }; - - paths.strings = entries; - paths.count = 1; - g_opts.paths = &paths; - - cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); - cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); - cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); - - cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); - - cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); - test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); - test_file_contents("./testrepo/new.txt", "my new file\n"); -} - -static void set_config_entry_to(const char *entry_name, bool value) -{ - git_config *cfg; - - cl_git_pass(git_repository_config(&cfg, g_repo)); - cl_git_pass(git_config_set_bool(cfg, entry_name, value)); - - git_config_free(cfg); -} - -static void set_core_autocrlf_to(bool value) -{ - set_config_entry_to("core.autocrlf", value); -} - -void test_checkout_tree__honor_the_gitattributes_directives(void) -{ - const char *attributes = - "branch_file.txt text eol=crlf\n" - "new.txt text eol=lf\n"; - - cl_git_mkfile("./testrepo/.gitattributes", attributes); - set_core_autocrlf_to(false); - - cl_git_pass(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); - - test_file_contents("./testrepo/README", "hey there\n"); - test_file_contents("./testrepo/new.txt", "my new file\n"); - test_file_contents("./testrepo/branch_file.txt", "hi\r\nbye!\r\n"); -} - -void test_checkout_tree__honor_coreautocrlf_setting_set_to_true(void) -{ -#ifdef GIT_WIN32 - const char *expected_readme_text = "hey there\r\n"; - - cl_git_pass(p_unlink("./testrepo/.gitattributes")); - set_core_autocrlf_to(true); - - cl_git_pass(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); - - test_file_contents("./testrepo/README", expected_readme_text); -#endif -} - -static void set_repo_symlink_handling_cap_to(bool value) -{ - set_config_entry_to("core.symlinks", value); -} - -void test_checkout_tree__honor_coresymlinks_setting_set_to_true(void) -{ - set_repo_symlink_handling_cap_to(true); - - cl_git_pass(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); - -#ifdef GIT_WIN32 - test_file_contents("./testrepo/link_to_new.txt", "new.txt"); -#else - { - char link_data[1024]; - size_t link_size = 1024; - - link_size = p_readlink("./testrepo/link_to_new.txt", link_data, link_size); - link_data[link_size] = '\0'; - cl_assert_equal_i(link_size, strlen("new.txt")); - cl_assert_equal_s(link_data, "new.txt"); - test_file_contents("./testrepo/link_to_new.txt", "my new file\n"); - } -#endif -} - -void test_checkout_tree__honor_coresymlinks_setting_set_to_false(void) -{ - set_repo_symlink_handling_cap_to(false); - - cl_git_pass(git_checkout_tree(g_repo, g_treeish, NULL, NULL)); - - test_file_contents("./testrepo/link_to_new.txt", "new.txt"); -} - -void test_checkout_tree__options_skip_existing_file(void) -{ - cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); - g_opts.existing_file_action = GIT_CHECKOUT_SKIP_EXISTING; - - cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); - - test_file_contents("./testrepo/new.txt", "This isn't what's stored!"); -} - -void test_checkout_tree__options_overwrite_existing_file(void) -{ - cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); - g_opts.existing_file_action = GIT_CHECKOUT_OVERWRITE_EXISTING; - - cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); - - test_file_contents("./testrepo/new.txt", "my new file\n"); -} - -void test_checkout_tree__options_disable_filters(void) -{ - cl_git_mkfile("./testrepo/.gitattributes", "*.txt text eol=crlf\n"); - - g_opts.disable_filters = false; - cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); - - test_file_contents("./testrepo/new.txt", "my new file\r\n"); - - p_unlink("./testrepo/new.txt"); - - g_opts.disable_filters = true; - cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); - - test_file_contents("./testrepo/new.txt", "my new file\n"); -} - -void test_checkout_tree__options_dir_modes(void) -{ -#ifndef GIT_WIN32 - struct stat st; - git_oid oid; - git_commit *commit; - - cl_git_pass(git_reference_name_to_oid(&oid, g_repo, "refs/heads/dir")); - cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); - - g_opts.dir_mode = 0701; - cl_git_pass(git_checkout_tree(g_repo, (git_object *)commit, &g_opts, NULL)); - - cl_git_pass(p_stat("./testrepo/a", &st)); - cl_assert_equal_i(st.st_mode & 0777, 0701); - - /* File-mode test, since we're on the 'dir' branch */ - cl_git_pass(p_stat("./testrepo/a/b.txt", &st)); - cl_assert_equal_i(st.st_mode & 0777, 0755); - - git_commit_free(commit); -#endif -} - -void test_checkout_tree__options_override_file_modes(void) -{ -#ifndef GIT_WIN32 - struct stat st; - - g_opts.file_mode = 0700; - - cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); - - cl_git_pass(p_stat("./testrepo/new.txt", &st)); - cl_assert_equal_i(st.st_mode & 0777, 0700); -#endif -} - -void test_checkout_tree__options_open_flags(void) -{ - cl_git_mkfile("./testrepo/new.txt", "hi\n"); - - g_opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND; - cl_git_pass(git_checkout_tree(g_repo, g_treeish, &g_opts, NULL)); - - test_file_contents("./testrepo/new.txt", "hi\nmy new file\n"); -} - void test_checkout_tree__cannot_checkout_a_non_treeish(void) { git_oid oid; From c214fa1caff937f20ca3a388652352cda92ce85b Mon Sep 17 00:00:00 2001 From: nulltoken Date: Thu, 6 Sep 2012 15:15:46 +0200 Subject: [PATCH 05/14] checkout: segregate checkout strategies --- include/git2/checkout.h | 11 +++++++---- src/checkout.c | 24 +++++++++++++++++++---- src/reset.c | 9 ++++++++- tests-clar/checkout/index.c | 38 +++++++++++++++++++++++++++---------- 4 files changed, 63 insertions(+), 19 deletions(-) diff --git a/include/git2/checkout.h b/include/git2/checkout.h index 3217ac9a0..5707de0d7 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -21,13 +21,16 @@ */ GIT_BEGIN_DECL - -#define GIT_CHECKOUT_OVERWRITE_EXISTING 0 /* default */ -#define GIT_CHECKOUT_SKIP_EXISTING 1 +enum { + GIT_CHECKOUT_DEFAULT = (1 << 0), + GIT_CHECKOUT_OVERWRITE_MODIFIED = (1 << 1), + GIT_CHECKOUT_CREATE_MISSING = (1 << 2), + GIT_CHECKOUT_REMOVE_UNTRACKED = (1 << 3), +}; /* Use zeros to indicate default settings */ typedef struct git_checkout_opts { - int existing_file_action; /* default: GIT_CHECKOUT_OVERWRITE_EXISTING */ + unsigned int checkout_strategy; /* default: GIT_CHECKOUT_DEFAULT */ int disable_filters; int dir_mode; /* default is 0755 */ int file_mode; /* default is 0644 */ diff --git a/src/checkout.c b/src/checkout.c index 6e34e50ab..beb8b5a63 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -143,6 +143,9 @@ static int checkout_diff_fn( switch (delta->status) { case GIT_DELTA_UNTRACKED: + if (!(data->checkout_opts->checkout_strategy & GIT_CHECKOUT_REMOVE_UNTRACKED)) + return 0; + if (!git__suffixcmp(delta->new_file.path, "/")) error = git_futils_rmdir_r(git_buf_cstr(data->path), GIT_DIRREMOVAL_FILES_AND_DIRS); else @@ -150,11 +153,24 @@ static int checkout_diff_fn( break; case GIT_DELTA_MODIFIED: - /* Deal with pre-existing files */ - if (data->checkout_opts->existing_file_action == GIT_CHECKOUT_SKIP_EXISTING) + if (!(data->checkout_opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED)) return 0; + if (checkout_blob( + data->owner, + &delta->old_file.oid, + git_buf_cstr(data->path), + delta->old_file.mode, + data->can_symlink, + data->checkout_opts) < 0) + goto cleanup; + + break; + case GIT_DELTA_DELETED: + if (!(data->checkout_opts->checkout_strategy & GIT_CHECKOUT_CREATE_MISSING)) + return 0; + if (checkout_blob( data->owner, &delta->old_file.oid, @@ -209,8 +225,8 @@ static void normalize_options(git_checkout_opts *normalized, git_checkout_opts * memmove(normalized, proposed, sizeof(git_checkout_opts)); /* Default options */ - if (!normalized->existing_file_action) - normalized->existing_file_action = GIT_CHECKOUT_OVERWRITE_EXISTING; + if (!normalized->checkout_strategy) + normalized->checkout_strategy = GIT_CHECKOUT_DEFAULT; /* opts->disable_filters is false by default */ if (!normalized->dir_mode) diff --git a/src/reset.c b/src/reset.c index efe3b6be9..4ce21e2cf 100644 --- a/src/reset.c +++ b/src/reset.c @@ -28,6 +28,7 @@ int git_reset( git_index *index = NULL; git_tree *tree = NULL; int error = -1; + git_checkout_opts opts; assert(repo && target); assert(reset_type == GIT_RESET_SOFT @@ -81,7 +82,13 @@ int git_reset( goto cleanup; } - if (git_checkout_index(repo, NULL, NULL, NULL) < 0) { + memset(&opts, 0, sizeof(opts)); + opts.checkout_strategy = + GIT_CHECKOUT_CREATE_MISSING + | GIT_CHECKOUT_OVERWRITE_MODIFIED + | GIT_CHECKOUT_REMOVE_UNTRACKED; + + if (git_checkout_index(repo, &opts, NULL) < 0) { giterr_set(GITERR_INDEX, "%s - Failed to checkout the index.", ERROR_MSG); goto cleanup; } diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c index b81aa9170..fad10be51 100644 --- a/tests-clar/checkout/index.c +++ b/tests-clar/checkout/index.c @@ -26,6 +26,7 @@ void test_checkout_index__initialize(void) git_tree *tree; memset(&g_opts, 0, sizeof(g_opts)); + g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING; g_repo = cl_git_sandbox_init("testrepo"); @@ -71,19 +72,34 @@ void test_checkout_index__cannot_checkout_a_bare_repository(void) cl_git_fail(git_checkout_index(g_repo, NULL, NULL)); } -void test_checkout_index__update_the_content_of_workdir_with_missing_files(void) +void test_checkout_index__can_create_missing_files(void) { cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); - cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); + g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING; + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); test_file_contents("./testrepo/README", "hey there\n"); test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); test_file_contents("./testrepo/new.txt", "my new file\n"); } +void test_checkout_index__can_remove_untracked_files(void) +{ + git_futils_mkdir("./testrepo/dir/subdir/subsubdir", NULL, 0755, GIT_MKDIR_PATH); + cl_git_mkfile("./testrepo/dir/one", "one\n"); + cl_git_mkfile("./testrepo/dir/subdir/two", "two\n"); + + cl_assert_equal_i(true, git_path_isdir("./testrepo/dir/subdir/subsubdir")); + + g_opts.checkout_strategy = GIT_CHECKOUT_REMOVE_UNTRACKED; + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + + cl_assert_equal_i(false, git_path_isdir("./testrepo/dir")); +} + void test_checkout_index__honor_the_specified_pathspecs(void) { git_strarray paths; @@ -128,7 +144,7 @@ void test_checkout_index__honor_the_gitattributes_directives(void) cl_git_mkfile("./testrepo/.gitattributes", attributes); set_core_autocrlf_to(false); - cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); test_file_contents("./testrepo/README", "hey there\n"); test_file_contents("./testrepo/new.txt", "my new file\n"); @@ -143,7 +159,7 @@ void test_checkout_index__honor_coreautocrlf_setting_set_to_true(void) cl_git_pass(p_unlink("./testrepo/.gitattributes")); set_core_autocrlf_to(true); - cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); test_file_contents("./testrepo/README", expected_readme_text); #endif @@ -158,7 +174,7 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_true(void) { set_repo_symlink_handling_cap_to(true); - cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); #ifdef GIT_WIN32 test_file_contents("./testrepo/link_to_new.txt", "new.txt"); @@ -180,26 +196,26 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_false(void) { set_repo_symlink_handling_cap_to(false); - cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); test_file_contents("./testrepo/link_to_new.txt", "new.txt"); } -void test_checkout_index__options_skip_existing_file(void) +void test_checkout_index__donot_overwrite_modified_file_by_default(void) { cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); - g_opts.existing_file_action = GIT_CHECKOUT_SKIP_EXISTING; + g_opts.checkout_strategy = 0; cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); test_file_contents("./testrepo/new.txt", "This isn't what's stored!"); } -void test_checkout_index__options_overwrite_existing_file(void) +void test_checkout_index__can_overwrite_modified_file(void) { cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); - g_opts.existing_file_action = GIT_CHECKOUT_OVERWRITE_EXISTING; + g_opts.checkout_strategy = GIT_CHECKOUT_OVERWRITE_MODIFIED; cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); test_file_contents("./testrepo/new.txt", "my new file\n"); @@ -267,6 +283,8 @@ void test_checkout_index__options_open_flags(void) cl_git_mkfile("./testrepo/new.txt", "hi\n"); g_opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND; + + g_opts.checkout_strategy |= GIT_CHECKOUT_OVERWRITE_MODIFIED; cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); test_file_contents("./testrepo/new.txt", "hi\nmy new file\n"); From 5af61863dd735887e73d98e9a8cba699276303fd Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 14 Sep 2012 11:15:49 +0200 Subject: [PATCH 06/14] checkout: drop git_checkout_reference() --- include/git2/checkout.h | 16 ---------------- src/checkout.c | 20 -------------------- 2 files changed, 36 deletions(-) diff --git a/include/git2/checkout.h b/include/git2/checkout.h index 5707de0d7..b15b56a33 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -57,22 +57,6 @@ GIT_EXTERN(int) git_checkout_head( git_checkout_opts *opts, git_indexer_stats *stats); -/** - * Updates files in the index and the working tree to match the content of the - * commit pointed at by the reference. - * - * - * @param ref reference to follow to a commit - * @param opts specifies checkout options (may be NULL) - * @param stats structure through which progress information is reported - * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information - * about the error) - */ -GIT_EXTERN(int) git_checkout_reference( - git_reference *ref, - git_checkout_opts *opts, - git_indexer_stats *stats); - /** * Updates files in the working tree to match the content of the index. * diff --git a/src/checkout.c b/src/checkout.c index beb8b5a63..c39bccbaa 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -358,23 +358,3 @@ int git_checkout_head( return error; } -int git_checkout_reference( - git_reference *ref, - git_checkout_opts *opts, - git_indexer_stats *stats) -{ - git_repository *repo= git_reference_owner(ref); - git_reference *head = NULL; - int error; - - if ((error = git_reference_create_symbolic( - &head, repo, GIT_HEAD_FILE, git_reference_name(ref), true)) < 0) - return error; - - error = git_checkout_head(git_reference_owner(ref), opts, stats); - - git_reference_free(head); - return error; -} - - From 10df95c3cac1c4e195bd39a0914de899245ee5e0 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 15 Sep 2012 12:23:49 +0200 Subject: [PATCH 07/14] checkout: add test coverage of dirs and subtrees --- tests-clar/checkout/tree.c | 56 ++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c index 32b64e5d7..5f99043f9 100644 --- a/tests-clar/checkout/tree.c +++ b/tests-clar/checkout/tree.c @@ -4,26 +4,66 @@ #include "repository.h" static git_repository *g_repo; +static git_checkout_opts g_opts; +static git_object *g_object; void test_checkout_tree__initialize(void) { g_repo = cl_git_sandbox_init("testrepo"); + + memset(&g_opts, 0, sizeof(g_opts)); + g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING; } void test_checkout_tree__cleanup(void) { + git_object_free(g_object); + cl_git_sandbox_cleanup(); } void test_checkout_tree__cannot_checkout_a_non_treeish(void) { - git_oid oid; - git_blob *blob; + /* blob */ + cl_git_pass(git_revparse_single(&g_object, g_repo, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")); - cl_git_pass(git_oid_fromstr(&oid, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")); - cl_git_pass(git_blob_lookup(&blob, g_repo, &oid)); - - cl_git_fail(git_checkout_tree(g_repo, (git_object *)blob, NULL, NULL)); - - git_blob_free(blob); + cl_git_fail(git_checkout_tree(g_repo, g_object, NULL, NULL)); +} + +void test_checkout_tree__can_checkout_a_subdirectory_from_a_commit(void) +{ + git_strarray paths; + char *entries[] = { "ab/de/" }; + + paths.strings = entries; + paths.count = 1; + g_opts.paths = &paths; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees")); + + cl_assert_equal_i(false, git_path_isdir("./testrepo/ab/")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts, NULL)); + + cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/2.txt")); + cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/fgh/1.txt")); +} + +void test_checkout_tree__can_checkout_a_subdirectory_from_a_subtree(void) +{ + git_strarray paths; + char *entries[] = { "de/" }; + + paths.strings = entries; + paths.count = 1; + g_opts.paths = &paths; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees:ab")); + + cl_assert_equal_i(false, git_path_isdir("./testrepo/de/")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts, NULL)); + + cl_assert_equal_i(true, git_path_isfile("./testrepo/de/2.txt")); + cl_assert_equal_i(true, git_path_isfile("./testrepo/de/fgh/1.txt")); } From f1ad25f6df10b1ca96a1f5fe3fc1a478c19043f5 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 15 Sep 2012 12:44:07 +0200 Subject: [PATCH 08/14] repository: separate head related tests --- tests-clar/repo/getters.c | 46 ---------------------------------- tests-clar/repo/head.c | 52 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 46 deletions(-) create mode 100644 tests-clar/repo/head.c diff --git a/tests-clar/repo/getters.c b/tests-clar/repo/getters.c index 966de1f16..ffcd171f2 100644 --- a/tests-clar/repo/getters.c +++ b/tests-clar/repo/getters.c @@ -23,52 +23,6 @@ void test_repo_getters__empty(void) git_repository_free(repo_empty); } -void test_repo_getters__head_detached(void) -{ - git_repository *repo; - git_reference *ref; - git_oid oid; - - cl_git_pass(git_repository_open(&repo, "testrepo.git")); - - cl_assert(git_repository_head_detached(repo) == 0); - - /* detach the HEAD */ - git_oid_fromstr(&oid, "c47800c7266a2be04c571c04d5a6614691ea99bd"); - cl_git_pass(git_reference_create_oid(&ref, repo, "HEAD", &oid, 1)); - cl_assert(git_repository_head_detached(repo) == 1); - git_reference_free(ref); - - /* take the reop back to it's original state */ - cl_git_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/master", 1)); - cl_assert(git_repository_head_detached(repo) == 0); - - git_reference_free(ref); - git_repository_free(repo); -} - -void test_repo_getters__head_orphan(void) -{ - git_repository *repo; - git_reference *ref; - - cl_git_pass(git_repository_open(&repo, "testrepo.git")); - - cl_assert(git_repository_head_orphan(repo) == 0); - - /* orphan HEAD */ - cl_git_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/orphan", 1)); - cl_assert(git_repository_head_orphan(repo) == 1); - git_reference_free(ref); - - /* take the reop back to it's original state */ - cl_git_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/master", 1)); - cl_assert(git_repository_head_orphan(repo) == 0); - - git_reference_free(ref); - git_repository_free(repo); -} - void test_repo_getters__retrieving_the_odb_honors_the_refcount(void) { git_odb *odb; diff --git a/tests-clar/repo/head.c b/tests-clar/repo/head.c new file mode 100644 index 000000000..eb1332aff --- /dev/null +++ b/tests-clar/repo/head.c @@ -0,0 +1,52 @@ +#include "clar_libgit2.h" +#include "refs.h" + +git_repository *repo; + +void test_repo_head__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_repo_head__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_repo_head__head_detached(void) +{ + git_reference *ref; + git_oid oid; + + cl_assert(git_repository_head_detached(repo) == 0); + + /* detach the HEAD */ + git_oid_fromstr(&oid, "c47800c7266a2be04c571c04d5a6614691ea99bd"); + cl_git_pass(git_reference_create_oid(&ref, repo, "HEAD", &oid, 1)); + cl_assert(git_repository_head_detached(repo) == 1); + git_reference_free(ref); + + /* take the reop back to it's original state */ + cl_git_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/master", 1)); + cl_assert(git_repository_head_detached(repo) == 0); + + git_reference_free(ref); +} + +void test_repo_head__head_orphan(void) +{ + git_reference *ref; + + cl_assert(git_repository_head_orphan(repo) == 0); + + /* orphan HEAD */ + cl_git_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/orphan", 1)); + cl_assert(git_repository_head_orphan(repo) == 1); + git_reference_free(ref); + + /* take the reop back to it's original state */ + cl_git_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/master", 1)); + cl_assert(git_repository_head_orphan(repo) == 0); + + git_reference_free(ref); +} From cc548c7b0ff0d8b33a44d90316abe0027cb6c7d9 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 15 Sep 2012 12:55:37 +0200 Subject: [PATCH 09/14] repository: fix documentation typo --- include/git2/repository.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/git2/repository.h b/include/git2/repository.h index 32ec58dae..a536c1398 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -456,7 +456,7 @@ GIT_EXTERN(int) git_repository_index(git_index **out, git_repository *repo); GIT_EXTERN(void) git_repository_set_index(git_repository *repo, git_index *index); /** - * Retrive git's prepared message + * Retrieve git's prepared message * * Operations such as git revert/cherry-pick/merge with the -n option * stop just short of creating a commit with the changes and save From 3f4c3072ea36877c07380d096d6277e9777f4587 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 15 Sep 2012 22:03:31 +0200 Subject: [PATCH 10/14] repository: introduce git_repository_detach_head() --- include/git2/repository.h | 19 +++++++++++++++++++ src/repository.c | 24 ++++++++++++++++++++++++ tests-clar/repo/head.c | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/include/git2/repository.h b/include/git2/repository.h index a536c1398..e68e0548f 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -506,6 +506,25 @@ GIT_EXTERN(int) git_repository_hashfile( git_otype type, const char *as_path); +/** + * Detach the HEAD. + * + * If the HEAD is already detached and points to a Commit, 0 is returned. + * + * If the HEAD is already detached and points to a Tag, the HEAD is + * updated into making it point to the peeled Commit, and 0 is returned. + * + * If the HEAD is already detached and points to a non commitish, the HEAD is + * unaletered, and -1 is returned. + * + * Otherwise, the HEAD will be detached and point to the peeled Commit. + * + * @param repo Repository pointer + * @return 0 on success, or an error code + */ +GIT_EXTERN(int) git_repository_detach_head( + git_repository* repo); + /** @} */ GIT_END_DECL #endif diff --git a/src/repository.c b/src/repository.c index 20a623a85..def96816f 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1442,3 +1442,27 @@ cleanup: return error; } +int git_repository_detach_head( + git_repository* repo) +{ + git_reference *old_head = NULL, + *new_head = NULL; + git_object *object = NULL; + int error = -1; + + assert(repo); + + if (git_repository_head(&old_head, repo) < 0) + return -1; + + if (git_object_lookup(&object, repo, git_reference_oid(old_head), GIT_OBJ_COMMIT) < 0) + goto cleanup; + + error = git_reference_create_oid(&new_head, repo, GIT_HEAD_FILE, git_reference_oid(old_head), 1); + +cleanup: + git_object_free(object); + git_reference_free(old_head); + git_reference_free(new_head); + return error; +} diff --git a/tests-clar/repo/head.c b/tests-clar/repo/head.c index eb1332aff..74d2a1c88 100644 --- a/tests-clar/repo/head.c +++ b/tests-clar/repo/head.c @@ -50,3 +50,38 @@ void test_repo_head__head_orphan(void) git_reference_free(ref); } + +static void assert_head_is_correctly_detached(void) +{ + git_reference *head; + git_object *commit; + + cl_assert_equal_i(true, git_repository_head_detached(repo)); + + cl_git_pass(git_repository_head(&head, repo)); + + cl_git_pass(git_object_lookup(&commit, repo, git_reference_oid(head), GIT_OBJ_COMMIT)); + + git_object_free(commit); + git_reference_free(head); +} + +void test_repo_head__detach_head_Detaches_HEAD_and_make_it_point_to_the_peeled_commit(void) +{ + cl_assert_equal_i(false, git_repository_head_detached(repo)); + + cl_git_pass(git_repository_detach_head(repo)); + + assert_head_is_correctly_detached(); +} + +void test_repo_head__detach_head_Fails_if_HEAD_and_point_to_a_non_commitish(void) +{ + git_reference *head; + + cl_git_pass(git_reference_create_symbolic(&head, repo, GIT_HEAD_FILE, "refs/tags/point_to_blob", 1)); + + cl_git_fail(git_repository_detach_head(repo)); + + git_reference_free(head); +} From 4ebe38bd589b7b99427b2822ca7a486c8bb3bf02 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 15 Sep 2012 22:07:09 +0200 Subject: [PATCH 11/14] repository: introduce git_repository_set_head_detached() --- include/git2/repository.h | 20 ++++++++++++++++++++ src/repository.c | 26 ++++++++++++++++++++++++++ tests-clar/repo/head.c | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+) diff --git a/include/git2/repository.h b/include/git2/repository.h index e68e0548f..59a7d2c98 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -506,6 +506,26 @@ GIT_EXTERN(int) git_repository_hashfile( git_otype type, const char *as_path); +/** + * Make the repository HEAD directly point to the Commit. + * + * If the provided committish cannot be found in the repository, the HEAD + * is unaltered and GIT_ENOTFOUND is returned. + * + * If the provided commitish cannot be peeled into a commit, the HEAD + * is unaltered and -1 is returned. + * + * Otherwise, the HEAD will eventually be detached and will directly point to + * the peeled Commit. + * + * @param repo Repository pointer + * @param commitish Object id of the Commit the HEAD should point to + * @return 0 on success, or an error code + */ +GIT_EXTERN(int) git_repository_set_head_detached( + git_repository* repo, + const git_oid* commitish); + /** * Detach the HEAD. * diff --git a/src/repository.c b/src/repository.c index def96816f..a3e781478 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1442,6 +1442,32 @@ cleanup: return error; } +int git_repository_set_head_detached( + git_repository* repo, + const git_oid* commitish) +{ + int error; + git_object *object, + *peeled = NULL; + git_reference *new_head = NULL; + + assert(repo && commitish); + + if ((error = git_object_lookup(&object, repo, commitish, GIT_OBJ_ANY)) < 0) + return error; + + if ((error = git_object_peel(&peeled, object, GIT_OBJ_COMMIT)) < 0) + goto cleanup; + + error = git_reference_create_oid(&new_head, repo, GIT_HEAD_FILE, git_object_id(peeled), 1); + +cleanup: + git_object_free(object); + git_object_free(peeled); + git_reference_free(new_head); + return error; +} + int git_repository_detach_head( git_repository* repo) { diff --git a/tests-clar/repo/head.c b/tests-clar/repo/head.c index 74d2a1c88..372cdd61d 100644 --- a/tests-clar/repo/head.c +++ b/tests-clar/repo/head.c @@ -66,6 +66,40 @@ static void assert_head_is_correctly_detached(void) git_reference_free(head); } +void test_repo_head__set_head_detached_Return_ENOTFOUND_when_the_object_doesnt_exist(void) +{ + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")); + + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_set_head_detached(repo, &oid)); +} + +void test_repo_head__set_head_detached_Fails_when_the_object_isnt_a_commitish(void) +{ + git_object *blob; + + cl_git_pass(git_revparse_single(&blob, repo, "point_to_blob")); + + cl_git_fail(git_repository_set_head_detached(repo, git_object_id(blob))); + + git_object_free(blob); +} + +void test_repo_head__set_head_detached_Detaches_HEAD_and_make_it_point_to_the_peeled_commit(void) +{ + git_object *tag; + + cl_git_pass(git_revparse_single(&tag, repo, "tags/test")); + cl_assert_equal_i(GIT_OBJ_TAG, git_object_type(tag)); + + cl_git_pass(git_repository_set_head_detached(repo, git_object_id(tag))); + + assert_head_is_correctly_detached(); + + git_object_free(tag); +} + void test_repo_head__detach_head_Detaches_HEAD_and_make_it_point_to_the_peeled_commit(void) { cl_assert_equal_i(false, git_repository_head_detached(repo)); From 44af67a8b6679ac33c3407d45fee042178d97e76 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Sat, 15 Sep 2012 22:07:45 +0200 Subject: [PATCH 12/14] repository: introduce git_repository_set_head() --- include/git2/repository.h | 22 ++++++++++++++++++++ src/repository.c | 32 ++++++++++++++++++++++++++++ tests-clar/repo/head.c | 44 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) diff --git a/include/git2/repository.h b/include/git2/repository.h index 59a7d2c98..025a0a95d 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -506,6 +506,28 @@ GIT_EXTERN(int) git_repository_hashfile( git_otype type, const char *as_path); +/** + * Make the repository HEAD point to the specified reference. + * + * If the provided reference points to a Tree or a Blob, the HEAD is + * unaltered and -1 is returned. + * + * If the provided reference points to a branch, the HEAD will point + * to that branch, staying attached, or become attached if it isn't yet. + * If the branch doesn't exist yet, no error will be return. The HEAD + * will then be attached to an unborn branch. + * + * Otherwise, the HEAD will be detached and will directly point to + * the Commit. + * + * @param repo Repository pointer + * @param refname Canonical name of the reference the HEAD should point at + * @return 0 on success, or an error code + */ +GIT_EXTERN(int) git_repository_set_head( + git_repository* repo, + const char* refname); + /** * Make the repository HEAD directly point to the Commit. * diff --git a/src/repository.c b/src/repository.c index a3e781478..734cab43d 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1442,6 +1442,38 @@ cleanup: return error; } +static bool looks_like_a_branch(const char *refname) +{ + return git__prefixcmp(refname, GIT_REFS_HEADS_DIR) == 0; +} + +int git_repository_set_head( + git_repository* repo, + const char* refname) +{ + git_reference *ref, + *new_head = NULL; + int error; + + assert(repo && refname); + + error = git_reference_lookup(&ref, repo, refname); + if (error < 0 && error != GIT_ENOTFOUND) + return error; + + if (!error) { + if (git_reference_is_branch(ref)) + error = git_reference_create_symbolic(&new_head, repo, GIT_HEAD_FILE, git_reference_name(ref), 1); + else + error = git_repository_set_head_detached(repo, git_reference_oid(ref)); + } else if (looks_like_a_branch(refname)) + error = git_reference_create_symbolic(&new_head, repo, GIT_HEAD_FILE, refname, 1); + + git_reference_free(ref); + git_reference_free(new_head); + return error; +} + int git_repository_set_head_detached( git_repository* repo, const git_oid* commitish) diff --git a/tests-clar/repo/head.c b/tests-clar/repo/head.c index 372cdd61d..64dec69dd 100644 --- a/tests-clar/repo/head.c +++ b/tests-clar/repo/head.c @@ -51,6 +51,41 @@ void test_repo_head__head_orphan(void) git_reference_free(ref); } +void test_repo_head__set_head_Attaches_HEAD_to_un_unborn_branch_when_the_branch_doesnt_exist(void) +{ + git_reference *head; + + cl_git_pass(git_repository_set_head(repo, "refs/heads/doesnt/exist/yet")); + + cl_assert_equal_i(false, git_repository_head_detached(repo)); + + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_head(&head, repo)); +} + +void test_repo_head__set_head_Returns_ENOTFOUND_when_the_reference_doesnt_exist(void) +{ + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_set_head(repo, "refs/tags/doesnt/exist/yet")); +} + +void test_repo_head__set_head_Fails_when_the_reference_points_to_a_non_commitish(void) +{ + cl_git_fail(git_repository_set_head(repo, "refs/tags/point_to_blob")); +} + +void test_repo_head__set_head_Attaches_HEAD_when_the_reference_points_to_a_branch(void) +{ + git_reference *head; + + cl_git_pass(git_repository_set_head(repo, "refs/heads/br2")); + + cl_assert_equal_i(false, git_repository_head_detached(repo)); + + cl_git_pass(git_repository_head(&head, repo)); + cl_assert_equal_s("refs/heads/br2", git_reference_name(head)); + + git_reference_free(head); +} + static void assert_head_is_correctly_detached(void) { git_reference *head; @@ -66,6 +101,15 @@ static void assert_head_is_correctly_detached(void) git_reference_free(head); } +void test_repo_head__set_head_Detaches_HEAD_when_the_reference_doesnt_point_to_a_branch(void) +{ + cl_git_pass(git_repository_set_head(repo, "refs/tags/test")); + + cl_assert_equal_i(true, git_repository_head_detached(repo)); + + assert_head_is_correctly_detached(); +} + void test_repo_head__set_head_detached_Return_ENOTFOUND_when_the_object_doesnt_exist(void) { git_oid oid; From 5e4cb4f4da0baef99683be95cb5eeb5288d8ba84 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Mon, 17 Sep 2012 10:38:57 +0200 Subject: [PATCH 13/14] checkout : reduce memory usage when not filtering --- src/checkout.c | 48 +++++++++++++++++++++++++++++++++++------------- src/filter.c | 19 ------------------- src/filter.h | 11 ----------- 3 files changed, 35 insertions(+), 43 deletions(-) diff --git a/src/checkout.c b/src/checkout.c index c39bccbaa..30799b608 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -59,28 +59,50 @@ static int blob_content_to_file( unsigned int entry_filemode, git_checkout_opts *opts) { - int retcode; - git_buf content = GIT_BUF_INIT; - int file_mode = opts->file_mode; + int error, nb_filters = 0, file_mode = opts->file_mode; + bool dont_free_filtered = false; + git_buf unfiltered = GIT_BUF_INIT, filtered = GIT_BUF_INIT; + git_vector filters = GIT_VECTOR_INIT; - /* Allow disabling of filters */ - if (opts->disable_filters) - retcode = git_blob__getbuf(&content, blob); - else - retcode = git_filter_blob_content(&content, blob, path); + if (opts->disable_filters || + (nb_filters = git_filters_load( + &filters, + git_object_owner((git_object *)blob), + path, + GIT_FILTER_TO_WORKTREE)) == 0) { - if (retcode < 0) - goto cleanup; + /* Create a fake git_buf from the blob raw data... */ + filtered.ptr = blob->odb_object->raw.data; + filtered.size = blob->odb_object->raw.len; + + /* ... and make sure it doesn't get unexpectedly freed */ + dont_free_filtered = true; + } + + if (nb_filters < 0) + return nb_filters; + + if (nb_filters > 0) { + if (git_blob__getbuf(&unfiltered, blob) < 0) + goto cleanup; + + if ((error = git_filters_apply(&filtered, &unfiltered, &filters)) < 0) + goto cleanup; + } /* Allow overriding of file mode */ if (!file_mode) file_mode = entry_filemode; - retcode = buffer_to_file(&content, path, opts->dir_mode, opts->file_open_flags, file_mode); + error = buffer_to_file(&filtered, path, opts->dir_mode, opts->file_open_flags, file_mode); cleanup: - git_buf_free(&content); - return retcode; + git_filters_free(&filters); + git_buf_free(&unfiltered); + if (!dont_free_filtered) + git_buf_free(&filtered); + + return error; } static int blob_content_to_link(git_blob *blob, const char *path, bool can_symlink) diff --git a/src/filter.c b/src/filter.c index 5b6bb286a..28a05235b 100644 --- a/src/filter.c +++ b/src/filter.c @@ -164,22 +164,3 @@ int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters) return 0; } - -int git_filter_blob_content(git_buf *out, git_blob *blob, const char *hintpath) -{ - git_buf unfiltered = GIT_BUF_INIT; - git_vector filters = GIT_VECTOR_INIT; - int retcode; - - retcode = git_blob__getbuf(&unfiltered, blob); - - git_buf_clear(out); - - if (git_filters_load(&filters, git_object_owner((git_object *)blob), hintpath, GIT_FILTER_TO_WORKTREE) >= 0) - retcode = git_filters_apply(out, &unfiltered, &filters); - - git_filters_free(&filters); - git_buf_free(&unfiltered); - - return retcode; -} diff --git a/src/filter.h b/src/filter.h index d58e173f9..b9beb4942 100644 --- a/src/filter.h +++ b/src/filter.h @@ -119,15 +119,4 @@ extern void git_text_gather_stats(git_text_stats *stats, const git_buf *text); */ extern int git_text_is_binary(git_text_stats *stats); - -/** - * Get the content of a blob after all filters have been run. - * - * @param out buffer to receive the contents - * @param hintpath path to the blob's output file, relative to the workdir root. - * Used to determine what git filters should be applied to the content. - * @return 0 on success, an error code otherwise - */ -extern int git_filter_blob_content(git_buf *out, git_blob *blob, const char *hintpath); - #endif From 397837197d1ce04b8bd4aaa57a7f5f67648dc57f Mon Sep 17 00:00:00 2001 From: nulltoken Date: Mon, 17 Sep 2012 20:27:28 +0200 Subject: [PATCH 14/14] checkout: Mimic git_diff_options storage of paths --- include/git2/checkout.h | 2 +- src/checkout.c | 6 ++---- tests-clar/checkout/index.c | 6 ++---- tests-clar/checkout/tree.c | 12 ++++-------- 4 files changed, 9 insertions(+), 17 deletions(-) diff --git a/include/git2/checkout.h b/include/git2/checkout.h index b15b56a33..42d47003d 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -39,7 +39,7 @@ typedef struct git_checkout_opts { /* when not NULL, arrays of fnmatch pattern specifying * which paths should be taken into account */ - git_strarray *paths; + git_strarray paths; } git_checkout_opts; /** diff --git a/src/checkout.c b/src/checkout.c index 30799b608..b20bd57e8 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -282,10 +282,8 @@ int git_checkout_index( diff_opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; - if (opts && opts->paths) { - diff_opts.pathspec.strings = opts->paths->strings; - diff_opts.pathspec.count = opts->paths->count; - } + if (opts && opts->paths.count > 0) + diff_opts.pathspec = opts->paths; if ((error = git_diff_workdir_to_index(repo, &diff_opts, &diff)) < 0) goto cleanup; diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c index fad10be51..d1c59e38c 100644 --- a/tests-clar/checkout/index.c +++ b/tests-clar/checkout/index.c @@ -102,12 +102,10 @@ void test_checkout_index__can_remove_untracked_files(void) void test_checkout_index__honor_the_specified_pathspecs(void) { - git_strarray paths; char *entries[] = { "*.txt" }; - paths.strings = entries; - paths.count = 1; - g_opts.paths = &paths; + g_opts.paths.strings = entries; + g_opts.paths.count = 1; cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c index 5f99043f9..6d573bfd7 100644 --- a/tests-clar/checkout/tree.c +++ b/tests-clar/checkout/tree.c @@ -32,12 +32,10 @@ void test_checkout_tree__cannot_checkout_a_non_treeish(void) void test_checkout_tree__can_checkout_a_subdirectory_from_a_commit(void) { - git_strarray paths; char *entries[] = { "ab/de/" }; - paths.strings = entries; - paths.count = 1; - g_opts.paths = &paths; + g_opts.paths.strings = entries; + g_opts.paths.count = 1; cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees")); @@ -51,12 +49,10 @@ void test_checkout_tree__can_checkout_a_subdirectory_from_a_commit(void) void test_checkout_tree__can_checkout_a_subdirectory_from_a_subtree(void) { - git_strarray paths; char *entries[] = { "de/" }; - paths.strings = entries; - paths.count = 1; - g_opts.paths = &paths; + g_opts.paths.strings = entries; + g_opts.paths.count = 1; cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees:ab"));