From 4dbdc42d9e7c3968ff7f690d00680419c9b8cb0f Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Tue, 9 Apr 2024 15:34:27 +0200 Subject: Adding upstream version 1:2.43.0. Signed-off-by: Daniel Baumann --- builtin/checkout.c | 2007 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2007 insertions(+) create mode 100644 builtin/checkout.c (limited to 'builtin/checkout.c') diff --git a/builtin/checkout.c b/builtin/checkout.c new file mode 100644 index 0000000..f02434b --- /dev/null +++ b/builtin/checkout.c @@ -0,0 +1,2007 @@ +#define USE_THE_INDEX_VARIABLE +#include "builtin.h" +#include "advice.h" +#include "blob.h" +#include "branch.h" +#include "cache-tree.h" +#include "checkout.h" +#include "commit.h" +#include "config.h" +#include "diff.h" +#include "dir.h" +#include "environment.h" +#include "gettext.h" +#include "hex.h" +#include "hook.h" +#include "merge-ll.h" +#include "lockfile.h" +#include "mem-pool.h" +#include "merge-recursive.h" +#include "object-name.h" +#include "object-store-ll.h" +#include "parse-options.h" +#include "path.h" +#include "preload-index.h" +#include "read-cache.h" +#include "refs.h" +#include "remote.h" +#include "resolve-undo.h" +#include "revision.h" +#include "run-command.h" +#include "setup.h" +#include "submodule.h" +#include "submodule-config.h" +#include "symlinks.h" +#include "trace2.h" +#include "tree.h" +#include "tree-walk.h" +#include "unpack-trees.h" +#include "wt-status.h" +#include "xdiff-interface.h" +#include "entry.h" +#include "parallel-checkout.h" +#include "add-interactive.h" + +static const char * const checkout_usage[] = { + N_("git checkout [] "), + N_("git checkout [] [] -- ..."), + NULL, +}; + +static const char * const switch_branch_usage[] = { + N_("git switch [] []"), + NULL, +}; + +static const char * const restore_usage[] = { + N_("git restore [] [--source=] ..."), + NULL, +}; + +struct checkout_opts { + int patch_mode; + int quiet; + int merge; + int force; + int force_detach; + int implicit_detach; + int writeout_stage; + int overwrite_ignore; + int ignore_skipworktree; + int ignore_other_worktrees; + int show_progress; + int count_checkout_paths; + int overlay_mode; + int dwim_new_local_branch; + int discard_changes; + int accept_ref; + int accept_pathspec; + int switch_branch_doing_nothing_is_ok; + int only_merge_on_switching_branches; + int can_switch_when_in_progress; + int orphan_from_empty_tree; + int empty_pathspec_ok; + int checkout_index; + int checkout_worktree; + const char *ignore_unmerged_opt; + int ignore_unmerged; + int pathspec_file_nul; + char *pathspec_from_file; + + const char *new_branch; + const char *new_branch_force; + const char *new_orphan_branch; + int new_branch_log; + enum branch_track track; + struct diff_options diff_options; + char *conflict_style; + + int branch_exists; + const char *prefix; + struct pathspec pathspec; + const char *from_treeish; + struct tree *source_tree; +}; + +struct branch_info { + char *name; /* The short name used */ + char *path; /* The full name of a real branch */ + struct commit *commit; /* The named commit */ + char *refname; /* The full name of the ref being checked out. */ + struct object_id oid; /* The object ID of the commit being checked out. */ + /* + * if not null the branch is detached because it's already + * checked out in this checkout + */ + char *checkout; +}; + +static void branch_info_release(struct branch_info *info) +{ + free(info->name); + free(info->path); + free(info->refname); + free(info->checkout); +} + +static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit, + int changed) +{ + return run_hooks_l("post-checkout", + oid_to_hex(old_commit ? &old_commit->object.oid : null_oid()), + oid_to_hex(new_commit ? &new_commit->object.oid : null_oid()), + changed ? "1" : "0", NULL); + /* "new_commit" can be NULL when checking out from the index before + a commit exists. */ + +} + +static int update_some(const struct object_id *oid, struct strbuf *base, + const char *pathname, unsigned mode, void *context UNUSED) +{ + int len; + struct cache_entry *ce; + int pos; + + if (S_ISDIR(mode)) + return READ_TREE_RECURSIVE; + + len = base->len + strlen(pathname); + ce = make_empty_cache_entry(&the_index, len); + oidcpy(&ce->oid, oid); + memcpy(ce->name, base->buf, base->len); + memcpy(ce->name + base->len, pathname, len - base->len); + ce->ce_flags = create_ce_flags(0) | CE_UPDATE; + ce->ce_namelen = len; + ce->ce_mode = create_ce_mode(mode); + + /* + * If the entry is the same as the current index, we can leave the old + * entry in place. Whether it is UPTODATE or not, checkout_entry will + * do the right thing. + */ + pos = index_name_pos(&the_index, ce->name, ce->ce_namelen); + if (pos >= 0) { + struct cache_entry *old = the_index.cache[pos]; + if (ce->ce_mode == old->ce_mode && + !ce_intent_to_add(old) && + oideq(&ce->oid, &old->oid)) { + old->ce_flags |= CE_UPDATE; + discard_cache_entry(ce); + return 0; + } + } + + add_index_entry(&the_index, ce, + ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); + return 0; +} + +static int read_tree_some(struct tree *tree, const struct pathspec *pathspec) +{ + read_tree(the_repository, tree, + pathspec, update_some, NULL); + + /* update the index with the given tree's info + * for all args, expanding wildcards, and exit + * with any non-zero return code. + */ + return 0; +} + +static int skip_same_name(const struct cache_entry *ce, int pos) +{ + while (++pos < the_index.cache_nr && + !strcmp(the_index.cache[pos]->name, ce->name)) + ; /* skip */ + return pos; +} + +static int check_stage(int stage, const struct cache_entry *ce, int pos, + int overlay_mode) +{ + while (pos < the_index.cache_nr && + !strcmp(the_index.cache[pos]->name, ce->name)) { + if (ce_stage(the_index.cache[pos]) == stage) + return 0; + pos++; + } + if (!overlay_mode) + return 0; + if (stage == 2) + return error(_("path '%s' does not have our version"), ce->name); + else + return error(_("path '%s' does not have their version"), ce->name); +} + +static int check_stages(unsigned stages, const struct cache_entry *ce, int pos) +{ + unsigned seen = 0; + const char *name = ce->name; + + while (pos < the_index.cache_nr) { + ce = the_index.cache[pos]; + if (strcmp(name, ce->name)) + break; + seen |= (1 << ce_stage(ce)); + pos++; + } + if ((stages & seen) != stages) + return error(_("path '%s' does not have all necessary versions"), + name); + return 0; +} + +static int checkout_stage(int stage, const struct cache_entry *ce, int pos, + const struct checkout *state, int *nr_checkouts, + int overlay_mode) +{ + while (pos < the_index.cache_nr && + !strcmp(the_index.cache[pos]->name, ce->name)) { + if (ce_stage(the_index.cache[pos]) == stage) + return checkout_entry(the_index.cache[pos], state, + NULL, nr_checkouts); + pos++; + } + if (!overlay_mode) { + unlink_entry(ce, NULL); + return 0; + } + if (stage == 2) + return error(_("path '%s' does not have our version"), ce->name); + else + return error(_("path '%s' does not have their version"), ce->name); +} + +static int checkout_merged(int pos, const struct checkout *state, + int *nr_checkouts, struct mem_pool *ce_mem_pool) +{ + struct cache_entry *ce = the_index.cache[pos]; + const char *path = ce->name; + mmfile_t ancestor, ours, theirs; + enum ll_merge_result merge_status; + int status; + struct object_id oid; + mmbuffer_t result_buf; + struct object_id threeway[3]; + unsigned mode = 0; + struct ll_merge_options ll_opts; + int renormalize = 0; + + memset(threeway, 0, sizeof(threeway)); + while (pos < the_index.cache_nr) { + int stage; + stage = ce_stage(ce); + if (!stage || strcmp(path, ce->name)) + break; + oidcpy(&threeway[stage - 1], &ce->oid); + if (stage == 2) + mode = create_ce_mode(ce->ce_mode); + pos++; + ce = the_index.cache[pos]; + } + if (is_null_oid(&threeway[1]) || is_null_oid(&threeway[2])) + return error(_("path '%s' does not have necessary versions"), path); + + read_mmblob(&ancestor, &threeway[0]); + read_mmblob(&ours, &threeway[1]); + read_mmblob(&theirs, &threeway[2]); + + memset(&ll_opts, 0, sizeof(ll_opts)); + git_config_get_bool("merge.renormalize", &renormalize); + ll_opts.renormalize = renormalize; + merge_status = ll_merge(&result_buf, path, &ancestor, "base", + &ours, "ours", &theirs, "theirs", + state->istate, &ll_opts); + free(ancestor.ptr); + free(ours.ptr); + free(theirs.ptr); + if (merge_status == LL_MERGE_BINARY_CONFLICT) + warning("Cannot merge binary files: %s (%s vs. %s)", + path, "ours", "theirs"); + if (merge_status < 0 || !result_buf.ptr) { + free(result_buf.ptr); + return error(_("path '%s': cannot merge"), path); + } + + /* + * NEEDSWORK: + * There is absolutely no reason to write this as a blob object + * and create a phony cache entry. This hack is primarily to get + * to the write_entry() machinery that massages the contents to + * work-tree format and writes out which only allows it for a + * cache entry. The code in write_entry() needs to be refactored + * to allow us to feed a instead of a cache + * entry. Such a refactoring would help merge_recursive as well + * (it also writes the merge result to the object database even + * when it may contain conflicts). + */ + if (write_object_file(result_buf.ptr, result_buf.size, OBJ_BLOB, &oid)) + die(_("Unable to add merge result for '%s'"), path); + free(result_buf.ptr); + ce = make_transient_cache_entry(mode, &oid, path, 2, ce_mem_pool); + if (!ce) + die(_("make_cache_entry failed for path '%s'"), path); + status = checkout_entry(ce, state, NULL, nr_checkouts); + return status; +} + +static void mark_ce_for_checkout_overlay(struct cache_entry *ce, + char *ps_matched, + const struct checkout_opts *opts) +{ + ce->ce_flags &= ~CE_MATCHED; + if (!opts->ignore_skipworktree && ce_skip_worktree(ce)) + return; + if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) + /* + * "git checkout tree-ish -- path", but this entry + * is in the original index but is not in tree-ish + * or does not match the pathspec; it will not be + * checked out to the working tree. We will not do + * anything to this entry at all. + */ + return; + /* + * Either this entry came from the tree-ish we are + * checking the paths out of, or we are checking out + * of the index. + * + * If it comes from the tree-ish, we already know it + * matches the pathspec and could just stamp + * CE_MATCHED to it from update_some(). But we still + * need ps_matched and read_tree (and + * eventually tree_entry_interesting) cannot fill + * ps_matched yet. Once it can, we can avoid calling + * match_pathspec() for _all_ entries when + * opts->source_tree != NULL. + */ + if (ce_path_match(&the_index, ce, &opts->pathspec, ps_matched)) + ce->ce_flags |= CE_MATCHED; +} + +static void mark_ce_for_checkout_no_overlay(struct cache_entry *ce, + char *ps_matched, + const struct checkout_opts *opts) +{ + ce->ce_flags &= ~CE_MATCHED; + if (!opts->ignore_skipworktree && ce_skip_worktree(ce)) + return; + if (ce_path_match(&the_index, ce, &opts->pathspec, ps_matched)) { + ce->ce_flags |= CE_MATCHED; + if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) + /* + * In overlay mode, but the path is not in + * tree-ish, which means we should remove it + * from the index and the working tree. + */ + ce->ce_flags |= CE_REMOVE | CE_WT_REMOVE; + } +} + +static int checkout_worktree(const struct checkout_opts *opts, + const struct branch_info *info) +{ + struct checkout state = CHECKOUT_INIT; + int nr_checkouts = 0, nr_unmerged = 0; + int errs = 0; + int pos; + int pc_workers, pc_threshold; + struct mem_pool ce_mem_pool; + + state.force = 1; + state.refresh_cache = 1; + state.istate = &the_index; + + mem_pool_init(&ce_mem_pool, 0); + get_parallel_checkout_configs(&pc_workers, &pc_threshold); + init_checkout_metadata(&state.meta, info->refname, + info->commit ? &info->commit->object.oid : &info->oid, + NULL); + + enable_delayed_checkout(&state); + + if (pc_workers > 1) + init_parallel_checkout(); + + for (pos = 0; pos < the_index.cache_nr; pos++) { + struct cache_entry *ce = the_index.cache[pos]; + if (ce->ce_flags & CE_MATCHED) { + if (!ce_stage(ce)) { + errs |= checkout_entry(ce, &state, + NULL, &nr_checkouts); + continue; + } + if (opts->writeout_stage) + errs |= checkout_stage(opts->writeout_stage, + ce, pos, + &state, + &nr_checkouts, opts->overlay_mode); + else if (opts->merge) + errs |= checkout_merged(pos, &state, + &nr_unmerged, + &ce_mem_pool); + pos = skip_same_name(ce, pos) - 1; + } + } + if (pc_workers > 1) + errs |= run_parallel_checkout(&state, pc_workers, pc_threshold, + NULL, NULL); + mem_pool_discard(&ce_mem_pool, should_validate_cache_entries()); + remove_marked_cache_entries(&the_index, 1); + remove_scheduled_dirs(); + errs |= finish_delayed_checkout(&state, opts->show_progress); + + if (opts->count_checkout_paths) { + if (nr_unmerged) + fprintf_ln(stderr, Q_("Recreated %d merge conflict", + "Recreated %d merge conflicts", + nr_unmerged), + nr_unmerged); + if (opts->source_tree) + fprintf_ln(stderr, Q_("Updated %d path from %s", + "Updated %d paths from %s", + nr_checkouts), + nr_checkouts, + repo_find_unique_abbrev(the_repository, &opts->source_tree->object.oid, + DEFAULT_ABBREV)); + else if (!nr_unmerged || nr_checkouts) + fprintf_ln(stderr, Q_("Updated %d path from the index", + "Updated %d paths from the index", + nr_checkouts), + nr_checkouts); + } + + return errs; +} + +static int checkout_paths(const struct checkout_opts *opts, + const struct branch_info *new_branch_info) +{ + int pos; + static char *ps_matched; + struct object_id rev; + struct commit *head; + int errs = 0; + struct lock_file lock_file = LOCK_INIT; + int checkout_index; + + trace2_cmd_mode(opts->patch_mode ? "patch" : "path"); + + if (opts->track != BRANCH_TRACK_UNSPECIFIED) + die(_("'%s' cannot be used with updating paths"), "--track"); + + if (opts->new_branch_log) + die(_("'%s' cannot be used with updating paths"), "-l"); + + if (opts->ignore_unmerged && opts->patch_mode) + die(_("'%s' cannot be used with updating paths"), + opts->ignore_unmerged_opt); + + if (opts->force_detach) + die(_("'%s' cannot be used with updating paths"), "--detach"); + + if (opts->merge && opts->patch_mode) + die(_("options '%s' and '%s' cannot be used together"), "--merge", "--patch"); + + if (opts->ignore_unmerged && opts->merge) + die(_("options '%s' and '%s' cannot be used together"), + opts->ignore_unmerged_opt, "-m"); + + if (opts->new_branch) + die(_("Cannot update paths and switch to branch '%s' at the same time."), + opts->new_branch); + + if (!opts->checkout_worktree && !opts->checkout_index) + die(_("neither '%s' or '%s' is specified"), + "--staged", "--worktree"); + + if (!opts->checkout_worktree && !opts->from_treeish) + die(_("'%s' must be used when '%s' is not specified"), + "--worktree", "--source"); + + /* + * Reject --staged option to the restore command when combined with + * merge-related options. Use the accept_ref flag to distinguish it + * from the checkout command, which does not accept --staged anyway. + * + * `restore --ours|--theirs --worktree --staged` could mean resolving + * conflicted paths to one side in both the worktree and the index, + * but does not currently. + * + * `restore --merge|--conflict=