summaryrefslogtreecommitdiff
path: root/src/checkout.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/checkout.c')
-rw-r--r--src/checkout.c230
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;
}