diff options
author | Vicent Martí <vicent@github.com> | 2012-08-19 01:26:06 -0700 |
---|---|---|
committer | Vicent Martí <vicent@github.com> | 2012-08-19 01:26:06 -0700 |
commit | f98c32f3fea0d5532db2d5733418aa62648b9e93 (patch) | |
tree | 5b53901f1848d73a72765ec014e2ad5188316eb4 | |
parent | 1a10fded40875f986164b80c6efd414cd1507cb8 (diff) | |
parent | eb87800ab631d19a7655f01ece130455b1cc976a (diff) | |
download | libgit2-f98c32f3fea0d5532db2d5733418aa62648b9e93.tar.gz |
Merge pull request #778 from ben/clone
Clone
42 files changed, 1304 insertions, 30 deletions
diff --git a/examples/network/Makefile b/examples/network/Makefile index 9afd49e5d..835be24cc 100644 --- a/examples/network/Makefile +++ b/examples/network/Makefile @@ -8,6 +8,7 @@ OBJECTS = \ git2.o \ ls-remote.o \ fetch.o \ + clone.o \ index-pack.o all: $(OBJECTS) diff --git a/examples/network/clone.c b/examples/network/clone.c new file mode 100644 index 000000000..fb571bd3a --- /dev/null +++ b/examples/network/clone.c @@ -0,0 +1,68 @@ +#include "common.h" +#include <git2.h> +#include <git2/clone.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <pthread.h> +#include <unistd.h> + +struct dl_data { + git_indexer_stats fetch_stats; + git_indexer_stats checkout_stats; + git_checkout_opts opts; + int ret; + int finished; + const char *url; + const char *path; +}; + +static void *clone_thread(void *ptr) +{ + struct dl_data *data = (struct dl_data *)ptr; + git_repository *repo = NULL; + + // Kick off the clone + data->ret = git_clone(&repo, data->url, data->path, + &data->fetch_stats, &data->checkout_stats, + &data->opts); + if (repo) git_repository_free(repo); + data->finished = 1; + + pthread_exit(&data->ret); +} + +int do_clone(git_repository *repo, int argc, char **argv) +{ + struct dl_data data = {0}; + pthread_t worker; + + // Validate args + if (argc < 3) { + printf("USAGE: %s <url> <path>\n", argv[0]); + return -1; + } + + // Data for background thread + data.url = argv[1]; + data.path = argv[2]; + data.opts.disable_filters = 1; + printf("Cloning '%s' to '%s'\n", data.url, data.path); + + // Create the worker thread + pthread_create(&worker, NULL, clone_thread, &data); + + // Watch for progress information + do { + usleep(10000); + printf("Fetch %d/%d – Checkout %d/%d\n", + data.fetch_stats.processed, data.fetch_stats.total, + data.checkout_stats.processed, data.checkout_stats.total); + } while (!data.finished); + printf("Fetch %d/%d – Checkout %d/%d\n", + data.fetch_stats.processed, data.fetch_stats.total, + data.checkout_stats.processed, data.checkout_stats.total); + + return data.ret; +} + diff --git a/examples/network/common.h b/examples/network/common.h index 29460bb36..c82eaa1c8 100644 --- a/examples/network/common.h +++ b/examples/network/common.h @@ -10,5 +10,6 @@ int parse_pkt_line(git_repository *repo, int argc, char **argv); int show_remote(git_repository *repo, int argc, char **argv); int fetch(git_repository *repo, int argc, char **argv); int index_pack(git_repository *repo, int argc, char **argv); +int do_clone(git_repository *repo, int argc, char **argv); #endif /* __COMMON_H__ */ diff --git a/examples/network/git2.c b/examples/network/git2.c index 1bfb13c9e..ecb16630b 100644 --- a/examples/network/git2.c +++ b/examples/network/git2.c @@ -13,6 +13,7 @@ struct { } commands[] = { {"ls-remote", ls_remote}, {"fetch", fetch}, + {"clone", do_clone}, {"index-pack", index_pack}, { NULL, NULL} }; diff --git a/include/git2.h b/include/git2.h index 2e33f9e4a..40167484b 100644 --- a/include/git2.h +++ b/include/git2.h @@ -37,6 +37,8 @@ #include "git2/index.h" #include "git2/config.h" #include "git2/remote.h" +#include "git2/clone.h" +#include "git2/checkout.h" #include "git2/attr.h" #include "git2/branch.h" diff --git a/include/git2/checkout.h b/include/git2/checkout.h new file mode 100644 index 000000000..ac31b3462 --- /dev/null +++ b/include/git2/checkout.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * 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_git_checkout_h__ +#define INCLUDE_git_checkout_h__ + +#include "common.h" +#include "types.h" +#include "indexer.h" + + +/** + * @file git2/checkout.h + * @brief Git checkout routines + * @defgroup git_checkout Git checkout routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + + +#define GIT_CHECKOUT_OVERWRITE_EXISTING 0 /* default */ +#define GIT_CHECKOUT_SKIP_EXISTING 1 + +/* Use zeros to indicate default settings */ +typedef struct git_checkout_opts { + int existing_file_action; /* default: GIT_CHECKOUT_OVERWRITE_EXISTING */ + int disable_filters; + int dir_mode; /* default is 0755 */ + int file_mode; /* default is 0644 */ + int file_open_flags; /* default is O_CREAT | O_TRUNC | O_WRONLY */ +} git_checkout_opts; + +/** + * Updates files in the working tree to match the commit pointed to by HEAD. + * + * @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_head(git_repository *repo, + git_checkout_opts *opts, + git_indexer_stats *stats); + + + +/** + * Updates files in the working tree to match a commit pointed to by a ref. + * + * @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); + + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/clone.h b/include/git2/clone.h new file mode 100644 index 000000000..40292ed59 --- /dev/null +++ b/include/git2/clone.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * 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_git_clone_h__ +#define INCLUDE_git_clone_h__ + +#include "common.h" +#include "types.h" +#include "indexer.h" +#include "checkout.h" + + +/** + * @file git2/clone.h + * @brief Git cloning routines + * @defgroup git_clone Git cloning routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Clone a remote repository, and checkout the branch pointed to by the remote + * HEAD. + * + * @param out pointer that will receive the resulting repository object + * @param origin_url repository to clone from + * @param workdir_path local directory to clone to + * @param fetch_stats pointer to structure that receives fetch progress information (may be NULL) + * @param checkout_opts options for the checkout step (may be NULL) + * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information about the error) + */ +GIT_EXTERN(int) git_clone(git_repository **out, + const char *origin_url, + const char *workdir_path, + git_indexer_stats *fetch_stats, + git_indexer_stats *checkout_stats, + git_checkout_opts *checkout_opts); + +/** + * Create a bare clone of a remote repository. + * + * @param out pointer that will receive the resulting repository object + * @param origin_url repository to clone from + * @param dest_path local directory to clone to + * @param fetch_stats pointer to structure that receives fetch progress information (may be NULL) + * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information about the error) + */ +GIT_EXTERN(int) git_clone_bare(git_repository **out, + const char *origin_url, + const char *dest_path, + git_indexer_stats *fetch_stats); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/index.h b/include/git2/index.h index 0093330e2..062932e1a 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -8,6 +8,7 @@ #define INCLUDE_git_index_h__ #include "common.h" +#include "indexer.h" #include "types.h" #include "oid.h" @@ -335,15 +336,17 @@ GIT_EXTERN(const git_index_entry_unmerged *) git_index_get_unmerged_byindex(git_ GIT_EXTERN(int) git_index_entry_stage(const git_index_entry *entry); /** - * Read a tree into the index file + * Read a tree into the index file with stats * - * The current index contents will be replaced by the specified tree. + * The current index contents will be replaced by the specified tree. The total + * node count is collected in stats. * * @param index an existing index object * @param tree tree to read + * @param stats structure that receives the total node count (may be NULL) * @return 0 or an error code */ -GIT_EXTERN(int) git_index_read_tree(git_index *index, git_tree *tree); +GIT_EXTERN(int) git_index_read_tree(git_index *index, git_tree *tree, git_indexer_stats *stats); /** @} */ GIT_END_DECL diff --git a/src/checkout.c b/src/checkout.c new file mode 100644 index 000000000..252d9c4ae --- /dev/null +++ b/src/checkout.c @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include <assert.h> + +#include "git2/checkout.h" +#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 "common.h" +#include "refs.h" +#include "buffer.h" +#include "repository.h" +#include "filter.h" +#include "blob.h" + +GIT_BEGIN_DECL + + +typedef struct tree_walk_data +{ + git_indexer_stats *stats; + git_checkout_opts *opts; + git_repository *repo; + git_odb *odb; + bool do_symlinks; +} tree_walk_data; + + +static int blob_contents_to_link(tree_walk_data *data, git_buf *fnbuf, + const git_oid *id) +{ + int retcode = GIT_ERROR; + git_blob *blob; + + /* 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->do_symlinks + ? p_symlink(new, old) + : git_futils_fake_symlink(new, old); + } + git_buf_free(&linktarget); + git_blob_free(blob); + } + + return retcode; +} + + +static int blob_contents_to_file(git_repository *repo, git_buf *fnbuf, + const git_tree_entry *entry, tree_walk_data *data) +{ + 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; + + /* 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; + + /* Allow overriding of file mode */ + if (!file_mode) + file_mode = git_tree_entry_attributes(entry); + + if ((retcode = git_futils_mkpath2file(git_buf_cstr(fnbuf), data->opts->dir_mode)) < 0) + goto bctf_cleanup; + + 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); + return retcode; +} + +static int checkout_walker(const char *path, const git_tree_entry *entry, void *payload) +{ + int retcode = 0; + tree_walk_data *data = (tree_walk_data*)payload; + int attr = git_tree_entry_attributes(entry); + git_buf fnbuf = GIT_BUF_INIT; + git_buf_join_n(&fnbuf, '/', 3, + git_repository_workdir(data->repo), + path, + git_tree_entry_name(entry)); + + switch(git_tree_entry_type(entry)) + { + case GIT_OBJ_TREE: + /* Nothing to do; the blob handling creates necessary directories. */ + 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_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; + } + + git_buf_free(&fnbuf); + data->stats->processed++; + return retcode; +} + + +int git_checkout_head(git_repository *repo, git_checkout_opts *opts, git_indexer_stats *stats) +{ + 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; + + assert(repo); + if (!opts) opts = &default_opts; + if (!stats) stats = &dummy_stats; + + /* 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 (git_repository_is_bare(repo)) { + giterr_set(GITERR_INVALID, "Checkout is not allowed for bare repositories"); + return GIT_ERROR; + } + + /* Determine if symlinks should be handled */ + if (!git_repository_config(&cfg, repo)) { + int temp = true; + if (!git_config_get_bool(&temp, cfg, "core.symlinks")) { + payload.do_symlinks = !!temp; + } + git_config_free(cfg); + } + + 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 (!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); + } + + git_odb_free(payload.odb); + return retcode; +} + + +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; + + if ((retcode = git_reference_create_symbolic(&head, repo, GIT_HEAD_FILE, + git_reference_name(ref), true)) < 0) + return retcode; + + retcode = git_checkout_head(git_reference_owner(ref), opts, stats); + + git_reference_free(head); + return retcode; +} + + +GIT_END_DECL diff --git a/src/clone.c b/src/clone.c new file mode 100644 index 000000000..33953d7a0 --- /dev/null +++ b/src/clone.c @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include <assert.h> + +#ifndef GIT_WIN32 +#include <dirent.h> +#endif + +#include "git2/clone.h" +#include "git2/remote.h" +#include "git2/revparse.h" +#include "git2/branch.h" +#include "git2/config.h" +#include "git2/checkout.h" +#include "git2/commit.h" +#include "git2/tree.h" + +#include "common.h" +#include "remote.h" +#include "fileops.h" +#include "refs.h" +#include "path.h" + +GIT_BEGIN_DECL + +struct HeadInfo { + git_repository *repo; + git_oid remote_head_oid; + git_buf branchname; +}; + +static int create_tracking_branch(git_repository *repo, const git_oid *target, const char *name) +{ + git_object *head_obj = NULL; + git_reference *branch_ref; + int retcode = GIT_ERROR; + + /* Find the target commit */ + if (git_object_lookup(&head_obj, repo, target, GIT_OBJ_ANY) < 0) + return GIT_ERROR; + + /* Create the new branch */ + if (!git_branch_create(&branch_ref, repo, name, head_obj, 0)) { + git_config *cfg; + + git_reference_free(branch_ref); + /* Set up tracking */ + if (!git_repository_config(&cfg, repo)) { + git_buf remote = GIT_BUF_INIT; + git_buf merge = GIT_BUF_INIT; + git_buf merge_target = GIT_BUF_INIT; + if (!git_buf_printf(&remote, "branch.%s.remote", name) && + !git_buf_printf(&merge, "branch.%s.merge", name) && + !git_buf_printf(&merge_target, "refs/heads/%s", name) && + !git_config_set_string(cfg, git_buf_cstr(&remote), "origin") && + !git_config_set_string(cfg, git_buf_cstr(&merge), git_buf_cstr(&merge_target))) { + retcode = 0; + } + git_buf_free(&remote); + git_buf_free(&merge); + git_buf_free(&merge_target); + git_config_free(cfg); + } + } + + git_object_free(head_obj); + return retcode; +} + +static int reference_matches_remote_head(const char *head_name, void *payload) +{ + struct HeadInfo *head_info = (struct HeadInfo *)payload; + git_oid oid; + + /* Stop looking if we've already found a match */ + if (git_buf_len(&head_info->branchname) > 0) return 0; + + if (!git_reference_name_to_oid(&oid, head_info->repo, head_name) && + !git_oid_cmp(&head_info->remote_head_oid, &oid)) { + git_buf_puts(&head_info->branchname, + head_name+strlen("refs/remotes/origin/")); + } + return 0; +} + +static int update_head_to_new_branch(git_repository *repo, const git_oid *target, const char *name) +{ + int retcode = GIT_ERROR; + + if (!create_tracking_branch(repo, target, name)) { + git_reference *head; + if (!git_reference_lookup(&head, repo, GIT_HEAD_FILE)) { + git_buf targetbuf = GIT_BUF_INIT; + if (!git_buf_printf(&targetbuf, "refs/heads/%s", name)) { + retcode = git_reference_set_target(head, git_buf_cstr(&targetbuf)); + } + git_buf_free(&targetbuf); + git_reference_free(head); + } + } + + return retcode; +} + +static int update_head_to_remote(git_repository *repo, git_remote *remote) +{ + int retcode = GIT_ERROR; + git_remote_head *remote_head; + git_oid oid; + struct HeadInfo head_info; + + /* Get the remote's HEAD. This is always the first ref in remote->refs. */ + remote_head = remote->refs.contents[0]; + git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid); + git_buf_init(&head_info.branchname, 16); + head_info.repo = repo; + + /* Check to see if "master" matches the remote head */ + if (!git_reference_name_to_oid(&oid, repo, "refs/remotes/origin/master") && + !git_oid_cmp(&remote_head->oid, &oid)) { + retcode = update_head_to_new_branch(repo, &oid, "master"); + } + /* Not master. Check all the other refs. */ + else if (!git_reference_foreach(repo, GIT_REF_LISTALL, + reference_matches_remote_head, + &head_info) && + git_buf_len(&head_info.branchname) > 0) { + retcode = update_head_to_new_branch(repo, &head_info.remote_head_oid, + git_buf_cstr(&head_info.branchname)); + } + + git_buf_free(&head_info.branchname); + return retcode; +} + +/* + * submodules? + */ + + + +static int setup_remotes_and_fetch(git_repository *repo, + const char *origin_url, + git_indexer_stats *fetch_stats) +{ + int retcode = GIT_ERROR; + git_remote *origin = NULL; + git_off_t bytes = 0; + git_indexer_stats dummy_stats; + + if (!fetch_stats) fetch_stats = &dummy_stats; + + /* Create the "origin" remote */ + if (!git_remote_add(&origin, repo, "origin", origin_url)) { + /* Connect and download everything */ + if (!git_remote_connect(origin, GIT_DIR_FETCH)) { + if (!git_remote_download(origin, &bytes, fetch_stats)) { + /* Create "origin/foo" branches for all remote branches */ + if (!git_remote_update_tips(origin)) { + /* Point HEAD to the same ref as the remote's head */ + if (!update_head_to_remote(repo, origin)) { + retcode = 0; + } + } + } + git_remote_disconnect(origin); + } + git_remote_free(origin); + } + + return retcode; +} + + +static bool path_is_okay(const char *path) +{ + /* The path must either not exist, or be an empty directory */ + if (!git_path_exists(path)) return true; + if (!git_path_is_empty_dir(path)) { + giterr_set(GITERR_INVALID, + "'%s' exists and is not an empty directory", path); + return false; + } + return true; +} + + +static int clone_internal(git_repository **out, + const char *origin_url, + const char *path, + git_indexer_stats *fetch_stats, + int is_bare) +{ + int retcode = GIT_ERROR; + git_repository *repo = NULL; + git_indexer_stats dummy_stats; + + if (!fetch_stats) fetch_stats = &dummy_stats; + + if (!path_is_okay(path)) { + return GIT_ERROR; + } + + if (!(retcode = git_repository_init(&repo, path, is_bare))) { + if ((retcode = setup_remotes_and_fetch(repo, origin_url, fetch_stats)) < 0) { + /* Failed to fetch; clean up */ + git_repository_free(repo); + git_futils_rmdir_r(path, GIT_DIRREMOVAL_FILES_AND_DIRS); + } else { + *out = repo; + retcode = 0; + } + } + + return retcode; +} + +int git_clone_bare(git_repository **out, + const char *origin_url, + const char *dest_path, + git_indexer_stats *fetch_stats) +{ + assert(out && origin_url && dest_path); + return clone_internal(out, origin_url, dest_path, fetch_stats, 1); +} + + +int git_clone(git_repository **out, + const char *origin_url, + const char *workdir_path, + git_indexer_stats *fetch_stats, + git_indexer_stats *checkout_stats, + git_checkout_opts *checkout_opts) +{ + int retcode = GIT_ERROR; + + assert(out && origin_url && workdir_path); + + if (!(retcode = clone_internal(out, origin_url, workdir_path, fetch_stats, 0))) { + retcode = git_checkout_head(*out, checkout_opts, checkout_stats); + } + + return retcode; +} + + + + +GIT_END_DECL diff --git a/src/crlf.c b/src/crlf.c index 303a46d3b..509e55897 100644 --- a/src/crlf.c +++ b/src/crlf.c @@ -184,7 +184,87 @@ static int crlf_apply_to_odb(git_filter *self, git_buf *dest, const git_buf *sou return drop_crlf(dest, source); } -int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path) +static int convert_line_endings(git_buf *dest, const git_buf *source, const char *ending) +{ + const char *scan = git_buf_cstr(source), + *next, + *scan_end = git_buf_cstr(source) + git_buf_len(source); + + while ((next = memchr(scan, '\n', scan_end - scan)) != NULL) { + if (next > scan) + git_buf_put(dest, scan, next-scan); + git_buf_puts(dest, ending); + scan = next + 1; + } + + git_buf_put(dest, scan, scan_end - scan); + return 0; +} + +static const char *line_ending(struct crlf_filter *filter) +{ + switch (filter->attrs.crlf_action) { + case GIT_CRLF_BINARY: + case GIT_CRLF_INPUT: + return "\n"; + + case GIT_CRLF_CRLF: + return "\r\n"; + + case GIT_CRLF_AUTO: + case GIT_CRLF_TEXT: + case GIT_CRLF_GUESS: + break; + + default: + goto line_ending_error; + } + + switch (filter->attrs.eol) { + case GIT_EOL_UNSET: + return GIT_EOL_NATIVE == GIT_EOL_CRLF + ? "\r\n" + : "\n"; + + case GIT_EOL_CRLF: + return "\r\n"; + + case GIT_EOL_LF: + return "\n"; + + default: + goto line_ending_error; + } + +line_ending_error: + giterr_set(GITERR_INVALID, "Invalid input to line ending filter"); + return NULL; +} + +static int crlf_apply_to_workdir(git_filter *self, git_buf *dest, const git_buf *source) +{ + struct crlf_filter *filter = (struct crlf_filter *)self; + const char *workdir_ending = NULL; + + assert (self && dest && source); + + /* Empty file? Nothing to do. */ + if (git_buf_len(source) == 0) + return 0; + + /* Determine proper line ending */ + workdir_ending = line_ending(filter); + if (!workdir_ending) return -1; + + /* If the line ending is '\n', just copy the input */ + if (!strcmp(workdir_ending, "\n")) + return git_buf_puts(dest, git_buf_cstr(source)); + + return convert_line_endings(dest, source, workdir_ending); +} + +static int find_and_add_filter(git_vector *filters, git_repository *repo, const char *path, + int (*apply)(struct git_filter *self, git_buf *dest, const git_buf *source)) { struct crlf_attrs ca; struct crlf_filter *filter; @@ -206,8 +286,7 @@ int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const if (ca.crlf_action == GIT_CRLF_GUESS) { int auto_crlf; - if ((error = git_repository__cvar( - &auto_crlf, repo, GIT_CVAR_AUTO_CRLF)) < 0) + if ((error = git_repository__cvar(&auto_crlf, repo, GIT_CVAR_AUTO_CRLF)) < 0) return error; if (auto_crlf == GIT_AUTO_CRLF_FALSE) @@ -219,10 +298,19 @@ int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const filter = git__malloc(sizeof(struct crlf_filter)); GITERR_CHECK_ALLOC(filter); - filter->f.apply = &crlf_apply_to_odb; + filter->f.apply = apply; filter->f.do_free = NULL; memcpy(&filter->attrs, &ca, sizeof(struct crlf_attrs)); return git_vector_insert(filters, filter); } +int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path) +{ + return find_and_add_filter(filters, repo, path, &crlf_apply_to_odb); +} + +int git_filter_add__crlf_to_workdir(git_vector *filters, git_repository *repo, const char *path) +{ + return find_and_add_filter(filters, repo, path, &crlf_apply_to_workdir); +} diff --git a/src/fileops.c b/src/fileops.c index e936c3e2b..4de58b0cc 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -484,3 +484,14 @@ int git_futils_find_global_file(git_buf *path, const char *filename) return 0; #endif } + +int git_futils_fake_symlink(const char *old, const char *new) +{ + int retcode = GIT_ERROR; + int fd = git_futils_creat_withpath(new, 0755, 0644); + if (fd >= 0) { + retcode = p_write(fd, old, strlen(old)); + p_close(fd); + } + return retcode; +} diff --git a/src/fileops.h b/src/fileops.h index b0c5779e5..594eacbd0 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -179,4 +179,14 @@ extern int git_futils_find_global_file(git_buf *path, const char *filename); */ extern int git_futils_find_system_file(git_buf *path, const char *filename); + +/** + * Create a "fake" symlink (text file containing the target path). + * + * @param new symlink file to be created + * @param old original symlink target + * @return 0 on success, -1 on error + */ +extern int git_futils_fake_symlink(const char *new, const char *old); + #endif /* INCLUDE_fileops_h__ */ diff --git a/src/filter.c b/src/filter.c index 8fa3eb684..e9517a259 100644 --- a/src/filter.c +++ b/src/filter.c @@ -11,6 +11,7 @@ #include "filter.h" #include "repository.h" #include "git2/config.h" +#include "blob.h" /* Tweaked from Core Git. I wonder what we could use this for... */ void git_text_gather_stats(git_text_stats *stats, const git_buf *text) @@ -95,8 +96,9 @@ int git_filters_load(git_vector *filters, git_repository *repo, const char *path if (error < 0) return error; } else { - giterr_set(GITERR_INVALID, "Worktree filters are not implemented yet"); - return -1; + error = git_filter_add__crlf_to_workdir(filters, repo, path); + if (error < 0) + return error; } return (int)filters->length; @@ -163,3 +165,37 @@ 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 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); + 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 66e370aef..5b7a25b04 100644 --- a/src/filter.h +++ b/src/filter.h @@ -96,6 +96,9 @@ extern void git_filters_free(git_vector *filters); /* Strip CRLF, from Worktree to ODB */ extern int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path); +/* Add CRLF, from ODB to worktree */ +extern int git_filter_add__crlf_to_workdir(git_vector *filters, git_repository *repo, const char *path); + /* * PLAINTEXT API @@ -116,4 +119,16 @@ 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 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 + * @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); + #endif diff --git a/src/index.c b/src/index.c index b6b1b779e..a1042b723 100644 --- a/src/index.c +++ b/src/index.c @@ -986,12 +986,19 @@ int git_index_entry_stage(const git_index_entry *entry) return (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT; } +typedef struct read_tree_data { + git_index *index; + git_indexer_stats *stats; +} read_tree_data; + static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *data) { - git_index *index = data; + read_tree_data *rtd = data; git_index_entry *entry = NULL; git_buf path = GIT_BUF_INIT; + rtd->stats->total++; + if (git_tree_entry__is_tree(tentry)) return 0; @@ -1006,7 +1013,7 @@ static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *da entry->path = git_buf_detach(&path); git_buf_free(&path); - if (index_insert(index, entry, 0) < 0) { + if (index_insert(rtd->index, entry, 0) < 0) { index_entry_free(entry); return -1; } @@ -1014,9 +1021,16 @@ static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *da return 0; } -int git_index_read_tree(git_index *index, git_tree *tree) +int git_index_read_tree(git_index *index, git_tree *tree, git_indexer_stats *stats) { + git_indexer_stats dummy_stats; + read_tree_data rtd = {index, NULL}; + + if (!stats) stats = &dummy_stats; + stats->total = 0; + rtd.stats = stats; + git_index_clear(index); - return git_tree_walk(tree, read_tree_cb, GIT_TREEWALK_POST, index); + return git_tree_walk(tree, read_tree_cb, GIT_TREEWALK_POST, &rtd); } diff --git a/src/path.c b/src/path.c index e9bc4871c..22391c52b 100644 --- a/src/path.c +++ b/src/path.c @@ -387,6 +387,69 @@ bool git_path_isfile(const char *path) return S_ISREG(st.st_mode) != 0; } +#ifdef GIT_WIN32 + +bool git_path_is_empty_dir(const char *path) +{ + git_buf pathbuf = GIT_BUF_INIT; + HANDLE hFind = INVALID_HANDLE_VALUE; + wchar_t *wbuf; + WIN32_FIND_DATAW ffd; + bool retval = true; + + if (!git_path_isdir(path)) return false; + + git_buf_printf(&pathbuf, "%s\\*", path); + wbuf = gitwin_to_utf16(git_buf_cstr(&pathbuf)); + + hFind = FindFirstFileW(wbuf, &ffd); + if (INVALID_HANDLE_VALUE == hFind) { + giterr_set(GITERR_OS, "Couldn't open '%s'", path); + return false; + } + + do { + if (!git_path_is_dot_or_dotdotW(ffd.cFileName)) { + retval = false; + } + } while (FindNextFileW(hFind, &ffd) != 0); + + FindClose(hFind); + git_buf_free(&pathbuf); + git__free(wbuf); + return retval; +} + +#else + +bool git_path_is_empty_dir(const char *path) +{ + DIR *dir = NULL; + struct dirent *e; + bool retval = true; + + if (!git_path_isdir(path)) return false; + + dir = opendir(path); + if (!dir) { + giterr_set(GITERR_OS, "Couldn't open '%s'", path); + return false; + } + + while ((e = readdir(dir)) != NULL) { + if (!git_path_is_dot_or_dotdot(e->d_name)) { + giterr_set(GITERR_INVALID, + "'%s' exists and is not an empty directory", path); + retval = false; + break; + } + } + closedir(dir); + + return retval; +} +#endif + int git_path_lstat(const char *path, struct stat *st) { int err = 0; @@ -551,14 +614,6 @@ int git_path_cmp( return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; } -/* Taken from git.git */ -GIT_INLINE(int) is_dot_or_dotdot(const char *name) -{ - return (name[0] == '.' && - (name[1] == '\0' || - (name[1] == '.' && name[2] == '\0'))); -} - int git_path_direach( git_buf *path, int (*fn)(void *, git_buf *), @@ -587,7 +642,7 @@ int git_path_direach( while (p_readdir_r(dir, de_buf, &de) == 0 && de != NULL) { int result; - if (is_dot_or_dotdot(de->d_name)) + if (git_path_is_dot_or_dotdot(de->d_name)) continue; if (git_buf_puts(path, de->d_name) < 0) { @@ -646,7 +701,7 @@ int git_path_dirload( char *entry_path; size_t entry_len; - if (is_dot_or_dotdot(de->d_name)) + if (git_path_is_dot_or_dotdot(de->d_name)) continue; entry_len = strlen(de->d_name); diff --git a/src/path.h b/src/path.h index d611428c1..14618b2fc 100644 --- a/src/path.h +++ b/src/path.h @@ -80,7 +80,24 @@ extern int git_path_to_dir(git_buf *path); */ extern void git_path_string_to_dir(char* path, size_t size); +/** + * Taken from git.git; returns nonzero if the given path is "." or "..". + */ +GIT_INLINE(int) git_path_is_dot_or_dotdot(const char *name) +{ + return (name[0] == '.' && + (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0'))); +} + #ifdef GIT_WIN32 +GIT_INLINE(int) git_path_is_dot_or_dotdotW(const wchar_t *name) +{ + return (name[0] == L'.' && + (name[1] == L'\0' || + (name[1] == L'.' && name[2] == L'\0'))); +} + /** * Convert backslashes in path to forward slashes. */ @@ -130,6 +147,11 @@ extern bool git_path_isdir(const char *path); extern bool git_path_isfile(const char *path); /** + * Check if the given path is a directory, and is empty. + */ +extern bool git_path_is_empty_dir(const char *path); + +/** * Stat a file and/or link and set error if needed. */ extern int git_path_lstat(const char *path, struct stat *st); diff --git a/src/reset.c b/src/reset.c index 14f7a236a..1379f6442 100644 --- a/src/reset.c +++ b/src/reset.c @@ -80,7 +80,7 @@ int git_reset( goto cleanup; } - if (git_index_read_tree(index, tree) < 0) { + if (git_index_read_tree(index, tree, NULL) < 0) { giterr_set(GITERR_INDEX, "%s - Failed to update the index.", ERROR_MSG); goto cleanup; } diff --git a/src/unix/posix.h b/src/unix/posix.h index 83fd8a189..7a3a388ec 100644 --- a/src/unix/posix.h +++ b/src/unix/posix.h @@ -19,6 +19,7 @@ #define p_lstat(p,b) lstat(p,b) #define p_readlink(a, b, c) readlink(a, b, c) #define p_link(o,n) link(o, n) +#define p_symlink(o,n) symlink(o,n) #define p_unlink(p) unlink(p) #define p_mkdir(p,m) mkdir(p, m) #define p_fsync(fd) fsync(fd) diff --git a/src/win32/posix.h b/src/win32/posix.h index baa4a3b4e..14caae418 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -33,6 +33,7 @@ GIT_INLINE(int) p_mkdir(const char *path, mode_t mode) extern int p_unlink(const char *path); extern int p_lstat(const char *file_name, struct stat *buf); extern int p_readlink(const char *link, char *target, size_t target_len); +extern int p_symlink(const char *old, const char *new); extern int p_hide_directory__w32(const char *path); extern char *p_realpath(const char *orig_path, char *buffer); extern int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr); diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index e1471cab4..aa34ad3ac 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -7,6 +7,7 @@ #include "../posix.h" #include "path.h" #include "utf-conv.h" +#include "repository.h" #include <errno.h> #include <io.h> #include <fcntl.h> @@ -224,6 +225,14 @@ int p_readlink(const char *link, char *target, size_t target_len) return dwRet; } +int p_symlink(const char *old, const char *new) +{ + /* Real symlinks on NTFS require admin privileges. Until this changes, + * libgit2 just creates a text file with the link target in the contents. + */ + return git_futils_fake_symlink(old, new); +} + int p_open(const char *path, int flags, ...) { int fd; diff --git a/tests-clar/checkout/checkout.c b/tests-clar/checkout/checkout.c new file mode 100644 index 000000000..80e30bbc3 --- /dev/null +++ b/tests-clar/checkout/checkout.c @@ -0,0 +1,186 @@ +#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}; + + fd = p_open(path, O_RDONLY); + cl_assert(fd >= 0); + + cl_assert_equal_i(p_read(fd, buffer, 1024), strlen(expectedcontents)); + cl_assert_equal_s(expectedcontents, buffer); + cl_git_pass(p_close(fd)); +} + + +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"; + const char *expected_readme_text = +#ifdef GIT_WIN32 + "hey there\r\n"; +#else + "hey there\n"; +#endif + cl_git_mkfile("./testrepo/.gitattributes", attributes); + cl_git_pass(git_checkout_head(g_repo, NULL, NULL)); + test_file_contents("./testrepo/README", expected_readme_text); + test_file_contents("./testrepo/new.txt", "my new file\n"); + test_file_contents("./testrepo/branch_file.txt", "hi\r\nbye!\r\n"); +} + +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/clone/clone.c b/tests-clar/clone/clone.c new file mode 100644 index 000000000..4cca15ffe --- /dev/null +++ b/tests-clar/clone/clone.c @@ -0,0 +1,139 @@ +#include "clar_libgit2.h" + +#include "git2/clone.h" +#include "repository.h" + +#define DO_LOCAL_TEST 0 +#define DO_LIVE_NETWORK_TESTS 0 +#define LIVE_REPO_URL "http://github.com/libgit2/node-gitteh" + + +static git_repository *g_repo; + +void test_clone_clone__initialize(void) +{ + g_repo = NULL; +} + +void test_clone_clone__cleanup(void) +{ + if (g_repo) { + git_repository_free(g_repo); + g_repo = NULL; + } +} + +// TODO: This is copy/pasted from network/remotelocal.c. +static void build_local_file_url(git_buf *out, const char *fixture) +{ + const char *in_buf; + + git_buf path_buf = GIT_BUF_INIT; + + cl_git_pass(git_path_prettify_dir(&path_buf, fixture, NULL)); + cl_git_pass(git_buf_puts(out, "file://")); + +#ifdef GIT_WIN32 + /* + * A FILE uri matches the following format: file://[host]/path + * where "host" can be empty and "path" is an absolute path to the resource. + * + * In this test, no hostname is used, but we have to ensure the leading triple slashes: + * + * *nix: file:///usr/home/... + * Windows: file:///C:/Users/... + */ + cl_git_pass(git_buf_putc(out, '/')); +#endif + + in_buf = git_buf_cstr(&path_buf); + + /* + * A very hacky Url encoding that only takes care of escaping the spaces + */ + while (*in_buf) { + if (*in_buf == ' ') + cl_git_pass(git_buf_puts(out, "%20")); + else + cl_git_pass(git_buf_putc(out, *in_buf)); + + in_buf++; + } + + git_buf_free(&path_buf); +} + + +void test_clone_clone__bad_url(void) +{ + /* Clone should clean up the mess if the URL isn't a git repository */ + cl_git_fail(git_clone(&g_repo, "not_a_repo", "./foo", NULL, NULL, NULL)); + cl_assert(!git_path_exists("./foo")); + cl_git_fail(git_clone_bare(&g_repo, "not_a_repo", "./foo.git", NULL)); + cl_assert(!git_path_exists("./foo.git")); +} + + +void test_clone_clone__local(void) +{ + git_buf src = GIT_BUF_INIT; + build_local_file_url(&src, cl_fixture("testrepo.git")); + +#if DO_LOCAL_TEST + cl_git_pass(git_clone(&g_repo, git_buf_cstr(&src), "./local", NULL, NULL, NULL)); + git_repository_free(g_repo); + git_futils_rmdir_r("./local", GIT_DIRREMOVAL_FILES_AND_DIRS); + cl_git_pass(git_clone_bare(&g_repo, git_buf_cstr(&src), "./local.git", NULL)); + git_futils_rmdir_r("./local.git", GIT_DIRREMOVAL_FILES_AND_DIRS); +#endif + + git_buf_free(&src); +} + + +void test_clone_clone__network_full(void) +{ +#if DO_LIVE_NETWORK_TESTS + git_remote *origin; + + cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./test2", NULL, NULL, NULL)); + cl_assert(!git_repository_is_bare(g_repo)); + cl_git_pass(git_remote_load(&origin, g_repo, "origin")); + git_futils_rmdir_r("./test2", GIT_DIRREMOVAL_FILES_AND_DIRS); +#endif +} + +void test_clone_clone__network_bare(void) +{ +#if DO_LIVE_NETWORK_TESTS + git_remote *origin; + + cl_git_pass(git_clone_bare(&g_repo, LIVE_REPO_URL, "test", NULL)); + cl_assert(git_repository_is_bare(g_repo)); + cl_git_pass(git_remote_load(&origin, g_repo, "origin")); + git_futils_rmdir_r("./test", GIT_DIRREMOVAL_FILES_AND_DIRS); +#endif +} + + +void test_clone_clone__already_exists(void) +{ +#if DO_LIVE_NETWORK_TESTS + /* Should pass with existing-but-empty dir */ + p_mkdir("./foo", GIT_DIR_MODE); + cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", NULL, NULL, NULL)); + git_repository_free(g_repo); g_repo = NULL; + git_futils_rmdir_r("./foo", GIT_DIRREMOVAL_FILES_AND_DIRS); +#endif + + /* Should fail with a file */ + cl_git_mkfile("./foo", "Bar!"); + cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", NULL, NULL, NULL)); + git_futils_rmdir_r("./foo", GIT_DIRREMOVAL_FILES_AND_DIRS); + + /* Should fail with existing-and-nonempty dir */ + p_mkdir("./foo", GIT_DIR_MODE); + cl_git_mkfile("./foo/bar", "Baz!"); + cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", NULL, NULL, NULL)); + git_futils_rmdir_r("./foo", GIT_DIRREMOVAL_FILES_AND_DIRS); +} diff --git a/tests-clar/index/read_tree.c b/tests-clar/index/read_tree.c index c657d4f71..0479332dc 100644 --- a/tests-clar/index/read_tree.c +++ b/tests-clar/index/read_tree.c @@ -33,7 +33,7 @@ void test_index_read_tree__read_write_involution(void) /* read-tree */ git_tree_lookup(&tree, repo, &expected); - cl_git_pass(git_index_read_tree(index, tree)); + cl_git_pass(git_index_read_tree(index, tree, NULL)); git_tree_free(tree); cl_git_pass(git_tree_create_fromindex(&tree_oid, index)); diff --git a/tests-clar/refs/create.c b/tests-clar/refs/create.c index dde4c5745..2e42cb607 100644 --- a/tests-clar/refs/create.c +++ b/tests-clar/refs/create.c @@ -4,7 +4,7 @@ #include "git2/reflog.h" #include "reflog.h" -static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; +static const char *current_master_tip = "099fabac3a9ea935598528c27f866e34089c2eff"; static const char *current_head_target = "refs/heads/master"; static git_repository *g_repo; diff --git a/tests-clar/refs/list.c b/tests-clar/refs/list.c index 2a7b157ca..ac3cc0058 100644 --- a/tests-clar/refs/list.c +++ b/tests-clar/refs/list.c @@ -36,7 +36,7 @@ void test_refs_list__all(void) /* We have exactly 9 refs in total if we include the packed ones: * there is a reference that exists both in the packfile and as * loose, but we only list it once */ - cl_assert(ref_list.count == 9); + cl_assert_equal_i(ref_list.count, 10); git_strarray_free(&ref_list); } diff --git a/tests-clar/resources/testrepo/.gitted/config b/tests-clar/resources/testrepo/.gitted/config index 1a5aacdfa..d0114012f 100644 --- a/tests-clar/resources/testrepo/.gitted/config +++ b/tests-clar/resources/testrepo/.gitted/config @@ -1,7 +1,7 @@ [core] repositoryformatversion = 0 filemode = true - bare = true + bare = false logallrefupdates = true [remote "test"] url = git://github.com/libgit2/libgit2 diff --git a/tests-clar/resources/testrepo/.gitted/objects/09/9fabac3a9ea935598528c27f866e34089c2eff b/tests-clar/resources/testrepo/.gitted/objects/09/9fabac3a9ea935598528c27f866e34089c2eff new file mode 100644 index 000000000..c60c78fb5 --- /dev/null +++ b/tests-clar/resources/testrepo/.gitted/objects/09/9fabac3a9ea935598528c27f866e34089c2eff @@ -0,0 +1 @@ +xQ P9^@B!1F'J?#7KJhMVE,.3uVsH-;U,MPIɉ&ĔסKO.2µո$8Nݗr!lCTklUgf0sÓG(
\ No newline at end of file diff --git a/tests-clar/resources/testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e b/tests-clar/resources/testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e Binary files differnew file mode 100644 index 000000000..b7d944fa1 --- /dev/null +++ b/tests-clar/resources/testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e diff --git a/tests-clar/resources/testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925 b/tests-clar/resources/testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925 Binary files differnew file mode 100644 index 000000000..d37b93e4f --- /dev/null +++ b/tests-clar/resources/testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925 diff --git a/tests-clar/resources/testrepo/.gitted/objects/45/dd856fdd4d89b884c340ba0e047752d9b085d6 b/tests-clar/resources/testrepo/.gitted/objects/45/dd856fdd4d89b884c340ba0e047752d9b085d6 Binary files differnew file mode 100644 index 000000000..a83ed9763 --- /dev/null +++ b/tests-clar/resources/testrepo/.gitted/objects/45/dd856fdd4d89b884c340ba0e047752d9b085d6 diff --git a/tests-clar/resources/testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63 b/tests-clar/resources/testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63 Binary files differnew file mode 100644 index 000000000..e9150214b --- /dev/null +++ b/tests-clar/resources/testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63 diff --git a/tests-clar/resources/testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc b/tests-clar/resources/testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc Binary files differnew file mode 100644 index 000000000..b669961d8 --- /dev/null +++ b/tests-clar/resources/testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc diff --git a/tests-clar/resources/testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735 b/tests-clar/resources/testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735 Binary files differnew file mode 100644 index 000000000..9ff5eb2b5 --- /dev/null +++ b/tests-clar/resources/testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735 diff --git a/tests-clar/resources/testrepo/.gitted/objects/87/380ae84009e9c503506c2f6143a4fc6c60bf80 b/tests-clar/resources/testrepo/.gitted/objects/87/380ae84009e9c503506c2f6143a4fc6c60bf80 Binary files differnew file mode 100644 index 000000000..3042f5790 --- /dev/null +++ b/tests-clar/resources/testrepo/.gitted/objects/87/380ae84009e9c503506c2f6143a4fc6c60bf80 diff --git a/tests-clar/resources/testrepo/.gitted/objects/c0/528fd6cc988c0a40ce0be11bc192fc8dc5346e b/tests-clar/resources/testrepo/.gitted/objects/c0/528fd6cc988c0a40ce0be11bc192fc8dc5346e Binary files differnew file mode 100644 index 000000000..0401ab489 --- /dev/null +++ b/tests-clar/resources/testrepo/.gitted/objects/c0/528fd6cc988c0a40ce0be11bc192fc8dc5346e diff --git a/tests-clar/resources/testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9 b/tests-clar/resources/testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9 Binary files differnew file mode 100644 index 000000000..7620c514f --- /dev/null +++ b/tests-clar/resources/testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9 diff --git a/tests-clar/resources/testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83 b/tests-clar/resources/testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83 Binary files differnew file mode 100644 index 000000000..00940f0f2 --- /dev/null +++ b/tests-clar/resources/testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83 diff --git a/tests-clar/resources/testrepo/.gitted/refs/heads/dir b/tests-clar/resources/testrepo/.gitted/refs/heads/dir new file mode 100644 index 000000000..4567d37fa --- /dev/null +++ b/tests-clar/resources/testrepo/.gitted/refs/heads/dir @@ -0,0 +1 @@ +144344043ba4d4a405da03de3844aa829ae8be0e diff --git a/tests-clar/resources/testrepo/.gitted/refs/heads/master b/tests-clar/resources/testrepo/.gitted/refs/heads/master index 3d8f0a402..f31fe781b 100644 --- a/tests-clar/resources/testrepo/.gitted/refs/heads/master +++ b/tests-clar/resources/testrepo/.gitted/refs/heads/master @@ -1 +1 @@ -a65fedf39aefe402d3bb6e24df4d4f5fe4547750 +099fabac3a9ea935598528c27f866e34089c2eff diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index bfd257a3b..2abf36833 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -484,7 +484,7 @@ static void fill_index_wth_head_entries(git_repository *repo, git_index *index) cl_git_pass(git_commit_lookup(&commit, repo, &oid)); cl_git_pass(git_commit_tree(&tree, commit)); - cl_git_pass(git_index_read_tree(index, tree)); + cl_git_pass(git_index_read_tree(index, tree, NULL)); cl_git_pass(git_index_write(index)); git_tree_free(tree); |