diff options
Diffstat (limited to 'src/checkout.c')
-rw-r--r-- | src/checkout.c | 230 |
1 files changed, 135 insertions, 95 deletions
diff --git a/src/checkout.c b/src/checkout.c index ee6e043d5..6f5cfffd7 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -30,6 +30,7 @@ struct checkout_diff_data git_indexer_stats *stats; git_repository *owner; bool can_symlink; + bool create_submodules; int error; }; @@ -40,18 +41,27 @@ static int buffer_to_file( int file_open_flags, mode_t file_mode) { - int fd, error_write, error_close; + int fd, error, error_close; - if (git_futils_mkpath2file(path, dir_mode) < 0) - return -1; + if ((error = git_futils_mkpath2file(path, dir_mode)) < 0) + return error; if ((fd = p_open(path, file_open_flags, file_mode)) < 0) - return -1; + return fd; + + error = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer)); - error_write = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer)); error_close = p_close(fd); - return error_write ? error_write : error_close; + if (!error) + error = error_close; + + 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( @@ -125,107 +135,122 @@ static int blob_content_to_link(git_blob *blob, const char *path, bool can_symli return error; } +static int checkout_submodule( + struct checkout_diff_data *data, + const git_diff_file *file) +{ + if (git_futils_mkdir( + file->path, git_repository_workdir(data->owner), + data->checkout_opts->dir_mode, GIT_MKDIR_PATH) < 0) + return -1; + + /* TODO: two cases: + * 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? + */ + + return 0; +} + static int checkout_blob( - git_repository *repo, - const git_oid *blob_oid, - const char *path, - mode_t filemode, - bool can_symlink, - git_checkout_opts *opts) + struct checkout_diff_data *data, + const git_diff_file *file) { git_blob *blob; int error; - if ((error = git_blob_lookup(&blob, repo, blob_oid)) < 0) - return error; /* Add an error message */ + git_buf_truncate(data->path, data->workdir_len); + if (git_buf_joinpath(data->path, git_buf_cstr(data->path), file->path) < 0) + return -1; + + if ((error = git_blob_lookup(&blob, data->owner, &file->oid)) < 0) + return error; - if (S_ISLNK(filemode)) - error = blob_content_to_link(blob, path, can_symlink); + 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, path, filemode, opts); + error = blob_content_to_file( + blob, git_buf_cstr(data->path), file->mode, data->checkout_opts); git_blob_free(blob); return error; } -static int checkout_diff_fn( - void *cb_data, - const git_diff_delta *delta, - float progress) +static int checkout_remove_the_old( + void *cb_data, const git_diff_delta *delta, float progress) { struct checkout_diff_data *data = cb_data; - int error = 0; git_checkout_opts *opts = data->checkout_opts; - bool do_delete = false, do_checkout_blob = false; - - data->stats->processed = (unsigned int)(data->stats->total * progress); - - switch (delta->status) { - case GIT_DELTA_UNTRACKED: - if ((opts->checkout_strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0) - do_delete = true; - break; - - case GIT_DELTA_MODIFIED: - case GIT_DELTA_TYPECHANGE: - if (!(opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED)) { - - if (opts->skipped_notify_cb != NULL && - opts->skipped_notify_cb( - delta->new_file.path, - &delta->old_file.oid, - delta->old_file.mode, - opts->notify_payload) != 0) - { - giterr_clear(); - error = GIT_EUSER; - } - - goto cleanup; - } - - do_checkout_blob = true; - if (delta->status == GIT_DELTA_TYPECHANGE) - do_delete = true; - break; + GIT_UNUSED(progress); + data->stats->processed++; + + if ((delta->status == GIT_DELTA_UNTRACKED && + (opts->checkout_strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0) || + (delta->status == GIT_DELTA_TYPECHANGE && + (opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED) != 0)) + { + data->error = git_futils_rmdir_r( + delta->new_file.path, + git_repository_workdir(data->owner), + GIT_DIRREMOVAL_FILES_AND_DIRS); + } - case GIT_DELTA_DELETED: - if ((opts->checkout_strategy & GIT_CHECKOUT_CREATE_MISSING) != 0) - do_checkout_blob = true; - break; + return data->error; +} - default: - giterr_set(GITERR_INVALID, "Unexpected status (%d) for path '%s'.", - delta->status, delta->new_file.path); - error = -1; - goto cleanup; +static int checkout_create_the_new( + void *cb_data, const git_diff_delta *delta, float progress) +{ + int error = 0; + struct checkout_diff_data *data = cb_data; + git_checkout_opts *opts = data->checkout_opts; + bool do_checkout = false, do_notify = false; + + GIT_UNUSED(progress); + data->stats->processed++; + + if (delta->status == GIT_DELTA_MODIFIED || + delta->status == GIT_DELTA_TYPECHANGE) + { + if ((opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED) != 0) + do_checkout = true; + else if (opts->skipped_notify_cb != NULL) + do_notify = !data->create_submodules; + } + else if (delta->status == GIT_DELTA_DELETED && + (opts->checkout_strategy & GIT_CHECKOUT_CREATE_MISSING) != 0) + do_checkout = true; + + if (do_notify) { + if (opts->skipped_notify_cb( + delta->old_file.path, &delta->old_file.oid, + delta->old_file.mode, opts->notify_payload)) + { + giterr_clear(); + error = GIT_EUSER; + } } - git_buf_truncate(data->path, data->workdir_len); - - if ((error = git_buf_joinpath( - data->path, git_buf_cstr(data->path), delta->new_file.path)) < 0) - goto cleanup; + if (do_checkout) { + bool is_submodule = S_ISGITLINK(delta->old_file.mode); - if (do_delete && - (error = git_futils_rmdir_r( - git_buf_cstr(data->path), GIT_DIRREMOVAL_FILES_AND_DIRS)) < 0) - goto cleanup; + if (!is_submodule && !data->create_submodules) + error = checkout_blob(data, &delta->old_file); - if (do_checkout_blob) - error = checkout_blob( - data->owner, - &delta->old_file.oid, - git_buf_cstr(data->path), - delta->old_file.mode, - data->can_symlink, - opts); + else if (is_submodule && data->create_submodules) + error = checkout_submodule(data, &delta->old_file); + } -cleanup: if (error) - data->error = error; /* preserve real error */ + data->error = error; return error; } @@ -278,7 +303,6 @@ int git_checkout_index( git_checkout_opts *opts, git_indexer_stats *stats) { - git_index *index = NULL; git_diff_list *diff = NULL; git_indexer_stats dummy_stats; @@ -292,11 +316,13 @@ int git_checkout_index( assert(repo); - if ((git_repository__ensure_not_bare(repo, "checkout")) < 0) - return GIT_EBAREREPO; + if ((error = git_repository__ensure_not_bare(repo, "checkout")) < 0) + return error; - diff_opts.flags = GIT_DIFF_INCLUDE_UNTRACKED | - GIT_DIFF_DONT_SPLIT_TYPECHANGE; + diff_opts.flags = + GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_INCLUDE_TYPECHANGE | + GIT_DIFF_SKIP_BINARY_CHECK; if (opts && opts->paths.count > 0) diff_opts.pathspec = opts->paths; @@ -313,11 +339,7 @@ int git_checkout_index( stats = &dummy_stats; stats->processed = 0; - - if ((git_repository_index(&index, repo)) < 0) - goto cleanup; - - stats->total = git_index_entrycount(index); + stats->total = (unsigned int)git_diff_num_deltas(diff) * 3 /* # passes */; memset(&data, 0, sizeof(data)); @@ -330,15 +352,33 @@ int git_checkout_index( if ((error = retrieve_symlink_capabilities(repo, &data.can_symlink)) < 0) goto cleanup; - error = git_diff_foreach(diff, &data, checkout_diff_fn, NULL, NULL); + /* Checkout is best performed with three passes through the diff. + * + * 1. First 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. + */ + if (!(error = git_diff_foreach( + diff, &data, checkout_remove_the_old, NULL, NULL)) && + !(error = git_diff_foreach( + diff, &data, checkout_create_the_new, NULL, NULL))) + { + data.create_submodules = true; + error = git_diff_foreach( + diff, &data, checkout_create_the_new, NULL, NULL); + } + +cleanup: if (error == GIT_EUSER) error = (data.error != 0) ? data.error : -1; -cleanup: - git_index_free(index); git_diff_list_free(diff); git_buf_free(&workdir); + return error; } |