mirror of
https://git.proxmox.com/git/libgit2
synced 2025-08-12 09:48:24 +00:00
Add index updating to checkout
Make checkout update entries in the index for all files that are updated and/or removed, unless flag GIT_CHECKOUT_DONT_UPDATE_INDEX is given. To do this, iterators were extended to allow a little more introspection into the index being iterated over, etc.
This commit is contained in:
parent
7e5c8a5b41
commit
5cf9875a4f
@ -140,8 +140,11 @@ typedef enum {
|
||||
/** Only update existing files, don't create new ones */
|
||||
GIT_CHECKOUT_UPDATE_ONLY = (1u << 7),
|
||||
|
||||
/** Normally checkout updates index entries as it goes; this stops that */
|
||||
GIT_CHECKOUT_DONT_UPDATE_INDEX = (1u << 8),
|
||||
|
||||
/** Don't refresh index/config/etc before doing checkout */
|
||||
GIT_CHECKOUT_NO_REFRESH = (1u << 8),
|
||||
GIT_CHECKOUT_NO_REFRESH = (1u << 9),
|
||||
|
||||
/**
|
||||
* THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED
|
||||
|
186
src/checkout.c
186
src/checkout.c
@ -194,6 +194,7 @@ typedef struct {
|
||||
bool opts_free_baseline;
|
||||
char *pfx;
|
||||
git_iterator *baseline;
|
||||
git_index *index;
|
||||
git_pool pool;
|
||||
git_vector removes;
|
||||
git_buf path;
|
||||
@ -261,16 +262,20 @@ static int checkout_notify(
|
||||
|
||||
static bool checkout_is_workdir_modified(
|
||||
checkout_data *data,
|
||||
const git_diff_file *wditem,
|
||||
const git_index_entry *baseitem)
|
||||
const git_diff_file *baseitem,
|
||||
const git_index_entry *wditem)
|
||||
{
|
||||
git_oid oid;
|
||||
|
||||
if (wditem->size != baseitem->file_size)
|
||||
/* depending on where base is coming from, we may or may not know
|
||||
* the actual size of the data, so we can't rely on this shortcut.
|
||||
*/
|
||||
if (baseitem->size && wditem->file_size != baseitem->size)
|
||||
return true;
|
||||
|
||||
if (git_diff__oid_for_file(
|
||||
data->repo, wditem->path, wditem->mode, wditem->size, &oid) < 0)
|
||||
data->repo, wditem->path, wditem->mode,
|
||||
wditem->file_size, &oid) < 0)
|
||||
return false;
|
||||
|
||||
return (git_oid_cmp(&baseitem->oid, &oid) != 0);
|
||||
@ -279,33 +284,6 @@ static bool checkout_is_workdir_modified(
|
||||
#define CHECKOUT_ACTION_IF(FLAG,YES,NO) \
|
||||
((data->strategy & GIT_CHECKOUT_##FLAG) ? CHECKOUT_ACTION__##YES : CHECKOUT_ACTION__##NO)
|
||||
|
||||
static const char *checkout_action_name_debug(int act)
|
||||
{
|
||||
if (act & CHECKOUT_ACTION__CONFLICT)
|
||||
return "CONFLICT";
|
||||
|
||||
if (act & CHECKOUT_ACTION__REMOVE) {
|
||||
if (act & CHECKOUT_ACTION__UPDATE_BLOB)
|
||||
return "REMOVE+UPDATE";
|
||||
if (act & CHECKOUT_ACTION__UPDATE_SUBMODULE)
|
||||
return "REMOVE+UPDATE SUB";
|
||||
return "REMOVE";
|
||||
}
|
||||
if (act & CHECKOUT_ACTION__DEFER_REMOVE) {
|
||||
if (act & CHECKOUT_ACTION__UPDATE_BLOB)
|
||||
return "UPDATE (WITH REMOVE)";
|
||||
if (act & CHECKOUT_ACTION__UPDATE_SUBMODULE)
|
||||
return "UPDATE SUB (WITH REMOVE)";
|
||||
return "DEFERRED REMOVE";
|
||||
}
|
||||
if (act & CHECKOUT_ACTION__UPDATE_BLOB)
|
||||
return "UPDATE";
|
||||
if (act & CHECKOUT_ACTION__UPDATE_SUBMODULE)
|
||||
return "UPDATE SUB";
|
||||
assert(act == 0);
|
||||
return "NONE";
|
||||
}
|
||||
|
||||
static int checkout_action_common(
|
||||
checkout_data *data,
|
||||
int action,
|
||||
@ -372,19 +350,26 @@ static int checkout_action_wd_only(
|
||||
const git_index_entry *wd,
|
||||
git_vector *pathspec)
|
||||
{
|
||||
bool ignored, remove;
|
||||
bool remove = false;
|
||||
git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE;
|
||||
const git_index_entry *entry;
|
||||
|
||||
if (!git_pathspec_match_path(
|
||||
pathspec, wd->path, false, workdir->ignore_case))
|
||||
return 0;
|
||||
|
||||
ignored = git_iterator_current_is_ignored(workdir);
|
||||
|
||||
if (ignored) {
|
||||
/* check if item is tracked in the index but not in the checkout diff */
|
||||
if (data->index != NULL &&
|
||||
(entry = git_index_get_bypath(data->index, wd->path, 0)) != NULL)
|
||||
{
|
||||
notify = GIT_CHECKOUT_NOTIFY_DIRTY;
|
||||
remove = ((data->strategy & GIT_CHECKOUT_FORCE) != 0);
|
||||
}
|
||||
else if (git_iterator_current_is_ignored(workdir)) {
|
||||
notify = GIT_CHECKOUT_NOTIFY_IGNORED;
|
||||
remove = ((data->strategy & GIT_CHECKOUT_REMOVE_IGNORED) != 0);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
notify = GIT_CHECKOUT_NOTIFY_UNTRACKED;
|
||||
remove = ((data->strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0);
|
||||
}
|
||||
@ -697,6 +682,7 @@ fail:
|
||||
}
|
||||
|
||||
static int buffer_to_file(
|
||||
struct stat *st,
|
||||
git_buf *buffer,
|
||||
const char *path,
|
||||
mode_t dir_mode,
|
||||
@ -717,6 +703,9 @@ static int buffer_to_file(
|
||||
giterr_set(GITERR_OS, "Could not write to '%s'", path);
|
||||
(void)p_close(fd);
|
||||
} else {
|
||||
if ((error = p_fstat(fd, st)) < 0)
|
||||
giterr_set(GITERR_OS, "Error while statting '%s'", path);
|
||||
|
||||
if ((error = p_close(fd)) < 0)
|
||||
giterr_set(GITERR_OS, "Error while closing '%s'", path);
|
||||
}
|
||||
@ -730,6 +719,7 @@ static int buffer_to_file(
|
||||
}
|
||||
|
||||
static int blob_content_to_file(
|
||||
struct stat *st,
|
||||
git_blob *blob,
|
||||
const char *path,
|
||||
mode_t entry_filemode,
|
||||
@ -772,7 +762,12 @@ static int blob_content_to_file(
|
||||
file_mode = entry_filemode;
|
||||
|
||||
error = buffer_to_file(
|
||||
&filtered, path, opts->dir_mode, opts->file_open_flags, file_mode);
|
||||
st, &filtered, path, opts->dir_mode, opts->file_open_flags, file_mode);
|
||||
|
||||
if (!error) {
|
||||
st->st_size = blob->odb_object->raw.len;
|
||||
st->st_mode = entry_filemode;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
git_filters_free(&filters);
|
||||
@ -784,7 +779,7 @@ cleanup:
|
||||
}
|
||||
|
||||
static int blob_content_to_link(
|
||||
git_blob *blob, const char *path, int can_symlink)
|
||||
struct stat *st, git_blob *blob, const char *path, int can_symlink)
|
||||
{
|
||||
git_buf linktarget = GIT_BUF_INIT;
|
||||
int error;
|
||||
@ -792,28 +787,57 @@ static int blob_content_to_link(
|
||||
if ((error = git_blob__getbuf(&linktarget, blob)) < 0)
|
||||
return error;
|
||||
|
||||
if (can_symlink)
|
||||
error = p_symlink(git_buf_cstr(&linktarget), path);
|
||||
else
|
||||
if (can_symlink) {
|
||||
if ((error = p_symlink(git_buf_cstr(&linktarget), path)) < 0)
|
||||
giterr_set(GITERR_CHECKOUT, "Could not create symlink %s\n", path);
|
||||
} else {
|
||||
error = git_futils_fake_symlink(git_buf_cstr(&linktarget), path);
|
||||
}
|
||||
|
||||
if (!error) {
|
||||
if ((error = p_lstat(path, st)) < 0)
|
||||
giterr_set(GITERR_CHECKOUT, "Could not stat symlink %s", path);
|
||||
|
||||
st->st_mode = GIT_FILEMODE_LINK;
|
||||
}
|
||||
|
||||
git_buf_free(&linktarget);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static int checkout_update_index(
|
||||
checkout_data *data,
|
||||
const git_diff_file *file,
|
||||
struct stat *st)
|
||||
{
|
||||
git_index_entry entry;
|
||||
|
||||
if (!data->index)
|
||||
return 0;
|
||||
|
||||
memset(&entry, 0, sizeof(entry));
|
||||
entry.path = (char *)file->path; /* cast to prevent warning */
|
||||
git_index_entry__init_from_stat(&entry, st);
|
||||
git_oid_cpy(&entry.oid, &file->oid);
|
||||
|
||||
return git_index_add(data->index, &entry);
|
||||
}
|
||||
|
||||
static int checkout_submodule(
|
||||
checkout_data *data,
|
||||
const git_diff_file *file)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
/* Until submodules are supported, UPDATE_ONLY means do nothing here */
|
||||
if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)
|
||||
return 0;
|
||||
|
||||
if (git_futils_mkdir(
|
||||
if ((error = git_futils_mkdir(
|
||||
file->path, git_repository_workdir(data->repo),
|
||||
data->opts.dir_mode, GIT_MKDIR_PATH) < 0)
|
||||
return -1;
|
||||
data->opts.dir_mode, GIT_MKDIR_PATH)) < 0)
|
||||
return error;
|
||||
|
||||
/* TODO: Support checkout_strategy options. Two circumstances:
|
||||
* 1 - submodule already checked out, but we need to move the HEAD
|
||||
@ -824,7 +848,26 @@ static int checkout_submodule(
|
||||
* command should probably be able to. Do we need a submodule callback?
|
||||
*/
|
||||
|
||||
return 0;
|
||||
/* update the index unless prevented */
|
||||
if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) {
|
||||
struct stat st;
|
||||
|
||||
git_buf_truncate(&data->path, data->workdir_len);
|
||||
if (git_buf_puts(&data->path, file->path) < 0)
|
||||
return -1;
|
||||
|
||||
if ((error = p_stat(git_buf_cstr(&data->path), &st)) < 0) {
|
||||
giterr_set(
|
||||
GITERR_CHECKOUT, "Could not stat submodule %s\n", file->path);
|
||||
return error;
|
||||
}
|
||||
|
||||
st.st_mode = GIT_FILEMODE_COMMIT;
|
||||
|
||||
error = checkout_update_index(data, file, &st);
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static void report_progress(
|
||||
@ -843,6 +886,7 @@ static int checkout_blob(
|
||||
{
|
||||
int error = 0;
|
||||
git_blob *blob;
|
||||
struct stat st;
|
||||
|
||||
git_buf_truncate(&data->path, data->workdir_len);
|
||||
if (git_buf_puts(&data->path, file->path) < 0)
|
||||
@ -853,10 +897,10 @@ static int checkout_blob(
|
||||
|
||||
if (S_ISLNK(file->mode))
|
||||
error = blob_content_to_link(
|
||||
blob, git_buf_cstr(&data->path), data->can_symlink);
|
||||
&st, 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);
|
||||
&st, blob, git_buf_cstr(&data->path), file->mode, &data->opts);
|
||||
|
||||
git_blob_free(blob);
|
||||
|
||||
@ -871,6 +915,10 @@ static int checkout_blob(
|
||||
error = 0;
|
||||
}
|
||||
|
||||
/* update the index unless prevented */
|
||||
if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0)
|
||||
error = checkout_update_index(data, file, &st);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
@ -896,6 +944,13 @@ static int checkout_remove_the_old(
|
||||
|
||||
data->completed_steps++;
|
||||
report_progress(data, delta->old_file.path);
|
||||
|
||||
if ((actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) == 0 &&
|
||||
(data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 &&
|
||||
data->index != NULL)
|
||||
{
|
||||
(void)git_index_remove(data->index, delta->old_file.path, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -906,6 +961,12 @@ static int checkout_remove_the_old(
|
||||
|
||||
data->completed_steps++;
|
||||
report_progress(data, str);
|
||||
|
||||
if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0 &&
|
||||
data->index != NULL)
|
||||
{
|
||||
(void)git_index_remove(data->index, str, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -926,6 +987,7 @@ static int checkout_deferred_remove(git_repository *repo, const char *path)
|
||||
#else
|
||||
GIT_UNUSED(repo);
|
||||
GIT_UNUSED(path);
|
||||
assert(false);
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
@ -1021,15 +1083,19 @@ static void checkout_data_clear(checkout_data *data)
|
||||
data->pfx = NULL;
|
||||
|
||||
git_buf_free(&data->path);
|
||||
|
||||
git_index_free(data->index);
|
||||
data->index = NULL;
|
||||
}
|
||||
|
||||
static int checkout_data_init(
|
||||
checkout_data *data,
|
||||
git_repository *repo,
|
||||
git_iterator *target,
|
||||
git_checkout_opts *proposed)
|
||||
{
|
||||
int error = 0;
|
||||
git_config *cfg;
|
||||
git_repository *repo = git_iterator_owner(target);
|
||||
|
||||
memset(data, 0, sizeof(*data));
|
||||
|
||||
@ -1054,8 +1120,24 @@ static int checkout_data_init(
|
||||
else
|
||||
memmove(&data->opts, proposed, sizeof(git_checkout_opts));
|
||||
|
||||
/* if you are forcing, definitely allow safe updates */
|
||||
/* refresh config and index content unless NO_REFRESH is given */
|
||||
if ((data->opts.checkout_strategy & GIT_CHECKOUT_NO_REFRESH) == 0) {
|
||||
if ((error = git_config_refresh(cfg)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
if (git_iterator_inner_type(target) == GIT_ITERATOR_INDEX) {
|
||||
/* if we are iterating over the index, don't reload */
|
||||
data->index = git_iterator_index_get_index(target);
|
||||
GIT_REFCOUNT_INC(data->index);
|
||||
} else {
|
||||
/* otherwise, grab and reload the index */
|
||||
if ((error = git_repository_index(&data->index, data->repo)) < 0 ||
|
||||
(error = git_index_read(data->index)) < 0)
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
/* if you are forcing, definitely allow safe updates */
|
||||
if ((data->opts.checkout_strategy & GIT_CHECKOUT_FORCE) != 0)
|
||||
data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE_CREATE;
|
||||
if ((data->opts.checkout_strategy & GIT_CHECKOUT_SAFE_CREATE) != 0)
|
||||
@ -1116,7 +1198,7 @@ int git_checkout_iterator(
|
||||
size_t *counts = NULL;
|
||||
|
||||
/* initialize structures and options */
|
||||
error = checkout_data_init(&data, git_iterator_owner(target), opts);
|
||||
error = checkout_data_init(&data, target, opts);
|
||||
if (error < 0)
|
||||
return error;
|
||||
|
||||
@ -1204,6 +1286,10 @@ cleanup:
|
||||
if (error == GIT_EUSER)
|
||||
giterr_clear();
|
||||
|
||||
if (!error && data.index != NULL &&
|
||||
(data.strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0)
|
||||
error = git_index_write(data.index);
|
||||
|
||||
git_diff_list_free(data.diff);
|
||||
git_iterator_free(workdir);
|
||||
git_iterator_free(data.baseline);
|
||||
|
@ -988,6 +988,33 @@ fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
git_index *git_iterator_index_get_index(git_iterator *iter)
|
||||
{
|
||||
if (iter->type == GIT_ITERATOR_SPOOLANDSORT)
|
||||
iter = ((spoolandsort_iterator *)iter)->wrapped;
|
||||
|
||||
if (iter->type == GIT_ITERATOR_INDEX)
|
||||
return ((index_iterator *)iter)->index;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
git_iterator_type_t git_iterator_inner_type(git_iterator *iter)
|
||||
{
|
||||
if (iter->type == GIT_ITERATOR_SPOOLANDSORT)
|
||||
iter = ((spoolandsort_iterator *)iter)->wrapped;
|
||||
|
||||
return iter->type;
|
||||
}
|
||||
|
||||
git_iterator *git_iterator_spoolandsort_inner_iterator(git_iterator *iter)
|
||||
{
|
||||
if (iter->type == GIT_ITERATOR_SPOOLANDSORT)
|
||||
return ((spoolandsort_iterator *)iter)->wrapped;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int git_iterator_current_tree_entry(
|
||||
git_iterator *iter, const git_tree_entry **tree_entry)
|
||||
{
|
||||
|
@ -193,4 +193,12 @@ extern int git_iterator_cmp(
|
||||
extern int git_iterator_current_workdir_path(
|
||||
git_iterator *iter, git_buf **path);
|
||||
|
||||
|
||||
extern git_index *git_iterator_index_get_index(git_iterator *iter);
|
||||
|
||||
extern git_iterator_type_t git_iterator_inner_type(git_iterator *iter);
|
||||
|
||||
extern git_iterator *git_iterator_spoolandsort_inner_iterator(
|
||||
git_iterator *iter);
|
||||
|
||||
#endif
|
||||
|
@ -50,28 +50,29 @@ void test_checkout_tree__can_checkout_a_subdirectory_from_a_commit(void)
|
||||
|
||||
void test_checkout_tree__can_checkout_and_remove_directory(void)
|
||||
{
|
||||
git_reference *head;
|
||||
cl_assert_equal_i(false, git_path_isdir("./testrepo/ab/"));
|
||||
|
||||
// Checkout brach "subtrees" and update HEAD, so that HEAD matches the current working tree
|
||||
/* Checkout brach "subtrees" and update HEAD, so that HEAD matches the
|
||||
* current working tree
|
||||
*/
|
||||
cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees"));
|
||||
cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
|
||||
cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD"));
|
||||
cl_git_pass(git_reference_symbolic_set_target(head, "refs/heads/subtrees"));
|
||||
git_reference_free(head);
|
||||
|
||||
|
||||
cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees"));
|
||||
|
||||
cl_assert_equal_i(true, git_path_isdir("./testrepo/ab/"));
|
||||
cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/2.txt"));
|
||||
cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/fgh/1.txt"));
|
||||
|
||||
// Checkout brach "master" and update HEAD, so that HEAD matches the current working tree
|
||||
/* Checkout brach "master" and update HEAD, so that HEAD matches the
|
||||
* current working tree
|
||||
*/
|
||||
cl_git_pass(git_revparse_single(&g_object, g_repo, "master"));
|
||||
cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
|
||||
cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD"));
|
||||
cl_git_pass(git_reference_symbolic_set_target(head, "refs/heads/master"));
|
||||
git_reference_free(head);
|
||||
|
||||
// This directory should no longer exist
|
||||
cl_git_pass(git_repository_set_head(g_repo, "refs/heads/master"));
|
||||
|
||||
/* This directory should no longer exist */
|
||||
cl_assert_equal_i(false, git_path_isdir("./testrepo/ab/"));
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user