diff options
author | Russell Belfer <rb@github.com> | 2012-11-08 17:05:07 -0800 |
---|---|---|
committer | Russell Belfer <rb@github.com> | 2012-11-09 13:52:07 -0800 |
commit | ad9a921b92a964a0f28a5f0d59079cde5a0ada1e (patch) | |
tree | 218caac93e8acd2686521974ce77d165386c4e4b /src/checkout.c | |
parent | 55cbd05b18960e761a4d237ce5f1ff06455da98d (diff) | |
download | libgit2-ad9a921b92a964a0f28a5f0d59079cde5a0ada1e.tar.gz |
Rework checkout with new strategy options
This is a major reworking of checkout strategy options. The
checkout code is now sensitive to the contents of the HEAD tree
and the new options allow you to update the working tree so that
it will match the index content only when it previously matched
the contents of the HEAD. This allows you to, for example, to
distinguish between removing files that are in the HEAD but not
in the index, vs just removing all untracked files.
Because of various corner cases that arise, etc., this required
some additional capabilities in rmdir and other utility functions.
This includes the beginnings of an implementation of code to read
a partial tree into the index based on a pathspec, but that is
not enabled because of the possibility of creating conflicting
index entries.
Diffstat (limited to 'src/checkout.c')
-rw-r--r-- | src/checkout.c | 466 |
1 files changed, 314 insertions, 152 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); |