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 /src | |
parent | 1a10fded40875f986164b80c6efd414cd1507cb8 (diff) | |
parent | eb87800ab631d19a7655f01ece130455b1cc976a (diff) | |
download | libgit2-f98c32f3fea0d5532db2d5733418aa62648b9e93.tar.gz |
Merge pull request #778 from ben/clone
Clone
Diffstat (limited to 'src')
-rw-r--r-- | src/checkout.c | 230 | ||||
-rw-r--r-- | src/clone.c | 254 | ||||
-rw-r--r-- | src/crlf.c | 96 | ||||
-rw-r--r-- | src/fileops.c | 11 | ||||
-rw-r--r-- | src/fileops.h | 10 | ||||
-rw-r--r-- | src/filter.c | 40 | ||||
-rw-r--r-- | src/filter.h | 15 | ||||
-rw-r--r-- | src/index.c | 22 | ||||
-rw-r--r-- | src/path.c | 75 | ||||
-rw-r--r-- | src/path.h | 22 | ||||
-rw-r--r-- | src/reset.c | 2 | ||||
-rw-r--r-- | src/unix/posix.h | 1 | ||||
-rw-r--r-- | src/win32/posix.h | 1 | ||||
-rw-r--r-- | src/win32/posix_w32.c | 9 |
14 files changed, 767 insertions, 21 deletions
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; |