author | Junio C Hamano <gitster@pobox.com> | |
Mon, 28 Nov 2011 18:41:43 +0000 (10:41 -0800) | ||
committer | Junio C Hamano <gitster@pobox.com> | |
Mon, 28 Nov 2011 18:41:43 +0000 (10:41 -0800) |
* nd/maint-ignore-exclude:
checkout,merge: loosen overwriting untracked file check based on info/exclude
checkout,merge: loosen overwriting untracked file check based on info/exclude
1 | 2 | |||
---|---|---|---|---|
builtin/checkout.c | patch | | diff1 | | diff2 | | blob | history |
builtin/merge.c | patch | | diff1 | | diff2 | | blob | history |
diff --combined builtin/checkout.c
index 2a8077242500d54ac50d5829a86b14803fc69126,2b90e6b5e21dc64132e68c66e4f7430c49724441..51840b9784f0daec21087ec101304eaa6cbf73cd
--- 1/builtin/checkout.c
--- 2/builtin/checkout.c
+++ b/builtin/checkout.c
#include "ll-merge.h"
#include "resolve-undo.h"
#include "submodule.h"
+#include "argv-array.h"
static const char * const checkout_usage[] = {
"git checkout [options] <branch>",
int quiet;
int merge;
int force;
+ int force_detach;
int writeout_stage;
int writeout_error;
hashcpy(ce->sha1, sha1);
memcpy(ce->name, base, baselen);
memcpy(ce->name + baselen, pathname, len - baselen);
- ce->ce_flags = create_ce_flags(len, 0);
+ ce->ce_flags = create_ce_flags(len, 0) | CE_UPDATE;
ce->ce_mode = create_ce_mode(mode);
add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
return 0;
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
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)
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;
}
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)
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);
* 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);
}
/*
*/
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;
}
static int checkout_paths(struct tree *source_tree, const char **pathspec,
- struct checkout_opts *opts)
+ const char *prefix, struct checkout_opts *opts)
{
int pos;
struct checkout state;
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);
for (pos = 0; pos < active_nr; pos++) {
struct cache_entry *ce = active_cache[pos];
+ if (source_tree && !(ce->ce_flags & CE_UPDATE))
+ continue;
match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched);
}
- if (report_path_error(ps_matched, pathspec, 0))
+ if (report_path_error(ps_matched, pathspec, prefix))
return 1;
/* "checkout -m path" to recreate conflicted state */
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;
}
state.refresh_cache = 1;
for (pos = 0; pos < active_nr; pos++) {
struct cache_entry *ce = active_cache[pos];
+ if (source_tree && !(ce->ce_flags & CE_UPDATE))
+ continue;
if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
if (!ce_stage(ce)) {
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");
+ die(_("unable to write new index file"));
resolve_ref("HEAD", rev, 0, &flag);
head = lookup_commit_reference_gently(rev, 1);
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);
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) {
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;
}
topts.fn = twoway_merge;
topts.dir = xcalloc(1, sizeof(*topts.dir));
topts.dir->flags |= DIR_SHOW_IGNORED;
- topts.dir->exclude_per_dir = ".gitignore";
+ setup_standard_excludes(topts.dir);
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);
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, &opts->diff_options);
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;
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",
- new->name);
- else if (opts->new_branch)
- fprintf(stderr, "Switched to%s branch '%s'\n",
- opts->branch_exists ? " and reset" : " a new",
+ if (old->path && !strcmp(new->path, old->path)) {
+ fprintf(stderr, _("Already on '%s'\n"),
new->name);
- else
- fprintf(stderr, "Switched to branch '%s'\n",
+ } 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];
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);
}
+static int add_pending_uninteresting_ref(const char *refname,
+ const unsigned char *sha1,
+ int flags, void *cb_data)
+{
+ add_pending_sha1(cb_data, refname, sha1, flags | UNINTERESTING);
+ 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_info revs;
+ struct object *object = &commit->object;
+ struct object_array refs;
+
+ init_revisions(&revs, NULL);
+ setup_revisions(0, NULL, &revs, NULL);
+
+ object->flags &= ~UNINTERESTING;
+ add_pending_object(&revs, object, sha1_to_hex(object->sha1));
+
+ for_each_ref(add_pending_uninteresting_ref, &revs);
+
+ refs = revs.pending;
+ revs.leak_pending = 1;
+
+ 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_for_object_array(&refs, ALL_REV_FLAGS);
+ free(refs.objects);
+}
+
static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
{
int ret = 0;
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/");
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);
}
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);
ret = post_checkout_hook(old.commit, new->commit, 1);
+ free((char *)old.path);
return ret || opts->writeout_error;
}
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 <ref> -- [<paths>]
+ *
+ * <ref> must be a valid tree, everything after the '--' must be
+ * a path.
+ *
+ * case 2: git checkout -- [<paths>]
+ *
+ * everything after the '--' must be paths.
+ *
+ * case 3: git checkout <something> [<paths>]
+ *
+ * With no paths, if <something> is a commit, that is to
+ * switch to the branch or detach HEAD at it. As a special case,
+ * if <something> 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 <something> is _not_ a commit, no -t nor -b
+ * was given, and there is a tracking branch whose name is
+ * <something> in one and only one remote, then this is a short-hand
+ * to fork local <something> from that remote-tracking branch.
+ *
+ * Otherwise <something> 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_refname_format(new->path, 0) &&
+ 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"),
PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
OPT_END(),
};
- int has_dash_dash;
memset(&opts, 0, sizeof(opts));
memset(&new, 0, sizeof(new));
/* 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;
}
}
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 <ref> -- [<paths>]
+ * Extract branch name from command line arguments, so
+ * all that is left is pathspecs.
*
- * <ref> must be a valid tree, everything after the '--' must be
- * a path.
+ * Handle
*
- * case 2: git checkout -- [<paths>]
- *
- * everything after the '--' must be paths.
- *
- * case 3: git checkout <something> [<paths>]
- *
- * With no paths, if <something> is a commit, that is to
- * switch to the branch or detach HEAD at it. As a special case,
- * if <something> 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 <something> is _not_ a commit, no -t nor -b
- * was given, and there is a tracking branch whose name is
- * <something> in one and only one remote, then this is a short-hand
- * to fork local <something> from that remote tracking branch.
- *
- * Otherwise <something> 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 <tree> -- [<paths>]
+ * 2) git checkout -- [<paths>]
+ * 3) git checkout <something> [<paths>]
*
+ * 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;
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);
/* 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);
+ return checkout_paths(source_tree, pathspec, prefix, &opts);
}
if (patch_mode)
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.",
- opts.new_branch);
- if (!get_sha1(buf.buf, rev)) {
- opts.branch_exists = 1;
- if (!opts.new_branch_force)
- die("git checkout: branch %s already exists",
- opts.new_branch);
- }
+
+ opts.branch_exists = validate_new_branchname(opts.new_branch, &buf,
+ !!opts.new_branch_force, 0);
+
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 builtin/merge.c
index 2870a6af6fbc868455f2961483cfbfdb065a1a14,29c43979bd2b5f24403433077ef43a980aef88a3..138737624807389ccb903a5cd03840d63b7cbf6f
--- 1/builtin/merge.c
--- 2/builtin/merge.c
+++ b/builtin/merge.c
#include "help.h"
#include "merge-recursive.h"
#include "resolve-undo.h"
+#include "remote.h"
#define DEFAULT_TWOHEAD (1<<0)
#define DEFAULT_OCTOPUS (1<<1)
};
static const char * const builtin_merge_usage[] = {
- "git merge [options] <remote>...",
- "git merge [options] <msg> HEAD <remote>",
+ "git merge [options] [<commit>...]",
+ "git merge [options] <msg> HEAD <commit>",
+ "git merge --abort",
NULL
};
-static int show_diffstat = 1, option_log, squash;
+static int show_diffstat = 1, shortlog_len, squash;
static int option_commit = 1, allow_fast_forward = 1;
-static int fast_forward_only;
+static int fast_forward_only, option_edit;
static int allow_trivial = 1, have_message;
static struct strbuf merge_msg;
static struct commit_list *remoteheads;
-static unsigned char head[20], stash[20];
static struct strategy **use_strategies;
static size_t use_strategies_nr, use_strategies_alloc;
static const char **xopts;
static size_t xopts_nr, xopts_alloc;
static const char *branch;
+static char *branch_mergeoptions;
static int option_renormalize;
static int verbosity;
static int allow_rerere_auto;
+static int abort_current_merge;
+static int show_progress = -1;
+static int default_to_upstream;
static struct strategy all_strategy[] = {
{ "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL },
strbuf_addf(buf, "%s%s", buf->len ? "\n\n" : "", arg);
have_message = 1;
} else
- return error("switch `m' requires a value");
+ return error(_("switch `m' requires a value"));
return 0;
}
exclude_cmds(&main_cmds, ¬_strategies);
}
if (!is_in_cmdlist(&main_cmds, name) && !is_in_cmdlist(&other_cmds, name)) {
- fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
- fprintf(stderr, "Available strategies are:");
+ fprintf(stderr, _("Could not find merge strategy '%s'.\n"), name);
+ fprintf(stderr, _("Available strategies are:"));
for (i = 0; i < main_cmds.cnt; i++)
fprintf(stderr, " %s", main_cmds.names[i]->name);
fprintf(stderr, ".\n");
if (other_cmds.cnt) {
- fprintf(stderr, "Available custom strategies are:");
+ fprintf(stderr, _("Available custom strategies are:"));
for (i = 0; i < other_cmds.cnt; i++)
fprintf(stderr, " %s", other_cmds.names[i]->name);
fprintf(stderr, ".\n");
OPT_BOOLEAN(0, "stat", &show_diffstat,
"show a diffstat at the end of the merge"),
OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"),
- OPT_BOOLEAN(0, "log", &option_log,
- "add list of one-line log to merge commit message"),
+ { OPTION_INTEGER, 0, "log", &shortlog_len, "n",
+ "add (at most <n>) entries from shortlog to merge commit message",
+ PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN },
OPT_BOOLEAN(0, "squash", &squash,
"create a single commit instead of doing a merge"),
OPT_BOOLEAN(0, "commit", &option_commit,
"perform a commit if the merge succeeds (default)"),
+ OPT_BOOLEAN('e', "edit", &option_edit,
+ "edit message before committing"),
OPT_BOOLEAN(0, "ff", &allow_fast_forward,
"allow fast-forward (default)"),
OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
OPT_CALLBACK('X', "strategy-option", &xopts, "option=value",
"option for selected merge strategy", option_parse_x),
OPT_CALLBACK('m', "message", &merge_msg, "message",
- "message to be used for the merge commit (if any)",
+ "merge commit message (for a non-fast-forward merge)",
option_parse_message),
OPT__VERBOSITY(&verbosity),
+ OPT_BOOLEAN(0, "abort", &abort_current_merge,
+ "abort the current in-progress merge"),
+ OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1),
OPT_END()
};
unlink(git_path("MERGE_MODE"));
}
-static void save_state(void)
+static int save_state(unsigned char *stash)
{
int len;
struct child_process cp;
cp.git_cmd = 1;
if (start_command(&cp))
- die("could not run stash.");
+ die(_("could not run stash."));
len = strbuf_read(&buffer, cp.out, 1024);
close(cp.out);
if (finish_command(&cp) || len < 0)
- die("stash failed");
- else if (!len)
- return;
+ die(_("stash failed"));
+ else if (!len) /* no changes */
+ return -1;
strbuf_setlen(&buffer, buffer.len-1);
if (get_sha1(buffer.buf, stash))
- die("not a valid object: %s", buffer.buf);
+ die(_("not a valid object: %s"), buffer.buf);
+ return 0;
+}
+
+static void read_empty(unsigned const char *sha1, int verbose)
+{
+ int i = 0;
+ const char *args[7];
+
+ args[i++] = "read-tree";
+ if (verbose)
+ args[i++] = "-v";
+ args[i++] = "-m";
+ args[i++] = "-u";
+ args[i++] = EMPTY_TREE_SHA1_HEX;
+ args[i++] = sha1_to_hex(sha1);
+ args[i] = NULL;
+
+ if (run_command_v_opt(args, RUN_GIT_CMD))
+ die(_("read-tree failed"));
}
static void reset_hard(unsigned const char *sha1, int verbose)
args[i] = NULL;
if (run_command_v_opt(args, RUN_GIT_CMD))
- die("read-tree failed");
+ die(_("read-tree failed"));
}
-static void restore_state(void)
+static void restore_state(const unsigned char *head,
+ const unsigned char *stash)
{
struct strbuf sb = STRBUF_INIT;
const char *args[] = { "stash", "apply", NULL, NULL };
static void finish_up_to_date(const char *msg)
{
if (verbosity >= 0)
- printf("%s%s\n", squash ? " (nothing to squash)" : "", msg);
+ printf("%s%s\n", squash ? _(" (nothing to squash)") : "", msg);
drop_save();
}
-static void squash_message(void)
+static void squash_message(struct commit *commit)
{
struct rev_info rev;
- struct commit *commit;
struct strbuf out = STRBUF_INIT;
struct commit_list *j;
+ const char *filename;
int fd;
struct pretty_print_context ctx = {0};
- printf("Squash commit -- not updating HEAD\n");
- fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+ printf(_("Squash commit -- not updating HEAD\n"));
+ filename = git_path("SQUASH_MSG");
+ fd = open(filename, O_WRONLY | O_CREAT, 0666);
if (fd < 0)
- die_errno("Could not write to '%s'", git_path("SQUASH_MSG"));
+ die_errno(_("Could not write to '%s'"), filename);
init_revisions(&rev, NULL);
rev.ignore_merges = 1;
rev.commit_format = CMIT_FMT_MEDIUM;
- commit = lookup_commit(head);
commit->object.flags |= UNINTERESTING;
add_pending_object(&rev, &commit->object, NULL);
setup_revisions(0, NULL, &rev, NULL);
if (prepare_revision_walk(&rev))
- die("revision walk setup failed");
+ die(_("revision walk setup failed"));
ctx.abbrev = rev.abbrev;
ctx.date_mode = rev.date_mode;
+ ctx.fmt = rev.commit_format;
strbuf_addstr(&out, "Squashed commit of the following:\n");
while ((commit = get_revision(&rev)) != NULL) {
strbuf_addch(&out, '\n');
strbuf_addf(&out, "commit %s\n",
sha1_to_hex(commit->object.sha1));
- pretty_print_commit(rev.commit_format, commit, &out, &ctx);
+ pretty_print_commit(&ctx, commit, &out);
}
if (write(fd, out.buf, out.len) < 0)
- die_errno("Writing SQUASH_MSG");
+ die_errno(_("Writing SQUASH_MSG"));
if (close(fd))
- die_errno("Finishing SQUASH_MSG");
+ die_errno(_("Finishing SQUASH_MSG"));
strbuf_release(&out);
}
-static void finish(const unsigned char *new_head, const char *msg)
+static void finish(struct commit *head_commit,
+ const unsigned char *new_head, const char *msg)
{
struct strbuf reflog_message = STRBUF_INIT;
+ const unsigned char *head = head_commit->object.sha1;
if (!msg)
strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
getenv("GIT_REFLOG_ACTION"), msg);
}
if (squash) {
- squash_message();
+ squash_message(head_commit);
} else {
if (verbosity >= 0 && !merge_msg.len)
- printf("No merge message -- not updating HEAD\n");
+ printf(_("No merge message -- not updating HEAD\n"));
else {
const char *argv_gc_auto[] = { "gc", "--auto", NULL };
update_ref(reflog_message.buf, "HEAD",
opts.output_format |=
DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
opts.detect_rename = DIFF_DETECT_RENAME;
- if (diff_use_color_default > 0)
- DIFF_OPT_SET(&opts, COLOR_DIFF);
if (diff_setup_done(&opts) < 0)
- die("diff_setup_done failed");
+ die(_("diff_setup_done failed"));
diff_tree_sha1(head, new_head, "", &opts);
diffcore_std(&opts);
diff_flush(&opts);
strbuf_release(&reflog_message);
}
+static struct object *want_commit(const char *name)
+{
+ struct object *obj;
+ unsigned char sha1[20];
+ if (get_sha1(name, sha1))
+ return NULL;
+ obj = parse_object(sha1);
+ return peel_to_type(name, 0, obj, OBJ_COMMIT);
+}
+
/* Get the name for the merge commit's message. */
static void merge_name(const char *remote, struct strbuf *msg)
{
remote = bname.buf;
memset(branch_head, 0, sizeof(branch_head));
- remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT);
+ remote_head = want_commit(remote);
if (!remote_head)
- die("'%s' does not point to a commit", remote);
+ die(_("'%s' does not point to a commit"), remote);
if (dwim_ref(remote, strlen(remote), branch_head, &found_ref) > 0) {
if (!prefixcmp(found_ref, "refs/heads/")) {
goto cleanup;
}
if (!prefixcmp(found_ref, "refs/remotes/")) {
- strbuf_addf(msg, "%s\t\tremote branch '%s' of .\n",
+ strbuf_addf(msg, "%s\t\tremote-tracking branch '%s' of .\n",
sha1_to_hex(branch_head), remote);
goto cleanup;
}
if (!strcmp(remote, "FETCH_HEAD") &&
!access(git_path("FETCH_HEAD"), R_OK)) {
+ const char *filename;
FILE *fp;
struct strbuf line = STRBUF_INIT;
char *ptr;
- fp = fopen(git_path("FETCH_HEAD"), "r");
+ filename = git_path("FETCH_HEAD");
+ fp = fopen(filename, "r");
if (!fp)
- die_errno("could not open '%s' for reading",
- git_path("FETCH_HEAD"));
+ die_errno(_("could not open '%s' for reading"),
+ filename);
strbuf_getline(&line, fp, '\n');
fclose(fp);
ptr = strstr(line.buf, "\tnot-for-merge\t");
strbuf_release(&bname);
}
+static void parse_branch_merge_options(char *bmo)
+{
+ const char **argv;
+ int argc;
+
+ if (!bmo)
+ return;
+ argc = split_cmdline(bmo, &argv);
+ if (argc < 0)
+ die(_("Bad branch.%s.mergeoptions string: %s"), branch,
+ split_cmdline_strerror(argc));
+ argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+ memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+ argc++;
+ argv[0] = "branch.*.mergeoptions";
+ parse_options(argc, argv, NULL, builtin_merge_options,
+ builtin_merge_usage, 0);
+ free(argv);
+}
+
static int git_merge_config(const char *k, const char *v, void *cb)
{
if (branch && !prefixcmp(k, "branch.") &&
!prefixcmp(k + 7, branch) &&
!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
- const char **argv;
- int argc;
- char *buf;
-
- buf = xstrdup(v);
- argc = split_cmdline(buf, &argv);
- if (argc < 0)
- die("Bad branch.%s.mergeoptions string: %s", branch,
- split_cmdline_strerror(argc));
- argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
- memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
- argc++;
- parse_options(argc, argv, NULL, builtin_merge_options,
- builtin_merge_usage, 0);
- free(buf);
+ free(branch_mergeoptions);
+ branch_mergeoptions = xstrdup(v);
+ return 0;
}
if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
return git_config_string(&pull_twohead, k, v);
else if (!strcmp(k, "pull.octopus"))
return git_config_string(&pull_octopus, k, v);
- else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary"))
- option_log = git_config_bool(k, v);
else if (!strcmp(k, "merge.renormalize"))
option_renormalize = git_config_bool(k, v);
+ else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) {
+ int is_bool;
+ shortlog_len = git_config_bool_or_int(k, v, &is_bool);
+ if (!is_bool && shortlog_len < 0)
+ return error(_("%s: negative length %s"), k, v);
+ if (is_bool && shortlog_len)
+ shortlog_len = DEFAULT_MERGE_LOG_LEN;
+ return 0;
+ } else if (!strcmp(k, "merge.ff")) {
+ int boolval = git_config_maybe_bool(k, v);
+ if (0 <= boolval) {
+ allow_fast_forward = boolval;
+ } else if (v && !strcmp(v, "only")) {
+ allow_fast_forward = 1;
+ fast_forward_only = 1;
+ } /* do not barf on values from future versions of git */
+ return 0;
+ } else if (!strcmp(k, "merge.defaulttoupstream")) {
+ default_to_upstream = git_config_bool(k, v);
+ return 0;
+ }
return git_diff_ui_config(k, v, cb);
}
static void write_tree_trivial(unsigned char *sha1)
{
if (write_cache_as_tree(sha1, 0, NULL))
- die("git write-tree failed to write a tree");
+ die(_("git write-tree failed to write a tree"));
+}
+
+static const char *merge_argument(struct commit *commit)
+{
+ if (commit)
+ return sha1_to_hex(commit->object.sha1);
+ else
+ return EMPTY_TREE_SHA1_HEX;
}
-int try_merge_command(const char *strategy, struct commit_list *common,
+int try_merge_command(const char *strategy, size_t xopts_nr,
+ const char **xopts, struct commit_list *common,
const char *head_arg, struct commit_list *remotes)
{
const char **args;
args[i++] = s;
}
for (j = common; j; j = j->next)
- args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+ args[i++] = xstrdup(merge_argument(j->item));
args[i++] = "--";
args[i++] = head_arg;
for (j = remotes; j; j = j->next)
- args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1));
+ args[i++] = xstrdup(merge_argument(j->item));
args[i] = NULL;
ret = run_command_v_opt(args, RUN_GIT_CMD);
strbuf_release(&buf);
free(args);
discard_cache();
if (read_cache() < 0)
- die("failed to read the cache");
+ die(_("failed to read the cache"));
resolve_undo_clear();
return ret;
}
static int try_merge_strategy(const char *strategy, struct commit_list *common,
- const char *head_arg)
+ struct commit *head, const char *head_arg)
{
int index_fd;
struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
if (active_cache_changed &&
(write_cache(index_fd, active_cache, active_nr) ||
commit_locked_index(lock)))
- return error("Unable to write index.");
+ return error(_("Unable to write index."));
rollback_lock_file(lock);
if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) {
struct commit_list *j;
if (remoteheads->next) {
- error("Not handling anything other than two heads merge.");
+ error(_("Not handling anything other than two heads merge."));
return 2;
}
o.subtree_shift = "";
o.renormalize = option_renormalize;
+ o.show_rename_progress =
+ show_progress == -1 ? isatty(2) : show_progress;
- /*
- * NEEDSWORK: merge with table in builtin/merge-recursive
- */
- for (x = 0; x < xopts_nr; x++) {
- if (!strcmp(xopts[x], "ours"))
- o.recursive_variant = MERGE_RECURSIVE_OURS;
- else if (!strcmp(xopts[x], "theirs"))
- o.recursive_variant = MERGE_RECURSIVE_THEIRS;
- else if (!strcmp(xopts[x], "subtree"))
- o.subtree_shift = "";
- else if (!prefixcmp(xopts[x], "subtree="))
- o.subtree_shift = xopts[x]+8;
- else if (!strcmp(xopts[x], "renormalize"))
- o.renormalize = 1;
- else if (!strcmp(xopts[x], "no-renormalize"))
- o.renormalize = 0;
- else
- die("Unknown option for merge-recursive: -X%s", xopts[x]);
- }
+ for (x = 0; x < xopts_nr; x++)
+ if (parse_merge_opt(&o, xopts[x]))
+ die(_("Unknown option for merge-recursive: -X%s"), xopts[x]);
o.branch1 = head_arg;
o.branch2 = remoteheads->item->util;
commit_list_insert(j->item, &reversed);
index_fd = hold_locked_index(lock, 1);
- clean = merge_recursive(&o, lookup_commit(head),
+ clean = merge_recursive(&o, head,
remoteheads->item, reversed, &result);
if (active_cache_changed &&
(write_cache(index_fd, active_cache, active_nr) ||
commit_locked_index(lock)))
- die ("unable to write %s", get_index_file());
+ die (_("unable to write %s"), get_index_file());
rollback_lock_file(lock);
return clean ? 0 : 1;
} else {
- return try_merge_command(strategy, common, head_arg, remoteheads);
+ return try_merge_command(strategy, xopts_nr, xopts,
+ common, head_arg, remoteheads);
}
}
memset(&t, 0, sizeof(t));
memset(&dir, 0, sizeof(dir));
dir.flags |= DIR_SHOW_IGNORED;
- dir.exclude_per_dir = ".gitignore";
+ setup_standard_excludes(&dir);
opts.dir = &dir;
opts.head_idx = 1;
return -1;
if (write_cache(fd, active_cache, active_nr) ||
commit_locked_index(lock_file))
- die("unable to write new index file");
+ die(_("unable to write new index file"));
return 0;
}
}
-static int merge_trivial(void)
+static void write_merge_msg(struct strbuf *msg)
+{
+ const char *filename = git_path("MERGE_MSG");
+ int fd = open(filename, O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die_errno(_("Could not open '%s' for writing"),
+ filename);
+ if (write_in_full(fd, msg->buf, msg->len) != msg->len)
+ die_errno(_("Could not write to '%s'"), filename);
+ close(fd);
+}
+
+static void read_merge_msg(struct strbuf *msg)
+{
+ const char *filename = git_path("MERGE_MSG");
+ strbuf_reset(msg);
+ if (strbuf_read_file(msg, filename, 0) < 0)
+ die_errno(_("Could not read from '%s'"), filename);
+}
+
+static void write_merge_state(void);
+static void abort_commit(const char *err_msg)
+{
+ if (err_msg)
+ error("%s", err_msg);
+ fprintf(stderr,
+ _("Not committing merge; use 'git commit' to complete the merge.\n"));
+ write_merge_state();
+ exit(1);
+}
+
+static void prepare_to_commit(void)
+{
+ struct strbuf msg = STRBUF_INIT;
+ strbuf_addbuf(&msg, &merge_msg);
+ strbuf_addch(&msg, '\n');
+ write_merge_msg(&msg);
+ run_hook(get_index_file(), "prepare-commit-msg",
+ git_path("MERGE_MSG"), "merge", NULL, NULL);
+ if (option_edit) {
+ if (launch_editor(git_path("MERGE_MSG"), NULL, NULL))
+ abort_commit(NULL);
+ }
+ read_merge_msg(&msg);
+ stripspace(&msg, option_edit);
+ if (!msg.len)
+ abort_commit(_("Empty commit message."));
+ strbuf_release(&merge_msg);
+ strbuf_addbuf(&merge_msg, &msg);
+ strbuf_release(&msg);
+}
+
+static int merge_trivial(struct commit *head)
{
unsigned char result_tree[20], result_commit[20];
struct commit_list *parent = xmalloc(sizeof(*parent));
write_tree_trivial(result_tree);
- printf("Wonderful.\n");
- parent->item = lookup_commit(head);
+ printf(_("Wonderful.\n"));
+ parent->item = head;
parent->next = xmalloc(sizeof(*parent->next));
parent->next->item = remoteheads->item;
parent->next->next = NULL;
+ prepare_to_commit();
commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
- finish(result_commit, "In-index merge");
+ finish(head, result_commit, "In-index merge");
drop_save();
return 0;
}
-static int finish_automerge(struct commit_list *common,
+static int finish_automerge(struct commit *head,
+ struct commit_list *common,
unsigned char *result_tree,
const char *wt_strategy)
{
free_commit_list(common);
if (allow_fast_forward) {
parents = remoteheads;
- commit_list_insert(lookup_commit(head), &parents);
+ commit_list_insert(head, &parents);
parents = reduce_heads(parents);
} else {
struct commit_list **pptr = &parents;
- pptr = &commit_list_insert(lookup_commit(head),
+ pptr = &commit_list_insert(head,
pptr)->next;
for (j = remoteheads; j; j = j->next)
pptr = &commit_list_insert(j->item, pptr)->next;
}
- free_commit_list(remoteheads);
strbuf_addch(&merge_msg, '\n');
+ prepare_to_commit();
+ free_commit_list(remoteheads);
commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
- strbuf_addf(&buf, "Merge made by %s.", wt_strategy);
- finish(result_commit, buf.buf);
+ strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
+ finish(head, result_commit, buf.buf);
strbuf_release(&buf);
drop_save();
return 0;
static int suggest_conflicts(int renormalizing)
{
+ const char *filename;
FILE *fp;
int pos;
- fp = fopen(git_path("MERGE_MSG"), "a");
+ filename = git_path("MERGE_MSG");
+ fp = fopen(filename, "a");
if (!fp)
- die_errno("Could not open '%s' for writing",
- git_path("MERGE_MSG"));
+ die_errno(_("Could not open '%s' for writing"), filename);
fprintf(fp, "\nConflicts:\n");
for (pos = 0; pos < active_nr; pos++) {
struct cache_entry *ce = active_cache[pos];
}
fclose(fp);
rerere(allow_rerere_auto);
- printf("Automatic merge failed; "
- "fix conflicts and then commit the result.\n");
+ printf(_("Automatic merge failed; "
+ "fix conflicts and then commit the result.\n"));
return 1;
}
-static struct commit *is_old_style_invocation(int argc, const char **argv)
+static struct commit *is_old_style_invocation(int argc, const char **argv,
+ const unsigned char *head)
{
struct commit *second_token = NULL;
if (argc > 2) {
return NULL;
second_token = lookup_commit_reference_gently(second_sha1, 0);
if (!second_token)
- die("'%s' is not a commit", argv[1]);
+ die(_("'%s' is not a commit"), argv[1]);
if (hashcmp(second_token->object.sha1, head))
return NULL;
}
return cnt;
}
+/*
+ * Pretend as if the user told us to merge with the tracking
+ * branch we have for the upstream of the current branch
+ */
+static int setup_with_upstream(const char ***argv)
+{
+ struct branch *branch = branch_get(NULL);
+ int i;
+ const char **args;
+
+ if (!branch)
+ die(_("No current branch."));
+ if (!branch->remote)
+ die(_("No remote for the current branch."));
+ if (!branch->merge_nr)
+ die(_("No default upstream defined for the current branch."));
+
+ args = xcalloc(branch->merge_nr + 1, sizeof(char *));
+ for (i = 0; i < branch->merge_nr; i++) {
+ if (!branch->merge[i]->dst)
+ die(_("No remote tracking branch for %s from %s"),
+ branch->merge[i]->src, branch->remote_name);
+ args[i] = branch->merge[i]->dst;
+ }
+ args[i] = NULL;
+ *argv = args;
+ return i;
+}
+
+static void write_merge_state(void)
+{
+ const char *filename;
+ int fd;
+ struct commit_list *j;
+ struct strbuf buf = STRBUF_INIT;
+
+ for (j = remoteheads; j; j = j->next)
+ strbuf_addf(&buf, "%s\n",
+ sha1_to_hex(j->item->object.sha1));
+ filename = git_path("MERGE_HEAD");
+ fd = open(filename, O_WRONLY | O_CREAT, 0666);
+ if (fd < 0)
+ die_errno(_("Could not open '%s' for writing"), filename);
+ if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+ die_errno(_("Could not write to '%s'"), filename);
+ close(fd);
+ strbuf_addch(&merge_msg, '\n');
+ write_merge_msg(&merge_msg);
+
+ filename = git_path("MERGE_MODE");
+ fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (fd < 0)
+ die_errno(_("Could not open '%s' for writing"), filename);
+ strbuf_reset(&buf);
+ if (!allow_fast_forward)
+ strbuf_addf(&buf, "no-ff");
+ if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+ die_errno(_("Could not write to '%s'"), filename);
+ close(fd);
+}
+
int cmd_merge(int argc, const char **argv, const char *prefix)
{
unsigned char result_tree[20];
+ unsigned char stash[20];
+ unsigned char head_sha1[20];
+ struct commit *head_commit;
struct strbuf buf = STRBUF_INIT;
const char *head_arg;
- int flag, head_invalid = 0, i;
+ int flag, i;
int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
struct commit_list *common = NULL;
const char *best_strategy = NULL, *wt_strategy = NULL;
struct commit_list **remotes = &remoteheads;
- if (read_cache_unmerged()) {
- die_resolve_conflict("merge");
- }
- if (file_exists(git_path("MERGE_HEAD"))) {
- /*
- * There is no unmerged entry, don't advise 'git
- * add/rm <file>', just 'git commit'.
- */
- if (advice_resolve_conflict)
- die("You have not concluded your merge (MERGE_HEAD exists).\n"
- "Please, commit your changes before you can merge.");
- else
- die("You have not concluded your merge (MERGE_HEAD exists).");
- }
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(builtin_merge_usage, builtin_merge_options);
- resolve_undo_clear();
/*
* Check if we are _not_ on a detached HEAD, i.e. if there is a
* current branch.
*/
- branch = resolve_ref("HEAD", head, 0, &flag);
+ branch = resolve_ref("HEAD", head_sha1, 0, &flag);
if (branch && !prefixcmp(branch, "refs/heads/"))
branch += 11;
- if (is_null_sha1(head))
- head_invalid = 1;
+ if (!branch || is_null_sha1(head_sha1))
+ head_commit = NULL;
+ else
+ head_commit = lookup_commit_or_die(head_sha1, "HEAD");
git_config(git_merge_config, NULL);
- /* for color.ui */
- if (diff_use_color_default == -1)
- diff_use_color_default = git_use_color_default;
-
+ if (branch_mergeoptions)
+ parse_branch_merge_options(branch_mergeoptions);
argc = parse_options(argc, argv, prefix, builtin_merge_options,
builtin_merge_usage, 0);
+
+ if (verbosity < 0 && show_progress == -1)
+ show_progress = 0;
+
+ if (abort_current_merge) {
+ int nargc = 2;
+ const char *nargv[] = {"reset", "--merge", NULL};
+
+ if (!file_exists(git_path("MERGE_HEAD")))
+ die(_("There is no merge to abort (MERGE_HEAD missing)."));
+
+ /* Invoke 'git reset --merge' */
+ return cmd_reset(nargc, nargv, prefix);
+ }
+
+ if (read_cache_unmerged())
+ die_resolve_conflict("merge");
+
+ if (file_exists(git_path("MERGE_HEAD"))) {
+ /*
+ * There is no unmerged entry, don't advise 'git
+ * add/rm <file>', just 'git commit'.
+ */
+ if (advice_resolve_conflict)
+ die(_("You have not concluded your merge (MERGE_HEAD exists).\n"
+ "Please, commit your changes before you can merge."));
+ else
+ die(_("You have not concluded your merge (MERGE_HEAD exists)."));
+ }
+ if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
+ if (advice_resolve_conflict)
+ die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
+ "Please, commit your changes before you can merge."));
+ else
+ die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)."));
+ }
+ resolve_undo_clear();
+
if (verbosity < 0)
show_diffstat = 0;
if (squash) {
if (!allow_fast_forward)
- die("You cannot combine --squash with --no-ff.");
+ die(_("You cannot combine --squash with --no-ff."));
option_commit = 0;
}
if (!allow_fast_forward && fast_forward_only)
- die("You cannot combine --no-ff with --ff-only.");
+ die(_("You cannot combine --no-ff with --ff-only."));
+ if (!abort_current_merge) {
+ if (!argc && default_to_upstream)
+ argc = setup_with_upstream(&argv);
+ else if (argc == 1 && !strcmp(argv[0], "-"))
+ argv[0] = "@{-1}";
+ }
if (!argc)
usage_with_options(builtin_merge_usage,
builtin_merge_options);
* additional safety measure to check for it.
*/
- if (!have_message && is_old_style_invocation(argc, argv)) {
+ if (!have_message && head_commit &&
+ is_old_style_invocation(argc, argv, head_commit->object.sha1)) {
strbuf_addstr(&merge_msg, argv[0]);
head_arg = argv[1];
argv += 2;
argc -= 2;
- } else if (head_invalid) {
+ } else if (!head_commit) {
struct object *remote_head;
/*
* If the merged head is a valid one there is no reason
* We do the same for "git pull".
*/
if (argc != 1)
- die("Can merge only exactly one commit into "
- "empty head");
+ die(_("Can merge only exactly one commit into "
+ "empty head"));
if (squash)
- die("Squash commit into empty head not supported yet");
+ die(_("Squash commit into empty head not supported yet"));
if (!allow_fast_forward)
- die("Non-fast-forward commit does not make sense into "
- "an empty head");
- remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT);
+ die(_("Non-fast-forward commit does not make sense into "
+ "an empty head"));
+ remote_head = want_commit(argv[0]);
if (!remote_head)
- die("%s - not something we can merge", argv[0]);
+ die(_("%s - not something we can merge"), argv[0]);
+ read_empty(remote_head->sha1, 0);
update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0,
DIE_ON_ERR);
- reset_hard(remote_head->sha1, 0);
return 0;
} else {
struct strbuf merge_names = STRBUF_INIT;
for (i = 0; i < argc; i++)
merge_name(argv[i], &merge_names);
- if (have_message && option_log)
- fmt_merge_msg_shortlog(&merge_names, &merge_msg);
- else if (!have_message)
- fmt_merge_msg(option_log, &merge_names, &merge_msg);
-
-
- if (!(have_message && !option_log) && merge_msg.len)
- strbuf_setlen(&merge_msg, merge_msg.len-1);
+ if (!have_message || shortlog_len) {
+ fmt_merge_msg(&merge_names, &merge_msg, !have_message,
+ shortlog_len);
+ if (merge_msg.len)
+ strbuf_setlen(&merge_msg, merge_msg.len - 1);
+ }
}
- if (head_invalid || !argc)
+ if (!head_commit || !argc)
usage_with_options(builtin_merge_usage,
builtin_merge_options);
struct object *o;
struct commit *commit;
- o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT);
+ o = want_commit(argv[i]);
if (!o)
- die("%s - not something we can merge", argv[i]);
+ die(_("%s - not something we can merge"), argv[i]);
commit = lookup_commit(o->sha1);
commit->util = (void *)argv[i];
remotes = &commit_list_insert(commit, remotes)->next;
}
if (!remoteheads->next)
- common = get_merge_bases(lookup_commit(head),
- remoteheads->item, 1);
+ common = get_merge_bases(head_commit, remoteheads->item, 1);
else {
struct commit_list *list = remoteheads;
- commit_list_insert(lookup_commit(head), &list);
+ commit_list_insert(head_commit, &list);
common = get_octopus_merge_bases(list);
free(list);
}
- update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
- DIE_ON_ERR);
+ update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.sha1,
+ NULL, 0, DIE_ON_ERR);
if (!common)
; /* No common ancestors found. We need a real merge. */
return 0;
} else if (allow_fast_forward && !remoteheads->next &&
!common->next &&
- !hashcmp(common->item->object.sha1, head)) {
+ !hashcmp(common->item->object.sha1, head_commit->object.sha1)) {
/* Again the most common case of merging one remote. */
struct strbuf msg = STRBUF_INIT;
struct object *o;
char hex[41];
- strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+ strcpy(hex, find_unique_abbrev(head_commit->object.sha1, DEFAULT_ABBREV));
if (verbosity >= 0)
- printf("Updating %s..%s\n",
+ printf(_("Updating %s..%s\n"),
hex,
find_unique_abbrev(remoteheads->item->object.sha1,
DEFAULT_ABBREV));
if (have_message)
strbuf_addstr(&msg,
" (no commit created; -m option ignored)");
- o = peel_to_type(sha1_to_hex(remoteheads->item->object.sha1),
- 0, NULL, OBJ_COMMIT);
+ o = want_commit(sha1_to_hex(remoteheads->item->object.sha1));
if (!o)
return 1;
- if (checkout_fast_forward(head, remoteheads->item->object.sha1))
+ if (checkout_fast_forward(head_commit->object.sha1, remoteheads->item->object.sha1))
return 1;
- finish(o->sha1, msg.buf);
+ finish(head_commit, o->sha1, msg.buf);
drop_save();
return 0;
} else if (!remoteheads->next && common->next)
if (allow_trivial && !fast_forward_only) {
/* See if it is really trivial. */
git_committer_info(IDENT_ERROR_ON_NO_NAME);
- printf("Trying really trivial in-index merge...\n");
+ printf(_("Trying really trivial in-index merge...\n"));
if (!read_tree_trivial(common->item->object.sha1,
- head, remoteheads->item->object.sha1))
- return merge_trivial();
- printf("Nope.\n");
+ head_commit->object.sha1, remoteheads->item->object.sha1))
+ return merge_trivial(head_commit);
+ printf(_("Nope.\n"));
}
} else {
/*
* merge_bases again, otherwise "git merge HEAD^
* HEAD^^" would be missed.
*/
- common_one = get_merge_bases(lookup_commit(head),
- j->item, 1);
+ common_one = get_merge_bases(head_commit, j->item, 1);
if (hashcmp(common_one->item->object.sha1,
j->item->object.sha1)) {
up_to_date = 0;
}
if (fast_forward_only)
- die("Not possible to fast-forward, aborting.");
+ die(_("Not possible to fast-forward, aborting."));
/* We are going to make a new commit. */
git_committer_info(IDENT_ERROR_ON_NO_NAME);
* sync with the head commit. The strategies are responsible
* to ensure this.
*/
- if (use_strategies_nr != 1) {
- /*
- * Stash away the local changes so that we can try more
- * than one.
- */
- save_state();
- } else {
- memcpy(stash, null_sha1, 20);
- }
+ if (use_strategies_nr == 1 ||
+ /*
+ * Stash away the local changes so that we can try more than one.
+ */
+ save_state(stash))
+ hashcpy(stash, null_sha1);
for (i = 0; i < use_strategies_nr; i++) {
int ret;
if (i) {
- printf("Rewinding the tree to pristine...\n");
- restore_state();
+ printf(_("Rewinding the tree to pristine...\n"));
+ restore_state(head_commit->object.sha1, stash);
}
if (use_strategies_nr != 1)
- printf("Trying merge strategy %s...\n",
+ printf(_("Trying merge strategy %s...\n"),
use_strategies[i]->name);
/*
* Remember which strategy left the state in the working
wt_strategy = use_strategies[i]->name;
ret = try_merge_strategy(use_strategies[i]->name,
- common, head_arg);
+ common, head_commit, head_arg);
if (!option_commit && !ret) {
merge_was_ok = 1;
/*
* auto resolved the merge cleanly.
*/
if (automerge_was_ok)
- return finish_automerge(common, result_tree, wt_strategy);
+ return finish_automerge(head_commit, common, result_tree,
+ wt_strategy);
/*
* Pick the result from the best strategy and have the user fix
* it up.
*/
if (!best_strategy) {
- restore_state();
+ restore_state(head_commit->object.sha1, stash);
if (use_strategies_nr > 1)
fprintf(stderr,
- "No merge strategy handled the merge.\n");
+ _("No merge strategy handled the merge.\n"));
else
- fprintf(stderr, "Merge with strategy %s failed.\n",
+ fprintf(stderr, _("Merge with strategy %s failed.\n"),
use_strategies[0]->name);
return 2;
} else if (best_strategy == wt_strategy)
; /* We already have its result in the working tree. */
else {
- printf("Rewinding the tree to pristine...\n");
- restore_state();
- printf("Using the %s to prepare resolving by hand.\n",
+ printf(_("Rewinding the tree to pristine...\n"));
+ restore_state(head_commit->object.sha1, stash);
+ printf(_("Using the %s to prepare resolving by hand.\n"),
best_strategy);
- try_merge_strategy(best_strategy, common, head_arg);
+ try_merge_strategy(best_strategy, common, head_commit, head_arg);
}
if (squash)
- finish(NULL, NULL);
- else {
- int fd;
- struct commit_list *j;
-
- for (j = remoteheads; j; j = j->next)
- strbuf_addf(&buf, "%s\n",
- sha1_to_hex(j->item->object.sha1));
- fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
- if (fd < 0)
- die_errno("Could not open '%s' for writing",
- git_path("MERGE_HEAD"));
- if (write_in_full(fd, buf.buf, buf.len) != buf.len)
- die_errno("Could not write to '%s'", git_path("MERGE_HEAD"));
- close(fd);
- strbuf_addch(&merge_msg, '\n');
- fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
- if (fd < 0)
- die_errno("Could not open '%s' for writing",
- git_path("MERGE_MSG"));
- if (write_in_full(fd, merge_msg.buf, merge_msg.len) !=
- merge_msg.len)
- die_errno("Could not write to '%s'", git_path("MERGE_MSG"));
- close(fd);
- fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
- if (fd < 0)
- die_errno("Could not open '%s' for writing",
- git_path("MERGE_MODE"));
- strbuf_reset(&buf);
- if (!allow_fast_forward)
- strbuf_addf(&buf, "no-ff");
- if (write_in_full(fd, buf.buf, buf.len) != buf.len)
- die_errno("Could not write to '%s'", git_path("MERGE_MODE"));
- close(fd);
- }
+ finish(head_commit, NULL, NULL);
+ else
+ write_merge_state();
if (merge_was_ok) {
- fprintf(stderr, "Automatic merge went well; "
- "stopped before committing as requested\n");
+ fprintf(stderr, _("Automatic merge went well; "
+ "stopped before committing as requested\n"));
return 0;
} else
return suggest_conflicts(option_renormalize);