Code

Merge branch 'dg/local-mod-error-messages'
authorJunio C Hamano <gitster@pobox.com>
Sun, 22 Aug 2010 06:26:46 +0000 (23:26 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 22 Aug 2010 06:26:46 +0000 (23:26 -0700)
* dg/local-mod-error-messages:
  t7609: test merge and checkout error messages
  unpack_trees: group error messages by type
  merge-recursive: distinguish "removed" and "overwritten" messages
  merge-recursive: porcelain messages for checkout
  Turn unpack_trees_options.msgs into an array + enum

Conflicts:
t/t3400-rebase.sh

1  2 
builtin/checkout.c
merge-recursive.c
t/t3400-rebase.sh
t/t3404-rebase-interactive.sh
unpack-trees.c

diff --combined builtin/checkout.c
index 7f81120c728dce6fe712280f40583925eda264e3,894bb84db515b1b371cf356785dd1807ed1ec5ab..7250e5c23cee7deda557c16945373b59d99424ee
@@@ -32,11 -32,7 +32,11 @@@ struct checkout_opts 
        int writeout_stage;
        int writeout_error;
  
 +      /* not set by parse_options */
 +      int branch_exists;
 +
        const char *new_branch;
 +      const char *new_branch_force;
        const char *new_orphan_branch;
        int new_branch_log;
        enum branch_track track;
@@@ -283,6 -279,7 +283,6 @@@ static void show_local_changes(struct o
        struct rev_info rev;
        /* I think we want full paths, even if we're in a subdirectory. */
        init_revisions(&rev, NULL);
 -      rev.abbrev = 0;
        rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
        if (diff_setup_done(&rev.diffopt) < 0)
                die("diff_setup_done failed");
@@@ -376,7 -373,7 +376,7 @@@ static int merge_working_tree(struct ch
                topts.src_index = &the_index;
                topts.dst_index = &the_index;
  
-               topts.msgs.not_uptodate_file = "You have local changes to '%s'; cannot switch branches.";
+               set_porcelain_error_msgs(topts.msgs, "checkout");
  
                refresh_cache(REFRESH_QUIET);
  
                topts.dir = xcalloc(1, sizeof(*topts.dir));
                topts.dir->flags |= DIR_SHOW_IGNORED;
                topts.dir->exclude_per_dir = ".gitignore";
+               topts.show_all_errors = 1;
                tree = parse_tree_indirect(old->commit ?
                                           old->commit->object.sha1 :
                                           (unsigned char *)EMPTY_TREE_SHA1_BIN);
@@@ -514,8 -512,7 +515,8 @@@ static void update_refs_for_switch(stru
                        }
                }
                else
 -                      create_branch(old->name, opts->new_branch, new->name, 0,
 +                      create_branch(old->name, opts->new_branch, new->name,
 +                                    opts->new_branch_force ? 1 : 0,
                                      opts->new_branch_log, opts->track);
                new->name = opts->new_branch;
                setup_branch_path(new);
                        if (old->path && !strcmp(new->path, old->path))
                                fprintf(stderr, "Already on '%s'\n",
                                        new->name);
 -                      else
 +                      else if (opts->new_branch)
                                fprintf(stderr, "Switched to%s branch '%s'\n",
 -                                      opts->new_branch ? " a new" : "",
 +                                      opts->branch_exists ? " and reset" : " a new",
 +                                      new->name);
 +                      else
 +                              fprintf(stderr, "Switched to branch '%s'\n",
                                        new->name);
                }
                if (old->path && old->name) {
@@@ -664,10 -658,7 +665,10 @@@ int cmd_checkout(int argc, const char *
        int dwim_new_local_branch = 1;
        struct option options[] = {
                OPT__QUIET(&opts.quiet),
 -              OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"),
 +              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",
                        BRANCH_TRACK_EXPLICIT),
        argc = parse_options(argc, argv, prefix, options, checkout_usage,
                             PARSE_OPT_KEEP_DASHDASH);
  
 +      /* 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");
 +
 +      /* 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");
  
        if (opts.new_orphan_branch) {
                if (opts.new_branch)
 -                      die("--orphan and -b are mutually exclusive");
 +                      die("--orphan and -b|-B are mutually exclusive");
                if (opts.track > 0)
                        die("--orphan cannot be used with -t");
                opts.new_branch = opts.new_orphan_branch;
@@@ -876,12 -859,8 +877,12 @@@ no_reference
                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))
 -                      die("git checkout: branch %s already exists", 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);
 +              }
                strbuf_release(&buf);
        }
  
diff --combined merge-recursive.c
index 6cff9cdc86b1db7c4bd0a75175a77b2474417dfb,10392d9661180c368736e2a64d91fbca53dfae11..656e769b447f26247aa9672baf327a256b6bfeb1
@@@ -136,10 -136,16 +136,10 @@@ static void output_commit_title(struct 
                if (parse_commit(commit) != 0)
                        printf("(bad commit)\n");
                else {
 -                      const char *s;
 -                      int len;
 -                      for (s = commit->buffer; *s; s++)
 -                              if (*s == '\n' && s[1] == '\n') {
 -                                      s += 2;
 -                                      break;
 -                              }
 -                      for (len = 0; s[len] && '\n' != s[len]; len++)
 -                              ; /* do nothing */
 -                      printf("%.*s\n", len, s);
 +                      const char *title;
 +                      int len = find_commit_subject(commit->buffer, &title);
 +                      if (len)
 +                              printf("%.*s\n", len, title);
                }
        }
  }
@@@ -179,7 -185,7 +179,7 @@@ static int git_merge_trees(int index_on
        opts.fn = threeway_merge;
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
-       opts.msgs = get_porcelain_error_msgs();
+       set_porcelain_error_msgs(opts.msgs, "merge");
  
        init_tree_desc_from_tree(t+0, common);
        init_tree_desc_from_tree(t+1, head);
@@@ -800,8 -806,7 +800,8 @@@ static int process_renames(struct merge
                           struct string_list *b_renames)
  {
        int clean_merge = 1, i, j;
 -      struct string_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0};
 +      struct string_list a_by_dst = STRING_LIST_INIT_NODUP;
 +      struct string_list b_by_dst = STRING_LIST_INIT_NODUP;
        const struct rename *sre;
  
        for (i = 0; i < a_renames->nr; i++) {
@@@ -1173,26 -1178,48 +1173,48 @@@ static int process_entry(struct merge_o
        return clean_merge;
  }
  
struct unpack_trees_error_msgs get_porcelain_error_msgs(void)
void set_porcelain_error_msgs(const char **msgs, const char *cmd)
  {
-       struct unpack_trees_error_msgs msgs = {
-               /* would_overwrite */
-               "Your local changes to '%s' would be overwritten by merge.  Aborting.",
-               /* not_uptodate_file */
-               "Your local changes to '%s' would be overwritten by merge.  Aborting.",
-               /* not_uptodate_dir */
-               "Updating '%s' would lose untracked files in it.  Aborting.",
-               /* would_lose_untracked */
-               "Untracked working tree file '%s' would be %s by merge.  Aborting",
-               /* bind_overlap -- will not happen here */
-               NULL,
-       };
-       if (advice_commit_before_merge) {
-               msgs.would_overwrite = msgs.not_uptodate_file =
-                       "Your local changes to '%s' would be overwritten by merge.  Aborting.\n"
-                       "Please, commit your changes or stash them before you can merge.";
-       }
-       return msgs;
+       const char *msg;
+       char *tmp;
+       const char *cmd2 = strcmp(cmd, "checkout") ? cmd : "switch branches";
+       if (advice_commit_before_merge)
+               msg = "Your local changes to the following files would be overwritten by %s:\n%%s"
+                       "Please, commit your changes or stash them before you can %s.";
+       else
+               msg = "Your local changes to the following files would be overwritten by %s:\n%%s";
+       tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen(cmd2) - 2);
+       sprintf(tmp, msg, cmd, cmd2);
+       msgs[ERROR_WOULD_OVERWRITE] = tmp;
+       msgs[ERROR_NOT_UPTODATE_FILE] = tmp;
+       msgs[ERROR_NOT_UPTODATE_DIR] =
+               "Updating the following directories would lose untracked files in it:\n%s";
+       if (advice_commit_before_merge)
+               msg = "The following untracked working tree files would be %s by %s:\n%%s"
+                       "Please move or remove them before you can %s.";
+       else
+               msg = "The following untracked working tree files would be %s by %s:\n%%s";
+       tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen("removed") + strlen(cmd2) - 4);
+       sprintf(tmp, msg, "removed", cmd, cmd2);
+       msgs[ERROR_WOULD_LOSE_UNTRACKED_REMOVED] = tmp;
+       tmp = xmalloc(strlen(msg) + strlen(cmd) + strlen("overwritten") + strlen(cmd2) - 4);
+       sprintf(tmp, msg, "overwritten", cmd, cmd2);
+       msgs[ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN] = tmp;
+       /*
+        * Special case: ERROR_BIND_OVERLAP refers to a pair of paths, we
+        * cannot easily display it as a list.
+        */
+       msgs[ERROR_BIND_OVERLAP] = "Entry '%s' overlaps with '%s'.  Cannot bind.";
+       msgs[ERROR_SPARSE_NOT_UPTODATE_FILE] =
+               "Cannot update sparse checkout: the following entries are not up-to-date:\n%s";
+       msgs[ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN] =
+               "The following Working tree files would be overwritten by sparse checkout update:\n%s";
+       msgs[ERROR_WOULD_LOSE_ORPHANED_REMOVED] =
+               "The following Working tree files would be removed by sparse checkout update:\n%s";
  }
  
  int merge_trees(struct merge_options *o,
diff --combined t/t3400-rebase.sh
index a19aeb6441cb29dc20478cf898b5f7767d770c94,45ef28281512e8f53eafc3cb23c1b99e0ef82c0f..349eebd54268c927249322dc22fc478869860f7c
@@@ -14,164 -14,141 +14,165 @@@ GIT_AUTHOR_NAME=author@nam
  GIT_AUTHOR_EMAIL=bogus@email@address
  export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
  
 -test_expect_success \
 -    'prepare repository with topic branches' \
 -    'git config core.logAllRefUpdates true &&
 -     echo First > A &&
 -     git update-index --add A &&
 -     git commit -m "Add A." &&
 -     git checkout -b my-topic-branch &&
 -     echo Second > B &&
 -     git update-index --add B &&
 -     git commit -m "Add B." &&
 -     git checkout -f master &&
 -     echo Third >> A &&
 -     git update-index A &&
 -     git commit -m "Modify A." &&
 -     git checkout -b side my-topic-branch &&
 -     echo Side >> C &&
 -     git add C &&
 -     git commit -m "Add C" &&
 -     git checkout -b nonlinear my-topic-branch &&
 -     echo Edit >> B &&
 -     git add B &&
 -     git commit -m "Modify B" &&
 -     git merge side &&
 -     git checkout -b upstream-merged-nonlinear &&
 -     git merge master &&
 -     git checkout -f my-topic-branch &&
 -     git tag topic
 +test_expect_success 'prepare repository with topic branches' '
 +      git config core.logAllRefUpdates true &&
 +      echo First >A &&
 +      git update-index --add A &&
 +      git commit -m "Add A." &&
 +      git checkout -b force-3way &&
 +      echo Dummy >Y &&
 +      git update-index --add Y &&
 +      git commit -m "Add Y." &&
 +      git checkout -b filemove &&
 +      git reset --soft master &&
 +      mkdir D &&
 +      git mv A D/A &&
 +      git commit -m "Move A." &&
 +      git checkout -b my-topic-branch master &&
 +      echo Second >B &&
 +      git update-index --add B &&
 +      git commit -m "Add B." &&
 +      git checkout -f master &&
 +      echo Third >>A &&
 +      git update-index A &&
 +      git commit -m "Modify A." &&
 +      git checkout -b side my-topic-branch &&
 +      echo Side >>C &&
 +      git add C &&
 +      git commit -m "Add C" &&
 +      git checkout -b nonlinear my-topic-branch &&
 +      echo Edit >>B &&
 +      git add B &&
 +      git commit -m "Modify B" &&
 +      git merge side &&
 +      git checkout -b upstream-merged-nonlinear &&
 +      git merge master &&
 +      git checkout -f my-topic-branch &&
 +      git tag topic
  '
  
  test_expect_success 'rebase on dirty worktree' '
 -     echo dirty >> A &&
 -     test_must_fail git rebase master'
 +      echo dirty >>A &&
 +      test_must_fail git rebase master
 +'
  
  test_expect_success 'rebase on dirty cache' '
 -     git add A &&
 -     test_must_fail git rebase master'
 +      git add A &&
 +      test_must_fail git rebase master
 +'
  
  test_expect_success 'rebase against master' '
 -     git reset --hard HEAD &&
 -     git rebase master'
 +      git reset --hard HEAD &&
 +      git rebase master
 +'
  
  test_expect_success 'rebase against master twice' '
 -     git rebase master >out &&
 -     grep "Current branch my-topic-branch is up to date" out
 +      git rebase master >out &&
 +      grep "Current branch my-topic-branch is up to date" out
  '
  
  test_expect_success 'rebase against master twice with --force' '
 -     git rebase --force-rebase master >out &&
 -     grep "Current branch my-topic-branch is up to date, rebase forced" out
 +      git rebase --force-rebase master >out &&
 +      grep "Current branch my-topic-branch is up to date, rebase forced" out
  '
  
  test_expect_success 'rebase against master twice from another branch' '
 -     git checkout my-topic-branch^ &&
 -     git rebase master my-topic-branch >out &&
 -     grep "Current branch my-topic-branch is up to date" out
 +      git checkout my-topic-branch^ &&
 +      git rebase master my-topic-branch >out &&
 +      grep "Current branch my-topic-branch is up to date" out
  '
  
  test_expect_success 'rebase fast-forward to master' '
 -     git checkout my-topic-branch^ &&
 -     git rebase my-topic-branch >out &&
 -     grep "Fast-forwarded HEAD to my-topic-branch" out
 +      git checkout my-topic-branch^ &&
 +      git rebase my-topic-branch >out &&
 +      grep "Fast-forwarded HEAD to my-topic-branch" out
  '
  
 -test_expect_success \
 -    'the rebase operation should not have destroyed author information' \
 -    '! (git log | grep "Author:" | grep "<>")'
 +test_expect_success 'the rebase operation should not have destroyed author information' '
 +      ! (git log | grep "Author:" | grep "<>")
 +'
  
 -test_expect_success \
 -    'the rebase operation should not have destroyed author information (2)' \
 -    "git log -1 | grep 'Author: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>'"
 +test_expect_success 'the rebase operation should not have destroyed author information (2)' "
 +      git log -1 |
 +      grep 'Author: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>'
 +"
  
  test_expect_success 'HEAD was detached during rebase' '
 -     test $(git rev-parse HEAD@{1}) != $(git rev-parse my-topic-branch@{1})
 +      test $(git rev-parse HEAD@{1}) != $(git rev-parse my-topic-branch@{1})
  '
  
  test_expect_success 'rebase after merge master' '
 -     git reset --hard topic &&
 -     git merge master &&
 -     git rebase master &&
 -     ! (git show | grep "^Merge:")
 +      git reset --hard topic &&
 +      git merge master &&
 +      git rebase master &&
 +      ! (git show | grep "^Merge:")
  '
  
  test_expect_success 'rebase of history with merges is linearized' '
 -     git checkout nonlinear &&
 -     test 4 = $(git rev-list master.. | wc -l) &&
 -     git rebase master &&
 -     test 3 = $(git rev-list master.. | wc -l)
 +      git checkout nonlinear &&
 +      test 4 = $(git rev-list master.. | wc -l) &&
 +      git rebase master &&
 +      test 3 = $(git rev-list master.. | wc -l)
  '
  
 -test_expect_success \
 -    'rebase of history with merges after upstream merge is linearized' '
 -     git checkout upstream-merged-nonlinear &&
 -     test 5 = $(git rev-list master.. | wc -l) &&
 -     git rebase master &&
 -     test 3 = $(git rev-list master.. | wc -l)
 +test_expect_success 'rebase of history with merges after upstream merge is linearized' '
 +      git checkout upstream-merged-nonlinear &&
 +      test 5 = $(git rev-list master.. | wc -l) &&
 +      git rebase master &&
 +      test 3 = $(git rev-list master.. | wc -l)
  '
  
  test_expect_success 'rebase a single mode change' '
 -     git checkout master &&
 -     echo 1 > X &&
 -     git add X &&
 -     test_tick &&
 -     git commit -m prepare &&
 -     git checkout -b modechange HEAD^ &&
 -     echo 1 > X &&
 -     git add X &&
 -     test_chmod +x A &&
 -     test_tick &&
 -     git commit -m modechange &&
 -     GIT_TRACE=1 git rebase master
 +      git checkout master &&
 +      echo 1 >X &&
 +      git add X &&
 +      test_tick &&
 +      git commit -m prepare &&
 +      git checkout -b modechange HEAD^ &&
 +      echo 1 >X &&
 +      git add X &&
 +      test_chmod +x A &&
 +      test_tick &&
 +      git commit -m modechange &&
 +      GIT_TRACE=1 git rebase master
 +'
 +
 +test_expect_success 'rebase is not broken by diff.renames' '
 +      git config diff.renames copies &&
 +      test_when_finished "git config --unset diff.renames" &&
 +      git checkout filemove &&
 +      GIT_TRACE=1 git rebase force-3way
 +'
 +
 +test_expect_success 'setup: recover' '
 +      test_might_fail git rebase --abort &&
 +      git reset --hard &&
 +      git checkout modechange
  '
  
  test_expect_success 'Show verbose error when HEAD could not be detached' '
 -     : > B &&
 -     test_must_fail git rebase topic 2> output.err > output.out &&
 -     grep "The following untracked working tree files would be overwritten by checkout:" output.err &&
 -     grep B output.err
 +      >B &&
 +      test_must_fail git rebase topic 2>output.err >output.out &&
-       grep "Untracked working tree file .B. would be overwritten" output.err
++      grep "The following untracked working tree files would be overwritten by checkout:" output.err &&
++      grep B output.err
  '
  rm -f B
  
  test_expect_success 'dump usage when upstream arg is missing' '
 -     git checkout -b usage topic &&
 -     test_must_fail git rebase 2>error1 &&
 -     grep "[Uu]sage" error1 &&
 -     test_must_fail git rebase --abort 2>error2 &&
 -     grep "No rebase in progress" error2 &&
 -     test_must_fail git rebase --onto master 2>error3 &&
 -     grep "[Uu]sage" error3 &&
 -     ! grep "can.t shift" error3
 +      git checkout -b usage topic &&
 +      test_must_fail git rebase 2>error1 &&
 +      grep "[Uu]sage" error1 &&
 +      test_must_fail git rebase --abort 2>error2 &&
 +      grep "No rebase in progress" error2 &&
 +      test_must_fail git rebase --onto master 2>error3 &&
 +      grep "[Uu]sage" error3 &&
 +      ! grep "can.t shift" error3
  '
  
  test_expect_success 'rebase -q is quiet' '
 -     git checkout -b quiet topic &&
 -     git rebase -q master > output.out 2>&1 &&
 -     test ! -s output.out
 +      git checkout -b quiet topic &&
 +      git rebase -q master >output.out 2>&1 &&
 +      test ! -s output.out
  '
  
  test_expect_success 'Rebase a commit that sprinkles CRs in' '
index 9f03ce699e8f4c3c654065deae675d1a7b259c74,0eb21cbdee6a5f5245bbd4d130f96c2c1b36de65..3af3f603fb2ad34368b4f53f523bdd71dffdf854
@@@ -150,8 -150,9 +150,9 @@@ test_expect_success 'abort with error w
        git rm --cached file1 &&
        git commit -m "remove file in base" &&
        test_must_fail git rebase -i master > output 2>&1 &&
-       grep "Untracked working tree file .file1. would be overwritten" \
+       grep "The following untracked working tree files would be overwritten by checkout:" \
                output &&
+       grep "file1" output &&
        ! test -d .git/rebase-merge &&
        git reset --hard HEAD^
  '
@@@ -637,19 -638,13 +638,19 @@@ test_expect_success 'set up commits wit
        git commit -a -m "end with slash\\" &&
        echo >>file1 &&
        test_tick &&
 +      git commit -a -m "something (\000) that looks like octal" &&
 +      echo >>file1 &&
 +      test_tick &&
 +      git commit -a -m "something (\n) that looks like a newline" &&
 +      echo >>file1 &&
 +      test_tick &&
        git commit -a -m "another commit"
  '
  
  test_expect_success 'rebase-i history with funny messages' '
        git rev-list A..funny >expect &&
        test_tick &&
 -      FAKE_LINES="1 2" git rebase -i A &&
 +      FAKE_LINES="1 2 3 4" git rebase -i A &&
        git rev-list A.. >actual &&
        test_cmp expect actual
  '
diff --combined unpack-trees.c
index f561d88156b827b15bad7dd4e00f944718149bd9,7b10f92fa4854f4b97ee67858467ca94e15cb0b3..62852aa7fb3fbd5e2d3ae1173d05deddba763d52
   * Error messages expected by scripts out of plumbing commands such as
   * read-tree.  Non-scripted Porcelain is not required to use these messages
   * and in fact are encouraged to reword them to better suit their particular
-  * situation better.  See how "git checkout" replaces not_uptodate_file to
-  * explain why it does not allow switching between branches when you have
-  * local changes, for example.
+  * situation better.  See how "git checkout" and "git merge" replaces
+  * them using set_porcelain_error_msgs(), for example.
   */
static struct unpack_trees_error_msgs unpack_plumbing_errors = {
-       /* would_overwrite */
const char *unpack_plumbing_errors[NB_UNPACK_TREES_ERROR_TYPES] = {
+       /* ERROR_WOULD_OVERWRITE */
        "Entry '%s' would be overwritten by merge. Cannot merge.",
  
-       /* not_uptodate_file */
+       /* ERROR_NOT_UPTODATE_FILE */
        "Entry '%s' not uptodate. Cannot merge.",
  
-       /* not_uptodate_dir */
+       /* ERROR_NOT_UPTODATE_DIR */
        "Updating '%s' would lose untracked files in it",
  
-       /* would_lose_untracked */
-       "Untracked working tree file '%s' would be %s by merge.",
+       /* ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN */
+       "Untracked working tree file '%s' would be overwritten by merge.",
  
-       /* bind_overlap */
+       /* ERROR_WOULD_LOSE_UNTRACKED_REMOVED */
+       "Untracked working tree file '%s' would be removed by merge.",
+       /* ERROR_BIND_OVERLAP */
        "Entry '%s' overlaps with '%s'.  Cannot bind.",
  
-       /* sparse_not_uptodate_file */
+       /* ERROR_SPARSE_NOT_UPTODATE_FILE */
        "Entry '%s' not uptodate. Cannot update sparse checkout.",
  
-       /* would_lose_orphaned */
-       "Working tree file '%s' would be %s by sparse checkout update.",
+       /* ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN */
+       "Working tree file '%s' would be overwritten by sparse checkout update.",
+       /* ERROR_WOULD_LOSE_ORPHANED_REMOVED */
+       "Working tree file '%s' would be removed by sparse checkout update.",
  };
  
- #define ERRORMSG(o,fld) \
-       ( ((o) && (o)->msgs.fld) \
-       ? ((o)->msgs.fld) \
-       : (unpack_plumbing_errors.fld) )
+ #define ERRORMSG(o,type) \
+       ( ((o) && (o)->msgs[(type)]) \
+         ? ((o)->msgs[(type)])      \
+         : (unpack_plumbing_errors[(type)]) )
  
  static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
        unsigned int set, unsigned int clear)
        add_index_entry(&o->result, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
  }
  
+ /*
+  * add error messages on path <path>
+  * corresponding to the type <e> with the message <msg>
+  * indicating if it should be display in porcelain or not
+  */
+ static int add_rejected_path(struct unpack_trees_options *o,
+                            enum unpack_trees_error_types e,
+                            const char *path)
+ {
+       struct rejected_paths_list *newentry;
+       int porcelain = o && (o)->msgs[e];
+       /*
+        * simply display the given error message if in plumbing mode
+        */
+       if (!porcelain)
+               o->show_all_errors = 0;
+       if (!o->show_all_errors)
+               return error(ERRORMSG(o, e), path);
+       /*
+        * Otherwise, insert in a list for future display by
+        * display_error_msgs()
+        */
+       newentry = xmalloc(sizeof(struct rejected_paths_list));
+       newentry->path = (char *)path;
+       newentry->next = o->unpack_rejects[e];
+       o->unpack_rejects[e] = newentry;
+       return -1;
+ }
+ /*
+  * free all the structures allocated for the error <e>
+  */
+ static void free_rejected_paths(struct unpack_trees_options *o,
+                               enum unpack_trees_error_types e)
+ {
+       while (o->unpack_rejects[e]) {
+               struct rejected_paths_list *del = o->unpack_rejects[e];
+               o->unpack_rejects[e] = o->unpack_rejects[e]->next;
+               free(del);
+       }
+       free(o->unpack_rejects[e]);
+ }
+ /*
+  * display all the error messages stored in a nice way
+  */
+ static void display_error_msgs(struct unpack_trees_options *o)
+ {
+       int e;
+       int something_displayed = 0;
+       for (e = 0; e < NB_UNPACK_TREES_ERROR_TYPES; e++) {
+               if (o->unpack_rejects[e]) {
+                       struct rejected_paths_list *rp;
+                       struct strbuf path = STRBUF_INIT;
+                       something_displayed = 1;
+                       for (rp = o->unpack_rejects[e]; rp; rp = rp->next)
+                               strbuf_addf(&path, "\t%s\n", rp->path);
+                       error(ERRORMSG(o, e), path.buf);
+                       strbuf_release(&path);
+                       free_rejected_paths(o, e);
+               }
+       }
+       if (something_displayed)
+               printf("Aborting\n");
+ }
  /*
   * Unlink the last component and schedule the leading directories for
   * removal, such that empty directories get removed.
@@@ -132,7 -204,7 +204,7 @@@ static int check_updates(struct unpack_
  }
  
  static int verify_uptodate_sparse(struct cache_entry *ce, struct unpack_trees_options *o);
- static int verify_absent_sparse(struct cache_entry *ce, const char *action, struct unpack_trees_options *o);
+ static int verify_absent_sparse(struct cache_entry *ce, enum unpack_trees_error_types, struct unpack_trees_options *o);
  
  static int will_have_skip_worktree(const struct cache_entry *ce, struct unpack_trees_options *o)
  {
@@@ -175,7 -247,7 +247,7 @@@ static int apply_sparse_checkout(struc
                ce->ce_flags |= CE_WT_REMOVE;
        }
        if (was_skip_worktree && !ce_skip_worktree(ce)) {
-               if (verify_absent_sparse(ce, "overwritten", o))
+               if (verify_absent_sparse(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o))
                        return -1;
                ce->ce_flags |= CE_UPDATE;
        }
@@@ -329,7 -401,6 +401,7 @@@ static int traverse_trees_recursive(in
  {
        int i, ret, bottom;
        struct tree_desc t[MAX_UNPACK_TREES];
 +      void *buf[MAX_UNPACK_TREES];
        struct traverse_info newinfo;
        struct name_entry *p;
  
                const unsigned char *sha1 = NULL;
                if (dirmask & 1)
                        sha1 = names[i].sha1;
 -              fill_tree_descriptor(t+i, sha1);
 +              buf[i] = fill_tree_descriptor(t+i, sha1);
        }
  
        bottom = switch_cache_bottom(&newinfo);
        ret = traverse_trees(n, t, &newinfo);
        restore_cache_bottom(&newinfo, bottom);
 +
 +      for (i = 0; i < n; i++)
 +              free(buf[i]);
 +
        return ret;
  }
  
@@@ -755,6 -822,7 +827,7 @@@ int unpack_trees(unsigned len, struct t
                setup_traverse_info(&info, prefix);
                info.fn = unpack_callback;
                info.data = o;
+               info.show_all_errors = o->show_all_errors;
  
                if (o->prefix) {
                        /*
@@@ -834,6 -902,8 +907,8 @@@ done
        return ret;
  
  return_failed:
+       if (o->show_all_errors)
+               display_error_msgs(o);
        mark_all_ce_unused(o->src_index);
        ret = unpack_failed(o, NULL);
        goto done;
  
  static int reject_merge(struct cache_entry *ce, struct unpack_trees_options *o)
  {
-       return error(ERRORMSG(o, would_overwrite), ce->name);
+       return add_rejected_path(o, ERROR_WOULD_OVERWRITE, ce->name);
  }
  
  static int same(struct cache_entry *a, struct cache_entry *b)
   */
  static int verify_uptodate_1(struct cache_entry *ce,
                                   struct unpack_trees_options *o,
-                                  const char *error_msg)
+                                  enum unpack_trees_error_types error_type)
  {
        struct stat st;
  
        if (errno == ENOENT)
                return 0;
        return o->gently ? -1 :
-               error(error_msg, ce->name);
+               add_rejected_path(o, error_type, ce->name);
  }
  
  static int verify_uptodate(struct cache_entry *ce,
  {
        if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o))
                return 0;
-       return verify_uptodate_1(ce, o, ERRORMSG(o, not_uptodate_file));
+       return verify_uptodate_1(ce, o, ERROR_NOT_UPTODATE_FILE);
  }
  
  static int verify_uptodate_sparse(struct cache_entry *ce,
                                  struct unpack_trees_options *o)
  {
-       return verify_uptodate_1(ce, o, ERRORMSG(o, sparse_not_uptodate_file));
+       return verify_uptodate_1(ce, o, ERROR_SPARSE_NOT_UPTODATE_FILE);
  }
  
  static void invalidate_ce_path(struct cache_entry *ce, struct unpack_trees_options *o)
   * Currently, git does not checkout subprojects during a superproject
   * checkout, so it is not going to overwrite anything.
   */
- static int verify_clean_submodule(struct cache_entry *ce, const char *action,
+ static int verify_clean_submodule(struct cache_entry *ce,
+                                     enum unpack_trees_error_types error_type,
                                      struct unpack_trees_options *o)
  {
        return 0;
  }
  
- static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
+ static int verify_clean_subdirectory(struct cache_entry *ce,
+                                     enum unpack_trees_error_types error_type,
                                      struct unpack_trees_options *o)
  {
        /*
                 */
                if (!hashcmp(sha1, ce->sha1))
                        return 0;
-               return verify_clean_submodule(ce, action, o);
+               return verify_clean_submodule(ce, error_type, o);
        }
  
        /*
        i = read_directory(&d, pathbuf, namelen+1, NULL);
        if (i)
                return o->gently ? -1 :
-                       error(ERRORMSG(o, not_uptodate_dir), ce->name);
+                       add_rejected_path(o, ERROR_NOT_UPTODATE_DIR, ce->name);
        free(pathbuf);
        return cnt;
  }
@@@ -1016,9 -1088,9 +1093,9 @@@ static int icase_exists(struct unpack_t
   * We do not want to remove or overwrite a working tree file that
   * is not tracked, unless it is ignored.
   */
- static int verify_absent_1(struct cache_entry *ce, const char *action,
-                                struct unpack_trees_options *o,
-                                const char *error_msg)
+ static int verify_absent_1(struct cache_entry *ce,
+                                enum unpack_trees_error_types error_type,
+                                struct unpack_trees_options *o)
  {
        struct stat st;
  
                         * files that are in "foo/" we would lose
                         * them.
                         */
-                       if (verify_clean_subdirectory(ce, action, o) < 0)
+                       if (verify_clean_subdirectory(ce, error_type, o) < 0)
                                return -1;
                        return 0;
                }
                }
  
                return o->gently ? -1 :
-                       error(ERRORMSG(o, would_lose_untracked), ce->name, action);
+                       add_rejected_path(o, error_type, ce->name);
        }
        return 0;
  }
- static int verify_absent(struct cache_entry *ce, const char *action,
+ static int verify_absent(struct cache_entry *ce,
+                        enum unpack_trees_error_types error_type,
                         struct unpack_trees_options *o)
  {
        if (!o->skip_sparse_checkout && will_have_skip_worktree(ce, o))
                return 0;
-       return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_untracked));
+       return verify_absent_1(ce, error_type, o);
  }
  
- static int verify_absent_sparse(struct cache_entry *ce, const char *action,
+ static int verify_absent_sparse(struct cache_entry *ce,
+                        enum unpack_trees_error_types error_type,
                         struct unpack_trees_options *o)
  {
-       return verify_absent_1(ce, action, o, ERRORMSG(o, would_lose_orphaned));
+       enum unpack_trees_error_types orphaned_error = error_type;
+       if (orphaned_error == ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN)
+               orphaned_error = ERROR_WOULD_LOSE_ORPHANED_OVERWRITTEN;
+       return verify_absent_1(ce, orphaned_error, o);
  }
  
  static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
        int update = CE_UPDATE;
  
        if (!old) {
-               if (verify_absent(merge, "overwritten", o))
+               if (verify_absent(merge, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o))
                        return -1;
                invalidate_ce_path(merge, o);
        } else if (!(old->ce_flags & CE_CONFLICTED)) {
@@@ -1135,7 -1213,7 +1218,7 @@@ static int deleted_entry(struct cache_e
  {
        /* Did it exist in the index? */
        if (!old) {
-               if (verify_absent(ce, "removed", o))
+               if (verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_REMOVED, o))
                        return -1;
                return 0;
        }
@@@ -1284,7 -1362,7 +1367,7 @@@ int threeway_merge(struct cache_entry *
                        if (index)
                                return deleted_entry(index, index, o);
                        if (ce && !head_deleted) {
-                               if (verify_absent(ce, "removed", o))
+                               if (verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_REMOVED, o))
                                        return -1;
                        }
                        return 0;
@@@ -1417,7 -1495,7 +1500,7 @@@ int bind_merge(struct cache_entry **src
                             o->merge_size);
        if (a && old)
                return o->gently ? -1 :
-                       error(ERRORMSG(o, bind_overlap), a->name, old->name);
+                       error(ERRORMSG(o, ERROR_BIND_OVERLAP), a->name, old->name);
        if (!a)
                return keep_entry(old, o);
        else