From: Junio C Hamano Date: Wed, 6 Jul 2011 22:37:42 +0000 (-0700) Subject: Merge commit 'v1.6.0' into jc/checkout-reflog-fix X-Git-Tag: v1.7.6.1~19^2~2 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/4d9e42f8f11c57b32b976a943c8ddaf6214e64b8?ds=inline;hp=-c Merge commit 'v1.6.0' into jc/checkout-reflog-fix * commit 'v1.6.0': (2063 commits) GIT 1.6.0 git-p4: chdir now properly sets PWD environment variable in msysGit Improve error output of git-rebase t9300: replace '!' with test_must_fail Git.pm: Make File::Spec and File::Temp requirement lazy Documentation: document the pager.* configuration setting git-stash: improve synopsis in help and manual page Makefile: building git in cygwin 1.7.0 git-am: ignore --binary option bash-completion: Add non-command git help files to bash-completion Fix t3700 on filesystems which do not support question marks in names Utilise our new p4_read_pipe and p4_write_pipe wrappers Add p4 read_pipe and write_pipe wrappers bash completion: Add '--merge' long option for 'git log' bash completion: Add completion for 'git mergetool' git format-patch documentation: clarify what --cover-letter does bash completion: 'git apply' should use 'fix' not 'strip' t5304-prune: adjust file mtime based on system time rather than file mtime test-parse-options: use appropriate cast in length_callback Fix escaping of glob special characters in pathspecs ... Conflicts: builtin-checkout.c --- 4d9e42f8f11c57b32b976a943c8ddaf6214e64b8 diff --combined builtin-checkout.c index 141a9e1873,411cc513c6..cff0179159 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@@ -12,6 -12,7 +12,7 @@@ #include "branch.h" #include "diff.h" #include "revision.h" + #include "remote.h" static const char * const checkout_usage[] = { "git checkout [options] ", @@@ -42,7 -43,7 +43,7 @@@ static int post_checkout_hook(struct co } static int update_some(const unsigned char *sha1, const char *base, int baselen, - const char *pathname, unsigned mode, int stage) + const char *pathname, unsigned mode, int stage, void *context) { int len; struct cache_entry *ce; @@@ -66,16 -67,7 +67,7 @@@ static int read_tree_some(struct tree *tree, const char **pathspec) { - int newfd; - struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - newfd = hold_locked_index(lock_file, 1); - read_cache(); - - read_tree_recursive(tree, "", 0, 0, pathspec, update_some); - - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(lock_file)) - die("unable to write new index file"); + read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL); /* update the index with the given tree's info * for all args, expanding wildcards, and exit @@@ -84,7 -76,7 +76,7 @@@ return 0; } - static int checkout_paths(const char **pathspec) + static int checkout_paths(struct tree *source_tree, const char **pathspec) { int pos; struct checkout state; @@@ -92,6 -84,16 +84,16 @@@ unsigned char rev[20]; int flag; struct commit *head; + int errs = 0; + + int newfd; + struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); + + newfd = hold_locked_index(lock_file, 1); + read_cache(); + + if (source_tree) + read_tree_some(source_tree, pathspec); for (pos = 0; pathspec[pos]; pos++) ; @@@ -105,20 -107,26 +107,26 @@@ if (report_path_error(ps_matched, pathspec, 0)) return 1; + /* Now we are committed to check them out */ memset(&state, 0, sizeof(state)); state.force = 1; state.refresh_cache = 1; for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; if (pathspec_match(pathspec, NULL, ce->name, 0)) { - checkout_entry(ce, &state, NULL); + errs |= checkout_entry(ce, &state, NULL); } } + if (write_cache(newfd, active_cache, active_nr) || + commit_locked_index(lock_file)) + die("unable to write new index file"); + resolve_ref("HEAD", rev, 0, &flag); head = lookup_commit_reference_gently(rev, 1); - return post_checkout_hook(head, head, 0); + errs |= post_checkout_hook(head, head, 0); + return errs; } static void show_local_changes(struct object *head) @@@ -137,57 -145,56 +145,56 @@@ static void describe_detached_head(cha struct strbuf sb; strbuf_init(&sb, 0); parse_commit(commit); - pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, 0, "", "", 0, 0); + pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, 0, NULL, NULL, 0, 0); fprintf(stderr, "%s %s... %s\n", msg, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf); strbuf_release(&sb); } - static int reset_to_new(struct tree *tree, int quiet) - { - struct unpack_trees_options opts; - struct tree_desc tree_desc; - memset(&opts, 0, sizeof(opts)); - opts.head_idx = -1; - opts.update = 1; - opts.reset = 1; - opts.merge = 1; - opts.fn = oneway_merge; - opts.verbose_update = !quiet; - parse_tree(tree); - init_tree_desc(&tree_desc, tree->buffer, tree->size); - if (unpack_trees(1, &tree_desc, &opts)) - return 128; - return 0; - } + struct checkout_opts { + int quiet; + int merge; + int force; + int writeout_error; + + char *new_branch; + int new_branch_log; + enum branch_track track; + }; - static void reset_clean_to_new(struct tree *tree, int quiet) + static int reset_tree(struct tree *tree, struct checkout_opts *o, int worktree) { struct unpack_trees_options opts; struct tree_desc tree_desc; + memset(&opts, 0, sizeof(opts)); opts.head_idx = -1; - opts.skip_unmerged = 1; + opts.update = worktree; + opts.skip_unmerged = !worktree; opts.reset = 1; opts.merge = 1; opts.fn = oneway_merge; - opts.verbose_update = !quiet; + opts.verbose_update = !o->quiet; + opts.src_index = &the_index; + opts.dst_index = &the_index; parse_tree(tree); init_tree_desc(&tree_desc, tree->buffer, tree->size); - if (unpack_trees(1, &tree_desc, &opts)) - exit(128); + switch (unpack_trees(1, &tree_desc, &opts)) { + case -2: + o->writeout_error = 1; + /* + * We return 0 nevertheless, as the index is all right + * and more importantly we have made best efforts to + * update paths in the work tree, and we cannot revert + * them. + */ + case 0: + return 0; + default: + return 128; + } } - struct checkout_opts { - int quiet; - int merge; - int force; - - char *new_branch; - int new_branch_log; - int track; - }; - struct branch_info { const char *name; /* The short name used */ const char *path; /* The full name of a real branch */ @@@ -204,8 -211,7 +211,7 @@@ static void setup_branch_path(struct br } static int merge_working_tree(struct checkout_opts *opts, - struct branch_info *old, struct branch_info *new, - const char *prefix) + struct branch_info *old, struct branch_info *new) { int ret; struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); @@@ -213,37 -219,44 +219,44 @@@ read_cache(); if (opts->force) { - ret = reset_to_new(new->commit->tree, opts->quiet); + ret = reset_tree(new->commit->tree, opts, 1); if (ret) return ret; } else { struct tree_desc trees[2]; struct tree *tree; struct unpack_trees_options topts; + memset(&topts, 0, sizeof(topts)); topts.head_idx = -1; + topts.src_index = &the_index; + topts.dst_index = &the_index; + + topts.msgs.not_uptodate_file = "You have local changes to '%s'; cannot switch branches."; refresh_cache(REFRESH_QUIET); if (unmerged_cache()) { - ret = opts->merge ? -1 : - error("you need to resolve your current index first"); - } else { - topts.update = 1; - topts.merge = 1; - topts.gently = opts->merge; - topts.fn = twoway_merge; - topts.dir = xcalloc(1, sizeof(*topts.dir)); - topts.dir->show_ignored = 1; - topts.dir->exclude_per_dir = ".gitignore"; - topts.prefix = prefix; - tree = parse_tree_indirect(old->commit->object.sha1); - 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); - ret = unpack_trees(2, trees, &topts); + error("you need to resolve your current index first"); + return 1; } - if (ret) { + + /* 2-way merge to the new branch */ + topts.update = 1; + topts.merge = 1; + topts.gently = opts->merge; + topts.verbose_update = !opts->quiet; + topts.fn = twoway_merge; + topts.dir = xcalloc(1, sizeof(*topts.dir)); + topts.dir->show_ignored = 1; + topts.dir->exclude_per_dir = ".gitignore"; + tree = parse_tree_indirect(old->commit->object.sha1); + 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); + + ret = unpack_trees(2, trees, &topts); + if (ret == -1) { /* * Unpack couldn't do a trivial merge; either * give up or do a real merge, depending on @@@ -268,15 -281,17 +281,17 @@@ * entries in the index. */ - add_files_to_cache(0, NULL, NULL); + add_files_to_cache(NULL, NULL, 0); work = write_tree_from_memory(); - ret = reset_to_new(new->commit->tree, opts->quiet); + ret = reset_tree(new->commit->tree, opts, 1); if (ret) return ret; merge_trees(new->commit->tree, work, old->commit->tree, new->name, "local", &result); - reset_clean_to_new(new->commit->tree, opts->quiet); + ret = reset_tree(new->commit->tree, opts, 0); + if (ret) + return ret; } } @@@ -290,6 -305,17 +305,17 @@@ return 0; } + static void report_tracking(struct branch_info *new) + { + struct strbuf sb = STRBUF_INIT; + struct branch *branch = branch_get(new->name); + + if (!format_tracking_info(branch, &sb)) + return; + fputs(sb.buf, stdout); + strbuf_release(&sb); + } + static void update_refs_for_switch(struct checkout_opts *opts, struct branch_info *old, struct branch_info *new) @@@ -332,22 -358,21 +358,23 @@@ } remove_branch_state(); strbuf_release(&msg); + if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD"))) + report_tracking(new); } - static int switch_branches(struct checkout_opts *opts, - struct branch_info *new, const char *prefix) + static int switch_branches(struct checkout_opts *opts, struct branch_info *new) { int ret = 0; struct branch_info old; unsigned char rev[20]; int flag; memset(&old, 0, sizeof(old)); - old.path = resolve_ref("HEAD", rev, 0, &flag); + old.path = xstrdup(resolve_ref("HEAD", rev, 0, &flag)); old.commit = lookup_commit_reference_gently(rev, 1); - if (!(flag & REF_ISSYMREF)) + if (!(flag & REF_ISSYMREF)) { + free((char *)old.path); old.path = NULL; + } if (old.path && !prefixcmp(old.path, "refs/heads/")) old.name = old.path + strlen("refs/heads/"); @@@ -378,23 -403,14 +405,15 @@@ opts->force = 1; } - ret = merge_working_tree(opts, &old, new, prefix); + ret = merge_working_tree(opts, &old, new); if (ret) return ret; update_refs_for_switch(opts, &old, new); - free((char *)old.path); - return post_checkout_hook(old.commit, new->commit, 1); - } - static int branch_track = 0; - - static int git_checkout_config(const char *var, const char *value) - { - if (!strcmp(var, "branch.autosetupmerge")) - branch_track = git_config_bool(var, value); - - return git_default_config(var, value); + ret = post_checkout_hook(old.commit, new->commit, 1); ++ free((char *)old.path); + return ret || opts->writeout_error; } int cmd_checkout(int argc, const char **argv, const char *prefix) @@@ -408,24 -424,72 +427,72 @@@ OPT__QUIET(&opts.quiet), OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"), OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"), - OPT_BOOLEAN( 0 , "track", &opts.track, "track"), + OPT_SET_INT('t', "track", &opts.track, "track", + BRANCH_TRACK_EXPLICIT), OPT_BOOLEAN('f', NULL, &opts.force, "force"), OPT_BOOLEAN('m', NULL, &opts.merge, "merge"), + OPT_END(), }; + int has_dash_dash; memset(&opts, 0, sizeof(opts)); memset(&new, 0, sizeof(new)); - git_config(git_checkout_config); + git_config(git_default_config, NULL); - opts.track = branch_track; + opts.track = git_branch_track; + + argc = parse_options(argc, argv, options, checkout_usage, + PARSE_OPT_KEEP_DASHDASH); + + if (!opts.new_branch && (opts.track != git_branch_track)) + die("git checkout: --track and --no-track require -b"); - argc = parse_options(argc, argv, options, checkout_usage, 0); + if (opts.force && opts.merge) + 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 -- [] + * + * 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. + * + * 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) { + if (!strcmp(argv[0], "--")) { /* case (2) */ + argv++; + argc--; + goto no_reference; + } + arg = argv[0]; - if (get_sha1(arg, rev)) - ; - else if ((new.commit = lookup_commit_reference_gently(rev, 1))) { + has_dash_dash = (argc > 1) && !strcmp(argv[1], "--"); + + if (get_sha1(arg, rev)) { + if (has_dash_dash) /* case (1) */ + die("invalid reference: %s", arg); + goto no_reference; /* case (3 -> 2) */ + } + + /* we can't end up being in (2) anymore, eat the argument */ + argv++; + argc--; + + if ((new.commit = lookup_commit_reference_gently(rev, 1))) { new.name = arg; setup_branch_path(&new); if (resolve_ref(new.path, rev, 1, NULL)) @@@ -434,27 -498,34 +501,34 @@@ new.path = NULL; parse_commit(new.commit); source_tree = new.commit->tree; - argv++; - argc--; - } else if ((source_tree = parse_tree_indirect(rev))) { + } 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--; } } - if (argc && !strcmp(argv[0], "--")) { - argv++; - argc--; - } - - if (!opts.new_branch && (opts.track != branch_track)) - die("git checkout: --track and --no-track require -b"); - - if (opts.force && opts.merge) - die("git checkout: -f and -m are incompatible"); - + no_reference: if (argc) { const char **pathspec = get_pathspec(prefix, argv); + + if (!pathspec) + die("invalid path specification"); + /* Checkout paths */ if (opts.new_branch || opts.force || opts.merge) { if (argc == 1) { @@@ -464,16 -535,12 +538,12 @@@ } } - if (source_tree) - read_tree_some(source_tree, pathspec); - else - read_cache(); - return checkout_paths(pathspec); + return checkout_paths(source_tree, pathspec); } if (new.name && !new.commit) { die("Cannot switch branch to a non-commit."); } - return switch_branches(&opts, &new, prefix); + return switch_branches(&opts, &new); }