mirror of
https://git.proxmox.com/git/libgit2
synced 2025-12-29 10:47:36 +00:00
This fixes some various warnings that showed up in Travis and a couple uses of uninitialized memory and one memory leak.
741 lines
18 KiB
C
741 lines
18 KiB
C
/*
|
|
* 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/blob.h"
|
|
#include "git2/config.h"
|
|
#include "git2/diff.h"
|
|
|
|
#include "common.h"
|
|
#include "refs.h"
|
|
#include "buffer.h"
|
|
#include "repository.h"
|
|
#include "filter.h"
|
|
#include "blob.h"
|
|
#include "diff.h"
|
|
#include "pathspec.h"
|
|
|
|
typedef struct {
|
|
git_repository *repo;
|
|
git_diff_list *diff;
|
|
git_checkout_opts *opts;
|
|
git_buf *path;
|
|
size_t workdir_len;
|
|
bool can_symlink;
|
|
int error;
|
|
size_t total_steps;
|
|
size_t completed_steps;
|
|
} checkout_diff_data;
|
|
|
|
static int buffer_to_file(
|
|
git_buf *buffer,
|
|
const char *path,
|
|
mode_t dir_mode,
|
|
int file_open_flags,
|
|
mode_t file_mode)
|
|
{
|
|
int fd, error;
|
|
|
|
if ((error = git_futils_mkpath2file(path, dir_mode)) < 0)
|
|
return error;
|
|
|
|
if ((fd = p_open(path, file_open_flags, file_mode)) < 0) {
|
|
giterr_set(GITERR_OS, "Could not open '%s' for writing", path);
|
|
return fd;
|
|
}
|
|
|
|
if ((error = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer))) < 0) {
|
|
giterr_set(GITERR_OS, "Could not write to '%s'", path);
|
|
(void)p_close(fd);
|
|
} else {
|
|
if ((error = p_close(fd)) < 0)
|
|
giterr_set(GITERR_OS, "Error while closing '%s'", path);
|
|
}
|
|
|
|
if (!error &&
|
|
(file_mode & 0100) != 0 &&
|
|
(error = p_chmod(path, file_mode)) < 0)
|
|
giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int blob_content_to_file(
|
|
git_blob *blob,
|
|
const char *path,
|
|
mode_t entry_filemode,
|
|
git_checkout_opts *opts)
|
|
{
|
|
int error = -1, nb_filters = 0;
|
|
mode_t file_mode = opts->file_mode;
|
|
bool dont_free_filtered = false;
|
|
git_buf unfiltered = GIT_BUF_INIT, filtered = GIT_BUF_INIT;
|
|
git_vector filters = GIT_VECTOR_INIT;
|
|
|
|
if (opts->disable_filters ||
|
|
(nb_filters = git_filters_load(
|
|
&filters,
|
|
git_object_owner((git_object *)blob),
|
|
path,
|
|
GIT_FILTER_TO_WORKTREE)) == 0) {
|
|
|
|
/* Create a fake git_buf from the blob raw data... */
|
|
filtered.ptr = blob->odb_object->raw.data;
|
|
filtered.size = blob->odb_object->raw.len;
|
|
|
|
/* ... and make sure it doesn't get unexpectedly freed */
|
|
dont_free_filtered = true;
|
|
}
|
|
|
|
if (nb_filters < 0)
|
|
return nb_filters;
|
|
|
|
if (nb_filters > 0) {
|
|
if ((error = git_blob__getbuf(&unfiltered, blob)) < 0)
|
|
goto cleanup;
|
|
|
|
if ((error = git_filters_apply(&filtered, &unfiltered, &filters)) < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Allow overriding of file mode */
|
|
if (!file_mode)
|
|
file_mode = entry_filemode;
|
|
|
|
error = buffer_to_file(
|
|
&filtered, path, opts->dir_mode, opts->file_open_flags, file_mode);
|
|
|
|
cleanup:
|
|
git_filters_free(&filters);
|
|
git_buf_free(&unfiltered);
|
|
if (!dont_free_filtered)
|
|
git_buf_free(&filtered);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int blob_content_to_link(
|
|
git_blob *blob, const char *path, bool can_symlink)
|
|
{
|
|
git_buf linktarget = GIT_BUF_INIT;
|
|
int error;
|
|
|
|
if ((error = git_blob__getbuf(&linktarget, blob)) < 0)
|
|
return error;
|
|
|
|
if (can_symlink)
|
|
error = p_symlink(git_buf_cstr(&linktarget), path);
|
|
else
|
|
error = git_futils_fake_symlink(git_buf_cstr(&linktarget), path);
|
|
|
|
git_buf_free(&linktarget);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int checkout_submodule(
|
|
checkout_diff_data *data,
|
|
const git_diff_file *file)
|
|
{
|
|
/* Until submodules are supported, UPDATE_ONLY means do nothing here */
|
|
if ((data->opts->checkout_strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)
|
|
return 0;
|
|
|
|
if (git_futils_mkdir(
|
|
file->path, git_repository_workdir(data->repo),
|
|
data->opts->dir_mode, GIT_MKDIR_PATH) < 0)
|
|
return -1;
|
|
|
|
/* TODO: Support checkout_strategy options. Two circumstances:
|
|
* 1 - submodule already checked out, but we need to move the HEAD
|
|
* to the new OID, or
|
|
* 2 - submodule not checked out and we should recursively check it out
|
|
*
|
|
* Checkout will not execute a pull on the submodule, but a clone
|
|
* command should probably be able to. Do we need a submodule callback?
|
|
*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void report_progress(
|
|
checkout_diff_data *data,
|
|
const char *path)
|
|
{
|
|
if (data->opts->progress_cb)
|
|
data->opts->progress_cb(
|
|
path, data->completed_steps, data->total_steps,
|
|
data->opts->progress_payload);
|
|
}
|
|
|
|
static int checkout_blob(
|
|
checkout_diff_data *data,
|
|
const git_diff_file *file)
|
|
{
|
|
int error = 0;
|
|
git_blob *blob;
|
|
|
|
git_buf_truncate(data->path, data->workdir_len);
|
|
if (git_buf_puts(data->path, file->path) < 0)
|
|
return -1;
|
|
|
|
if ((error = git_blob_lookup(&blob, data->repo, &file->oid)) < 0)
|
|
return error;
|
|
|
|
if (S_ISLNK(file->mode))
|
|
error = blob_content_to_link(
|
|
blob, git_buf_cstr(data->path), data->can_symlink);
|
|
else
|
|
error = blob_content_to_file(
|
|
blob, git_buf_cstr(data->path), file->mode, data->opts);
|
|
|
|
git_blob_free(blob);
|
|
|
|
return error;
|
|
}
|
|
|
|
static int retrieve_symlink_caps(git_repository *repo, bool *can_symlink)
|
|
{
|
|
git_config *cfg;
|
|
int error;
|
|
|
|
if (git_repository_config__weakptr(&cfg, repo) < 0)
|
|
return -1;
|
|
|
|
error = git_config_get_bool((int *)can_symlink, cfg, "core.symlinks");
|
|
|
|
/* If "core.symlinks" is not found anywhere, default to true. */
|
|
if (error == GIT_ENOTFOUND) {
|
|
*can_symlink = true;
|
|
error = 0;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
static void normalize_options(
|
|
git_checkout_opts *normalized, git_checkout_opts *proposed)
|
|
{
|
|
assert(normalized);
|
|
|
|
if (!proposed)
|
|
memset(normalized, 0, sizeof(git_checkout_opts));
|
|
else
|
|
memmove(normalized, proposed, sizeof(git_checkout_opts));
|
|
|
|
/* implied checkout strategies */
|
|
if ((normalized->checkout_strategy & GIT_CHECKOUT_UPDATE_MODIFIED) != 0 ||
|
|
(normalized->checkout_strategy & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0)
|
|
normalized->checkout_strategy |= GIT_CHECKOUT_UPDATE_UNMODIFIED;
|
|
|
|
if ((normalized->checkout_strategy & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0)
|
|
normalized->checkout_strategy |= GIT_CHECKOUT_UPDATE_MISSING;
|
|
|
|
/* opts->disable_filters is false by default */
|
|
|
|
if (!normalized->dir_mode)
|
|
normalized->dir_mode = GIT_DIR_MODE;
|
|
|
|
if (!normalized->file_open_flags)
|
|
normalized->file_open_flags = O_CREAT | O_TRUNC | O_WRONLY;
|
|
}
|
|
|
|
enum {
|
|
CHECKOUT_ACTION__NONE = 0,
|
|
CHECKOUT_ACTION__REMOVE = 1,
|
|
CHECKOUT_ACTION__UPDATE_BLOB = 2,
|
|
CHECKOUT_ACTION__UPDATE_SUBMODULE = 4,
|
|
CHECKOUT_ACTION__CONFLICT = 8,
|
|
CHECKOUT_ACTION__MAX = 8
|
|
};
|
|
|
|
static int checkout_confirm_update_blob(
|
|
checkout_diff_data *data,
|
|
const git_diff_delta *delta,
|
|
int action)
|
|
{
|
|
int error;
|
|
unsigned int strat = data->opts->checkout_strategy;
|
|
struct stat st;
|
|
bool update_only = ((strat & GIT_CHECKOUT_UPDATE_ONLY) != 0);
|
|
|
|
/* for typechange, remove the old item first */
|
|
if (delta->status == GIT_DELTA_TYPECHANGE) {
|
|
if (update_only)
|
|
action = CHECKOUT_ACTION__NONE;
|
|
else
|
|
action |= CHECKOUT_ACTION__REMOVE;
|
|
|
|
return action;
|
|
}
|
|
|
|
git_buf_truncate(data->path, data->workdir_len);
|
|
if (git_buf_puts(data->path, delta->new_file.path) < 0)
|
|
return -1;
|
|
|
|
if ((error = p_stat(git_buf_cstr(data->path), &st)) < 0) {
|
|
if (errno == ENOENT) {
|
|
if (update_only)
|
|
action = CHECKOUT_ACTION__NONE;
|
|
} else if (errno == ENOTDIR) {
|
|
/* File exists where a parent dir needs to go - i.e. untracked
|
|
* typechange. Ignore if UPDATE_ONLY, remove if allowed.
|
|
*/
|
|
if (update_only)
|
|
action = CHECKOUT_ACTION__NONE;
|
|
else if ((strat & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0)
|
|
action |= CHECKOUT_ACTION__REMOVE;
|
|
else
|
|
action = CHECKOUT_ACTION__CONFLICT;
|
|
}
|
|
/* otherwise let error happen when we attempt blob checkout later */
|
|
}
|
|
else if (S_ISDIR(st.st_mode)) {
|
|
/* Directory exists where a blob needs to go - i.e. untracked
|
|
* typechange. Ignore if UPDATE_ONLY, remove if allowed.
|
|
*/
|
|
if (update_only)
|
|
action = CHECKOUT_ACTION__NONE;
|
|
else if ((strat & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0)
|
|
action |= CHECKOUT_ACTION__REMOVE;
|
|
else
|
|
action = CHECKOUT_ACTION__CONFLICT;
|
|
}
|
|
|
|
return action;
|
|
}
|
|
|
|
static int checkout_action_for_delta(
|
|
checkout_diff_data *data,
|
|
const git_diff_delta *delta,
|
|
const git_index_entry *head_entry)
|
|
{
|
|
int action = CHECKOUT_ACTION__NONE;
|
|
unsigned int strat = data->opts->checkout_strategy;
|
|
|
|
switch (delta->status) {
|
|
case GIT_DELTA_UNMODIFIED:
|
|
if (!head_entry) {
|
|
/* file independently created in wd, even though not in HEAD */
|
|
if ((strat & GIT_CHECKOUT_UPDATE_MISSING) == 0)
|
|
action = CHECKOUT_ACTION__CONFLICT;
|
|
}
|
|
else if (!git_oid_equal(&head_entry->oid, &delta->old_file.oid)) {
|
|
/* working directory was independently updated to match index */
|
|
if ((strat & GIT_CHECKOUT_UPDATE_MODIFIED) == 0)
|
|
action = CHECKOUT_ACTION__CONFLICT;
|
|
}
|
|
break;
|
|
|
|
case GIT_DELTA_ADDED:
|
|
/* Impossible. New files should be UNTRACKED or TYPECHANGE */
|
|
action = CHECKOUT_ACTION__CONFLICT;
|
|
break;
|
|
|
|
case GIT_DELTA_DELETED:
|
|
if (head_entry && /* working dir missing, but exists in HEAD */
|
|
(strat & GIT_CHECKOUT_UPDATE_MISSING) == 0)
|
|
action = CHECKOUT_ACTION__CONFLICT;
|
|
else
|
|
action = CHECKOUT_ACTION__UPDATE_BLOB;
|
|
break;
|
|
|
|
case GIT_DELTA_MODIFIED:
|
|
case GIT_DELTA_TYPECHANGE:
|
|
if (!head_entry) {
|
|
/* working dir was independently updated & does not match index */
|
|
if ((strat & GIT_CHECKOUT_UPDATE_UNTRACKED) == 0)
|
|
action = CHECKOUT_ACTION__CONFLICT;
|
|
else
|
|
action = CHECKOUT_ACTION__UPDATE_BLOB;
|
|
}
|
|
else if (git_oid_equal(&head_entry->oid, &delta->new_file.oid))
|
|
action = CHECKOUT_ACTION__UPDATE_BLOB;
|
|
else if ((strat & GIT_CHECKOUT_UPDATE_MODIFIED) == 0)
|
|
action = CHECKOUT_ACTION__CONFLICT;
|
|
else
|
|
action = CHECKOUT_ACTION__UPDATE_BLOB;
|
|
break;
|
|
|
|
case GIT_DELTA_UNTRACKED:
|
|
if (!head_entry) {
|
|
if ((strat & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0)
|
|
action = CHECKOUT_ACTION__REMOVE;
|
|
}
|
|
else if ((strat & GIT_CHECKOUT_UPDATE_MODIFIED) != 0) {
|
|
action = CHECKOUT_ACTION__REMOVE;
|
|
} else if ((strat & GIT_CHECKOUT_UPDATE_UNMODIFIED) != 0) {
|
|
git_oid wd_oid;
|
|
|
|
/* if HEAD matches workdir, then remove, else conflict */
|
|
|
|
if (git_oid_iszero(&delta->new_file.oid) &&
|
|
git_diff__oid_for_file(
|
|
data->repo, delta->new_file.path, delta->new_file.mode,
|
|
delta->new_file.size, &wd_oid) < 0)
|
|
action = -1;
|
|
else if (git_oid_equal(&head_entry->oid, &wd_oid))
|
|
action = CHECKOUT_ACTION__REMOVE;
|
|
else
|
|
action = CHECKOUT_ACTION__CONFLICT;
|
|
} else {
|
|
/* present in HEAD and workdir, but absent in index */
|
|
action = CHECKOUT_ACTION__CONFLICT;
|
|
}
|
|
break;
|
|
|
|
case GIT_DELTA_IGNORED:
|
|
default:
|
|
/* just skip these files */
|
|
break;
|
|
}
|
|
|
|
if (action > 0 && (action & CHECKOUT_ACTION__UPDATE_BLOB) != 0) {
|
|
if (S_ISGITLINK(delta->old_file.mode))
|
|
action = (action & ~CHECKOUT_ACTION__UPDATE_BLOB) |
|
|
CHECKOUT_ACTION__UPDATE_SUBMODULE;
|
|
|
|
action = checkout_confirm_update_blob(data, delta, action);
|
|
}
|
|
|
|
if (action == CHECKOUT_ACTION__CONFLICT &&
|
|
data->opts->conflict_cb != NULL &&
|
|
data->opts->conflict_cb(
|
|
delta->old_file.path, &delta->old_file.oid,
|
|
delta->old_file.mode, delta->new_file.mode,
|
|
data->opts->conflict_payload) != 0)
|
|
{
|
|
giterr_clear();
|
|
action = GIT_EUSER;
|
|
}
|
|
|
|
if (action > 0 && (strat & GIT_CHECKOUT_UPDATE_ONLY) != 0)
|
|
action = (action & ~CHECKOUT_ACTION__REMOVE);
|
|
|
|
return action;
|
|
}
|
|
|
|
static int checkout_get_actions(
|
|
uint32_t **actions_ptr,
|
|
size_t **counts_ptr,
|
|
checkout_diff_data *data)
|
|
{
|
|
int error;
|
|
git_diff_list *diff = data->diff;
|
|
git_diff_delta *delta;
|
|
size_t i, *counts = NULL;
|
|
uint32_t *actions = NULL;
|
|
git_tree *head = NULL;
|
|
git_iterator *hiter = NULL;
|
|
char *pfx = git_pathspec_prefix(&data->opts->paths);
|
|
const git_index_entry *he;
|
|
|
|
/* if there is no HEAD, that's okay - we'll make an empty iterator */
|
|
(void)git_repository_head_tree(&head, data->repo);
|
|
|
|
if ((error = git_iterator_for_tree_range(
|
|
&hiter, data->repo, head, pfx, pfx)) < 0)
|
|
goto fail;
|
|
|
|
if ((diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 &&
|
|
!hiter->ignore_case &&
|
|
(error = git_iterator_spoolandsort(
|
|
&hiter, hiter, diff->entrycomp, true)) < 0)
|
|
goto fail;
|
|
|
|
if ((error = git_iterator_current(hiter, &he)) < 0)
|
|
goto fail;
|
|
|
|
git__free(pfx);
|
|
pfx = NULL;
|
|
|
|
*counts_ptr = counts = git__calloc(CHECKOUT_ACTION__MAX+1, sizeof(size_t));
|
|
*actions_ptr = actions = git__calloc(diff->deltas.length, sizeof(uint32_t));
|
|
if (!counts || !actions) {
|
|
error = -1;
|
|
goto fail;
|
|
}
|
|
|
|
git_vector_foreach(&diff->deltas, i, delta) {
|
|
int cmp = -1, act;
|
|
|
|
/* try to track HEAD entries parallel to deltas */
|
|
while (he) {
|
|
cmp = S_ISDIR(delta->new_file.mode) ?
|
|
diff->pfxcomp(he->path, delta->new_file.path) :
|
|
diff->strcomp(he->path, delta->old_file.path);
|
|
if (cmp >= 0)
|
|
break;
|
|
if (git_iterator_advance(hiter, &he) < 0)
|
|
he = NULL;
|
|
}
|
|
|
|
act = checkout_action_for_delta(data, delta, !cmp ? he : NULL);
|
|
|
|
if (act < 0) {
|
|
error = act;
|
|
goto fail;
|
|
}
|
|
|
|
if (!cmp && git_iterator_advance(hiter, &he) < 0)
|
|
he = NULL;
|
|
|
|
actions[i] = act;
|
|
|
|
if (act & CHECKOUT_ACTION__REMOVE)
|
|
counts[CHECKOUT_ACTION__REMOVE]++;
|
|
if (act & CHECKOUT_ACTION__UPDATE_BLOB)
|
|
counts[CHECKOUT_ACTION__UPDATE_BLOB]++;
|
|
if (act & CHECKOUT_ACTION__UPDATE_SUBMODULE)
|
|
counts[CHECKOUT_ACTION__UPDATE_SUBMODULE]++;
|
|
if (act & CHECKOUT_ACTION__CONFLICT)
|
|
counts[CHECKOUT_ACTION__CONFLICT]++;
|
|
}
|
|
|
|
if (counts[CHECKOUT_ACTION__CONFLICT] > 0 &&
|
|
(data->opts->checkout_strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) == 0)
|
|
{
|
|
giterr_set(GITERR_CHECKOUT, "%d conflicts prevent checkout",
|
|
(int)counts[CHECKOUT_ACTION__CONFLICT]);
|
|
goto fail;
|
|
}
|
|
|
|
git_iterator_free(hiter);
|
|
git_tree_free(head);
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
*counts_ptr = NULL;
|
|
git__free(counts);
|
|
*actions_ptr = NULL;
|
|
git__free(actions);
|
|
|
|
git_iterator_free(hiter);
|
|
git_tree_free(head);
|
|
git__free(pfx);
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int checkout_remove_the_old(
|
|
git_diff_list *diff,
|
|
unsigned int *actions,
|
|
checkout_diff_data *data)
|
|
{
|
|
git_diff_delta *delta;
|
|
size_t i;
|
|
|
|
git_buf_truncate(data->path, data->workdir_len);
|
|
|
|
git_vector_foreach(&diff->deltas, i, delta) {
|
|
if (actions[i] & CHECKOUT_ACTION__REMOVE) {
|
|
int error = git_futils_rmdir_r(
|
|
delta->new_file.path,
|
|
git_buf_cstr(data->path), /* here set to work dir root */
|
|
GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_EMPTY_PARENTS |
|
|
GIT_RMDIR_REMOVE_BLOCKERS);
|
|
if (error < 0)
|
|
return error;
|
|
|
|
data->completed_steps++;
|
|
report_progress(data, delta->new_file.path);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int checkout_create_the_new(
|
|
git_diff_list *diff,
|
|
unsigned int *actions,
|
|
checkout_diff_data *data)
|
|
{
|
|
git_diff_delta *delta;
|
|
size_t i;
|
|
|
|
git_vector_foreach(&diff->deltas, i, delta) {
|
|
if (actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) {
|
|
int error = checkout_blob(data, &delta->old_file);
|
|
if (error < 0)
|
|
return error;
|
|
|
|
data->completed_steps++;
|
|
report_progress(data, delta->old_file.path);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int checkout_create_submodules(
|
|
git_diff_list *diff,
|
|
unsigned int *actions,
|
|
checkout_diff_data *data)
|
|
{
|
|
git_diff_delta *delta;
|
|
size_t i;
|
|
|
|
git_vector_foreach(&diff->deltas, i, delta) {
|
|
if (actions[i] & CHECKOUT_ACTION__UPDATE_SUBMODULE) {
|
|
int error = checkout_submodule(data, &delta->old_file);
|
|
if (error < 0)
|
|
return error;
|
|
|
|
data->completed_steps++;
|
|
report_progress(data, delta->old_file.path);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int git_checkout_index(
|
|
git_repository *repo,
|
|
git_checkout_opts *opts)
|
|
{
|
|
git_diff_list *diff = NULL;
|
|
git_diff_options diff_opts = {0};
|
|
git_checkout_opts checkout_opts;
|
|
|
|
checkout_diff_data data;
|
|
git_buf workdir = GIT_BUF_INIT;
|
|
uint32_t *actions = NULL;
|
|
size_t *counts = NULL;
|
|
int error;
|
|
|
|
assert(repo);
|
|
|
|
if ((error = git_repository__ensure_not_bare(repo, "checkout")) < 0)
|
|
return error;
|
|
|
|
diff_opts.flags =
|
|
GIT_DIFF_INCLUDE_UNMODIFIED | GIT_DIFF_INCLUDE_UNTRACKED |
|
|
GIT_DIFF_INCLUDE_TYPECHANGE | GIT_DIFF_SKIP_BINARY_CHECK;
|
|
|
|
if (opts && opts->paths.count > 0)
|
|
diff_opts.pathspec = opts->paths;
|
|
|
|
if ((error = git_diff_workdir_to_index(repo, &diff_opts, &diff)) < 0)
|
|
goto cleanup;
|
|
|
|
if ((error = git_buf_puts(&workdir, git_repository_workdir(repo))) < 0)
|
|
goto cleanup;
|
|
|
|
normalize_options(&checkout_opts, opts);
|
|
|
|
/* Checkout is best performed with up to four passes through the diff.
|
|
*
|
|
* 0. Figure out what actions should be taken and record for later.
|
|
* 1. Next do removes, because we iterate in alphabetical order, thus
|
|
* a new untracked directory will end up sorted *after* a blob that
|
|
* should be checked out with the same name.
|
|
* 2. Then checkout all blobs.
|
|
* 3. Then checkout all submodules in case a new .gitmodules blob was
|
|
* checked out during pass #2.
|
|
*/
|
|
|
|
memset(&data, 0, sizeof(data));
|
|
data.path = &workdir;
|
|
data.workdir_len = git_buf_len(&workdir);
|
|
data.repo = repo;
|
|
data.diff = diff;
|
|
data.opts = &checkout_opts;
|
|
|
|
if ((error = checkout_get_actions(&actions, &counts, &data)) < 0)
|
|
goto cleanup;
|
|
|
|
data.total_steps = counts[CHECKOUT_ACTION__REMOVE] +
|
|
counts[CHECKOUT_ACTION__UPDATE_BLOB] +
|
|
counts[CHECKOUT_ACTION__UPDATE_SUBMODULE];
|
|
|
|
if ((error = retrieve_symlink_caps(repo, &data.can_symlink)) < 0)
|
|
goto cleanup;
|
|
|
|
report_progress(&data, NULL); /* establish 0 baseline */
|
|
|
|
if (counts[CHECKOUT_ACTION__REMOVE] > 0 &&
|
|
(error = checkout_remove_the_old(diff, actions, &data)) < 0)
|
|
goto cleanup;
|
|
|
|
if (counts[CHECKOUT_ACTION__UPDATE_BLOB] > 0 &&
|
|
(error = checkout_create_the_new(diff, actions, &data)) < 0)
|
|
goto cleanup;
|
|
|
|
if (counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] > 0 &&
|
|
(error = checkout_create_submodules(diff, actions, &data)) < 0)
|
|
goto cleanup;
|
|
|
|
assert(data.completed_steps == data.total_steps);
|
|
|
|
cleanup:
|
|
if (error == GIT_EUSER)
|
|
giterr_clear();
|
|
|
|
git__free(actions);
|
|
git__free(counts);
|
|
git_diff_list_free(diff);
|
|
git_buf_free(&workdir);
|
|
|
|
return error;
|
|
}
|
|
|
|
int git_checkout_tree(
|
|
git_repository *repo,
|
|
git_object *treeish,
|
|
git_checkout_opts *opts)
|
|
{
|
|
int error = 0;
|
|
git_index *index = NULL;
|
|
git_tree *tree = NULL;
|
|
|
|
assert(repo && treeish);
|
|
|
|
if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) {
|
|
giterr_set(
|
|
GITERR_CHECKOUT, "Provided object cannot be peeled to a tree");
|
|
return -1;
|
|
}
|
|
|
|
/* load paths in tree that match pathspec into index */
|
|
if (!(error = git_repository_index(&index, repo)) &&
|
|
!(error = git_index_read_tree_match(
|
|
index, tree, opts ? &opts->paths : NULL)) &&
|
|
!(error = git_index_write(index)))
|
|
error = git_checkout_index(repo, opts);
|
|
|
|
git_index_free(index);
|
|
git_tree_free(tree);
|
|
|
|
return error;
|
|
}
|
|
|
|
int git_checkout_head(
|
|
git_repository *repo,
|
|
git_checkout_opts *opts)
|
|
{
|
|
int error;
|
|
git_reference *head = NULL;
|
|
git_object *tree = NULL;
|
|
|
|
assert(repo);
|
|
|
|
if (!(error = git_repository_head(&head, repo)) &&
|
|
!(error = git_reference_peel(&tree, head, GIT_OBJ_TREE)))
|
|
error = git_checkout_tree(repo, tree, opts);
|
|
|
|
git_reference_free(head);
|
|
git_object_free(tree);
|
|
|
|
return error;
|
|
}
|