From: Junio C Hamano Date: Thu, 30 Jun 2011 00:03:12 +0000 (-0700) Subject: Merge branch 'jc/maint-1.7.3-checkout-describe' X-Git-Tag: v1.7.7-rc0~120 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/b985f2aecaa6358b55d545385d0624d76ff83fb8?hp=-c Merge branch 'jc/maint-1.7.3-checkout-describe' * jc/maint-1.7.3-checkout-describe: checkout -b : correctly detect existing branch --- b985f2aecaa6358b55d545385d0624d76ff83fb8 diff --combined builtin/checkout.c index 015730fbd5,88708d48bf..f152adf9ab --- a/builtin/checkout.c +++ b/builtin/checkout.c @@@ -18,7 -18,6 +18,7 @@@ #include "xdiff-interface.h" #include "ll-merge.h" #include "resolve-undo.h" +#include "submodule.h" static const char * const checkout_usage[] = { "git checkout [options] ", @@@ -30,7 -29,6 +30,7 @@@ struct checkout_opts int quiet; int merge; int force; + int force_detach; int writeout_stage; int writeout_error; @@@ -42,7 -40,6 +42,7 @@@ const char *new_orphan_branch; int new_branch_log; enum branch_track track; + struct diff_options diff_options; }; static int post_checkout_hook(struct commit *old, struct commit *new, @@@ -79,10 -76,7 +79,10 @@@ static int update_some(const unsigned c static int read_tree_some(struct tree *tree, const char **pathspec) { - read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL); + struct pathspec ps; + init_pathspec(&ps, pathspec); + read_tree_recursive(tree, "", 0, 0, &ps, update_some, NULL); + free_pathspec(&ps); /* update the index with the given tree's info * for all args, expanding wildcards, and exit @@@ -107,10 -101,9 +107,10 @@@ static int check_stage(int stage, struc return 0; pos++; } - return error("path '%s' does not have %s version", - ce->name, - (stage == 2) ? "our" : "their"); + 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_all_stages(struct cache_entry *ce, int pos) @@@ -121,7 -114,7 +121,7 @@@ ce_stage(active_cache[pos+1]) != 2 || strcmp(active_cache[pos+2]->name, ce->name) || ce_stage(active_cache[pos+2]) != 3) - return error("path '%s' does not have all three versions", + return error(_("path '%s' does not have all three versions"), ce->name); return 0; } @@@ -135,10 -128,9 +135,10 @@@ static int checkout_stage(int stage, st return checkout_entry(active_cache[pos], state, NULL); pos++; } - return error("path '%s' does not have %s version", - ce->name, - (stage == 2) ? "our" : "their"); + 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, struct checkout *state) @@@ -156,24 -148,20 +156,24 @@@ ce_stage(active_cache[pos+1]) != 2 || strcmp(active_cache[pos+2]->name, path) || ce_stage(active_cache[pos+2]) != 3) - return error("path '%s' does not have all 3 versions", path); + return error(_("path '%s' does not have all 3 versions"), path); read_mmblob(&ancestor, active_cache[pos]->sha1); read_mmblob(&ours, active_cache[pos+1]->sha1); read_mmblob(&theirs, active_cache[pos+2]->sha1); + /* + * NEEDSWORK: re-create conflicts from merges with + * merge.renormalize set, too + */ status = ll_merge(&result_buf, path, &ancestor, "base", - &ours, "ours", &theirs, "theirs", 0); + &ours, "ours", &theirs, "theirs", NULL); free(ancestor.ptr); free(ours.ptr); free(theirs.ptr); if (status < 0 || !result_buf.ptr) { free(result_buf.ptr); - return error("path '%s': cannot merge", path); + return error(_("path '%s': cannot merge"), path); } /* @@@ -190,12 -178,12 +190,12 @@@ */ if (write_sha1_file(result_buf.ptr, result_buf.size, blob_type, sha1)) - die("Unable to add merge result for '%s'", path); + die(_("Unable to add merge result for '%s'"), path); ce = make_cache_entry(create_ce_mode(active_cache[pos+1]->ce_mode), sha1, path, 2, 0); if (!ce) - die("make_cache_entry failed for path '%s'", path); + die(_("make_cache_entry failed for path '%s'"), path); status = checkout_entry(ce, state, NULL); return status; } @@@ -217,7 -205,7 +217,7 @@@ static int checkout_paths(struct tree * newfd = hold_locked_index(lock_file, 1); if (read_cache_preload(pathspec) < 0) - return error("corrupt index file"); + return error(_("corrupt index file")); if (source_tree) read_tree_some(source_tree, pathspec); @@@ -245,14 -233,14 +245,14 @@@ if (!ce_stage(ce)) continue; if (opts->force) { - warning("path '%s' is unmerged", ce->name); + warning(_("path '%s' is unmerged"), ce->name); } else if (stage) { errs |= check_stage(stage, ce, pos); } else if (opts->merge) { errs |= check_all_stages(ce, pos); } else { errs = 1; - error("path '%s' is unmerged", ce->name); + error(_("path '%s' is unmerged"), ce->name); } pos = skip_same_name(ce, pos) - 1; } @@@ -281,7 -269,7 +281,7 @@@ if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(lock_file)) - die("unable to write new index file"); + die(_("unable to write new index file")); resolve_ref("HEAD", rev, 0, &flag); head = lookup_commit_reference_gently(rev, 1); @@@ -290,24 -278,25 +290,24 @@@ return errs; } -static void show_local_changes(struct object *head) +static void show_local_changes(struct object *head, struct diff_options *opts) { struct rev_info rev; /* I think we want full paths, even if we're in a subdirectory. */ init_revisions(&rev, NULL); - rev.abbrev = 0; + rev.diffopt.flags = opts->flags; rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS; if (diff_setup_done(&rev.diffopt) < 0) - die("diff_setup_done failed"); + die(_("diff_setup_done failed")); add_pending_object(&rev, head, NULL); run_diff_index(&rev, 0); } -static void describe_detached_head(char *msg, struct commit *commit) +static void describe_detached_head(const char *msg, struct commit *commit) { struct strbuf sb = STRBUF_INIT; - struct pretty_print_context ctx = {0}; parse_commit(commit); - pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, &ctx); + 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); strbuf_release(&sb); @@@ -371,7 -360,7 +371,7 @@@ static int merge_working_tree(struct ch int newfd = hold_locked_index(lock_file, 1); if (read_cache_preload(NULL) < 0) - return error("corrupt index file"); + return error(_("corrupt index file")); resolve_undo_clear(); if (opts->force) { @@@ -388,12 -377,12 +388,12 @@@ topts.src_index = &the_index; topts.dst_index = &the_index; - topts.msgs.not_uptodate_file = "You have local changes to '%s'; cannot switch branches."; + setup_unpack_trees_porcelain(&topts, "checkout"); refresh_cache(REFRESH_QUIET); if (unmerged_cache()) { - error("you need to resolve your current index first"); + error(_("you need to resolve your current index first")); return 1; } @@@ -409,7 -398,7 +409,7 @@@ topts.dir->exclude_per_dir = ".gitignore"; tree = parse_tree_indirect(old->commit ? old->commit->object.sha1 : - (unsigned char *)EMPTY_TREE_SHA1_BIN); + EMPTY_TREE_SHA1_BIN); init_tree_desc(&trees[0], tree->buffer, tree->size); tree = parse_tree_indirect(new->commit->object.sha1); init_tree_desc(&trees[1], tree->buffer, tree->size); @@@ -448,13 -437,6 +448,13 @@@ */ add_files_to_cache(NULL, NULL, 0); + /* + * NEEDSWORK: carrying over local changes + * when branches have different end-of-line + * normalization (or clean+smudge rules) is + * a pain; plumb in an option to set + * o.renormalize? + */ init_merge_options(&o); o.verbosity = 0; work = write_tree_from_memory(&o); @@@ -475,10 -457,10 +475,10 @@@ if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(lock_file)) - die("unable to write new index file"); + die(_("unable to write new index file")); if (!opts->force && !opts->quiet) - show_local_changes(&new->commit->object); + show_local_changes(&new->commit->object, &opts->diff_options); return 0; } @@@ -524,7 -506,7 +524,7 @@@ static void update_refs_for_switch(stru 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", + fprintf(stderr, _("Can not do reflog for '%s'\n"), opts->new_orphan_branch); log_all_ref_updates = temp; return; @@@ -546,31 -528,16 +546,31 @@@ strbuf_addf(&msg, "checkout: moving from %s to %s", old_desc ? old_desc : "(invalid)", new->name); - if (new->path) { + 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); + if (!opts->quiet) { + if (old->path && advice_detached_head) + detach_advice(old->path, new->name); + describe_detached_head(_("HEAD is now at"), new->commit); + } + } else if (new->path) { /* Switch branches. */ create_symref("HEAD", new->path, msg.buf); if (!opts->quiet) { - if (old->path && !strcmp(new->path, old->path)) - fprintf(stderr, "Already on '%s'\n", + if (old->path && !strcmp(new->path, old->path)) { + fprintf(stderr, _("Already on '%s'\n"), new->name); - else - fprintf(stderr, "Switched to%s branch '%s'\n", - opts->branch_exists ? " and reset" : " a new", + } else if (opts->new_branch) { + if (opts->branch_exists) + fprintf(stderr, _("Switched to and reset branch '%s'\n"), new->name); + else + fprintf(stderr, _("Switched to a new branch '%s'\n"), new->name); + } else { + fprintf(stderr, _("Switched to branch '%s'\n"), new->name); + } } if (old->path && old->name) { char log_file[PATH_MAX], ref_file[PATH_MAX]; @@@ -580,135 -547,21 +580,135 @@@ if (!file_exists(ref_file) && file_exists(log_file)) remove_path(log_file); } - } else if (strcmp(new->name, "HEAD")) { - update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL, - REF_NODEREF, DIE_ON_ERR); - if (!opts->quiet) { - if (old->path && advice_detached_head) - detach_advice(old->path, new->name); - describe_detached_head("HEAD is now at", new->commit); - } } remove_branch_state(); strbuf_release(&msg); - if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD"))) + if (!opts->quiet && + (new->path || (!opts->force_detach && !strcmp(new->name, "HEAD")))) report_tracking(new); } +struct rev_list_args { + int argc; + int alloc; + const char **argv; +}; + +static void add_one_rev_list_arg(struct rev_list_args *args, const char *s) +{ + ALLOC_GROW(args->argv, args->argc + 1, args->alloc); + args->argv[args->argc++] = s; +} + +static int add_one_ref_to_rev_list_arg(const char *refname, + const unsigned char *sha1, + int flags, + void *cb_data) +{ + add_one_rev_list_arg(cb_data, refname); + return 0; +} + +static int clear_commit_marks_from_one_ref(const char *refname, + const unsigned char *sha1, + int flags, + void *cb_data) +{ + struct commit *commit = lookup_commit_reference_gently(sha1, 1); + if (commit) + clear_commit_marks(commit, -1); + return 0; +} + +static void describe_one_orphan(struct strbuf *sb, struct commit *commit) +{ + parse_commit(commit); + strbuf_addstr(sb, " "); + strbuf_addstr(sb, + find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); + strbuf_addch(sb, ' '); + pp_commit_easy(CMIT_FMT_ONELINE, commit, sb); + strbuf_addch(sb, '\n'); +} + +#define ORPHAN_CUTOFF 4 +static void suggest_reattach(struct commit *commit, struct rev_info *revs) +{ + struct commit *c, *last = NULL; + struct strbuf sb = STRBUF_INIT; + int lost = 0; + while ((c = get_revision(revs)) != NULL) { + if (lost < ORPHAN_CUTOFF) + describe_one_orphan(&sb, c); + last = c; + lost++; + } + if (ORPHAN_CUTOFF < lost) { + int more = lost - ORPHAN_CUTOFF; + if (more == 1) + describe_one_orphan(&sb, last); + else + strbuf_addf(&sb, _(" ... and %d more.\n"), more); + } + + fprintf(stderr, + Q_( + /* The singular version */ + "Warning: you are leaving %d commit behind, " + "not connected to\n" + "any of your branches:\n\n" + "%s\n", + /* The plural version */ + "Warning: you are leaving %d commits behind, " + "not connected to\n" + "any of your branches:\n\n" + "%s\n", + /* Give ngettext() the count */ + lost), + lost, + sb.buf); + strbuf_release(&sb); + + if (advice_detached_head) + fprintf(stderr, + _( + "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"), + sha1_to_hex(commit->object.sha1)); +} + +/* + * We are about to leave commit that was at the tip of a detached + * HEAD. If it is not reachable from any ref, this is the last chance + * for the user to do so without resorting to reflog. + */ +static void orphaned_commit_warning(struct commit *commit) +{ + struct rev_list_args args = { 0, 0, NULL }; + struct rev_info revs; + + add_one_rev_list_arg(&args, "(internal)"); + add_one_rev_list_arg(&args, sha1_to_hex(commit->object.sha1)); + add_one_rev_list_arg(&args, "--not"); + for_each_ref(add_one_ref_to_rev_list_arg, &args); + add_one_rev_list_arg(&args, "--"); + add_one_rev_list_arg(&args, NULL); + + init_revisions(&revs, NULL); + if (setup_revisions(args.argc - 1, args.argv, &revs, NULL) != 1) + die(_("internal error: only -- alone should have been left")); + if (prepare_revision_walk(&revs)) + die(_("internal error in revision walk")); + if (!(commit->object.flags & UNINTERESTING)) + suggest_reattach(commit, &revs); + else + describe_detached_head(_("Previous HEAD position was"), commit); + + clear_commit_marks(commit, -1); + for_each_ref(clear_commit_marks_from_one_ref, NULL); +} + static int switch_branches(struct checkout_opts *opts, struct branch_info *new) { int ret = 0; @@@ -728,7 -581,7 +728,7 @@@ new->name = "HEAD"; new->commit = old.commit; if (!new->commit) - die("You are on a branch yet to be born"); + die(_("You are on a branch yet to be born")); parse_commit(new->commit); } @@@ -736,8 -589,13 +736,8 @@@ if (ret) return ret; - /* - * If we were on a detached HEAD, but have now moved to - * a new commit, we want to mention the old commit once more - * to remind the user that it might be lost. - */ if (!opts->quiet && !old.path && old.commit && new->commit != old.commit) - describe_detached_head("Previous HEAD position was", old.commit); + orphaned_commit_warning(old.commit); update_refs_for_switch(opts, &old, new); @@@ -747,16 -605,7 +747,16 @@@ static int git_checkout_config(const char *var, const char *value, void *cb) { - return git_xmerge_config(var, value, cb); + if (!strcmp(var, "diff.ignoresubmodules")) { + struct checkout_opts *opts = cb; + handle_ignore_submodules_arg(&opts->diff_options, value); + return 0; + } + + if (!prefixcmp(var, "submodule.")) + return parse_submodule_config_option(var, value); + + return git_xmerge_config(var, value, NULL); } static int interactive_checkout(const char *revision, const char **pathspec, @@@ -801,145 -650,32 +801,145 @@@ static const char *unique_tracking_name return NULL; } +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) +{ + int argcount = 0; + unsigned char branch_rev[20]; + const char *arg; + int has_dash_dash; + + /* + * case 1: git checkout -- [] + * + * must be a valid tree, everything after the '--' must be + * a path. + * + * case 2: git checkout -- [] + * + * everything after the '--' must be paths. + * + * case 3: git checkout [] + * + * With no paths, if is a commit, that is to + * switch to the branch or detach HEAD at it. As a special case, + * if is A...B (missing A or B means HEAD but you can + * omit at most one side), and if there is a unique merge base + * between A and B, A...B names that merge base. + * + * With no paths, if is _not_ a commit, no -t nor -b + * was given, and there is a tracking branch whose name is + * in one and only one remote, then this is a short-hand + * to fork local from that remote-tracking branch. + * + * Otherwise shall not be ambiguous. + * - If it's *only* a reference, treat it like case (1). + * - If it's only a path, treat it like case (2). + * - else: fail. + * + */ + if (!argc) + return 0; + + if (!strcmp(argv[0], "--")) /* case (2) */ + return 1; + + arg = argv[0]; + has_dash_dash = (argc > 1) && !strcmp(argv[1], "--"); + + if (!strcmp(arg, "-")) + arg = "@{-1}"; + + if (get_sha1_mb(arg, rev)) { + if (has_dash_dash) /* case (1) */ + die(_("invalid reference: %s"), arg); + if (dwim_new_local_branch_ok && + !check_filename(NULL, arg) && + argc == 1) { + const char *remote = unique_tracking_name(arg); + if (!remote || get_sha1(remote, rev)) + return argcount; + *new_branch = arg; + arg = remote; + /* DWIMmed to create local branch */ + } else { + return argcount; + } + } + + /* we can't end up being in (2) anymore, eat the argument */ + argcount++; + argv++; + argc--; + + new->name = arg; + setup_branch_path(new); + + if (check_ref_format(new->path) == CHECK_REF_FORMAT_OK && + resolve_ref(new->path, branch_rev, 1, NULL)) + hashcpy(rev, branch_rev); + else + new->path = NULL; /* not an existing branch */ + + new->commit = lookup_commit_reference_gently(rev, 1); + if (!new->commit) { + /* not a commit */ + *source_tree = parse_tree_indirect(rev); + } else { + parse_commit(new->commit); + *source_tree = new->commit->tree; + } + + if (!*source_tree) /* case (1): want a tree */ + die(_("reference is not a tree: %s"), arg); + if (!has_dash_dash) {/* case (3 -> 1) */ + /* + * Do not complain the most common case + * git checkout branch + * even if there happen to be a file called 'branch'; + * it would be extremely annoying. + */ + if (argc) + verify_non_filename(NULL, arg); + } else { + argcount++; + argv++; + argc--; + } + + return argcount; +} + int cmd_checkout(int argc, const char **argv, const char *prefix) { struct checkout_opts opts; unsigned char rev[20]; - const char *arg; struct branch_info new; struct tree *source_tree = NULL; char *conflict_style = NULL; int patch_mode = 0; int dwim_new_local_branch = 1; struct option options[] = { - OPT__QUIET(&opts.quiet), + OPT__QUIET(&opts.quiet, "suppress progress reporting"), OPT_STRING('b', NULL, &opts.new_branch, "branch", "create and checkout a new branch"), OPT_STRING('B', NULL, &opts.new_branch_force, "branch", "create/reset and checkout a branch"), - OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"), - OPT_SET_INT('t', "track", &opts.track, "track", + OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "create reflog for new branch"), + OPT_BOOLEAN(0, "detach", &opts.force_detach, "detach the HEAD at named commit"), + OPT_SET_INT('t', "track", &opts.track, "set upstream info for new branch", BRANCH_TRACK_EXPLICIT), OPT_STRING(0, "orphan", &opts.new_orphan_branch, "new branch", "new unparented branch"), - OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage", + OPT_SET_INT('2', "ours", &opts.writeout_stage, "checkout our version for unmerged files", 2), - OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage", + OPT_SET_INT('3', "theirs", &opts.writeout_stage, "checkout their version for unmerged files", 3), - OPT_BOOLEAN('f', "force", &opts.force, "force"), - OPT_BOOLEAN('m', "merge", &opts.merge, "merge"), + OPT__FORCE(&opts.force, "force checkout (throw away local modifications)"), + OPT_BOOLEAN('m', "merge", &opts.merge, "perform a 3-way merge with the new branch"), OPT_STRING(0, "conflict", &conflict_style, "style", "conflict style (merge or diff3)"), OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), @@@ -948,12 -684,12 +948,12 @@@ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, OPT_END(), }; - int has_dash_dash; memset(&opts, 0, sizeof(opts)); memset(&new, 0, sizeof(new)); - git_config(git_checkout_config, NULL); + gitmodules_config(); + git_config(git_checkout_config, &opts); opts.track = BRANCH_TRACK_UNSPECIFIED; @@@ -962,42 -698,36 +962,42 @@@ /* we can assume from now on new_branch = !new_branch_force */ if (opts.new_branch && opts.new_branch_force) - die("-B cannot be used with -b"); + die(_("-B cannot be used with -b")); /* copy -B over to -b, so that we can just check the latter */ if (opts.new_branch_force) opts.new_branch = opts.new_branch_force; if (patch_mode && (opts.track > 0 || opts.new_branch - || opts.new_branch_log || opts.merge || opts.force)) - die ("--patch is incompatible with all other options"); + || opts.new_branch_log || opts.merge || opts.force + || opts.force_detach)) + die (_("--patch is incompatible with all other options")); + + if (opts.force_detach && (opts.new_branch || opts.new_orphan_branch)) + die(_("--detach cannot be used with -b/-B/--orphan")); + if (opts.force_detach && 0 < opts.track) + die(_("--detach cannot be used with -t")); /* --track without -b should DWIM */ if (0 < opts.track && !opts.new_branch) { const char *argv0 = argv[0]; if (!argc || !strcmp(argv0, "--")) - die ("--track needs a branch name"); + die (_("--track needs a branch name")); if (!prefixcmp(argv0, "refs/")) argv0 += 5; if (!prefixcmp(argv0, "remotes/")) argv0 += 8; argv0 = strchr(argv0, '/'); if (!argv0 || !argv0[1]) - die ("Missing branch name; try -b"); + die (_("Missing branch name; try -b")); opts.new_branch = argv0 + 1; } if (opts.new_orphan_branch) { if (opts.new_branch) - die("--orphan and -b|-B are mutually exclusive"); + die(_("--orphan and -b|-B are mutually exclusive")); if (opts.track > 0) - die("--orphan cannot be used with -t"); + die(_("--orphan cannot be used with -t")); opts.new_branch = opts.new_orphan_branch; } @@@ -1007,33 -737,108 +1007,33 @@@ } if (opts.force && opts.merge) - die("git checkout: -f and -m are incompatible"); + die(_("git checkout: -f and -m are incompatible")); /* - * case 1: git checkout -- [] - * - * must be a valid tree, everything after the '--' must be - * a path. - * - * case 2: git checkout -- [] + * Extract branch name from command line arguments, so + * all that is left is pathspecs. * - * everything after the '--' must be paths. + * Handle * - * case 3: git checkout [] - * - * With no paths, if is a commit, that is to - * switch to the branch or detach HEAD at it. As a special case, - * if is A...B (missing A or B means HEAD but you can - * omit at most one side), and if there is a unique merge base - * between A and B, A...B names that merge base. - * - * With no paths, if is _not_ a commit, no -t nor -b - * was given, and there is a tracking branch whose name is - * in one and only one remote, then this is a short-hand - * to fork local from that remote tracking branch. - * - * Otherwise shall not be ambiguous. - * - If it's *only* a reference, treat it like case (1). - * - If it's only a path, treat it like case (2). - * - else: fail. + * 1) git checkout -- [] + * 2) git checkout -- [] + * 3) git checkout [] * + * including "last branch" syntax and DWIM-ery for names of + * remote branches, erroring out for invalid or ambiguous cases. */ if (argc) { - if (!strcmp(argv[0], "--")) { /* case (2) */ - argv++; - argc--; - goto no_reference; - } - - arg = argv[0]; - has_dash_dash = (argc > 1) && !strcmp(argv[1], "--"); - - if (!strcmp(arg, "-")) - arg = "@{-1}"; - - if (get_sha1_mb(arg, rev)) { - if (has_dash_dash) /* case (1) */ - die("invalid reference: %s", arg); - if (!patch_mode && - dwim_new_local_branch && - opts.track == BRANCH_TRACK_UNSPECIFIED && - !opts.new_branch && - !check_filename(NULL, arg) && - argc == 1) { - const char *remote = unique_tracking_name(arg); - if (!remote || get_sha1(remote, rev)) - goto no_reference; - opts.new_branch = arg; - arg = remote; - /* DWIMmed to create local branch */ - } - else - goto no_reference; - } - - /* we can't end up being in (2) anymore, eat the argument */ - argv++; - argc--; - - new.name = arg; - if ((new.commit = lookup_commit_reference_gently(rev, 1))) { - setup_branch_path(&new); - - if ((check_ref_format(new.path) == CHECK_REF_FORMAT_OK) && - resolve_ref(new.path, rev, 1, NULL)) - ; - else - new.path = NULL; - parse_commit(new.commit); - source_tree = new.commit->tree; - } else - source_tree = parse_tree_indirect(rev); - - if (!source_tree) /* case (1): want a tree */ - die("reference is not a tree: %s", arg); - if (!has_dash_dash) {/* case (3 -> 1) */ - /* - * Do not complain the most common case - * git checkout branch - * even if there happen to be a file called 'branch'; - * it would be extremely annoying. - */ - if (argc) - verify_non_filename(NULL, arg); - } - else { - argv++; - argc--; - } + int dwim_ok = + !patch_mode && + dwim_new_local_branch && + opts.track == BRANCH_TRACK_UNSPECIFIED && + !opts.new_branch; + int n = parse_branchname_arg(argc, argv, dwim_ok, + &new, &source_tree, rev, &opts.new_branch); + argv += n; + argc -= n; } -no_reference: - if (opts.track == BRANCH_TRACK_UNSPECIFIED) opts.track = git_branch_track; @@@ -1041,7 -846,7 +1041,7 @@@ const char **pathspec = get_pathspec(prefix, argv); if (!pathspec) - die("invalid path specification"); + die(_("invalid path specification")); if (patch_mode) return interactive_checkout(new.name, pathspec, &opts); @@@ -1049,17 -854,14 +1049,17 @@@ /* Checkout paths */ if (opts.new_branch) { if (argc == 1) { - die("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]); + die(_("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?"), argv[0]); } else { - die("git checkout: updating paths is incompatible with switching branches."); + die(_("git checkout: updating paths is incompatible with switching branches.")); } } + if (opts.force_detach) + die(_("git checkout: --detach does not take a path argument")); + if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge) - die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index."); + die(_("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index.")); return checkout_paths(source_tree, pathspec, &opts); } @@@ -1070,22 -872,22 +1070,22 @@@ if (opts.new_branch) { struct strbuf buf = STRBUF_INIT; if (strbuf_check_branch_ref(&buf, opts.new_branch)) - die("git checkout: we do not like '%s' as a branch name.", + die(_("git checkout: we do not like '%s' as a branch name."), opts.new_branch); - if (!get_sha1(buf.buf, rev)) { + if (ref_exists(buf.buf)) { opts.branch_exists = 1; if (!opts.new_branch_force) - die("git checkout: branch %s already exists", + die(_("git checkout: branch %s already exists"), opts.new_branch); } strbuf_release(&buf); } if (new.name && !new.commit) { - die("Cannot switch branch to a non-commit."); + die(_("Cannot switch branch to a non-commit.")); } if (opts.writeout_stage) - die("--ours/--theirs is incompatible with switching branches."); + die(_("--ours/--theirs is incompatible with switching branches.")); return switch_branches(&opts, &new); } diff --combined refs.c index b10419a698,92cd0d14c3..3a8789d385 --- a/refs.c +++ b/refs.c @@@ -157,7 -157,7 +157,7 @@@ static struct cached_refs char did_packed; struct ref_list *loose; struct ref_list *packed; -} cached_refs; +} cached_refs, submodule_refs; static struct ref_list *current_ref; static struct ref_list *extra_refs; @@@ -229,45 -229,23 +229,45 @@@ void clear_extra_refs(void extra_refs = NULL; } -static struct ref_list *get_packed_refs(void) +static struct ref_list *get_packed_refs(const char *submodule) { - if (!cached_refs.did_packed) { - FILE *f = fopen(git_path("packed-refs"), "r"); - cached_refs.packed = NULL; + const char *packed_refs_file; + struct cached_refs *refs; + + if (submodule) { + packed_refs_file = git_path_submodule(submodule, "packed-refs"); + refs = &submodule_refs; + free_ref_list(refs->packed); + } else { + packed_refs_file = git_path("packed-refs"); + refs = &cached_refs; + } + + if (!refs->did_packed || submodule) { + FILE *f = fopen(packed_refs_file, "r"); + refs->packed = NULL; if (f) { - read_packed_refs(f, &cached_refs); + read_packed_refs(f, refs); fclose(f); } - cached_refs.did_packed = 1; + refs->did_packed = 1; } - return cached_refs.packed; + return refs->packed; } -static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) +static struct ref_list *get_ref_dir(const char *submodule, const char *base, + struct ref_list *list) { - DIR *dir = opendir(git_path("%s", base)); + DIR *dir; + const char *path; + + if (submodule) + path = git_path_submodule(submodule, "%s", base); + else + path = git_path("%s", base); + + + dir = opendir(path); if (dir) { struct dirent *de; @@@ -283,7 -261,6 +283,7 @@@ struct stat st; int flag; int namelen; + const char *refdir; if (de->d_name[0] == '.') continue; @@@ -293,27 -270,16 +293,27 @@@ if (has_extension(de->d_name, ".lock")) continue; memcpy(ref + baselen, de->d_name, namelen+1); - if (stat(git_path("%s", ref), &st) < 0) + refdir = submodule + ? git_path_submodule(submodule, "%s", ref) + : git_path("%s", ref); + if (stat(refdir, &st) < 0) continue; if (S_ISDIR(st.st_mode)) { - list = get_ref_dir(ref, list); + list = get_ref_dir(submodule, ref, list); continue; } - if (!resolve_ref(ref, sha1, 1, &flag)) { + if (submodule) { hashclr(sha1); - flag |= REF_BROKEN; - } + flag = 0; + if (resolve_gitlink_ref(submodule, ref, sha1) < 0) { + hashclr(sha1); + flag |= REF_BROKEN; + } + } else + if (!resolve_ref(ref, sha1, 1, &flag)) { + hashclr(sha1); + flag |= REF_BROKEN; + } list = add_ref(ref, sha1, flag, list, NULL); } free(ref); @@@ -356,16 -322,10 +356,16 @@@ void warn_dangling_symref(FILE *fp, con for_each_rawref(warn_if_dangling_symref, &data); } -static struct ref_list *get_loose_refs(void) +static struct ref_list *get_loose_refs(const char *submodule) { + if (submodule) { + free_ref_list(submodule_refs.loose); + submodule_refs.loose = get_ref_dir(submodule, "refs", NULL); + return submodule_refs.loose; + } + if (!cached_refs.did_loose) { - cached_refs.loose = get_ref_dir("refs", NULL); + cached_refs.loose = get_ref_dir(NULL, "refs", NULL); cached_refs.did_loose = 1; } return cached_refs.loose; @@@ -499,7 -459,7 +499,7 @@@ const char *resolve_ref(const char *ref git_snpath(path, sizeof(path), "%s", ref); /* Special case: non-existing file. */ if (lstat(path, &st) < 0) { - struct ref_list *list = get_packed_refs(); + struct ref_list *list = get_packed_refs(NULL); while (list) { if (!strcmp(ref, list->name)) { hashcpy(sha1, list->sha1); @@@ -628,7 -588,7 +628,7 @@@ int peel_ref(const char *ref, unsigned return -1; if ((flag & REF_ISPACKED)) { - struct ref_list *list = get_packed_refs(); + struct ref_list *list = get_packed_refs(NULL); while (list) { if (!strcmp(list->name, ref)) { @@@ -655,12 -615,12 +655,12 @@@ fallback return -1; } -static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, - int flags, void *cb_data) +static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn fn, + int trim, int flags, void *cb_data) { int retval = 0; - struct ref_list *packed = get_packed_refs(); - struct ref_list *loose = get_loose_refs(); + struct ref_list *packed = get_packed_refs(submodule); + struct ref_list *loose = get_loose_refs(submodule); struct ref_list *extra; @@@ -697,54 -657,24 +697,54 @@@ end_each return retval; } -int head_ref(each_ref_fn fn, void *cb_data) + +static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data) { unsigned char sha1[20]; int flag; + if (submodule) { + if (resolve_gitlink_ref(submodule, "HEAD", sha1) == 0) + return fn("HEAD", sha1, 0, cb_data); + + return 0; + } + if (resolve_ref("HEAD", sha1, 1, &flag)) return fn("HEAD", sha1, flag, cb_data); + return 0; } +int head_ref(each_ref_fn fn, void *cb_data) +{ + return do_head_ref(NULL, fn, cb_data); +} + +int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) +{ + return do_head_ref(submodule, fn, cb_data); +} + int for_each_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/", fn, 0, 0, cb_data); + return do_for_each_ref(NULL, "refs/", fn, 0, 0, cb_data); +} + +int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref(submodule, "refs/", fn, 0, 0, cb_data); } int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data) { - return do_for_each_ref(prefix, fn, strlen(prefix), 0, cb_data); + return do_for_each_ref(NULL, prefix, fn, strlen(prefix), 0, cb_data); +} + +int for_each_ref_in_submodule(const char *submodule, const char *prefix, + each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref(submodule, prefix, fn, strlen(prefix), 0, cb_data); } int for_each_tag_ref(each_ref_fn fn, void *cb_data) @@@ -752,34 -682,19 +752,34 @@@ return for_each_ref_in("refs/tags/", fn, cb_data); } +int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) +{ + return for_each_ref_in_submodule(submodule, "refs/tags/", fn, cb_data); +} + int for_each_branch_ref(each_ref_fn fn, void *cb_data) { return for_each_ref_in("refs/heads/", fn, cb_data); } +int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) +{ + return for_each_ref_in_submodule(submodule, "refs/heads/", fn, cb_data); +} + int for_each_remote_ref(each_ref_fn fn, void *cb_data) { return for_each_ref_in("refs/remotes/", fn, cb_data); } +int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) +{ + return for_each_ref_in_submodule(submodule, "refs/remotes/", fn, cb_data); +} + int for_each_replace_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/replace/", fn, 13, 0, cb_data); + return do_for_each_ref(NULL, "refs/replace/", fn, 13, 0, cb_data); } int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, @@@ -819,7 -734,7 +819,7 @@@ int for_each_glob_ref(each_ref_fn fn, c int for_each_rawref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/", fn, 0, + return do_for_each_ref(NULL, "refs/", fn, 0, DO_FOR_EACH_INCLUDE_BROKEN, cb_data); } @@@ -1043,7 -958,7 +1043,7 @@@ static struct ref_lock *lock_ref_sha1_b * name is a proper prefix of our refname. */ if (missing && - !is_refname_available(ref, NULL, get_packed_refs(), 0)) { + !is_refname_available(ref, NULL, get_packed_refs(NULL), 0)) { last_errno = ENOTDIR; goto error_return; } @@@ -1106,7 -1021,7 +1106,7 @@@ static int repack_without_ref(const cha int fd; int found = 0; - packed_ref_list = get_packed_refs(); + packed_ref_list = get_packed_refs(NULL); for (list = packed_ref_list; list; list = list->next) { if (!strcmp(refname, list->name)) { found = 1; @@@ -1175,15 -1090,6 +1175,15 @@@ int delete_ref(const char *refname, con return ret; } +/* + * People using contrib's git-new-workdir have .git/logs/refs -> + * /some/other/path/.git/logs/refs, and that may live on another device. + * + * IOW, to avoid cross device rename errors, the temporary renamed log must + * live into logs/refs. + */ +#define TMP_RENAMED_LOG "logs/refs/.tmp-renamed-log" + int rename_ref(const char *oldref, const char *newref, const char *logmsg) { static const char renamed_ref[] = "RENAMED-REF"; @@@ -1204,10 -1110,10 +1204,10 @@@ if (!symref) return error("refname %s not found", oldref); - if (!is_refname_available(newref, oldref, get_packed_refs(), 0)) + if (!is_refname_available(newref, oldref, get_packed_refs(NULL), 0)) return 1; - if (!is_refname_available(newref, oldref, get_loose_refs(), 0)) + if (!is_refname_available(newref, oldref, get_loose_refs(NULL), 0)) return 1; lock = lock_ref_sha1_basic(renamed_ref, NULL, 0, NULL); @@@ -1217,8 -1123,8 +1217,8 @@@ if (write_ref_sha1(lock, orig_sha1, logmsg)) return error("unable to save current sha1 in %s", renamed_ref); - if (log && rename(git_path("logs/%s", oldref), git_path("tmp-renamed-log"))) - return error("unable to move logfile logs/%s to tmp-renamed-log: %s", + if (log && rename(git_path("logs/%s", oldref), git_path(TMP_RENAMED_LOG))) + return error("unable to move logfile logs/%s to "TMP_RENAMED_LOG": %s", oldref, strerror(errno)); if (delete_ref(oldref, orig_sha1, REF_NODEREF)) { @@@ -1244,7 -1150,7 +1244,7 @@@ } retry: - if (log && rename(git_path("tmp-renamed-log"), git_path("logs/%s", newref))) { + if (log && rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newref))) { if (errno==EISDIR || errno==ENOTDIR) { /* * rename(a, b) when b is an existing @@@ -1257,7 -1163,7 +1257,7 @@@ } goto retry; } else { - error("unable to move logfile tmp-renamed-log to logs/%s: %s", + error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s", newref, strerror(errno)); goto rollback; } @@@ -1297,8 -1203,8 +1297,8 @@@ error("unable to restore logfile %s from %s: %s", oldref, newref, strerror(errno)); if (!logmoved && log && - rename(git_path("tmp-renamed-log"), git_path("logs/%s", oldref))) - error("unable to restore logfile %s from tmp-renamed-log: %s", + rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", oldref))) + error("unable to restore logfile %s from "TMP_RENAMED_LOG": %s", oldref, strerror(errno)); return 1; @@@ -1451,7 -1357,7 +1451,7 @@@ int write_ref_sha1(struct ref_lock *loc } o = parse_object(sha1); if (!o) { - error("Trying to write ref %s with nonexistant object %s", + error("Trying to write ref %s with nonexistent object %s", lock->ref_name, sha1_to_hex(sha1)); unlock_ref(lock); return -1; @@@ -1826,6 -1732,12 +1826,12 @@@ int update_ref(const char *action, cons return 0; } + int ref_exists(char *refname) + { + unsigned char sha1[20]; + return !!resolve_ref(refname, sha1, 1, NULL); + } + struct ref *find_ref_by_name(const struct ref *list, const char *name) { for ( ; list; list = list->next) diff --combined refs.h index 5e7a9a59f5,070a7d9caa..5de06e57e7 --- a/refs.h +++ b/refs.h @@@ -28,14 -28,6 +28,14 @@@ extern int for_each_replace_ref(each_re extern int for_each_glob_ref(each_ref_fn, const char *pattern, void *); extern int for_each_glob_ref_in(each_ref_fn, const char *pattern, const char* prefix, void *); +extern int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data); +extern int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data); +extern int for_each_ref_in_submodule(const char *submodule, const char *prefix, + each_ref_fn fn, void *cb_data); +extern int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data); +extern int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data); +extern int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data); + static inline const char *has_glob_specials(const char *pattern) { return strpbrk(pattern, "?*["); @@@ -54,6 -46,7 +54,7 @@@ extern void warn_dangling_symref(FILE * */ extern void add_extra_ref(const char *refname, const unsigned char *sha1, int flags); extern void clear_extra_refs(void); + extern int ref_exists(char *); extern int peel_ref(const char *, unsigned char *); diff --combined t/t2018-checkout-branch.sh index fa69016381,741d84257b..a42e03967b --- a/t/t2018-checkout-branch.sh +++ b/t/t2018-checkout-branch.sh @@@ -124,12 -124,6 +124,12 @@@ test_expect_success 'checkout -B to an do_checkout branch2 "" -B ' +test_expect_success 'checkout -B to an existing branch from detached HEAD resets branch to HEAD' ' + git checkout $(git rev-parse --verify HEAD) && + + do_checkout branch2 "" -B +' + test_expect_success 'checkout -B to an existing branch with an explicit ref resets branch to that ref' ' git checkout branch1 && @@@ -169,4 -163,15 +169,15 @@@ test_expect_success 'checkout -f -B to test_must_fail test_dirty_mergeable ' + test_expect_success 'checkout -b ' ' + git tag -f -m "First commit" initial initial && + git checkout -f change1 && + name=$(git describe) && + git checkout -b $name && + git diff --exit-code change1 && + echo "refs/heads/$name" >expect && + git symbolic-ref HEAD >actual && + test_cmp expect actual + ' + test_done