diff options
Diffstat (limited to 'src/checkout.c')
-rw-r--r-- | src/checkout.c | 230 |
1 files changed, 230 insertions, 0 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 |