Merge branch 'jc/better-conflict-resolution'
authorShawn O. Pearce <spearce@spearce.org>
Mon, 29 Sep 2008 17:04:21 +0000 (10:04 -0700)
committerShawn O. Pearce <spearce@spearce.org>
Mon, 29 Sep 2008 17:15:07 +0000 (10:15 -0700)
* jc/better-conflict-resolution:
Fix AsciiDoc errors in merge documentation
git-merge documentation: describe how conflict is presented
checkout --conflict=<style>: recreate merge in a non-default style
checkout -m: recreate merge when checking out of unmerged index
git-merge-recursive: learn to honor merge.conflictstyle
merge.conflictstyle: choose between "merge" and "diff3 -m" styles
rerere: understand "diff3 -m" style conflicts with the original
rerere.c: use symbolic constants to keep track of parsing states
xmerge.c: "diff3 -m" style clips merge reduction level to EAGER or less
xmerge.c: minimum readability fixups
xdiff-merge: optionally show conflicts in "diff3 -m" style
xdl_fill_merge_buffer(): separate out a too deeply nested function
checkout --ours/--theirs: allow checking out one side of a conflicting merge
checkout -f: allow ignoring unmerged paths when checking out of the index

Conflicts:
Documentation/git-checkout.txt
builtin-checkout.c
builtin-merge-recursive.c
t/t7201-co.sh

1  2 
Documentation/config.txt
Documentation/git-checkout.txt
Documentation/git-merge.txt
builtin-checkout.c
rerere.c
t/t6023-merge-file.sh
t/t7201-co.sh
xdiff-interface.c
xdiff-interface.h
diff --combined Documentation/config.txt
index 1f805b2ecac4dad9e066b3392fdafc62caaa5b96,8c62ba4e7bceef724f4d5cfd0b0a790f1e380f48..bbe38ccaa2697f88936a8eff63a99b5c89435ac8
@@@ -363,17 -363,8 +363,17 @@@ core.pager:
        variable.  Note that git sets the `LESS` environment
        variable to `FRSX` if it is unset when it runs the
        pager.  One can change these settings by setting the
 -      `LESS` variable to some other value or by giving the
 -      `core.pager` option a value such as "`less -+FRSX`".
 +      `LESS` variable to some other value.  Alternately,
 +      these settings can be overridden on a project or
 +      global basis by setting the `core.pager` option.
 +      Setting `core.pager` has no affect on the `LESS`
 +      environment variable behaviour above, so if you want
 +      to override git's default settings this way, you need
 +      to be explicit.  For example, to disable the S option
 +      in a backward compatible manner, set `core.pager`
 +      to "`less -+$LESS -FRX`".  This will be passed to the
 +      shell by git, which will translate the final command to
 +      "`LESS=FRSX less -+FRSX -FRX`".
  
  core.whitespace::
        A comma separated list of common whitespace problems to
@@@ -581,10 -572,6 +581,10 @@@ diff.autorefreshindex:
        affects only 'git-diff' Porcelain, and not lower level
        'diff' commands, such as 'git-diff-files'.
  
 +diff.suppress-blank-empty::
 +      A boolean to inhibit the standard behavior of printing a space
 +      before each empty output line. Defaults to false.
 +
  diff.external::
        If this config variable is set, diff generation is not
        performed using the internal diff machinery, but using the
        you want to use an external diff program only on a subset of
        your files, you might want to use linkgit:gitattributes[5] instead.
  
 +diff.mnemonicprefix::
 +      If set, 'git-diff' uses a prefix pair that is different from the
 +      standard "a/" and "b/" depending on what is being compared.  When
 +      this configuration is in effect, reverse diff output also swaps
 +      the order of the prefixes:
 +'git-diff';;
 +      compares the (i)ndex and the (w)ork tree;
 +'git-diff HEAD';;
 +       compares a (c)ommit and the (w)ork tree;
 +'git diff --cached';;
 +      compares a (c)ommit and the (i)ndex;
 +'git-diff HEAD:file1 file2';;
 +      compares an (o)bject and a (w)ork tree entity;
 +'git diff --no-index a b';;
 +      compares two non-git things (1) and (2).
 +
  diff.renameLimit::
        The number of files to consider when performing the copy/rename
        detection; equivalent to the 'git-diff' option '-l'.
@@@ -722,7 -693,7 +722,7 @@@ gitcvs.logfile:
        Path to a log file where the CVS server interface well... logs
        various stuff. See linkgit:git-cvsserver[1].
  
 -gitcvs.usecrlfattr
 +gitcvs.usecrlfattr::
        If true, the server will look up the `crlf` attribute for
        files to determine the '-k' modes to use. If `crlf` is set,
        the '-k' mode will be left blank, so cvs clients will
@@@ -815,15 -786,6 +815,15 @@@ help.format:
        Values 'man', 'info', 'web' and 'html' are supported. 'man' is
        the default. 'web' and 'html' are the same.
  
 +help.autocorrect::
 +      Automatically correct and execute mistyped commands after
 +      waiting for the given number of deciseconds (0.1 sec). If more
 +      than one command can be deduced from the entered text, nothing
 +      will be executed.  If the value of this option is negative,
 +      the corrected command will be executed immediately. If the
 +      value is 0 - the command will be just shown but not executed.
 +      This is the default.
 +
  http.proxy::
        Override the HTTP proxy, normally configured using the 'http_proxy'
        environment variable (see linkgit:curl[1]).  This can be overridden
@@@ -927,6 -889,14 +927,14 @@@ man.<tool>.path:
        Override the path for the given tool that may be used to
        display help in the 'man' format. See linkgit:git-help[1].
  
+ merge.conflictstyle::
+       Specify the style in which conflicted hunks are written out to
+       working tree files upon merge.  The default is "merge", which
+       shows `<<<<<<<` conflict marker, change made by one side,
+       `=======` marker, change made by the other side, and then
+       `>>>>>>>` marker.  An alternate style, "diff3", adds `|||||||`
+       marker and the original text before `=======` marker.
  mergetool.<tool>.path::
        Override the path for the given tool.  This is useful in case
        your tool is not in the PATH.
index be54a0299fb2b6849993aa27d18c829ce91e7e00,13b106d6264686da90a543d9a84670348eba6939..82e154de4970c1c2cb35335591cfe457e80a89c1
@@@ -8,8 -8,8 +8,8 @@@ git-checkout - Checkout a branch or pat
  SYNOPSIS
  --------
  [verse]
 -'git checkout' [-q] [-f] [[--track | --no-track] -b <new_branch> [-l]] [-m] [<branch>]
 +'git checkout' [-q] [-f] [--track | --no-track] [-b <new_branch> [-l]] [-m] [<branch>]
- 'git checkout' [<tree-ish>] [--] <paths>...
+ 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
  
  DESCRIPTION
  -----------
@@@ -21,20 -21,22 +21,26 @@@ specified, <new_branch>.  Using -b wil
  be created; in this case you can use the --track or --no-track
  options, which will be passed to `git branch`.
  
 +As a convenience, --track will default to create a branch whose
 +name is constructed from the specified branch name by stripping
 +the first namespace level.
 +
  When <paths> are given, this command does *not* switch
  branches.  It updates the named paths in the working tree from
- the index file (i.e. it runs `git checkout-index -f -u`), or
- from a named commit.  In
- this case, the `-f` and `-b` options are meaningless and giving
+ the index file, or from a named commit.  In
+ this case, the `-b` options is meaningless and giving
  either of them results in an error.  <tree-ish> argument can be
  used to specify a specific tree-ish (i.e. commit, tag or tree)
  to update the index for the given paths before updating the
  working tree.
  
+ The index may contain unmerged entries after a failed merge.  By
+ default, if you try to check out such an entry from the index, the
+ checkout operation will fail and nothing will be checked out.
+ Using -f will ignore these unmerged entries.  The contents from a
+ specific side of the merge can be checked out of the index by
+ using --ours or --theirs.  With -m, changes made to the working tree
+ file can be discarded to recreate the original conflicted merge result.
  
  OPTIONS
  -------
        Quiet, suppress feedback messages.
  
  -f::
-       Proceed even if the index or the working tree differs
-       from HEAD.  This is used to throw away local changes.
+       When switching branches, proceed even if the index or the
+       working tree differs from HEAD.  This is used to throw away
+       local changes.
+ +
+ When checking out paths from the index, do not fail upon unmerged
+ entries; instead, unmerged entries are ignored.
+ --ours::
+ --theirs::
+       When checking out paths from the index, check out stage #2
+       ('ours') or #3 ('theirs') for unmerged paths.
  
  -b::
        Create a new branch named <new_branch> and start it at
        'git-checkout' and 'git-branch' to always behave as if '--no-track' were
        given. Set it to `always` if you want this behavior when the
        start-point is either a local or remote branch.
 ++
 +If no '-b' option was given, the name of the new branch will be
 +derived from the remote branch, by attempting to guess the name
 +of the branch on remote system.  If "remotes/" or "refs/remotes/"
 +are prefixed, it is stripped away, and then the part up to the
 +next slash (which would be the nickname of the remote) is removed.
 +This would tell us to use "hack" as the local branch when branching
 +off of "origin/hack" (or "remotes/origin/hack", or even
 +"refs/remotes/origin/hack").  If the given name has no slash, or the above
 +guessing results in an empty name, the guessing is aborted.  You can
 +exlicitly give a name with '-b' in such a case.
  
  --no-track::
        Ignore the branch.autosetupmerge configuration variable.
@@@ -84,7 -84,9 +99,9 @@@
        based sha1 expressions such as "<branchname>@\{yesterday}".
  
  -m::
-       If you have local modifications to one or more files that
+ --merge::
+       When switching branches,
+       if you have local modifications to one or more files that
        are different between the current branch and the branch to
        which you are switching, the command refuses to switch
        branches in order to preserve your modifications in context.
@@@ -96,6 -98,16 +113,16 @@@ When a merge conflict happens, the inde
  paths are left unmerged, and you need to resolve the conflicts
  and mark the resolved paths with `git add` (or `git rm` if the merge
  should result in deletion of the path).
+ +
+ When checking out paths from the index, this option lets you recreate
+ the conflicted merge in the specified paths.
+ --conflict=<style>::
+       The same as --merge option above, but changes the way the
+       conflicting hunks are presented, overriding the
+       merge.conflictstyle configuration variable.  Possible values are
+       "merge" (default) and "diff3" (in addition to what is shown by
+       "merge" style, shows the original contents).
  
  <new_branch>::
        Name for the new branch.
index 685e1fed586cb74a50b95d10fff6c8773cc7c8e7,18750467d8b8f6fb2cb6ef2fb124c3a5b0faaf6a..1f30830d46a7bd054986b9c1965f848cb5b47e20
@@@ -119,6 -119,71 +119,71 @@@ When there are conflicts, these things 
     same and the index entries for them stay as they were,
     i.e. matching `HEAD`.
  
+ HOW CONFLICTS ARE PRESENTED
+ ---------------------------
+ During a merge, the working tree files are updated to reflect the result
+ of the merge.  Among the changes made to the common ancestor's version,
+ non-overlapping ones (that is, you changed an area of the file while the
+ other side left that area intact, or vice versa) are incorporated in the
+ final result verbatim.  When both sides made changes to the same area,
+ however, git cannot randomly pick one side over the other, and asks you to
+ resolve it by leaving what both sides did to that area.
+ By default, git uses the same style as that is used by "merge" program
+ from the RCS suite to present such a conflicted hunk, like this:
+ ------------
+ Here are lines that are either unchanged from the common
+ ancestor, or cleanly resolved because only one side changed.
+ <<<<<<< yours:sample.txt
+ Conflict resolution is hard;
+ let's go shopping.
+ =======
+ Git makes conflict resolution easy.
+ >>>>>>> theirs:sample.txt
+ And here is another line that is cleanly resolved or unmodified.
+ ------------
+ The area a pair of conflicting changes happened is marked with markers
+ "`<<<<<<<`", "`=======`", and "`>>>>>>>`".  The part before the "`=======`"
+ is typically your side, and the part after it is typically their side.
+ The default format does not show what the original said in the conflicted
+ area.  You cannot tell how many lines are deleted and replaced with the
+ Barbie's remark by your side.  The only thing you can tell is that your
+ side wants to say it is hard and you'd prefer to go shopping, while the
+ other side wants to claim it is easy.
+ An alternative style can be used by setting the "merge.conflictstyle"
+ configuration variable to "diff3".  In "diff3" style, the above conflict
+ may look like this:
+ ------------
+ Here are lines that are either unchanged from the common
+ ancestor, or cleanly resolved because only one side changed.
+ <<<<<<< yours:sample.txt
+ Conflict resolution is hard;
+ let's go shopping.
+ |||||||
+ Conflict resolution is hard.
+ =======
+ Git makes conflict resolution easy.
+ >>>>>>> theirs:sample.txt
+ And here is another line that is cleanly resolved or unmodified.
+ ------------
+ In addition to the "`<<<<<<<`", "`=======`", and "`>>>>>>>`" markers, it uses
+ another "`|||||||`" marker that is followed by the original text.  You can
+ tell that the original just stated a fact, and your side simply gave in to
+ that statement and gave up, while the other side tried to have a more
+ positive attitude.  You can sometimes come up with a better resolution by
+ viewing the original.
+ HOW TO RESOLVE CONFLICTS
+ ------------------------
  After seeing a conflict, you can do two things:
  
   * Decide not to merge.  The only clean-up you need are to reset
     up working tree changes made by 2. and 3.; 'git-reset --hard' can
     be used for this.
  
 - * Resolve the conflicts.  `git diff` would report only the
 -   conflicting paths because of the above 2. and 3.
 -   Edit the working tree files into a desirable shape
 -   ('git mergetool' can ease this task), 'git-add' or 'git-rm'
 -   them, to make the index file contain what the merge result
 -   should be, and run 'git-commit' to commit the result.
 + * Resolve the conflicts.  Git will mark the conflicts in
 +   the working tree.  Edit the files into shape and
 +   'git-add' to the index.  'git-commit' to seal the deal.
  
 +You can work through the conflict with a number of tools:
 +
 + * Use a mergetool.  'git mergetool' to launch a graphical
 +   mergetool which will work you through the merge.
 +
 + * Look at the diffs.  'git diff' will show a three-way diff,
 +   highlighting changes from both the HEAD and remote versions.
 +
 + * Look at the diffs on their own. 'git log --merge -p <path>'
 +   will show diffs first for the HEAD version and then the
 +   remote version.
 +
 + * Look at the originals.  'git show :1:filename' shows the
 +   common ancestor, 'git show :2:filename' shows the HEAD
 +   version and 'git show :3:filename' shows the remote version.
  
  SEE ALSO
  --------
diff --combined builtin-checkout.c
index 075667c9cc8aabe4157900635b2aabc4f55f448c,79214327b0d99369b3ce384ed1900342f5874c87..b572b3bf69c791912717eae313942316cd77ac37
@@@ -13,6 -13,9 +13,9 @@@
  #include "diff.h"
  #include "revision.h"
  #include "remote.h"
+ #include "blob.h"
+ #include "xdiff-interface.h"
+ #include "ll-merge.h"
  
  static const char * const checkout_usage[] = {
        "git checkout [options] <branch>",
        NULL,
  };
  
+ struct checkout_opts {
+       int quiet;
+       int merge;
+       int force;
+       int writeout_stage;
+       int writeout_error;
+       const char *new_branch;
+       int new_branch_log;
+       enum branch_track track;
+ };
  static int post_checkout_hook(struct commit *old, struct commit *new,
                              int changed)
  {
@@@ -84,8 -99,119 +99,119 @@@ static int skip_same_name(struct cache_
        return pos;
  }
  
+ static int check_stage(int stage, struct cache_entry *ce, int pos)
+ {
+       while (pos < active_nr &&
+              !strcmp(active_cache[pos]->name, ce->name)) {
+               if (ce_stage(active_cache[pos]) == stage)
+                       return 0;
+               pos++;
+       }
+       return error("path '%s' does not have %s version",
+                    ce->name,
+                    (stage == 2) ? "our" : "their");
+ }
+ static int check_all_stages(struct cache_entry *ce, int pos)
+ {
+       if (ce_stage(ce) != 1 ||
+           active_nr <= pos + 2 ||
+           strcmp(active_cache[pos+1]->name, ce->name) ||
+           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",
+                            ce->name);
+       return 0;
+ }
+ static int checkout_stage(int stage, struct cache_entry *ce, int pos,
+                         struct checkout *state)
+ {
+       while (pos < active_nr &&
+              !strcmp(active_cache[pos]->name, ce->name)) {
+               if (ce_stage(active_cache[pos]) == stage)
+                       return checkout_entry(active_cache[pos], state, NULL);
+               pos++;
+       }
+       return error("path '%s' does not have %s version",
+                    ce->name,
+                    (stage == 2) ? "our" : "their");
+ }
+ /* NEEDSWORK: share with merge-recursive */
+ static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
+ {
+       unsigned long size;
+       enum object_type type;
+       if (!hashcmp(sha1, null_sha1)) {
+               mm->ptr = xstrdup("");
+               mm->size = 0;
+               return;
+       }
+       mm->ptr = read_sha1_file(sha1, &type, &size);
+       if (!mm->ptr || type != OBJ_BLOB)
+               die("unable to read blob object %s", sha1_to_hex(sha1));
+       mm->size = size;
+ }
+ static int checkout_merged(int pos, struct checkout *state)
+ {
+       struct cache_entry *ce = active_cache[pos];
+       const char *path = ce->name;
+       mmfile_t ancestor, ours, theirs;
+       int status;
+       unsigned char sha1[20];
+       mmbuffer_t result_buf;
+       if (ce_stage(ce) != 1 ||
+           active_nr <= pos + 2 ||
+           strcmp(active_cache[pos+1]->name, path) ||
+           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);
+       fill_mm(active_cache[pos]->sha1, &ancestor);
+       fill_mm(active_cache[pos+1]->sha1, &ours);
+       fill_mm(active_cache[pos+2]->sha1, &theirs);
+       status = ll_merge(&result_buf, path, &ancestor,
+                         &ours, "ours", &theirs, "theirs", 1);
+       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);
+       }
+       /*
+        * NEEDSWORK:
+        * There is absolutely no reason to write this as a blob object
+        * and create a phoney cache entry just to leak.  This hack is
+        * primarily to get to the write_entry() machinery that massages
+        * the contents to work-tree format and writes out which only
+        * allows it for a cache entry.  The code in write_entry() needs
+        * to be refactored to allow us to feed a <buffer, size, mode>
+        * instead of a cache entry.  Such a refactoring would help
+        * merge_recursive as well (it also writes the merge result to the
+        * object database even when it may contain conflicts).
+        */
+       if (write_sha1_file(result_buf.ptr, result_buf.size,
+                           blob_type, sha1))
+               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);
+       status = checkout_entry(ce, state, NULL);
+       return status;
+ }
  
- static int checkout_paths(struct tree *source_tree, const char **pathspec)
+ static int checkout_paths(struct tree *source_tree, const char **pathspec,
+                         struct checkout_opts *opts)
  {
        int pos;
        struct checkout state;
        int flag;
        struct commit *head;
        int errs = 0;
+       int stage = opts->writeout_stage;
+       int merge = opts->merge;
        int newfd;
        struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
  
                if (pathspec_match(pathspec, NULL, ce->name, 0)) {
                        if (!ce_stage(ce))
                                continue;
-                       errs = 1;
-                       error("path '%s' is unmerged", ce->name);
+                       if (opts->force) {
+                               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);
+                       }
                        pos = skip_same_name(ce, pos) - 1;
                }
        }
                                errs |= checkout_entry(ce, &state, NULL);
                                continue;
                        }
+                       if (stage)
+                               errs |= checkout_stage(stage, ce, pos, &state);
+                       else if (merge)
+                               errs |= checkout_merged(pos, &state);
                        pos = skip_same_name(ce, pos) - 1;
                }
        }
@@@ -178,17 -317,6 +317,6 @@@ static void describe_detached_head(cha
        strbuf_release(&sb);
  }
  
- struct checkout_opts {
-       int quiet;
-       int merge;
-       int force;
-       int writeout_error;
-       const char *new_branch;
-       int new_branch_log;
-       enum branch_track track;
- };
  static int reset_tree(struct tree *tree, struct checkout_opts *o, int worktree)
  {
        struct unpack_trees_options opts;
@@@ -269,8 -397,6 +397,8 @@@ static int merge_working_tree(struct ch
                }
  
                /* 2-way merge to the new branch */
 +              topts.initial_checkout = (!active_nr &&
 +                                        (old->commit == new->commit));
                topts.update = 1;
                topts.merge = 1;
                topts.gently = opts->merge;
                         */
                        struct tree *result;
                        struct tree *work;
 +                      struct merge_options o;
                        if (!opts->merge)
                                return 1;
                        parse_commit(old->commit);
                         */
  
                        add_files_to_cache(NULL, NULL, 0);
 -                      work = write_tree_from_memory();
 +                      init_merge_options(&o);
 +                      o.verbosity = 0;
 +                      work = write_tree_from_memory(&o);
  
                        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);
 +                      o.branch1 = new->name;
 +                      o.branch2 = "local";
 +                      merge_trees(&o, new->commit->tree, work,
 +                              old->commit->tree, &result);
                        ret = reset_tree(new->commit->tree, opts, 0);
                        if (ret)
                                return ret;
            commit_locked_index(lock_file))
                die("unable to write new index file");
  
 -      if (!opts->force)
 +      if (!opts->force && !opts->quiet)
                show_local_changes(&new->commit->object);
  
        return 0;
@@@ -420,11 -541,13 +548,11 @@@ static int switch_branches(struct check
        }
  
        /*
 -       * If the new thing isn't a branch and isn't HEAD and we're
 -       * not starting a new branch, and we want messages, and we
 -       * weren't on a branch, and we're moving to a new commit,
 -       * describe the old commit.
 +       * If we were on a detached HEAD, but we are now moving to
 +       * a new commit, we want to mention the old commit once more
 +       * to remind the user that it might be lost.
         */
 -      if (!new->path && strcmp(new->name, "HEAD") && !opts->new_branch &&
 -          !opts->quiet && !old.path && new->commit != old.commit)
 +      if (!opts->quiet && !old.path && new->commit != old.commit)
                describe_detached_head("Previous HEAD position was", old.commit);
  
        if (!old.commit) {
        return ret || opts->writeout_error;
  }
  
+ static int git_checkout_config(const char *var, const char *value, void *cb)
+ {
+       return git_xmerge_config(var, value, cb);
+ }
  int cmd_checkout(int argc, const char **argv, const char *prefix)
  {
        struct checkout_opts opts;
        const char *arg;
        struct branch_info new;
        struct tree *source_tree = NULL;
+       char *conflict_style = NULL;
        struct option options[] = {
                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_SET_INT('t', "track",  &opts.track, "track",
                        BRANCH_TRACK_EXPLICIT),
+               OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage",
+                           2),
+               OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage",
+                           3),
                OPT_BOOLEAN('f', NULL, &opts.force, "force"),
-               OPT_BOOLEAN('m', NULL, &opts.merge, "merge"),
+               OPT_BOOLEAN('m', "merge", &opts.merge, "merge"),
+               OPT_STRING(0, "conflict", &conflict_style, "style",
+                          "conflict style (merge or diff3)"),
                OPT_END(),
        };
        int has_dash_dash;
        memset(&opts, 0, sizeof(opts));
        memset(&new, 0, sizeof(new));
  
-       git_config(git_default_config, NULL);
+       git_config(git_checkout_config, NULL);
  
 -      opts.track = git_branch_track;
 +      opts.track = BRANCH_TRACK_UNSPECIFIED;
  
        argc = parse_options(argc, argv, options, checkout_usage,
                             PARSE_OPT_KEEP_DASHDASH);
  
 +      /* --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");
 +              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");
 +              opts.new_branch = argv0 + 1;
 +      }
 +
 +      if (opts.track == BRANCH_TRACK_UNSPECIFIED)
 +              opts.track = git_branch_track;
+       if (conflict_style) {
+               opts.merge = 1; /* implied */
+               git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
+       }
+       if (!opts.new_branch && (opts.track != git_branch_track))
+               die("git checkout: --track and --no-track require -b");
  
        if (opts.force && opts.merge)
                die("git checkout: -f and -m are incompatible");
@@@ -574,32 -699,25 +721,37 @@@ no_reference
                        die("invalid path specification");
  
                /* Checkout paths */
-               if (opts.new_branch || opts.force || opts.merge) {
+               if (opts.new_branch) {
                        if (argc == 1) {
-                               die("git checkout: updating paths is incompatible with switching branches/forcing\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/forcing");
+                               die("git checkout: updating paths is incompatible with switching branches.");
                        }
                }
  
-               return checkout_paths(source_tree, pathspec);
+               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.");
+               return checkout_paths(source_tree, pathspec, &opts);
        }
  
 +      if (opts.new_branch) {
 +              struct strbuf buf;
 +              strbuf_init(&buf, 0);
 +              strbuf_addstr(&buf, "refs/heads/");
 +              strbuf_addstr(&buf, opts.new_branch);
 +              if (!get_sha1(buf.buf, rev))
 +                      die("git checkout: branch %s already exists", opts.new_branch);
 +              if (check_ref_format(buf.buf))
 +                      die("git checkout: we do not like '%s' as a branch name.", opts.new_branch);
 +              strbuf_release(&buf);
 +      }
 +
        if (new.name && !new.commit) {
                die("Cannot switch branch to a non-commit.");
        }
+       if (opts.writeout_stage)
+               die("--ours/--theirs is incompatible with switching branches.");
  
        return switch_branches(&opts, &new);
  }
diff --combined rerere.c
index c38886b22af677083e749148604320fcada1ee75,4e2c9dd5b7276acd11ddb6ffa6619962ef9f57f3..8447caeebc3c030398cd00d25f1fb61bc952e150
+++ b/rerere.c
@@@ -75,7 -75,10 +75,10 @@@ static int handle_file(const char *path
  {
        SHA_CTX ctx;
        char buf[1024];
-       int hunk = 0, hunk_no = 0;
+       int hunk_no = 0;
+       enum {
+               RR_CONTEXT = 0, RR_SIDE_1, RR_SIDE_2, RR_ORIGINAL,
+       } hunk = RR_CONTEXT;
        struct strbuf one, two;
        FILE *f = fopen(path, "r");
        FILE *out = NULL;
        strbuf_init(&two,  0);
        while (fgets(buf, sizeof(buf), f)) {
                if (!prefixcmp(buf, "<<<<<<< ")) {
-                       if (hunk)
+                       if (hunk != RR_CONTEXT)
                                goto bad;
-                       hunk = 1;
+                       hunk = RR_SIDE_1;
+               } else if (!prefixcmp(buf, "|||||||") && isspace(buf[7])) {
+                       if (hunk != RR_SIDE_1)
+                               goto bad;
+                       hunk = RR_ORIGINAL;
                } else if (!prefixcmp(buf, "=======") && isspace(buf[7])) {
-                       if (hunk != 1)
+                       if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL)
                                goto bad;
-                       hunk = 2;
+                       hunk = RR_SIDE_2;
                } else if (!prefixcmp(buf, ">>>>>>> ")) {
-                       if (hunk != 2)
+                       if (hunk != RR_SIDE_2)
                                goto bad;
                        if (strbuf_cmp(&one, &two) > 0)
                                strbuf_swap(&one, &two);
                        hunk_no++;
-                       hunk = 0;
+                       hunk = RR_CONTEXT;
                        if (out) {
                                fputs("<<<<<<<\n", out);
                                fwrite(one.buf, one.len, 1, out);
                        }
                        strbuf_reset(&one);
                        strbuf_reset(&two);
-               } else if (hunk == 1)
+               } else if (hunk == RR_SIDE_1)
                        strbuf_addstr(&one, buf);
-               else if (hunk == 2)
+               else if (hunk == RR_ORIGINAL)
+                       ; /* discard */
+               else if (hunk == RR_SIDE_2)
                        strbuf_addstr(&two, buf);
                else if (out)
                        fputs(buf, out);
                fclose(out);
        if (sha1)
                SHA1_Final(sha1, &ctx);
-       if (hunk) {
+       if (hunk != RR_CONTEXT) {
                if (output)
                        unlink(output);
                return error("Could not parse conflict hunks in %s", path);
@@@ -319,6 -328,7 +328,6 @@@ static int git_rerere_config(const cha
  
  static int is_rerere_enabled(void)
  {
 -      struct stat st;
        const char *rr_cache;
        int rr_cache_exists;
  
                return 0;
  
        rr_cache = git_path("rr-cache");
 -      rr_cache_exists = !stat(rr_cache, &st) && S_ISDIR(st.st_mode);
 +      rr_cache_exists = is_directory(rr_cache);
        if (rerere_enabled < 0)
                return rr_cache_exists;
  
diff --combined t/t6023-merge-file.sh
index 5e18d68c97dc52537365adce5c3c84d291fe20be,b76bca888002bf16e1dc8e0a09a717aa99c1cd28..f8942bc8908fb92b1b11580e3554a81377dc3ba1
@@@ -136,7 -136,7 +136,7 @@@ test_expect_success "expected conflict 
  
  test_expect_success 'binary files cannot be merged' '
        test_must_fail git merge-file -p \
 -              orig.txt ../test4012.png new1.txt 2> merge.err &&
 +              orig.txt "$TEST_DIRECTORY"/test4012.png new1.txt 2> merge.err &&
        grep "Cannot merge binary files" merge.err
  '
  
@@@ -150,8 -150,8 +150,8 @@@ test_expect_success 'MERGE_ZEALOUS simp
  
  '
  
 -sed -e 's/deerit./&\n\n\n\n/' -e "s/locavit,/locavit;/" < new6.txt > new8.txt
 -sed -e 's/deerit./&\n\n\n\n/' -e "s/locavit,/locavit --/" < new7.txt > new9.txt
 +sed -e 's/deerit./&%%%%/' -e "s/locavit,/locavit;/"< new6.txt | tr '%' '\012' > new8.txt
 +sed -e 's/deerit./&%%%%/' -e "s/locavit,/locavit --/" < new7.txt | tr '%' '\012' > new9.txt
  
  test_expect_success 'ZEALOUS_ALNUM' '
  
  
  '
  
+ cat >expect <<\EOF
+ Dominus regit me,
+ <<<<<<< new8.txt
+ et nihil mihi deerit;
+ In loco pascuae ibi me collocavit;
+ super aquam refectionis educavit me.
+ |||||||
+ et nihil mihi deerit.
+ In loco pascuae ibi me collocavit,
+ super aquam refectionis educavit me;
+ =======
+ et nihil mihi deerit,
+ In loco pascuae ibi me collocavit --
+ super aquam refectionis educavit me,
+ >>>>>>> new9.txt
+ animam meam convertit,
+ deduxit me super semitas jusitiae,
+ propter nomen suum.
+ Nam et si ambulavero in medio umbrae mortis,
+ non timebo mala, quoniam TU mecum es:
+ virga tua et baculus tuus ipsa me consolata sunt.
+ EOF
+ test_expect_success '"diff3 -m" style output (1)' '
+       test_must_fail git merge-file -p --diff3 \
+               new8.txt new5.txt new9.txt >actual &&
+       test_cmp expect actual
+ '
+ test_expect_success '"diff3 -m" style output (2)' '
+       git config merge.conflictstyle diff3 &&
+       test_must_fail git merge-file -p \
+               new8.txt new5.txt new9.txt >actual &&
+       test_cmp expect actual
+ '
  test_done
diff --combined t/t7201-co.sh
index 62d73f934a0c0be2d41a30d11308430838f6c9c0,ac49311cf2c845475eed00ce5b7d8f364770d1c4..82769b89fc79d91982420ddfc83ce6c3dd672095
@@@ -3,7 -3,7 +3,7 @@@
  # Copyright (c) 2006 Junio C Hamano
  #
  
 -test_description='git-checkout tests.
 +test_description='git checkout tests.
  
  Creates master, forks renamer and side branches from it.
  Test switching across them.
@@@ -337,39 -337,7 +337,39 @@@ test_expect_success 
      test refs/heads/delete-me = "$(git symbolic-ref HEAD)" &&
      test_must_fail git checkout --track -b track'
  
- test_expect_success 'checkout an unmerged path should fail' '
 +test_expect_success \
 +    'checkout with --track fakes a sensible -b <name>' '
 +    git update-ref refs/remotes/origin/koala/bear renamer &&
 +    git update-ref refs/new/koala/bear renamer &&
 +
 +    git checkout --track origin/koala/bear &&
 +    test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
 +    test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" &&
 +
 +    git checkout master && git branch -D koala/bear &&
 +
 +    git checkout --track refs/remotes/origin/koala/bear &&
 +    test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
 +    test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" &&
 +
 +    git checkout master && git branch -D koala/bear &&
 +
 +    git checkout --track remotes/origin/koala/bear &&
 +    test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
 +    test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" &&
 +
 +    git checkout master && git branch -D koala/bear &&
 +
 +    git checkout --track refs/new/koala/bear &&
 +    test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
 +    test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)"
 +'
 +
 +test_expect_success \
 +    'checkout with --track, but without -b, fails with too short tracked name' '
 +    test_must_fail git checkout --track renamer'
 +
+ setup_conflicting_index () {
        rm -f .git/index &&
        O=$(echo original | git hash-object -w --stdin) &&
        A=$(echo ourside | git hash-object -w --stdin) &&
                echo "100644 $A 2       file" &&
                echo "100644 $B 3       file" &&
                echo "100644 $A 0       filf"
-       ) | git update-index --index-info &&
+       ) | git update-index --index-info
+ }
+ test_expect_success 'checkout an unmerged path should fail' '
+       setup_conflicting_index &&
        echo "none of the above" >sample &&
        cat sample >fild &&
        cat sample >file &&
        test_cmp sample file
  '
  
+ test_expect_success 'checkout with an unmerged path can be ignored' '
+       setup_conflicting_index &&
+       echo "none of the above" >sample &&
+       echo ourside >expect &&
+       cat sample >fild &&
+       cat sample >file &&
+       cat sample >filf &&
+       git checkout -f fild file filf &&
+       test_cmp expect fild &&
+       test_cmp expect filf &&
+       test_cmp sample file
+ '
+ test_expect_success 'checkout unmerged stage' '
+       setup_conflicting_index &&
+       echo "none of the above" >sample &&
+       echo ourside >expect &&
+       cat sample >fild &&
+       cat sample >file &&
+       cat sample >filf &&
+       git checkout --ours . &&
+       test_cmp expect fild &&
+       test_cmp expect filf &&
+       test_cmp expect file &&
+       git checkout --theirs file &&
+       test ztheirside = "z$(cat file)"
+ '
+ test_expect_success 'checkout with --merge' '
+       setup_conflicting_index &&
+       echo "none of the above" >sample &&
+       echo ourside >expect &&
+       cat sample >fild &&
+       cat sample >file &&
+       cat sample >filf &&
+       git checkout -m -- fild file filf &&
+       (
+               echo "<<<<<<< ours"
+               echo ourside
+               echo "======="
+               echo theirside
+               echo ">>>>>>> theirs"
+       ) >merged &&
+       test_cmp expect fild &&
+       test_cmp expect filf &&
+       test_cmp merged file
+ '
+ test_expect_success 'checkout with --merge, in diff3 -m style' '
+       git config merge.conflictstyle diff3 &&
+       setup_conflicting_index &&
+       echo "none of the above" >sample &&
+       echo ourside >expect &&
+       cat sample >fild &&
+       cat sample >file &&
+       cat sample >filf &&
+       git checkout -m -- fild file filf &&
+       (
+               echo "<<<<<<< ours"
+               echo ourside
+               echo "|||||||"
+               echo original
+               echo "======="
+               echo theirside
+               echo ">>>>>>> theirs"
+       ) >merged &&
+       test_cmp expect fild &&
+       test_cmp expect filf &&
+       test_cmp merged file
+ '
+ test_expect_success 'checkout --conflict=merge, overriding config' '
+       git config merge.conflictstyle diff3 &&
+       setup_conflicting_index &&
+       echo "none of the above" >sample &&
+       echo ourside >expect &&
+       cat sample >fild &&
+       cat sample >file &&
+       cat sample >filf &&
+       git checkout --conflict=merge -- fild file filf &&
+       (
+               echo "<<<<<<< ours"
+               echo ourside
+               echo "======="
+               echo theirside
+               echo ">>>>>>> theirs"
+       ) >merged &&
+       test_cmp expect fild &&
+       test_cmp expect filf &&
+       test_cmp merged file
+ '
+ test_expect_success 'checkout --conflict=diff3' '
+       git config --unset merge.conflictstyle
+       setup_conflicting_index &&
+       echo "none of the above" >sample &&
+       echo ourside >expect &&
+       cat sample >fild &&
+       cat sample >file &&
+       cat sample >filf &&
+       git checkout --conflict=diff3 -- fild file filf &&
+       (
+               echo "<<<<<<< ours"
+               echo ourside
+               echo "|||||||"
+               echo original
+               echo "======="
+               echo theirside
+               echo ">>>>>>> theirs"
+       ) >merged &&
+       test_cmp expect fild &&
+       test_cmp expect filf &&
+       test_cmp merged file
+ '
 +test_expect_success 'failing checkout -b should not break working tree' '
 +      git reset --hard master &&
 +      git symbolic-ref HEAD refs/heads/master &&
 +      test_must_fail git checkout -b renamer side^ &&
 +      test $(git symbolic-ref HEAD) = refs/heads/master &&
 +      git diff --exit-code &&
 +      git diff --cached --exit-code
 +
 +'
 +
  test_done
diff --combined xdiff-interface.c
index 944ad9887f5c94ca0e63f9a6c901447634f871ce,295198333db439c09dee0abeaa6644369835ad06..8457601bc417129803c298df57d5d12c6e317096
@@@ -1,12 -1,5 +1,12 @@@
  #include "cache.h"
  #include "xdiff-interface.h"
 +#include "strbuf.h"
 +
 +struct xdiff_emit_state {
 +      xdiff_emit_consume_fn consume;
 +      void *consume_callback_data;
 +      struct strbuf remainder;
 +};
  
  static int parse_num(char **cp_p, int *num_p)
  {
@@@ -62,13 -55,13 +62,13 @@@ static void consume_one(void *priv_, ch
                unsigned long this_size;
                ep = memchr(s, '\n', size);
                this_size = (ep == NULL) ? size : (ep - s + 1);
 -              priv->consume(priv, s, this_size);
 +              priv->consume(priv->consume_callback_data, s, this_size);
                size -= this_size;
                s += this_size;
        }
  }
  
 -int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf)
 +static int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf)
  {
        struct xdiff_emit_state *priv = priv_;
        int i;
        for (i = 0; i < nbuf; i++) {
                if (mb[i].ptr[mb[i].size-1] != '\n') {
                        /* Incomplete line */
 -                      priv->remainder = xrealloc(priv->remainder,
 -                                                 priv->remainder_size +
 -                                                 mb[i].size);
 -                      memcpy(priv->remainder + priv->remainder_size,
 -                             mb[i].ptr, mb[i].size);
 -                      priv->remainder_size += mb[i].size;
 +                      strbuf_add(&priv->remainder, mb[i].ptr, mb[i].size);
                        continue;
                }
  
                /* we have a complete line */
 -              if (!priv->remainder) {
 +              if (!priv->remainder.len) {
                        consume_one(priv, mb[i].ptr, mb[i].size);
                        continue;
                }
 -              priv->remainder = xrealloc(priv->remainder,
 -                                         priv->remainder_size +
 -                                         mb[i].size);
 -              memcpy(priv->remainder + priv->remainder_size,
 -                     mb[i].ptr, mb[i].size);
 -              consume_one(priv, priv->remainder,
 -                          priv->remainder_size + mb[i].size);
 -              free(priv->remainder);
 -              priv->remainder = NULL;
 -              priv->remainder_size = 0;
 +              strbuf_add(&priv->remainder, mb[i].ptr, mb[i].size);
 +              consume_one(priv, priv->remainder.buf, priv->remainder.len);
 +              strbuf_reset(&priv->remainder);
        }
 -      if (priv->remainder) {
 -              consume_one(priv, priv->remainder, priv->remainder_size);
 -              free(priv->remainder);
 -              priv->remainder = NULL;
 -              priv->remainder_size = 0;
 +      if (priv->remainder.len) {
 +              consume_one(priv, priv->remainder.buf, priv->remainder.len);
 +              strbuf_reset(&priv->remainder);
        }
        return 0;
  }
@@@ -134,25 -141,6 +134,25 @@@ int xdi_diff(mmfile_t *mf1, mmfile_t *m
        return xdl_diff(&a, &b, xpp, xecfg, xecb);
  }
  
 +int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2,
 +                xdiff_emit_consume_fn fn, void *consume_callback_data,
 +                xpparam_t const *xpp,
 +                xdemitconf_t const *xecfg, xdemitcb_t *xecb)
 +{
 +      int ret;
 +      struct xdiff_emit_state state;
 +
 +      memset(&state, 0, sizeof(state));
 +      state.consume = fn;
 +      state.consume_callback_data = consume_callback_data;
 +      xecb->outf = xdiff_outf;
 +      xecb->priv = &state;
 +      strbuf_init(&state.remainder, 0);
 +      ret = xdi_diff(mf1, mf2, xpp, xecfg, xecb);
 +      strbuf_release(&state.remainder);
 +      return ret;
 +}
 +
  int read_mmfile(mmfile_t *ptr, const char *filename)
  {
        struct stat st;
@@@ -249,3 -237,23 +249,23 @@@ void xdiff_set_find_func(xdemitconf_t *
                value = ep + 1;
        }
  }
+ int git_xmerge_style = -1;
+ int git_xmerge_config(const char *var, const char *value, void *cb)
+ {
+       if (!strcasecmp(var, "merge.conflictstyle")) {
+               if (!value)
+                       die("'%s' is not a boolean", var);
+               if (!strcmp(value, "diff3"))
+                       git_xmerge_style = XDL_MERGE_DIFF3;
+               else if (!strcmp(value, "merge"))
+                       git_xmerge_style = 0;
+               else
+                       die("unknown style '%s' given for '%s'",
+                           value, var);
+               return 0;
+       }
+       return git_default_config(var, value, cb);
+ }
diff --combined xdiff-interface.h
index 558492ba38c50351a98f80c16e83a058ace849ab,cfe3215cb2e774f5f088fbc1b0e841a27e474eda..b3b5c933bfc3aac2d6c0b59aa72bb18e0b1f7557
@@@ -3,13 -3,18 +3,13 @@@
  
  #include "xdiff/xdiff.h"
  
 -struct xdiff_emit_state;
 -
  typedef void (*xdiff_emit_consume_fn)(void *, char *, unsigned long);
  
 -struct xdiff_emit_state {
 -      xdiff_emit_consume_fn consume;
 -      char *remainder;
 -      unsigned long remainder_size;
 -};
 -
  int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *ecb);
 -int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf);
 +int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2,
 +                xdiff_emit_consume_fn fn, void *consume_callback_data,
 +                xpparam_t const *xpp,
 +                xdemitconf_t const *xecfg, xdemitcb_t *xecb);
  int parse_hunk_header(char *line, int len,
                      int *ob, int *on,
                      int *nb, int *nn);
@@@ -17,5 -22,7 +17,7 @@@ int read_mmfile(mmfile_t *ptr, const ch
  int buffer_is_binary(const char *ptr, unsigned long size);
  
  extern void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line);
+ extern int git_xmerge_config(const char *var, const char *value, void *cb);
+ extern int git_xmerge_style;
  
  #endif