summaryrefslogtreecommitdiff
path: root/src/checkout.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/checkout.c')
-rw-r--r--src/checkout.c1023
1 files changed, 632 insertions, 391 deletions
diff --git a/src/checkout.c b/src/checkout.c
index 8e8c41bd5..5aeb0624c 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -126,7 +126,7 @@
* There are four tiers of safe cases:
* - SAFE == completely safe to update
* - SAFE+MISSING == safe except the workdir is missing the expect content
- * - MAYBE SAFE == safe if workdir tree matches (or is missing) expected
+ * - MAYBE SAFE == safe if workdir tree matches (or is missing) baseline
* content, which is unknown at this point
* - FORCEABLE == conflict unless FORCE is given
* - DIRTY == no conflict but change is not applied unless FORCE
@@ -146,9 +146,9 @@
* which are ok on their own, but core git treat this as a conflict.
* If not forced, this is a conflict. If forced, this actually doesn't
* have to write anything and leaves the new blob as an untracked file.
- * 32 - This is the only case where the expected and desired values match
+ * 32 - This is the only case where the baseline and target values match
* and yet we will still write to the working directory. In all other
- * cases, if expected == desired, we don't touch the workdir (it is
+ * cases, if baseline == target, we don't touch the workdir (it is
* either already right or is "dirty"). However, since this case also
* implies that a ?/B1/x case will exist as well, it can be skipped.
*
@@ -182,271 +182,460 @@ enum {
CHECKOUT_ACTION__UPDATE_SUBMODULE = 4,
CHECKOUT_ACTION__CONFLICT = 8,
CHECKOUT_ACTION__MAX = 8,
- CHECKOUT_ACTION__REMOVE_EMPTY = 16,
+ CHECKOUT_ACTION__DEFER_REMOVE = 16,
+ CHECKOUT_ACTION__REMOVE_AND_UPDATE =
+ (CHECKOUT_ACTION__UPDATE_BLOB | CHECKOUT_ACTION__REMOVE),
};
typedef struct {
git_repository *repo;
git_diff_list *diff;
- git_checkout_opts *opts;
- const char *pfx;
- git_buf *path;
+ git_checkout_opts opts;
+ bool opts_free_baseline;
+ char *pfx;
+ git_iterator *baseline;
+ git_pool pool;
+ git_vector removes;
+ git_buf path;
size_t workdir_len;
- bool can_symlink;
- int error;
+ unsigned int strategy;
+ int can_symlink;
size_t total_steps;
size_t completed_steps;
-} checkout_diff_data;
+} checkout_data;
static int checkout_notify(
- checkout_diff_data *data,
+ checkout_data *data,
git_checkout_notify_t why,
const git_diff_delta *delta,
- const git_index_entry *wditem)
+ const git_index_entry *baseitem)
{
- GIT_UNUSED(data);
- GIT_UNUSED(why);
- GIT_UNUSED(delta);
- GIT_UNUSED(wditem);
- return 0;
+ git_diff_file basefile;
+ const git_diff_file *baseline = NULL, *target = NULL, *workdir = NULL;
+
+ if (!data->opts.notify_cb)
+ return 0;
+
+ if ((why & data->opts.notify_flags) == 0)
+ return 0;
+
+ if (baseitem) {
+ memset(&basefile, 0, sizeof(basefile));
+
+ git_oid_cpy(&basefile.oid, &baseitem->oid);
+ basefile.path = baseitem->path;
+ basefile.size = baseitem->file_size;
+ basefile.flags = GIT_DIFF_FILE_VALID_OID;
+ basefile.mode = baseitem->mode;
+
+ baseline = &basefile;
+ }
+
+ if ((why & GIT_CHECKOUT__NOTIFY_CONFLICT_TREE) != 0) {
+ /* baseitem is a blob that conflicts with a tree in the workdir */
+ } else {
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED:
+ case GIT_DELTA_MODIFIED:
+ case GIT_DELTA_TYPECHANGE:
+ default:
+ target = &delta->old_file;
+ workdir = &delta->new_file;
+ break;
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_IGNORED:
+ case GIT_DELTA_UNTRACKED:
+ workdir = &delta->new_file;
+ break;
+ case GIT_DELTA_DELETED:
+ target = &delta->old_file;
+ break;
+ }
+ }
+
+ return data->opts.notify_cb(
+ why, delta->old_file.path,
+ baseline, target, workdir,
+ data->opts.notify_payload);
}
static bool checkout_is_workdir_modified(
- checkout_diff_data *data,
- const git_diff_file *item,
- const git_index_entry *wditem)
+ checkout_data *data,
+ const git_diff_file *wditem,
+ const git_index_entry *baseitem)
{
git_oid oid;
- if (item->size != wditem->file_size)
+ if (wditem->size != baseitem->file_size)
return true;
if (git_diff__oid_for_file(
- data->repo, wditem->path, wditem->mode,
- wditem->file_size, &oid) < 0)
+ data->repo, wditem->path, wditem->mode, wditem->size, &oid) < 0)
return false;
- return (git_oid_cmp(&item->oid, &oid) != 0);
+ return (git_oid_cmp(&baseitem->oid, &oid) != 0);
+}
+
+#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_for_delta(
- checkout_diff_data *data,
+static int checkout_action_common(
+ checkout_data *data,
+ int action,
const git_diff_delta *delta,
- const git_index_entry *wditem)
+ const git_index_entry *wd)
+{
+ git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE;
+
+ if (action <= 0)
+ return action;
+
+ if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)
+ action = (action & ~CHECKOUT_ACTION__REMOVE);
+
+ if ((action & CHECKOUT_ACTION__UPDATE_BLOB) != 0) {
+ if (S_ISGITLINK(delta->new_file.mode))
+ action = (action & ~CHECKOUT_ACTION__UPDATE_BLOB) |
+ CHECKOUT_ACTION__UPDATE_SUBMODULE;
+
+ notify = GIT_CHECKOUT_NOTIFY_UPDATED;
+ }
+
+ if ((action & CHECKOUT_ACTION__CONFLICT) != 0)
+ notify = GIT_CHECKOUT_NOTIFY_CONFLICT;
+
+ if (notify != GIT_CHECKOUT_NOTIFY_NONE &&
+ checkout_notify(data, notify, delta, wd) != 0)
+ return GIT_EUSER;
+
+ return action;
+}
+
+static int checkout_action_no_wd(
+ checkout_data *data,
+ const git_diff_delta *delta)
{
int action = CHECKOUT_ACTION__NONE;
- unsigned int strat = data->opts->checkout_strategy;
- int safe = ((strat & GIT_CHECKOUT_SAFE) != 0) ?
- CHECKOUT_ACTION__UPDATE_BLOB : CHECKOUT_ACTION__NONE;
- int force = ((strat & GIT_CHECKOUT_FORCE) != 0) ?
- CHECKOUT_ACTION__UPDATE_BLOB : CHECKOUT_ACTION__CONFLICT;
-
- /* nothing in workdir, so this is pretty easy */
- if (!wditem) {
- switch (delta->status) {
- case GIT_DELTA_UNMODIFIED: /* case 12 */
- if ((strat & GIT_CHECKOUT_SAFE_CREATE) != 0)
- action = CHECKOUT_ACTION__UPDATE_BLOB;
- if (checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL))
- return GIT_EUSER;
- break;
- case GIT_DELTA_ADDED: /* case 2 or 28 (and 5 but not really) */
- case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */
- action = safe;
- break;
- case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/
- if (!S_ISDIR(delta->new_file.mode))
- action = safe;
- break;
- case GIT_DELTA_DELETED: /* case 8 or 25 */
- default: /* impossible */ break;
- }
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED: /* case 12 */
+ if (checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL))
+ return GIT_EUSER;
+ action = CHECKOUT_ACTION_IF(SAFE_CREATE, UPDATE_BLOB, NONE);
+ break;
+ case GIT_DELTA_ADDED: /* case 2 or 28 (and 5 but not really) */
+ case GIT_DELTA_MODIFIED: /* case 13 (and 35 but not really) */
+ action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ break;
+ case GIT_DELTA_TYPECHANGE: /* case 21 (B->T) and 28 (T->B)*/
+ if (delta->new_file.mode == GIT_FILEMODE_TREE)
+ action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ break;
+ case GIT_DELTA_DELETED: /* case 8 or 25 */
+ default: /* impossible */
+ break;
}
- /* workdir has a directory where this entry should be */
- else if (S_ISDIR(wditem->mode)) {
- switch (delta->status) {
- case GIT_DELTA_UNMODIFIED: /* case 19 or 24 (or 34 but not really) */
- if (checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL) ||
- checkout_notify(
- data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wditem))
- return GIT_EUSER;
- break;
- case GIT_DELTA_ADDED:/* case 4 (and 7 for dir) */
- case GIT_DELTA_MODIFIED: /* case 20 (or 37 but not really) */
- if (!S_ISDIR(delta->new_file.mode))
- action = force;
- break;
- case GIT_DELTA_DELETED: /* case 11 (and 27 for dir) */
- if (!S_ISDIR(delta->old_file.mode) &&
- checkout_notify(
- data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wditem))
- return GIT_EUSER;
- break;
- case GIT_DELTA_TYPECHANGE: /* case 24 or 31 */
- /* For typechange to dir, dir is already created so no action */
-
- /* For typechange to blob, remove dir and add blob, but it is
- * not safe to remove dir if it contains modified files.
- * However, safely removing child files will remove the parent
- * directory if is it left empty, so we only need to remove dir
- * if it is already empty and has no children to remove.
- */
- if (S_ISDIR(delta->old_file.mode)) {
- action = safe;
- if (action != 0)
- action |= CHECKOUT_ACTION__REMOVE |
- CHECKOUT_ACTION__REMOVE_EMPTY;
- }
- break;
- default: /* impossible */ break;
- }
+ return checkout_action_common(data, action, delta, NULL);
+}
+
+static int checkout_action_wd_only(
+ checkout_data *data,
+ git_iterator *workdir,
+ const git_index_entry *wd,
+ git_vector *pathspec)
+{
+ bool ignored, remove;
+ git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE;
+
+ if (!git_pathspec_match_path(
+ pathspec, wd->path, false, workdir->ignore_case))
+ return 0;
+
+ ignored = git_iterator_current_is_ignored(workdir);
+
+ if (ignored) {
+ notify = GIT_CHECKOUT_NOTIFY_IGNORED;
+ remove = ((data->strategy & GIT_CHECKOUT_REMOVE_IGNORED) != 0);
+ } else {
+ notify = GIT_CHECKOUT_NOTIFY_UNTRACKED;
+ remove = ((data->strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0);
}
- /* workdir has a blob (or submodule) */
- else {
- switch (delta->status) {
- case GIT_DELTA_UNMODIFIED: /* case 14/15 or 33 */
- if (S_ISDIR(delta->old_file.mode) ||
- checkout_is_workdir_modified(data, &delta->old_file, wditem))
- {
- if (checkout_notify(
- data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wditem))
- return GIT_EUSER;
+ if (checkout_notify(data, notify, NULL, wd))
+ return GIT_EUSER;
- if (force)
- action = CHECKOUT_ACTION__UPDATE_BLOB;
- }
- break;
- case GIT_DELTA_ADDED: /* case 3, 4 or 6 */
- action = force;
- break;
- case GIT_DELTA_DELETED: /* case 9 or 10 (or 26 but not really) */
- if (checkout_is_workdir_modified(data, &delta->old_file, wditem))
- action = force ?
- CHECKOUT_ACTION__REMOVE : CHECKOUT_ACTION__CONFLICT;
- else
- action = safe ?
- CHECKOUT_ACTION__REMOVE : CHECKOUT_ACTION__NONE;
- break;
- case GIT_DELTA_MODIFIED: /* case 16, 17, 18 (or 36 but not really) */
- if (checkout_is_workdir_modified(data, &delta->old_file, wditem))
- action = force;
- else
- action = safe;
- break;
- case GIT_DELTA_TYPECHANGE: /* case 22, 23, 29, 30 */
- if (S_ISDIR(delta->old_file.mode) ||
- checkout_is_workdir_modified(data, &delta->old_file, wditem))
- action = force;
- else
- action = safe;
- break;
- default: /* impossible */ break;
+ if (remove) {
+ char *path = git_pool_strdup(&data->pool, wd->path);
+ GITERR_CHECK_ALLOC(path);
+
+ if (git_vector_insert(&data->removes, path) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int checkout_action_with_wd(
+ checkout_data *data,
+ const git_diff_delta *delta,
+ const git_index_entry *wd)
+{
+ int action = CHECKOUT_ACTION__NONE;
+
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED: /* case 14/15 or 33 */
+ if (S_ISDIR(delta->old_file.mode) ||
+ checkout_is_workdir_modified(data, &delta->old_file, wd))
+ {
+ if (checkout_notify(
+ data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd))
+ return GIT_EUSER;
+ action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, NONE);
}
+ break;
+ case GIT_DELTA_ADDED: /* case 3, 4 or 6 */
+ action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT);
+ break;
+ case GIT_DELTA_DELETED: /* case 9 or 10 (or 26 but not really) */
+ if (checkout_is_workdir_modified(data, &delta->old_file, wd))
+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);
+ else
+ action = CHECKOUT_ACTION_IF(SAFE, REMOVE, NONE);
+ break;
+ case GIT_DELTA_MODIFIED: /* case 16, 17, 18 (or 36 but not really) */
+ if (checkout_is_workdir_modified(data, &delta->old_file, wd))
+ action = CHECKOUT_ACTION_IF(FORCE, UPDATE_BLOB, CONFLICT);
+ else
+ action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ break;
+ case GIT_DELTA_TYPECHANGE: /* case 22, 23, 29, 30 */
+ if (delta->new_file.mode == GIT_FILEMODE_TREE)
+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);
+ else if (checkout_is_workdir_modified(data, &delta->old_file, wd))
+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ else
+ action = CHECKOUT_ACTION_IF(SAFE, REMOVE_AND_UPDATE, NONE);
+ break;
+ default: /* impossible */
+ break;
}
- if (action > 0 && (strat & GIT_CHECKOUT_UPDATE_ONLY) != 0)
- action = (action & ~CHECKOUT_ACTION__REMOVE);
+ return checkout_action_common(data, action, delta, wd);
+}
- if (action > 0 && (action & CHECKOUT_ACTION__UPDATE_BLOB) != 0) {
- if (S_ISGITLINK(delta->new_file.mode))
- action = (action & ~CHECKOUT_ACTION__UPDATE_BLOB) |
- CHECKOUT_ACTION__UPDATE_SUBMODULE;
+static int checkout_action_with_wd_blocker(
+ checkout_data *data,
+ const git_diff_delta *delta,
+ const git_index_entry *wd)
+{
+ int action = CHECKOUT_ACTION__NONE;
- if (checkout_notify(data, GIT_CHECKOUT_NOTIFY_UPDATED, delta, wditem))
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED:
+ /* should show delta as dirty / deleted */
+ if (checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, wd))
return GIT_EUSER;
+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, NONE);
+ break;
+ case GIT_DELTA_ADDED:
+ case GIT_DELTA_MODIFIED:
+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ break;
+ case GIT_DELTA_DELETED:
+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE, CONFLICT);
+ break;
+ case GIT_DELTA_TYPECHANGE:
+ /* not 100% certain about this... */
+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ break;
+ default: /* impossible */
+ break;
}
- if ((action & CHECKOUT_ACTION__CONFLICT) != 0) {
- if (checkout_notify(
- data, GIT_CHECKOUT_NOTIFY_CONFLICTS, delta, wditem))
+ return checkout_action_common(data, action, delta, wd);
+}
+
+static int checkout_action_with_wd_dir(
+ checkout_data *data,
+ const git_diff_delta *delta,
+ const git_index_entry *wd)
+{
+ int action = CHECKOUT_ACTION__NONE;
+
+ switch (delta->status) {
+ case GIT_DELTA_UNMODIFIED: /* case 19 or 24 (or 34 but not really) */
+ if (checkout_notify(data, GIT_CHECKOUT_NOTIFY_DIRTY, delta, NULL) ||
+ checkout_notify(
+ data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd))
return GIT_EUSER;
+ break;
+ case GIT_DELTA_ADDED:/* case 4 (and 7 for dir) */
+ case GIT_DELTA_MODIFIED: /* case 20 (or 37 but not really) */
+ if (delta->new_file.mode != GIT_FILEMODE_TREE)
+ action = CHECKOUT_ACTION_IF(FORCE, REMOVE_AND_UPDATE, CONFLICT);
+ break;
+ case GIT_DELTA_DELETED: /* case 11 (and 27 for dir) */
+ if (delta->old_file.mode != GIT_FILEMODE_TREE &&
+ checkout_notify(
+ data, GIT_CHECKOUT_NOTIFY_UNTRACKED, NULL, wd))
+ return GIT_EUSER;
+ break;
+ case GIT_DELTA_TYPECHANGE: /* case 24 or 31 */
+ /* For typechange to dir, dir is already created so no action */
+
+ /* For typechange to blob, remove dir and add blob, but it is
+ * not safe to remove dir if it contains modified files.
+ * However, safely removing child files will remove the parent
+ * directory if is it left empty, so we can defer removing the
+ * dir and it will succeed if no children are left.
+ */
+ if (delta->old_file.mode == GIT_FILEMODE_TREE) {
+ action = CHECKOUT_ACTION_IF(SAFE, UPDATE_BLOB, NONE);
+ if (action != CHECKOUT_ACTION__NONE)
+ action |= CHECKOUT_ACTION__DEFER_REMOVE;
+ }
+ break;
+ default: /* impossible */
+ break;
}
- return action;
+ return checkout_action_common(data, action, delta, wd);
}
-static int checkout_track_wd(
- int *cmp_out,
- const git_index_entry **wditem_ptr,
- checkout_diff_data *data,
- git_iterator *actual,
+static int checkout_action(
+ checkout_data *data,
git_diff_delta *delta,
+ git_iterator *workdir,
+ const git_index_entry **wditem_ptr,
git_vector *pathspec)
{
- int cmp = -1;
- const git_index_entry *wditem = *wditem_ptr;
+ const git_index_entry *wd = *wditem_ptr;
+ int cmp = -1, act;
+ int (*strcomp)(const char *, const char *) = data->diff->strcomp;
+ int (*pfxcomp)(const char *str, const char *pfx) = data->diff->pfxcomp;
+
+ /* move workdir iterator to follow along with deltas */
+
+ while (1) {
+ if (!wd)
+ return checkout_action_no_wd(data, delta);
+
+ cmp = strcomp(wd->path, delta->old_file.path);
+
+ /* 1. wd before delta ("a/a" before "a/b")
+ * 2. wd prefixes delta & should expand ("a/" before "a/b")
+ * 3. wd prefixes delta & cannot expand ("a/b" before "a/b/c")
+ * 4. wd equals delta ("a/b" and "a/b")
+ * 5. wd after delta & delta prefixes wd ("a/b/c" after "a/b/" or "a/b")
+ * 6. wd after delta ("a/c" after "a/b")
+ */
+
+ if (cmp < 0) {
+ cmp = pfxcomp(delta->old_file.path, wd->path);
+
+ if (cmp == 0) {
+ if (wd->mode == GIT_FILEMODE_TREE) {
+ /* case 2 - descend in wd */
+ if (git_iterator_advance_into_directory(workdir, &wd) < 0)
+ goto fail;
+ continue;
+ }
+
+ /* case 3 - wd contains non-dir where dir expected */
+ act = checkout_action_with_wd_blocker(data, delta, wd);
+ *wditem_ptr = git_iterator_advance(workdir, &wd) ? NULL : wd;
+ return act;
+ }
- while (wditem) {
- bool notify = false;
+ /* case 1 - handle wd item (if it matches pathspec) */
+ if (checkout_action_wd_only(data, workdir, wd, pathspec) < 0 ||
+ git_iterator_advance(workdir, &wd) < 0)
+ goto fail;
- cmp = data->diff->strcomp(delta->new_file.path, wditem->path);
- if (cmp >= 0)
- break;
+ *wditem_ptr = wd;
+ continue;
+ }
- if (!git_pathspec_match_path(
- pathspec, wditem->path, false, actual->ignore_case))
- notify = false;
+ if (cmp == 0) {
+ /* case 4 */
+ act = checkout_action_with_wd(data, delta, wd);
+ *wditem_ptr = git_iterator_advance(workdir, &wd) ? NULL : wd;
+ return act;
+ }
- else if (S_ISDIR(wditem->mode)) {
- cmp = data->diff->pfxcomp(delta->new_file.path, wditem->path);
+ cmp = pfxcomp(wd->path, delta->old_file.path);
- if (cmp < 0)
- notify = true; /* notify untracked/ignored tree */
- else if (!cmp) {
- /* workdir is prefix of current, so dive in and continue */
- if (git_iterator_advance_into_directory(actual, &wditem) < 0)
- return -1;
- continue;
+ if (cmp == 0) { /* case 5 */
+ if (delta->status == GIT_DELTA_TYPECHANGE &&
+ (delta->new_file.mode == GIT_FILEMODE_TREE ||
+ delta->new_file.mode == GIT_FILEMODE_COMMIT ||
+ delta->old_file.mode == GIT_FILEMODE_TREE ||
+ delta->old_file.mode == GIT_FILEMODE_COMMIT))
+ {
+ act = checkout_action_with_wd(data, delta, wd);
+ *wditem_ptr = git_iterator_advance(workdir, &wd) ? NULL : wd;
+ return act;
}
- else /* how can the wditem->path be < 0 but a prefix be > 0 */
- assert(false);
- } else
- notify = true; /* notify untracked/ignored blob */
-
- if (notify && checkout_notify(
- data, git_iterator_current_is_ignored(actual) ?
- GIT_CHECKOUT_NOTIFY_IGNORED : GIT_CHECKOUT_NOTIFY_UNTRACKED,
- NULL, wditem))
- return GIT_EUSER;
- if (git_iterator_advance(actual, wditem_ptr) < 0)
- break;
+ return checkout_action_with_wd_dir(data, delta, wd);
+ }
- wditem = *wditem_ptr;
- cmp = -1;
+ /* case 6 - wd is after delta */
+ return checkout_action_no_wd(data, delta);
}
- *cmp_out = cmp;
-
- return 0;
+fail:
+ *wditem_ptr = NULL;
+ return -1;
}
static int checkout_get_actions(
uint32_t **actions_ptr,
size_t **counts_ptr,
- checkout_diff_data *data)
+ checkout_data *data,
+ git_iterator *workdir)
{
int error = 0;
- git_iterator *actual = NULL;
const git_index_entry *wditem;
git_vector pathspec = GIT_VECTOR_INIT, *deltas;
git_pool pathpool = GIT_POOL_INIT_STRINGPOOL;
git_diff_delta *delta;
size_t i, *counts = NULL;
uint32_t *actions = NULL;
- bool allow_conflicts =
- ((data->opts->checkout_strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) != 0);
- if (data->opts->paths.count > 0 &&
- git_pathspec_init(&pathspec, &data->opts->paths, &pathpool) < 0)
+ if (data->opts.paths.count > 0 &&
+ git_pathspec_init(&pathspec, &data->opts.paths, &pathpool) < 0)
return -1;
- if ((error = git_iterator_for_workdir_range(
- &actual, data->repo, data->pfx, data->pfx)) < 0 ||
- (error = git_iterator_current(actual, &wditem)) < 0)
+ if ((error = git_iterator_current(workdir, &wditem)) < 0)
goto fail;
deltas = &data->diff->deltas;
@@ -460,23 +649,13 @@ static int checkout_get_actions(
}
git_vector_foreach(deltas, i, delta) {
- int cmp = -1, act;
+ int act = checkout_action(data, delta, workdir, &wditem, &pathspec);
- /* move workdir iterator to follow along with deltas */
- if (wditem != NULL &&
- (error = checkout_track_wd(
- &cmp, &wditem, data, actual, delta, &pathspec)) < 0)
- goto fail;
-
- act = checkout_action_for_delta(data, delta, !cmp ? wditem : NULL);
if (act < 0) {
error = act;
goto fail;
}
- if (!cmp && git_iterator_advance(actual, &wditem) < 0)
- wditem = NULL;
-
actions[i] = act;
if (act & CHECKOUT_ACTION__REMOVE)
@@ -489,14 +668,17 @@ static int checkout_get_actions(
counts[CHECKOUT_ACTION__CONFLICT]++;
}
- if (counts[CHECKOUT_ACTION__CONFLICT] > 0 && !allow_conflicts) {
+ counts[CHECKOUT_ACTION__REMOVE] += data->removes.length;
+
+ if (counts[CHECKOUT_ACTION__CONFLICT] > 0 &&
+ (data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) == 0)
+ {
giterr_set(GITERR_CHECKOUT, "%d conflicts prevent checkout",
(int)counts[CHECKOUT_ACTION__CONFLICT]);
error = -1;
goto fail;
}
- git_iterator_free(actual);
git_pathspec_free(&pathspec);
git_pool_clear(&pathpool);
@@ -508,7 +690,6 @@ fail:
*actions_ptr = NULL;
git__free(actions);
- git_iterator_free(actual);
git_pathspec_free(&pathspec);
git_pool_clear(&pathpool);
@@ -603,7 +784,7 @@ cleanup:
}
static int blob_content_to_link(
- git_blob *blob, const char *path, bool can_symlink)
+ git_blob *blob, const char *path, int can_symlink)
{
git_buf linktarget = GIT_BUF_INIT;
int error;
@@ -622,16 +803,16 @@ static int blob_content_to_link(
}
static int checkout_submodule(
- checkout_diff_data *data,
+ checkout_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)
+ if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0)
return 0;
if (git_futils_mkdir(
file->path, git_repository_workdir(data->repo),
- data->opts->dir_mode, GIT_MKDIR_PATH) < 0)
+ data->opts.dir_mode, GIT_MKDIR_PATH) < 0)
return -1;
/* TODO: Support checkout_strategy options. Two circumstances:
@@ -647,24 +828,24 @@ static int checkout_submodule(
}
static void report_progress(
- checkout_diff_data *data,
+ checkout_data *data,
const char *path)
{
- if (data->opts->progress_cb)
- data->opts->progress_cb(
+ if (data->opts.progress_cb)
+ data->opts.progress_cb(
path, data->completed_steps, data->total_steps,
- data->opts->progress_payload);
+ data->opts.progress_payload);
}
static int checkout_blob(
- checkout_diff_data *data,
+ checkout_data *data,
const git_diff_file *file)
{
int error = 0;
git_blob *blob;
- git_buf_truncate(data->path, data->workdir_len);
- if (git_buf_puts(data->path, file->path) < 0)
+ git_buf_truncate(&data->path, data->workdir_len);
+ if (git_buf_puts(&data->path, file->path) < 0)
return -1;
if ((error = git_blob_lookup(&blob, data->repo, &file->oid)) < 0)
@@ -672,42 +853,45 @@ static int checkout_blob(
if (S_ISLNK(file->mode))
error = blob_content_to_link(
- blob, git_buf_cstr(data->path), data->can_symlink);
+ 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);
+ blob, git_buf_cstr(&data->path), file->mode, &data->opts);
git_blob_free(blob);
+ /* if we try to create the blob and an existing directory blocks it from
+ * being written, then there must have been a typechange conflict in a
+ * parent directory - suppress the error and try to continue.
+ */
+ if ((data->strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) != 0 &&
+ (error == GIT_ENOTFOUND || error == GIT_EEXISTS))
+ {
+ giterr_clear();
+ error = 0;
+ }
+
return error;
}
static int checkout_remove_the_old(
unsigned int *actions,
- checkout_diff_data *data)
+ checkout_data *data)
{
int error = 0;
git_diff_delta *delta;
+ const char *str;
size_t i;
- const char *workdir = git_buf_cstr(data->path);
+ const char *workdir = git_buf_cstr(&data->path);
+ uint32_t flg = GIT_RMDIR_EMPTY_PARENTS |
+ GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS;
- git_buf_truncate(data->path, data->workdir_len);
+ git_buf_truncate(&data->path, data->workdir_len);
git_vector_foreach(&data->diff->deltas, i, delta) {
if (actions[i] & CHECKOUT_ACTION__REMOVE) {
- uint32_t flg = GIT_RMDIR_EMPTY_PARENTS;
- bool empty_only =
- ((actions[i] & CHECKOUT_ACTION__REMOVE_EMPTY) != 0);
-
- if (!empty_only)
- flg |= GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS;
-
error = git_futils_rmdir_r(delta->old_file.path, workdir, flg);
-
- /* ignore error if empty_only, because that just means we lacked
- * info to do the right thing when the action was picked.
- */
- if (error < 0 && !empty_only)
+ if (error < 0)
return error;
data->completed_steps++;
@@ -715,19 +899,57 @@ static int checkout_remove_the_old(
}
}
+ git_vector_foreach(&data->removes, i, str) {
+ error = git_futils_rmdir_r(str, workdir, flg);
+ if (error < 0)
+ return error;
+
+ data->completed_steps++;
+ report_progress(data, str);
+ }
+
+ return 0;
+}
+
+static int checkout_deferred_remove(git_repository *repo, const char *path)
+{
+#if 0
+ int error = git_futils_rmdir_r(
+ path, git_repository_workdir(repo), GIT_RMDIR_EMPTY_PARENTS);
+
+ if (error == GIT_ENOTFOUND) {
+ error = 0;
+ giterr_clear();
+ }
+
+ return error;
+#else
+ GIT_UNUSED(repo);
+ GIT_UNUSED(path);
return 0;
+#endif
}
static int checkout_create_the_new(
unsigned int *actions,
- checkout_diff_data *data)
+ checkout_data *data)
{
+ int error = 0;
git_diff_delta *delta;
size_t i;
git_vector_foreach(&data->diff->deltas, i, delta) {
+ if (actions[i] & CHECKOUT_ACTION__DEFER_REMOVE) {
+ /* this had a blocker directory that should only be removed iff
+ * all of the contents of the directory were safely removed
+ */
+ if ((error = checkout_deferred_remove(
+ data->repo, delta->old_file.path)) < 0)
+ return error;
+ }
+
if (actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) {
- int error = checkout_blob(data, &delta->new_file);
+ error = checkout_blob(data, &delta->new_file);
if (error < 0)
return error;
@@ -741,12 +963,22 @@ static int checkout_create_the_new(
static int checkout_create_submodules(
unsigned int *actions,
- checkout_diff_data *data)
+ checkout_data *data)
{
+ int error = 0;
git_diff_delta *delta;
size_t i;
git_vector_foreach(&data->diff->deltas, i, delta) {
+ if (actions[i] & CHECKOUT_ACTION__DEFER_REMOVE) {
+ /* this has a blocker directory that should only be removed iff
+ * all of the contents of the directory were safely removed
+ */
+ if ((error = checkout_deferred_remove(
+ data->repo, delta->old_file.path)) < 0)
+ return error;
+ }
+
if (actions[i] & CHECKOUT_ACTION__UPDATE_SUBMODULE) {
int error = checkout_submodule(data, &delta->new_file);
if (error < 0)
@@ -760,71 +992,177 @@ static int checkout_create_submodules(
return 0;
}
-static int retrieve_symlink_caps(git_repository *repo, bool *out)
+static int checkout_lookup_head_tree(git_tree **out, git_repository *repo)
+{
+ int error = 0;
+ git_reference *ref = NULL;
+ git_object *head;
+
+ if (!(error = git_repository_head(&ref, repo)) &&
+ !(error = git_reference_peel(&head, ref, GIT_OBJ_TREE)))
+ *out = (git_tree *)head;
+
+ git_reference_free(ref);
+
+ return error;
+}
+
+static void checkout_data_clear(checkout_data *data)
+{
+ if (data->opts_free_baseline) {
+ git_tree_free(data->opts.baseline);
+ data->opts.baseline = NULL;
+ }
+
+ git_vector_free(&data->removes);
+ git_pool_clear(&data->pool);
+
+ git__free(data->pfx);
+ data->pfx = NULL;
+
+ git_buf_free(&data->path);
+}
+
+static int checkout_data_init(
+ checkout_data *data,
+ git_repository *repo,
+ git_checkout_opts *proposed)
{
+ int error = 0;
git_config *cfg;
- int error, can_symlink = 0;
- if (git_repository_config__weakptr(&cfg, repo) < 0)
+ memset(data, 0, sizeof(*data));
+
+ if (!repo) {
+ giterr_set(GITERR_CHECKOUT, "Cannot checkout nothing");
return -1;
+ }
- error = git_config_get_bool(&can_symlink, cfg, "core.symlinks");
+ if ((error = git_repository__ensure_not_bare(repo, "checkout")) < 0)
+ return error;
- /* If "core.symlinks" is not found anywhere, default to true. */
- if (error == GIT_ENOTFOUND) {
- can_symlink = true;
+ if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
+ return error;
+
+ data->repo = repo;
+
+ GITERR_CHECK_VERSION(
+ proposed, GIT_CHECKOUT_OPTS_VERSION, "git_checkout_opts");
+
+ if (!proposed)
+ GIT_INIT_STRUCTURE(&data->opts, GIT_CHECKOUT_OPTS_VERSION);
+ else
+ memmove(&data->opts, proposed, sizeof(git_checkout_opts));
+
+ /* 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)
+ data->opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
+
+ data->strategy = data->opts.checkout_strategy;
+
+ /* opts->disable_filters is false by default */
+
+ if (!data->opts.dir_mode)
+ data->opts.dir_mode = GIT_DIR_MODE;
+
+ if (!data->opts.file_open_flags)
+ data->opts.file_open_flags = O_CREAT | O_TRUNC | O_WRONLY;
+
+ data->pfx = git_pathspec_prefix(&data->opts.paths);
+
+ error = git_config_get_bool(&data->can_symlink, cfg, "core.symlinks");
+ if (error < 0) {
+ if (error != GIT_ENOTFOUND)
+ goto cleanup;
+
+ /* If "core.symlinks" is not found anywhere, default to true. */
+ data->can_symlink = true;
+ giterr_clear();
error = 0;
}
- *out = can_symlink;
+ if (!data->opts.baseline) {
+ data->opts_free_baseline = true;
+ if ((error = checkout_lookup_head_tree(&data->opts.baseline, repo)) < 0)
+ goto cleanup;
+ }
+
+ if ((error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 ||
+ (error = git_pool_init(&data->pool, 1, 0)) < 0 ||
+ (error = git_buf_puts(&data->path, git_repository_workdir(repo))) < 0)
+ goto cleanup;
+
+ data->workdir_len = git_buf_len(&data->path);
+
+cleanup:
+ if (error < 0)
+ checkout_data_clear(data);
return error;
}
-int git_checkout__from_iterators(
- git_iterator *desired,
- git_iterator *expected,
- git_checkout_opts *opts,
- const char *pathspec_pfx)
+int git_checkout_iterator(
+ git_iterator *target,
+ git_checkout_opts *opts)
{
int error = 0;
- checkout_diff_data data;
+ git_iterator *baseline = NULL, *workdir = NULL;
+ checkout_data data = {0};
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
- git_buf workdir = GIT_BUF_INIT;
uint32_t *actions = NULL;
size_t *counts = NULL;
- memset(&data, 0, sizeof(data));
+ /* initialize structures and options */
+ error = checkout_data_init(&data, git_iterator_owner(target), opts);
+ if (error < 0)
+ return error;
- data.repo = git_iterator_owner(desired);
- if (!data.repo) data.repo = git_iterator_owner(expected);
- if (!data.repo) {
- giterr_set(GITERR_CHECKOUT, "Cannot checkout nothing");
- return -1;
+ diff_opts.flags =
+ GIT_DIFF_INCLUDE_UNMODIFIED |
+ GIT_DIFF_INCLUDE_UNTRACKED |
+ GIT_DIFF_RECURSE_UNTRACKED_DIRS | /* needed to match baseline */
+ GIT_DIFF_INCLUDE_IGNORED |
+ GIT_DIFF_INCLUDE_TYPECHANGE |
+ GIT_DIFF_INCLUDE_TYPECHANGE_TREES |
+ GIT_DIFF_SKIP_BINARY_CHECK;
+ if (data.opts.paths.count > 0)
+ diff_opts.pathspec = data.opts.paths;
+
+ /* set up iterators */
+ if ((error = git_iterator_reset(target, data.pfx, data.pfx)) < 0 ||
+ (error = git_iterator_for_workdir_range(
+ &workdir, data.repo, data.pfx, data.pfx)) < 0 ||
+ (error = git_iterator_for_tree_range(
+ &baseline, data.opts.baseline, data.pfx, data.pfx)) < 0)
+ goto cleanup;
+
+ /* Handle case insensitivity for baseline if necessary */
+ if (workdir->ignore_case && !baseline->ignore_case) {
+ if ((error = git_iterator_spoolandsort(
+ &baseline, baseline, git_index_entry__cmp_icase, true)) < 0)
+ goto cleanup;
}
- diff_opts.flags =
- GIT_DIFF_INCLUDE_UNMODIFIED | GIT_DIFF_INCLUDE_UNTRACKED |
- GIT_DIFF_INCLUDE_TYPECHANGE | GIT_DIFF_SKIP_BINARY_CHECK;
- if (opts->paths.count > 0)
- diff_opts.pathspec = opts->paths;
-
- /* By analyzing the cases above, it becomes clear that checkout can work
- * off the diff between the desired and expected trees, instead of using
- * a work dir diff. This should make things somewhat faster...
+ /* Checkout can be driven either off a target-to-workdir diff or a
+ * baseline-to-target diff. There are pros and cons of each.
+ *
+ * Target-to-workdir means the diff includes every file that could be
+ * modified, which simplifies bookkeeping, but the code to constantly
+ * refer back to the baseline gets complicated.
+ *
+ * Baseline-to-target has simpler code because the diff defines the
+ * action to take, but needs special handling for untracked and ignored
+ * files, if they need to be removed.
+ *
+ * I've implemented both versions and opted for the second.
*/
if ((error = git_diff__from_iterators(
- &data.diff, data.repo, expected, desired, &diff_opts)) < 0)
- goto cleanup;
-
- if ((error = git_buf_puts(&workdir, git_repository_workdir(data.repo))) < 0)
+ &data.diff, data.repo, baseline, target, &diff_opts)) < 0)
goto cleanup;
- data.opts = opts;
- data.pfx = pathspec_pfx;
- data.path = &workdir;
- data.workdir_len = git_buf_len(&workdir);
-
/* In order to detect conflicts prior to performing any operations,
* and in order to deal with some order dependencies, checkout is best
* performed with up to four passes through the diff.
@@ -837,16 +1175,13 @@ int git_checkout__from_iterators(
* 3. Then update all submodules in case a new .gitmodules blob was
* checked out during pass #2.
*/
- if ((error = checkout_get_actions(&actions, &counts, &data)) < 0)
+ if ((error = checkout_get_actions(&actions, &counts, &data, workdir)) < 0)
goto cleanup;
data.total_steps = counts[CHECKOUT_ACTION__REMOVE] +
counts[CHECKOUT_ACTION__UPDATE_BLOB] +
counts[CHECKOUT_ACTION__UPDATE_SUBMODULE];
- if ((error = retrieve_symlink_caps(data.repo, &data.can_symlink)) < 0)
- goto cleanup;
-
report_progress(&data, NULL); /* establish 0 baseline */
/* TODO: add ability to update index entries while checking out */
@@ -870,107 +1205,35 @@ cleanup:
giterr_clear();
git_diff_list_free(data.diff);
- git_buf_free(&workdir);
+ git_iterator_free(workdir);
+ git_iterator_free(data.baseline);
git__free(actions);
git__free(counts);
+ checkout_data_clear(&data);
return error;
}
-static int checkout_lookup_head_tree(git_tree **out, git_repository *repo)
-{
- int error = 0;
- git_reference *ref = NULL;
- git_object *head;
-
- if (!(error = git_repository_head(&ref, repo)) &&
- !(error = git_reference_peel(&head, ref, GIT_OBJ_TREE)))
- *out = (git_tree *)head;
-
- git_reference_free(ref);
-
- return error;
-}
-
-static int checkout_normalize_opts(
- git_checkout_opts *normalized,
- char **pfx,
- git_repository *repo,
- git_checkout_opts *proposed)
-{
- assert(normalized);
-
- GITERR_CHECK_VERSION(
- proposed, GIT_CHECKOUT_OPTS_VERSION, "git_checkout_opts");
-
- if (!proposed)
- GIT_INIT_STRUCTURE(normalized, GIT_CHECKOUT_OPTS_VERSION);
- else
- memmove(normalized, proposed, sizeof(git_checkout_opts));
-
- /* if you are forcing, definitely allow safe updates */
-
- if ((normalized->checkout_strategy & GIT_CHECKOUT_FORCE) != 0)
- normalized->checkout_strategy |= GIT_CHECKOUT_SAFE_CREATE;
- if ((normalized->checkout_strategy & GIT_CHECKOUT_SAFE_CREATE) != 0)
- normalized->checkout_strategy |= GIT_CHECKOUT_SAFE;
-
- /* opts->disable_filters is false by default */
-
- if (!normalized->dir_mode)
- normalized->dir_mode = GIT_DIR_MODE;
-
- if (!normalized->file_open_flags)
- normalized->file_open_flags = O_CREAT | O_TRUNC | O_WRONLY;
-
- if (pfx)
- *pfx = git_pathspec_prefix(&normalized->paths);
-
- if (!normalized->baseline) {
- normalized->checkout_strategy |= GIT_CHECKOUT__FREE_BASELINE;
-
- return checkout_lookup_head_tree(&normalized->baseline, repo);
- }
-
- return 0;
-}
-
-static void checkout_cleanup_opts(git_checkout_opts *opts)
-{
- if ((opts->checkout_strategy & GIT_CHECKOUT__FREE_BASELINE) != 0)
- git_tree_free(opts->baseline);
-}
-
int git_checkout_index(
git_repository *repo,
git_index *index,
git_checkout_opts *opts)
{
int error;
- git_checkout_opts co_opts;
- git_iterator *base_i, *index_i;
- char *pfx;
-
- assert(repo);
-
- GITERR_CHECK_VERSION(opts, GIT_CHECKOUT_OPTS_VERSION, "git_checkout_opts");
+ git_iterator *index_i;
if ((error = git_repository__ensure_not_bare(repo, "checkout index")) < 0)
return error;
if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0)
return error;
+ GIT_REFCOUNT_INC(index);
- if (!(error = checkout_normalize_opts(&co_opts, &pfx, repo, opts)) &&
- !(error = git_iterator_for_tree_range(
- &base_i, co_opts.baseline, pfx, pfx)) &&
- !(error = git_iterator_for_index_range(&index_i, index, pfx, pfx)))
- error = git_checkout__from_iterators(index_i, base_i, &co_opts, pfx);
+ if (!(error = git_iterator_for_index(&index_i, index)))
+ error = git_checkout_iterator(index_i, opts);
- git__free(pfx);
git_iterator_free(index_i);
- git_iterator_free(base_i);
- checkout_cleanup_opts(&co_opts);
+ git_index_free(index);
return error;
}
@@ -981,12 +1244,8 @@ int git_checkout_tree(
git_checkout_opts *opts)
{
int error;
- git_checkout_opts co_opts;
- git_tree *tree;
- git_iterator *tree_i, *base_i;
- char *pfx;
-
- assert(repo);
+ git_tree *tree = NULL;
+ git_iterator *tree_i = NULL;
if ((error = git_repository__ensure_not_bare(repo, "checkout tree")) < 0)
return error;
@@ -997,17 +1256,11 @@ int git_checkout_tree(
return -1;
}
- if (!(error = checkout_normalize_opts(&co_opts, &pfx, repo, opts)) &&
- !(error = git_iterator_for_tree_range(
- &base_i, co_opts.baseline, pfx, pfx)) &&
- !(error = git_iterator_for_tree_range(&tree_i, tree, pfx, pfx)))
- error = git_checkout__from_iterators(tree_i, base_i, &co_opts, pfx);
+ if (!(error = git_iterator_for_tree(&tree_i, tree)))
+ error = git_checkout_iterator(tree_i, opts);
- git__free(pfx);
git_iterator_free(tree_i);
- git_iterator_free(base_i);
git_tree_free(tree);
- checkout_cleanup_opts(&co_opts);
return error;
}
@@ -1017,30 +1270,18 @@ int git_checkout_head(
git_checkout_opts *opts)
{
int error;
- git_checkout_opts co_opts;
- git_tree *head;
- git_iterator *i1, *i2;
- char *pfx;
-
- assert(repo);
+ git_tree *head = NULL;
+ git_iterator *head_i = NULL;
if ((error = git_repository__ensure_not_bare(repo, "checkout head")) < 0)
return error;
- if ((error = checkout_lookup_head_tree(&head, repo)) < 0)
- return error;
-
- if (!(error = checkout_normalize_opts(&co_opts, &pfx, repo, opts)) &&
- !(error = git_iterator_for_tree_range(
- &i1, co_opts.baseline, pfx, pfx)) &&
- !(error = git_iterator_for_tree_range(&i2, head, pfx, pfx)))
- error = git_checkout__from_iterators(i1, i2, &co_opts, pfx);
+ if (!(error = checkout_lookup_head_tree(&head, repo)) &&
+ !(error = git_iterator_for_tree(&head_i, head)))
+ error = git_checkout_iterator(head_i, opts);
- git__free(pfx);
- git_iterator_free(i1);
- git_iterator_free(i2);
+ git_iterator_free(head_i);
git_tree_free(head);
- checkout_cleanup_opts(&co_opts);
return error;
}