summaryrefslogtreecommitdiff
path: root/builtin/checkout.c
diff options
context:
space:
mode:
Diffstat (limited to 'builtin/checkout.c')
-rw-r--r--builtin/checkout.c185
1 files changed, 119 insertions, 66 deletions
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 07cf555309..e8110a9243 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"
@@ -18,12 +18,12 @@
#include "xdiff-interface.h"
#include "ll-merge.h"
#include "resolve-undo.h"
+#include "submodule-config.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 +36,8 @@ struct checkout_opts {
int writeout_stage;
int overwrite_ignore;
int ignore_skipworktree;
+ int ignore_other_worktrees;
+ int show_progress;
const char *new_branch;
const char *new_branch_force;
@@ -54,31 +56,49 @@ static int post_checkout_hook(struct commit *old, struct commit *new,
int changed)
{
return run_hook_le(NULL, "post-checkout",
- sha1_to_hex(old ? old->object.sha1 : null_sha1),
- sha1_to_hex(new ? new->object.sha1 : null_sha1),
+ sha1_to_hex(old ? old->object.oid.hash : null_sha1),
+ sha1_to_hex(new ? new->object.oid.hash : null_sha1),
changed ? "1" : "0", NULL);
/* "new" can be NULL when checking out from the index before
a commit exists. */
}
-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 +245,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,14 +275,14 @@ 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"));
if (opts->source_tree)
read_tree_some(opts->source_tree, &opts->pathspec);
- ps_matched = xcalloc(1, opts->pathspec.nr);
+ ps_matched = xcalloc(opts->pathspec.nr, 1);
/*
* Make sure all pathspecs participated in locating the paths
@@ -337,6 +356,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 +372,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);
@@ -382,7 +401,7 @@ static void describe_detached_head(const char *msg, struct commit *commit)
if (!parse_commit(commit))
pp_commit_easy(CMIT_FMT_ONELINE, commit, &sb);
fprintf(stderr, "%s %s... %s\n", msg,
- find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf);
+ find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV), sb.buf);
strbuf_release(&sb);
}
@@ -399,7 +418,7 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o,
opts.reset = 1;
opts.merge = 1;
opts.fn = oneway_merge;
- opts.verbose_update = !o->quiet && isatty(2);
+ opts.verbose_update = o->show_progress;
opts.src_index = &the_index;
opts.dst_index = &the_index;
parse_tree(tree);
@@ -424,6 +443,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 +468,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"));
@@ -478,7 +502,7 @@ static int merge_working_tree(const struct checkout_opts *opts,
topts.update = 1;
topts.merge = 1;
topts.gently = opts->merge && old->commit;
- topts.verbose_update = !opts->quiet && isatty(2);
+ topts.verbose_update = opts->show_progress;
topts.fn = twoway_merge;
if (opts->overwrite_ignore) {
topts.dir = xcalloc(1, sizeof(*topts.dir));
@@ -486,10 +510,10 @@ static int merge_working_tree(const struct checkout_opts *opts,
setup_standard_excludes(topts.dir);
}
tree = parse_tree_indirect(old->commit ?
- old->commit->object.sha1 :
+ old->commit->object.oid.hash :
EMPTY_TREE_SHA1_BIN);
init_tree_desc(&trees[0], tree->buffer, tree->size);
- tree = parse_tree_indirect(new->commit->object.sha1);
+ tree = parse_tree_indirect(new->commit->object.oid.hash);
init_tree_desc(&trees[1], tree->buffer, tree->size);
ret = unpack_trees(2, trees, &topts);
@@ -553,8 +577,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 +612,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
@@ -611,7 +641,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
old_desc = old->name;
if (!old_desc && old->commit)
- old_desc = sha1_to_hex(old->commit->object.sha1);
+ old_desc = oid_to_hex(&old->commit->object.oid);
reflog_msg = getenv("GIT_REFLOG_ACTION");
if (!reflog_msg)
@@ -623,8 +653,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
if (!strcmp(new->name, "HEAD") && !new->path && !opts->force_detach) {
/* 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);
+ update_ref(msg.buf, "HEAD", new->commit->object.oid.hash, NULL,
+ REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
if (!opts->quiet) {
if (old->path && advice_detached_head)
detach_advice(new->name);
@@ -651,12 +681,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 +693,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;
}
@@ -678,7 +704,7 @@ static void describe_one_orphan(struct strbuf *sb, struct commit *commit)
{
strbuf_addstr(sb, " ");
strbuf_addstr(sb,
- find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
+ find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV));
strbuf_addch(sb, ' ');
if (!parse_commit(commit))
pp_commit_easy(CMIT_FMT_ONELINE, commit, sb);
@@ -725,11 +751,18 @@ 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"),
- find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV));
+ " git branch <new-branch-name> %s\n\n",
+ /* Give ngettext() the count */
+ lost),
+ find_unique_abbrev(commit->object.oid.hash, DEFAULT_ABBREV));
}
/*
@@ -747,10 +780,10 @@ static void orphaned_commit_warning(struct commit *old, struct commit *new)
setup_revisions(0, NULL, &revs, NULL);
object->flags &= ~UNINTERESTING;
- add_pending_object(&revs, object, sha1_to_hex(object->sha1));
+ add_pending_object(&revs, object, oid_to_hex(&object->oid));
for_each_ref(add_pending_uninteresting_ref, &revs);
- add_pending_sha1(&revs, "HEAD", new->object.sha1, UNINTERESTING);
+ add_pending_sha1(&revs, "HEAD", new->object.oid.hash, UNINTERESTING);
refs = revs.pending;
revs.leak_pending = 1;
@@ -775,13 +808,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 +898,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 +1102,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 +1154,10 @@ 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_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")),
OPT_END(),
};
@@ -1117,6 +1165,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
memset(&new, 0, sizeof(new));
opts.overwrite_ignore = 1;
opts.prefix = prefix;
+ opts.show_progress = -1;
gitmodules_config();
git_config(git_checkout_config, &opts);
@@ -1126,6 +1175,13 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, checkout_usage,
PARSE_OPT_KEEP_DASHDASH);
+ if (opts.show_progress < 0) {
+ if (opts.quiet)
+ opts.show_progress = 0;
+ else
+ opts.show_progress = isatty(2);
+ }
+
if (conflict_style) {
opts.merge = 1; /* implied */
git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
@@ -1150,10 +1206,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 +1235,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;
}