diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/checkout.c | 466 | ||||
| -rw-r--r-- | src/fileops.c | 48 | ||||
| -rw-r--r-- | src/fileops.h | 2 | ||||
| -rw-r--r-- | src/index.c | 53 | ||||
| -rw-r--r-- | src/index.h | 3 | ||||
| -rw-r--r-- | src/path.c | 5 | ||||
| -rw-r--r-- | src/reset.c | 5 | ||||
| -rw-r--r-- | src/stash.c | 4 |
8 files changed, 423 insertions, 163 deletions
diff --git a/src/checkout.c b/src/checkout.c index e4a397cd5..2bad06501 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -22,20 +22,19 @@ #include "filter.h" #include "blob.h" #include "diff.h" +#include "pathspec.h" -struct checkout_diff_data -{ +typedef struct { + git_repository *repo; + git_diff_list *diff; + git_checkout_opts *opts; git_buf *path; size_t workdir_len; - git_checkout_opts *checkout_opts; - git_repository *owner; bool can_symlink; - bool found_submodules; - bool create_submodules; int error; size_t total_steps; size_t completed_steps; -}; +} checkout_diff_data; static int buffer_to_file( git_buf *buffer, @@ -112,7 +111,8 @@ static int blob_content_to_file( if (!file_mode) file_mode = entry_filemode; - error = buffer_to_file(&filtered, path, opts->dir_mode, opts->file_open_flags, file_mode); + error = buffer_to_file( + &filtered, path, opts->dir_mode, opts->file_open_flags, file_mode); cleanup: git_filters_free(&filters); @@ -123,7 +123,8 @@ cleanup: return error; } -static int blob_content_to_link(git_blob *blob, const char *path, bool can_symlink) +static int blob_content_to_link( + git_blob *blob, const char *path, bool can_symlink) { git_buf linktarget = GIT_BUF_INIT; int error; @@ -142,58 +143,52 @@ static int blob_content_to_link(git_blob *blob, const char *path, bool can_symli } static int checkout_submodule( - struct checkout_diff_data *data, + 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->owner), - data->checkout_opts->dir_mode, GIT_MKDIR_PATH) < 0) + file->path, git_repository_workdir(data->repo), + data->opts->dir_mode, GIT_MKDIR_PATH) < 0) return -1; - /* TODO: two cases: + /* 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 request on the submodule, but a - * clone command should probably be able to. Do we need a submodule - * callback option? + * 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( - struct checkout_diff_data *data, + checkout_diff_data *data, const char *path) { - if (data->checkout_opts->progress_cb) - data->checkout_opts->progress_cb( - path, - data->completed_steps, - data->total_steps, - data->checkout_opts->progress_payload); + if (data->opts->progress_cb) + data->opts->progress_cb( + path, data->completed_steps, data->total_steps, + data->opts->progress_payload); } static int checkout_blob( - struct checkout_diff_data *data, + checkout_diff_data *data, const git_diff_file *file) { - git_blob *blob; int error = 0; + git_blob *blob; git_buf_truncate(data->path, data->workdir_len); - if (git_buf_joinpath(data->path, git_buf_cstr(data->path), file->path) < 0) + if (git_buf_puts(data->path, file->path) < 0) return -1; - /* If there is a directory where this blob should go, then there is an - * existing tree that conflicts with this file, but REMOVE_UNTRACKED must - * not have been passed in. Signal to caller with GIT_ENOTFOUND. - */ - if (git_path_isdir(git_buf_cstr(data->path))) - return GIT_ENOTFOUND; - - if ((error = git_blob_lookup(&blob, data->owner, &file->oid)) < 0) + if ((error = git_blob_lookup(&blob, data->repo, &file->oid)) < 0) return error; if (S_ISLNK(file->mode)) @@ -201,14 +196,14 @@ static int checkout_blob( blob, git_buf_cstr(data->path), data->can_symlink); else error = blob_content_to_file( - blob, git_buf_cstr(data->path), file->mode, data->checkout_opts); + blob, git_buf_cstr(data->path), file->mode, data->opts); git_blob_free(blob); return error; } -static int retrieve_symlink_capabilities(git_repository *repo, bool *can_symlink) +static int retrieve_symlink_caps(git_repository *repo, bool *can_symlink) { git_config *cfg; int error; @@ -218,10 +213,7 @@ static int retrieve_symlink_capabilities(git_repository *repo, bool *can_symlink error = git_config_get_bool((int *)can_symlink, cfg, "core.symlinks"); - /* - * When no "core.symlinks" entry is found in any of the configuration - * store (local, global or system), default value is "true". - */ + /* If "core.symlinks" is not found anywhere, default to true. */ if (error == GIT_ENOTFOUND) { *can_symlink = true; error = 0; @@ -230,7 +222,8 @@ static int retrieve_symlink_capabilities(git_repository *repo, bool *can_symlink return error; } -static void normalize_options(git_checkout_opts *normalized, git_checkout_opts *proposed) +static void normalize_options( + git_checkout_opts *normalized, git_checkout_opts *proposed) { assert(normalized); @@ -239,11 +232,16 @@ static void normalize_options(git_checkout_opts *normalized, git_checkout_opts * else memmove(normalized, proposed, sizeof(git_checkout_opts)); - /* Default options */ - if (!normalized->checkout_strategy) - normalized->checkout_strategy = GIT_CHECKOUT_DEFAULT; + /* 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; @@ -254,50 +252,174 @@ static void normalize_options(git_checkout_opts *normalized, git_checkout_opts * enum { CHECKOUT_ACTION__NONE = 0, CHECKOUT_ACTION__REMOVE = 1, - CHECKOUT_ACTION__CREATE_BLOB = 2, - CHECKOUT_ACTION__CREATE_SUBMODULE = 4, - CHECKOUT_ACTION__NOTIFY = 8 + CHECKOUT_ACTION__UPDATE_BLOB = 2, + CHECKOUT_ACTION__UPDATE_SUBMODULE = 4, + CHECKOUT_ACTION__CONFLICT = 8, + CHECKOUT_ACTION__MAX = 8 }; -static uint32_t checkout_action_for_delta( - git_checkout_opts *opts, - const git_diff_delta *delta) +static int checkout_confirm_update_blob( + checkout_diff_data *data, + const git_diff_delta *delta, + int action) { - uint32_t action = CHECKOUT_ACTION__NONE; + 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; - switch (delta->status) { - case GIT_DELTA_UNTRACKED: - if ((opts->checkout_strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0) + 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_TYPECHANGE: - if ((opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED) != 0) - action |= CHECKOUT_ACTION__REMOVE | CHECKOUT_ACTION__CREATE_BLOB; - else if (opts->skipped_notify_cb != NULL) - action = CHECKOUT_ACTION__NOTIFY; + case GIT_DELTA_ADDED: + /* Impossible. New files should be UNTRACKED or TYPECHANGE */ + action = CHECKOUT_ACTION__CONFLICT; break; case GIT_DELTA_DELETED: - if ((opts->checkout_strategy & GIT_CHECKOUT_CREATE_MISSING) != 0) - action |= CHECKOUT_ACTION__CREATE_BLOB; + 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: - if ((opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED) != 0) - action |= CHECKOUT_ACTION__CREATE_BLOB; - else if (opts->skipped_notify_cb != NULL) - action = CHECKOUT_ACTION__NOTIFY; + 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 & CHECKOUT_ACTION__CREATE_BLOB) != 0 && - S_ISGITLINK(delta->old_file.mode)) - action = (action & ~CHECKOUT_ACTION__CREATE_BLOB) | - CHECKOUT_ACTION__CREATE_SUBMODULE; + 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; } @@ -305,53 +427,119 @@ static uint32_t checkout_action_for_delta( static int checkout_get_actions( uint32_t **actions_ptr, size_t **counts_ptr, - git_diff_list *diff, - git_checkout_opts *opts) + checkout_diff_data *data) { + int error; + git_diff_list *diff = data->diff; git_diff_delta *delta; size_t i, *counts; uint32_t *actions; + 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->entrycmp, true)) < 0) + goto fail; + + if ((error = git_iterator_current(hiter, &he)) < 0) + goto fail; + + git__free(pfx); + + *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; + } - *counts_ptr = counts = - git__calloc(CHECKOUT_ACTION__NOTIFY, sizeof(size_t)); - GITERR_CHECK_ALLOC(counts); + 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->prefixcmp(he->path, delta->new_file.path) : + diff->strcmp(he->path, delta->old_file.path); + if (cmp >= 0) + break; + if (git_iterator_advance(hiter, &he) < 0) + he = NULL; + } - *actions_ptr = actions = - git__calloc(diff->deltas.length, sizeof(uint32_t)); - GITERR_CHECK_ALLOC(actions); + act = checkout_action_for_delta(data, delta, !cmp ? he : NULL); - git_vector_foreach(&diff->deltas, i, delta) { - actions[i] = checkout_action_for_delta(opts, delta); + if (act < 0) { + error = act; + goto fail; + } - if (actions[i] & CHECKOUT_ACTION__REMOVE) - counts[CHECKOUT_ACTION__REMOVE]++; + if (!cmp && git_iterator_advance(hiter, &he) < 0) + he = NULL; - if (actions[i] & CHECKOUT_ACTION__CREATE_BLOB) - counts[CHECKOUT_ACTION__CREATE_BLOB]++; + actions[i] = act; - if (actions[i] & CHECKOUT_ACTION__CREATE_SUBMODULE) - counts[CHECKOUT_ACTION__CREATE_SUBMODULE]++; + 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 (actions[i] & CHECKOUT_ACTION__NOTIFY) - counts[CHECKOUT_ACTION__NOTIFY]++; + 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); return 0; + +fail: + *counts_ptr = NULL; + git__free(counts); + *actions_ptr = NULL; + git__free(actions); + + git_iterator_free(hiter); + git__free(pfx); + + return -1; } static int checkout_remove_the_old( git_diff_list *diff, unsigned int *actions, - struct checkout_diff_data *data) + 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), - GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_EMPTY_PARENTS); + 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; @@ -366,35 +554,20 @@ static int checkout_remove_the_old( static int checkout_create_the_new( git_diff_list *diff, unsigned int *actions, - struct checkout_diff_data *data) + checkout_diff_data *data) { git_diff_delta *delta; size_t i; git_vector_foreach(&diff->deltas, i, delta) { - if (actions[i] & CHECKOUT_ACTION__CREATE_BLOB) { + if (actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) { int error = checkout_blob(data, &delta->old_file); - - /* ENOTFOUND means unable to create the file because of - * an existing parent dir. Probably flags were given - * asking to create new blobs without allowing removal - * of a conflicting tree (or vice versa). - */ - if (error == GIT_ENOTFOUND) - giterr_clear(); - else if (error < 0) + if (error < 0) return error; data->completed_steps++; report_progress(data, delta->old_file.path); } - - if (actions[i] & CHECKOUT_ACTION__NOTIFY) { - if (data->checkout_opts->skipped_notify_cb( - delta->old_file.path, &delta->old_file.oid, - delta->old_file.mode, data->checkout_opts->notify_payload)) - return GIT_EUSER; - } } return 0; @@ -403,13 +576,13 @@ static int checkout_create_the_new( static int checkout_create_submodules( git_diff_list *diff, unsigned int *actions, - struct checkout_diff_data *data) + checkout_diff_data *data) { git_diff_delta *delta; size_t i; git_vector_foreach(&diff->deltas, i, delta) { - if (actions[i] & CHECKOUT_ACTION__CREATE_SUBMODULE) { + if (actions[i] & CHECKOUT_ACTION__UPDATE_SUBMODULE) { int error = checkout_submodule(data, &delta->old_file); if (error < 0) return error; @@ -427,16 +600,13 @@ int git_checkout_index( git_checkout_opts *opts) { git_diff_list *diff = NULL; - git_diff_options diff_opts = {0}; git_checkout_opts checkout_opts; - struct checkout_diff_data data; + checkout_diff_data data; git_buf workdir = GIT_BUF_INIT; - uint32_t *actions = NULL; size_t *counts = NULL; - int error; assert(repo); @@ -445,9 +615,8 @@ int git_checkout_index( return error; diff_opts.flags = - GIT_DIFF_INCLUDE_UNTRACKED | - GIT_DIFF_INCLUDE_TYPECHANGE | - GIT_DIFF_SKIP_BINARY_CHECK; + 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; @@ -470,33 +639,35 @@ int git_checkout_index( * 3. Then checkout all submodules in case a new .gitmodules blob was * checked out during pass #2. */ - if ((error = checkout_get_actions(&actions, &counts, diff, &checkout_opts)) < 0) - goto cleanup; memset(&data, 0, sizeof(data)); data.path = &workdir; data.workdir_len = git_buf_len(&workdir); - data.checkout_opts = &checkout_opts; - data.owner = repo; + 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__CREATE_BLOB] + - counts[CHECKOUT_ACTION__CREATE_SUBMODULE]; + counts[CHECKOUT_ACTION__UPDATE_BLOB] + + counts[CHECKOUT_ACTION__UPDATE_SUBMODULE]; - if ((error = retrieve_symlink_capabilities(repo, &data.can_symlink)) < 0) + if ((error = retrieve_symlink_caps(repo, &data.can_symlink)) < 0) goto cleanup; - report_progress(&data, NULL); + 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__CREATE_BLOB] + - counts[CHECKOUT_ACTION__NOTIFY]) > 0 && + if (counts[CHECKOUT_ACTION__UPDATE_BLOB] > 0 && (error = checkout_create_the_new(diff, actions, &data)) < 0) goto cleanup; - if (counts[CHECKOUT_ACTION__CREATE_SUBMODULE] > 0 && + if (counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] > 0 && (error = checkout_create_submodules(diff, actions, &data)) < 0) goto cleanup; @@ -519,32 +690,28 @@ int git_checkout_tree( git_object *treeish, git_checkout_opts *opts) { + int error = 0; git_index *index = NULL; git_tree *tree = NULL; - int error; - assert(repo && treeish); if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) { - giterr_set(GITERR_INVALID, "Provided treeish cannot be peeled into a tree."); - return GIT_ERROR; + giterr_set( + GITERR_CHECKOUT, "Provided object cannot be peeled to a tree"); + return -1; } - if ((error = git_repository_index(&index, repo)) < 0) - goto cleanup; + /* 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); - if ((error = git_index_read_tree(index, tree)) < 0) - goto cleanup; - - if ((error = git_index_write(index)) < 0) - goto cleanup; - - error = git_checkout_index(repo, opts); - -cleanup: git_index_free(index); git_tree_free(tree); + return error; } @@ -552,21 +719,16 @@ int git_checkout_head( git_repository *repo, git_checkout_opts *opts) { - git_reference *head; int error; + git_reference *head = NULL; git_object *tree = NULL; assert(repo); - if ((error = git_repository_head(&head, repo)) < 0) - return error; - - if ((error = git_reference_peel(&tree, head, GIT_OBJ_TREE)) < 0) - goto cleanup; + if (!(error = git_repository_head(&head, repo)) && + !(error = git_reference_peel(&tree, head, GIT_OBJ_TREE))) + error = git_checkout_tree(repo, tree, opts); - error = git_checkout_tree(repo, tree, opts); - -cleanup: git_reference_free(head); git_object_free(tree); diff --git a/src/fileops.c b/src/fileops.c index d2cbd046d..5eebc5057 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -351,6 +351,7 @@ int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode) } typedef struct { + const char *base; uint32_t flags; int error; } futils__rmdir_data; @@ -366,11 +367,52 @@ static int futils__error_cannot_rmdir(const char *path, const char *filemsg) return -1; } +static int futils__rm_first_parent(git_buf *path, const char *ceiling) +{ + int error = GIT_ENOTFOUND; + struct stat st; + + while (error == GIT_ENOTFOUND) { + git_buf_rtruncate_at_char(path, '/'); + + if (!path->size || git__prefixcmp(path->ptr, ceiling) != 0) + error = 0; + else if (p_lstat(path->ptr, &st) == 0) { + if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) + error = p_unlink(path->ptr); + else if (!S_ISDIR(st.st_mode)) + error = -1; /* fail to remove non-regular file */ + } else if (errno != ENOTDIR) + error = -1; + } + + if (error) + futils__error_cannot_rmdir(path->ptr, "cannot remove parent"); + + return error; +} + static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path) { + struct stat st; futils__rmdir_data *data = opaque; - if (git_path_isdir(path->ptr) == true) { + if ((data->error = p_lstat(path->ptr, &st)) < 0) { + if (errno == ENOENT) + data->error = 0; + else if (errno == ENOTDIR) { + /* asked to remove a/b/c/d/e and a/b is a normal file */ + if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0) + data->error = futils__rm_first_parent(path, data->base); + else + futils__error_cannot_rmdir( + path->ptr, "parent is not directory"); + } + else + futils__error_cannot_rmdir(path->ptr, "cannot access"); + } + + else if (S_ISDIR(st.st_mode)) { int error = git_path_direach(path, futils__rmdir_recurs_foreach, data); if (error < 0) return (error == GIT_EUSER) ? data->error : error; @@ -393,9 +435,8 @@ static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path) futils__error_cannot_rmdir(path->ptr, "cannot be removed"); } - else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0) { + else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0) data->error = futils__error_cannot_rmdir(path->ptr, "still present"); - } return data->error; } @@ -434,6 +475,7 @@ int git_futils_rmdir_r( if (git_path_join_unrooted(&fullpath, path, base, NULL) < 0) return -1; + data.base = base ? base : ""; data.flags = flags; data.error = 0; diff --git a/src/fileops.h b/src/fileops.h index 6952c463c..a74f8b758 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -109,6 +109,7 @@ extern int git_futils_mkpath2file(const char *path, const mode_t mode); * * GIT_RMDIR_SKIP_NONEMPTY - skip non-empty directories with no error. * * GIT_RMDIR_EMPTY_PARENTS - remove containing directories up to base * if removing this item leaves them empty + * * GIT_RMDIR_REMOVE_BLOCKERS - remove blocking file that causes ENOTDIR * * The old values translate into the new as follows: * @@ -121,6 +122,7 @@ typedef enum { GIT_RMDIR_REMOVE_FILES = (1 << 0), GIT_RMDIR_SKIP_NONEMPTY = (1 << 1), GIT_RMDIR_EMPTY_PARENTS = (1 << 2), + GIT_RMDIR_REMOVE_BLOCKERS = (1 << 3), } git_futils_rmdir_flags; /** diff --git a/src/index.c b/src/index.c index c1b4565a3..0ae1b4479 100644 --- a/src/index.c +++ b/src/index.c @@ -13,6 +13,8 @@ #include "tree.h" #include "tree-cache.h" #include "hash.h" +#include "iterator.h" +#include "pathspec.h" #include "git2/odb.h" #include "git2/oid.h" #include "git2/blob.h" @@ -1590,3 +1592,54 @@ git_repository *git_index_owner(const git_index *index) { return INDEX_OWNER(index); } + +int git_index_read_tree_match( + git_index *index, git_tree *tree, git_strarray *strspec) +{ +#if 0 + git_iterator *iter = NULL; + const git_index_entry *entry; + char *pfx = NULL; + git_vector pathspec = GIT_VECTOR_INIT; + git_pool pathpool = GIT_POOL_INIT_STRINGPOOL; +#endif + + if (!git_pathspec_is_interesting(strspec)) + return git_index_read_tree(index, tree); + + return git_index_read_tree(index, tree); + +#if 0 + /* The following loads the matches into the index, but doesn't + * erase obsoleted entries (e.g. you load a blob at "a/b" which + * should obsolete a blob at "a/b/c/d" since b is no longer a tree) + */ + + if (git_pathspec_init(&pathspec, strspec, &pathpool) < 0) + return -1; + + pfx = git_pathspec_prefix(strspec); + + if ((error = git_iterator_for_tree_range( + &iter, INDEX_OWNER(index), tree, pfx, pfx)) < 0 || + (error = git_iterator_current(iter, &entry)) < 0) + goto cleanup; + + while (entry != NULL) { + if (git_pathspec_match_path(&pathspec, entry->path, false, false) && + (error = git_index_add(index, entry)) < 0) + goto cleanup; + + if ((error = git_iterator_advance(iter, &entry)) < 0) + goto cleanup; + } + +cleanup: + git_iterator_free(iter); + git_pathspec_free(&pathspec); + git_pool_clear(&pathpool); + git__free(pfx); + + return error; +#endif +} diff --git a/src/index.h b/src/index.h index 9778a543a..f0dcd64d5 100644 --- a/src/index.h +++ b/src/index.h @@ -48,4 +48,7 @@ extern unsigned int git_index__prefix_position(git_index *index, const char *pat extern int git_index_entry__cmp(const void *a, const void *b); extern int git_index_entry__cmp_icase(const void *a, const void *b); +extern int git_index_read_tree_match( + git_index *index, git_tree *tree, git_strarray *strspec); + #endif diff --git a/src/path.c b/src/path.c index 09556bd3f..98351bec3 100644 --- a/src/path.c +++ b/src/path.c @@ -382,9 +382,10 @@ int git_path_walk_up( iter.asize = path->asize; while (scan >= stop) { - if ((error = cb(data, &iter)) < 0) - break; + error = cb(data, &iter); iter.ptr[scan] = oldc; + if (error < 0) + break; scan = git_buf_rfind_next(&iter, '/'); if (scan >= 0) { scan++; diff --git a/src/reset.c b/src/reset.c index 7df1c1a57..69a9c4f04 100644 --- a/src/reset.c +++ b/src/reset.c @@ -137,10 +137,7 @@ int git_reset( } memset(&opts, 0, sizeof(opts)); - opts.checkout_strategy = - GIT_CHECKOUT_CREATE_MISSING - | GIT_CHECKOUT_OVERWRITE_MODIFIED - | GIT_CHECKOUT_REMOVE_UNTRACKED; + opts.checkout_strategy = GIT_CHECKOUT_FORCE; if (git_checkout_index(repo, &opts) < 0) { giterr_set(GITERR_INDEX, "%s - Failed to checkout the index.", ERROR_MSG); diff --git a/src/stash.c b/src/stash.c index 1d6940e3c..7bff466d1 100644 --- a/src/stash.c +++ b/src/stash.c @@ -501,8 +501,8 @@ static int reset_index_and_workdir( memset(&opts, 0, sizeof(git_checkout_opts)); - opts.checkout_strategy = - GIT_CHECKOUT_CREATE_MISSING | GIT_CHECKOUT_OVERWRITE_MODIFIED; + opts.checkout_strategy = + GIT_CHECKOUT_UPDATE_MODIFIED | GIT_CHECKOUT_UPDATE_UNTRACKED; if (remove_untracked) opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED; |
