diff options
21 files changed, 893 insertions, 16 deletions
diff --git a/include/git2.h b/include/git2.h index 2e33f9e4a..edb73e8a5 100644 --- a/include/git2.h +++ b/include/git2.h @@ -37,6 +37,7 @@ #include "git2/index.h" #include "git2/config.h" #include "git2/remote.h" +#include "git2/clone.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..313d52f76 --- /dev/null +++ b/include/git2/checkout.h @@ -0,0 +1,35 @@ +/* + * 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 + +/** + * Updates files in the working tree to match the version in the index. + * + * @param repo repository to check out (must be non-bare) + * @param stats pointer to structure that receives progress information (may be NULL) + * @return 0 on success, GIT_ERROR otherwise (use git_error_last for information about the error) + */ +GIT_EXTERN(int) git_checkout_force(git_repository *repo, 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..5468f09be --- /dev/null +++ b/include/git2/clone.h @@ -0,0 +1,48 @@ +/* + * 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" + + +/** + * @file git2/clone.h + * @brief Git cloning routines + * @defgroup git_clone Git cloning routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * TODO + * + * @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 stats pointer to structure that receives progress information (may be NULL) + * @return 0 on success, GIT_ERROR otherwise (use git_error_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 *stats); + +/** + * TODO + * + * @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 stats pointer to structure that receives progress information (may be NULL) + * @return 0 on success, GIT_ERROR otherwise (use git_error_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 *stats); + +/** @} */ +GIT_END_DECL +#endif diff --git a/src/checkout.c b/src/checkout.c new file mode 100644 index 000000000..8ba3cf536 --- /dev/null +++ b/src/checkout.c @@ -0,0 +1,157 @@ +/* + * 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 "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_repository *repo; + git_odb *odb; +} tree_walk_data; + + +static int blob_contents_to_link(git_repository *repo, git_buf *fnbuf, + const git_oid *id) +{ + int retcode = GIT_ERROR; + git_blob *blob; + + /* Get the link target */ + if (!(retcode = git_blob_lookup(&blob, repo, id))) { + git_buf linktarget = GIT_BUF_INIT; + if (!(retcode = git_blob__getbuf(&linktarget, blob))) { + /* Create the link */ + retcode = p_symlink(git_buf_cstr(&linktarget), + git_buf_cstr(fnbuf)); + } + git_buf_free(&linktarget); + git_blob_free(blob); + } + + return retcode; +} + + +static int blob_contents_to_file(git_repository *repo, git_buf *fnbuf, + const git_oid *id, int mode) +{ + int retcode = GIT_ERROR; + + git_buf filteredcontents = GIT_BUF_INIT; + if (!git_filter_blob_contents(&filteredcontents, repo, id, git_buf_cstr(fnbuf))) { + int fd = git_futils_creat_withpath(git_buf_cstr(fnbuf), + GIT_DIR_MODE, mode); + if (fd >= 0) { + if (!p_write(fd, git_buf_cstr(&filteredcontents), + git_buf_len(&filteredcontents))) + retcode = 0; + else + retcode = GIT_ERROR; + p_close(fd); + } + } + git_buf_free(&filteredcontents); + + return retcode; +} + +static int checkout_walker(const char *path, git_tree_entry *entry, void *payload) +{ + int retcode = 0; + tree_walk_data *data = (tree_walk_data*)payload; + int attr = git_tree_entry_attributes(entry); + + /* TODO: handle submodules */ + + switch(git_tree_entry_type(entry)) + { + case GIT_OBJ_TREE: + /* Nothing to do; the blob handling creates necessary directories. */ + break; + + case GIT_OBJ_BLOB: + { + git_buf fnbuf = GIT_BUF_INIT; + git_buf_join_n(&fnbuf, '/', 3, + git_repository_workdir(data->repo), + path, + git_tree_entry_name(entry)); + if (S_ISLNK(attr)) { + retcode = blob_contents_to_link(data->repo, &fnbuf, + git_tree_entry_id(entry)); + } else { + retcode = blob_contents_to_file(data->repo, &fnbuf, + git_tree_entry_id(entry), attr); + } + git_buf_free(&fnbuf); + } + break; + + default: + retcode = -1; + break; + } + + data->stats->processed++; + return retcode; +} + + +int git_checkout_force(git_repository *repo, git_indexer_stats *stats) +{ + int retcode = GIT_ERROR; + git_indexer_stats dummy_stats; + git_tree *tree; + tree_walk_data payload; + + assert(repo); + if (!stats) stats = &dummy_stats; + + if (git_repository_is_bare(repo)) { + giterr_set(GITERR_INVALID, "Checkout is not allowed for bare repositories"); + return GIT_ERROR; + } + + stats->total = stats->processed = 0; + payload.stats = stats; + payload.repo = repo; + if (git_repository_odb(&payload.odb, repo) < 0) return GIT_ERROR; + + /* TODO: stats->total is never calculated. */ + + if (!git_repository_head_tree(&tree, repo)) { + /* Checkout the files */ + if (!git_tree_walk(tree, checkout_walker, GIT_TREEWALK_POST, &payload)) { + retcode = 0; + } + git_tree_free(tree); + } + + git_odb_free(payload.odb); + return retcode; +} + + +GIT_END_DECL diff --git a/src/clone.c b/src/clone.c new file mode 100644 index 000000000..803338ebb --- /dev/null +++ b/src/clone.c @@ -0,0 +1,266 @@ +/* + * 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_oid branch_oid; + 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_oid, repo, name, head_obj, 0)) { + /* Set up tracking */ + git_config *cfg; + 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) && + !git_reference_set_target(head, git_buf_cstr(&targetbuf))) { + /* Read the tree into the index */ + git_commit *commit; + if (!git_commit_lookup(&commit, repo, target)) { + git_tree *tree; + if (!git_commit_tree(&tree, commit)) { + git_index *index; + if (!git_repository_index(&index, repo)) { + if (!git_index_read_tree(index, tree)) { + git_index_write(index); + retcode = 0; + } + git_index_free(index); + } + git_tree_free(tree); + } + git_commit_free(commit); + } + } + 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 *stats) +{ + int retcode = GIT_ERROR; + git_remote *origin = NULL; + git_off_t bytes = 0; + git_indexer_stats dummy_stats; + + if (!stats) 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, stats)) { + /* Create "origin/foo" branches for all remote branches */ + if (!git_remote_update_tips(origin, NULL)) { + /* 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; +} + + +/* TODO: p_opendir, p_closedir */ +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 *stats, + int is_bare) +{ + int retcode = GIT_ERROR; + git_repository *repo = NULL; + + 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, 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 *stats) +{ + assert(out && origin_url && dest_path); + return clone_internal(out, origin_url, dest_path, stats, 1); +} + + +int git_clone(git_repository **out, + const char *origin_url, + const char *workdir_path, + git_indexer_stats *stats) +{ + int retcode = GIT_ERROR; + + assert(out && origin_url && workdir_path); + + if (!(retcode = clone_internal(out, origin_url, workdir_path, stats, 0))) { + git_indexer_stats checkout_stats; + retcode = git_checkout_force(*out, &checkout_stats); + } + + return retcode; +} + + + + +GIT_END_DECL diff --git a/src/crlf.c b/src/crlf.c index 303a46d3b..888d86c36 100644 --- a/src/crlf.c +++ b/src/crlf.c @@ -184,7 +184,8 @@ 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 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; @@ -219,10 +220,25 @@ 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); } +static int crlf_apply_to_workdir(git_filter *self, git_buf *dest, const git_buf *source) +{ + /* TODO */ + return 0; +} + +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/filter.c b/src/filter.c index 8fa3eb684..ecdc809a4 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,34 @@ 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); + + 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/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 d68393b3d..5eecb7261 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/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 4e0150fb5..c0d66c7ff 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -217,6 +217,12 @@ int p_readlink(const char *link, char *target, size_t target_len) return dwRet; } +int p_symlink(const char *old, const char *new) +{ + /* TODO */ + return -1; +} + 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..9ad41d032 --- /dev/null +++ b/tests-clar/checkout/checkout.c @@ -0,0 +1,81 @@ +#include "clar_libgit2.h" + +#include "git2/checkout.h" +#include "repository.h" + +#define DO_LOCAL_TEST 0 +#define DO_LIVE_NETWORK_TESTS 1 +#define LIVE_REPO_URL "http://github.com/libgit2/node-gitteh" + + +static git_repository *g_repo; + +void test_checkout_checkout__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +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); + 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_force(g_repo, NULL)); +} + +void test_checkout_checkout__default(void) +{ + cl_git_pass(git_checkout_force(g_repo, 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" + "README text eol=cr\n" + "new.txt text eol=lf\n"; + cl_git_mkfile("./testrepo/.gitattributes", attributes); + cl_git_pass(git_checkout_force(g_repo, 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__stats(void) +{ + /* TODO */ +} + +void test_checkout_checkout__links(void) +{ + char link_data[1024]; + size_t link_size = 1024; + + cl_git_pass(git_checkout_force(g_repo, NULL)); + link_size = p_readlink("./testrepo/link_to_new.txt", link_data, link_size); + cl_assert_equal_i(link_size, strlen("new.txt")); + link_data[link_size] = '\0'; + cl_assert_equal_s(link_data, "new.txt"); + test_file_contents("./testrepo/link_to_new.txt", "my new file\n"); +} diff --git a/tests-clar/clone/clone.c b/tests-clar/clone/clone.c new file mode 100644 index 000000000..3fba91cac --- /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_LIVE_NETWORK_TESTS 0 +#define DO_LOCAL_TEST 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + git_futils_rmdir_r("./foo", GIT_DIRREMOVAL_FILES_AND_DIRS); +} 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/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/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/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 |
