summaryrefslogtreecommitdiff
path: root/src/checkout.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/checkout.c')
-rw-r--r--src/checkout.c230
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