diff options
Diffstat (limited to 'builtin/checkout.c')
-rw-r--r-- | builtin/checkout.c | 146 |
1 files changed, 94 insertions, 52 deletions
diff --git a/builtin/checkout.c b/builtin/checkout.c index 07cf555309..e1403bec27 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1,5 +1,5 @@ -#include "cache.h" #include "builtin.h" +#include "lockfile.h" #include "parse-options.h" #include "refs.h" #include "commit.h" @@ -19,11 +19,10 @@ #include "ll-merge.h" #include "resolve-undo.h" #include "submodule.h" -#include "argv-array.h" static const char * const checkout_usage[] = { - N_("git checkout [options] <branch>"), - N_("git checkout [options] [<branch>] -- <file>..."), + N_("git checkout [<options>] <branch>"), + N_("git checkout [<options>] [<branch>] -- <file>..."), NULL, }; @@ -36,6 +35,7 @@ struct checkout_opts { int writeout_stage; int overwrite_ignore; int ignore_skipworktree; + int ignore_other_worktrees; const char *new_branch; const char *new_branch_force; @@ -62,23 +62,41 @@ static int post_checkout_hook(struct commit *old, struct commit *new, } -static int update_some(const unsigned char *sha1, const char *base, int baselen, +static int update_some(const unsigned char *sha1, struct strbuf *base, const char *pathname, unsigned mode, int stage, void *context) { int len; struct cache_entry *ce; + int pos; if (S_ISDIR(mode)) return READ_TREE_RECURSIVE; - len = baselen + strlen(pathname); + len = base->len + strlen(pathname); ce = xcalloc(1, cache_entry_size(len)); hashcpy(ce->sha1, sha1); - memcpy(ce->name, base, baselen); - memcpy(ce->name + baselen, pathname, len - baselen); + 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 = cache_name_pos(ce->name, ce->ce_namelen); + if (pos >= 0) { + struct cache_entry *old = active_cache[pos]; + if (ce->ce_mode == old->ce_mode && + !hashcmp(ce->sha1, old->sha1)) { + old->ce_flags |= CE_UPDATE; + free(ce); + return 0; + } + } + add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); return 0; } @@ -225,7 +243,6 @@ static int checkout_paths(const struct checkout_opts *opts, int flag; struct commit *head; int errs = 0; - int newfd; struct lock_file *lock_file; if (opts->track != BRANCH_TRACK_UNSPECIFIED) @@ -256,7 +273,7 @@ static int checkout_paths(const struct checkout_opts *opts, lock_file = xcalloc(1, sizeof(struct lock_file)); - newfd = hold_locked_index(lock_file, 1); + hold_locked_index(lock_file, 1); if (read_cache_preload(&opts->pathspec) < 0) return error(_("corrupt index file")); @@ -337,6 +354,7 @@ static int checkout_paths(const struct checkout_opts *opts, memset(&state, 0, sizeof(state)); state.force = 1; state.refresh_cache = 1; + state.istate = &the_index; for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; if (ce->ce_flags & CE_MATCHED) { @@ -352,11 +370,10 @@ static int checkout_paths(const struct checkout_opts *opts, } } - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(lock_file)) + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); - read_ref_full("HEAD", rev, 0, &flag); + read_ref_full("HEAD", 0, rev, &flag); head = lookup_commit_reference_gently(rev, 1); errs |= post_checkout_hook(head, head, 0); @@ -424,6 +441,11 @@ struct branch_info { const char *name; /* The short name used */ const char *path; /* The full name of a real branch */ struct commit *commit; /* The named commit */ + /* + * if not null the branch is detached because it's already + * checked out in this checkout + */ + char *checkout; }; static void setup_branch_path(struct branch_info *branch) @@ -444,8 +466,8 @@ static int merge_working_tree(const struct checkout_opts *opts, { int ret; struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - int newfd = hold_locked_index(lock_file, 1); + hold_locked_index(lock_file, 1); if (read_cache_preload(NULL) < 0) return error(_("corrupt index file")); @@ -553,8 +575,13 @@ static int merge_working_tree(const struct checkout_opts *opts, } } - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(lock_file)) + if (!active_cache_tree) + active_cache_tree = cache_tree(); + + if (!cache_tree_fully_valid(active_cache_tree)) + cache_tree_update(&the_index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR); + + if (write_locked_index(&the_index, lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); if (!opts->force && !opts->quiet) @@ -583,19 +610,20 @@ static void update_refs_for_switch(const struct checkout_opts *opts, if (opts->new_branch) { if (opts->new_orphan_branch) { if (opts->new_branch_log && !log_all_ref_updates) { - int temp; - char log_file[PATH_MAX]; - char *ref_name = mkpath("refs/heads/%s", opts->new_orphan_branch); - - temp = log_all_ref_updates; - log_all_ref_updates = 1; - if (log_ref_setup(ref_name, log_file, sizeof(log_file))) { - fprintf(stderr, _("Can not do reflog for '%s'\n"), - opts->new_orphan_branch); - log_all_ref_updates = temp; + int ret; + char *refname; + struct strbuf err = STRBUF_INIT; + + refname = mkpathdup("refs/heads/%s", opts->new_orphan_branch); + ret = safe_create_reflog(refname, 1, &err); + free(refname); + if (ret) { + fprintf(stderr, _("Can not do reflog for '%s': %s\n"), + opts->new_orphan_branch, err.buf); + strbuf_release(&err); return; } - log_all_ref_updates = temp; + strbuf_release(&err); } } else @@ -624,7 +652,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts, /* Nothing to do. */ } else if (opts->force_detach || !new->path) { /* No longer on any branch. */ update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL, - REF_NODEREF, DIE_ON_ERR); + REF_NODEREF, UPDATE_REFS_DIE_ON_ERR); if (!opts->quiet) { if (old->path && advice_detached_head) detach_advice(new->name); @@ -651,12 +679,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts, } } if (old->path && old->name) { - char log_file[PATH_MAX], ref_file[PATH_MAX]; - - git_snpath(log_file, sizeof(log_file), "logs/%s", old->path); - git_snpath(ref_file, sizeof(ref_file), "%s", old->path); - if (!file_exists(ref_file) && file_exists(log_file)) - remove_path(log_file); + if (!ref_exists(old->path) && reflog_exists(old->path)) + delete_reflog(old->path); } } remove_branch_state(); @@ -667,10 +691,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts, } static int add_pending_uninteresting_ref(const char *refname, - const unsigned char *sha1, + const struct object_id *oid, int flags, void *cb_data) { - add_pending_sha1(cb_data, refname, sha1, UNINTERESTING); + add_pending_sha1(cb_data, refname, oid->hash, UNINTERESTING); return 0; } @@ -725,10 +749,17 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs) if (advice_detached_head) fprintf(stderr, - _( + Q_( + /* The singular version */ + "If you want to keep it by creating a new branch, " + "this may be a good time\nto do so with:\n\n" + " git branch <new-branch-name> %s\n\n", + /* The plural version */ "If you want to keep them by creating a new branch, " "this may be a good time\nto do so with:\n\n" - " git branch new_branch_name %s\n\n"), + " git branch <new-branch-name> %s\n\n", + /* Give ngettext() the count */ + lost), find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); } @@ -775,13 +806,13 @@ static int switch_branches(const struct checkout_opts *opts, unsigned char rev[20]; int flag, writeout_error = 0; memset(&old, 0, sizeof(old)); - old.path = path_to_free = resolve_refdup("HEAD", rev, 0, &flag); + old.path = path_to_free = resolve_refdup("HEAD", 0, rev, &flag); old.commit = lookup_commit_reference_gently(rev, 1); if (!(flag & REF_ISSYMREF)) old.path = NULL; - if (old.path && starts_with(old.path, "refs/heads/")) - old.name = old.path + strlen("refs/heads/"); + if (old.path) + skip_prefix(old.path, "refs/heads/", &old.name); if (!new->name) { new->name = "HEAD"; @@ -865,10 +896,11 @@ static const char *unique_tracking_name(const char *name, unsigned char *sha1) static int parse_branchname_arg(int argc, const char **argv, int dwim_new_local_branch_ok, struct branch_info *new, - struct tree **source_tree, - unsigned char rev[20], - const char **new_branch) + struct checkout_opts *opts, + unsigned char rev[20]) { + struct tree **source_tree = &opts->source_tree; + const char **new_branch = &opts->new_branch; int argcount = 0; unsigned char branch_rev[20]; const char *arg; @@ -1068,11 +1100,22 @@ static int checkout_branch(struct checkout_opts *opts, die(_("Cannot switch branch to a non-commit '%s'"), new->name); + if (new->path && !opts->force_detach && !opts->new_branch && + !opts->ignore_other_worktrees) { + unsigned char sha1[20]; + int flag; + char *head_ref = resolve_refdup("HEAD", 0, sha1, &flag); + if (head_ref && + (!(flag & REF_ISSYMREF) || strcmp(head_ref, new->path))) + die_if_checked_out(new->path); + free(head_ref); + } + if (!new->commit && opts->new_branch) { unsigned char rev[20]; int flag; - if (!read_ref_full("HEAD", rev, 0, &flag) && + if (!read_ref_full("HEAD", 0, rev, &flag) && (flag & REF_ISSYMREF) && is_null_sha1(rev)) return switch_unborn_to_new_branch(opts); } @@ -1109,7 +1152,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree, N_("do not limit pathspecs to sparse entries only")), OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch, - N_("second guess 'git checkout no-such-branch'")), + N_("second guess 'git checkout <no-such-branch>'")), + OPT_BOOL(0, "ignore-other-worktrees", &opts.ignore_other_worktrees, + N_("do not check if another worktree is holding the given ref")), OPT_END(), }; @@ -1150,10 +1195,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) const char *argv0 = argv[0]; if (!argc || !strcmp(argv0, "--")) die (_("--track needs a branch name")); - if (starts_with(argv0, "refs/")) - argv0 += 5; - if (starts_with(argv0, "remotes/")) - argv0 += 8; + skip_prefix(argv0, "refs/", &argv0); + skip_prefix(argv0, "remotes/", &argv0); argv0 = strchr(argv0, '/'); if (!argv0 || !argv0[1]) die (_("Missing branch name; try -b")); @@ -1181,8 +1224,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) opts.track == BRANCH_TRACK_UNSPECIFIED && !opts.new_branch; int n = parse_branchname_arg(argc, argv, dwim_ok, - &new, &opts.source_tree, - rev, &opts.new_branch); + &new, &opts, rev); argv += n; argc -= n; } |