/* * 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 #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_filemode(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_filemode(entry); git_buf fnbuf = GIT_BUF_INIT; git_buf_join_n(&fnbuf, '/', 3, git_repository_workdir(data->repo), path, git_tree_entry_name(entry)); 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