Merge branch 'jx/clean-interactive'
authorJunio C Hamano <gitster@pobox.com>
Mon, 22 Jul 2013 18:24:11 +0000 (11:24 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 22 Jul 2013 18:24:11 +0000 (11:24 -0700)
Add "interactive" mode to "git clean".

The early part to refactor relative path related helper functions
looked sensible.

* jx/clean-interactive:
test: run testcases with POSIX absolute paths on Windows
test: add t7301 for git-clean--interactive
git-clean: add documentation for interactive git-clean
git-clean: add ask each interactive action
git-clean: add select by numbers interactive action
git-clean: add filter by pattern interactive action
git-clean: use a git-add-interactive compatible UI
git-clean: add colors to interactive git-clean
git-clean: show items of del_list in columns
git-clean: add support for -i/--interactive
git-clean: refactor git-clean into two phases
write_name{_quoted_relative,}(): remove redundant parameters
quote_path_relative(): remove redundant parameter
quote.c: substitute path_relative with relative_path
path.c: refactor relative_path(), not only strip prefix
test: add test cases for relative_path

1  2 
Documentation/config.txt
builtin/clean.c
builtin/grep.c
builtin/ls-files.c
cache.h
setup.c
wt-status.c
diff --combined Documentation/config.txt
index 81856dd5a2559149b9dcdf263ed5c3212a07d480,836138071a2a0cb3a14a828d5e3a2f7733763e6c..50067ee15c516be0e1648dd81adbed667c94d4cb
@@@ -199,9 -199,6 +199,9 @@@ advice.*:
        amWorkDir::
                Advice that shows the location of the patch file when
                linkgit:git-am[1] fails to apply it.
 +      rmHints::
 +              In case of failure in the output of linkgit:git-rm[1],
 +              show directions on how to proceed from the current state.
  --
  
  core.fileMode::
@@@ -879,16 -876,17 +879,17 @@@ The values of these variables may be sp
  
  color.interactive::
        When set to `always`, always use colors for interactive prompts
-       and displays (such as those used by "git-add --interactive").
-       When false (or `never`), never.  When set to `true` or `auto`, use
-       colors only when the output is to the terminal. Defaults to false.
+       and displays (such as those used by "git-add --interactive" and
+       "git-clean --interactive"). When false (or `never`), never.
+       When set to `true` or `auto`, use colors only when the output is
+       to the terminal. Defaults to false.
  
  color.interactive.<slot>::
-       Use customized color for 'git add --interactive'
-       output. `<slot>` may be `prompt`, `header`, `help` or `error`, for
-       four distinct types of normal output from interactive
-       commands.  The values of these variables may be specified as
-       in color.branch.<slot>.
+       Use customized color for 'git add --interactive' and 'git clean
+       --interactive' output. `<slot>` may be `prompt`, `header`, `help`
+       or `error`, for four distinct types of normal output from
+       interactive commands.  The values of these variables may be
+       specified as in color.branch.<slot>.
  
  color.pager::
        A boolean to enable/disable colored output when the pager is in
@@@ -922,21 -920,17 +923,21 @@@ color.ui:
        as `color.diff` and `color.grep` that control the use of color
        per command family. Its scope will expand as more commands learn
        configuration to set a default for the `--color` option.  Set it
 -      to `always` if you want all output not intended for machine
 -      consumption to use color, to `true` or `auto` if you want such
 -      output to use color when written to the terminal, or to `false` or
 -      `never` if you prefer Git commands not to use color unless enabled
 -      explicitly with some other configuration or the `--color` option.
 +      to `false` or `never` if you prefer Git commands not to use
 +      color unless enabled explicitly with some other configuration
 +      or the `--color` option. Set it to `always` if you want all
 +      output not intended for machine consumption to use color, to
 +      `true` or `auto` (this is the default since Git 1.8.4) if you
 +      want such output to use color when written to the terminal.
  
  column.ui::
        Specify whether supported commands should output in columns.
        This variable consists of a list of tokens separated by spaces
        or commas:
  +
 +These options control when the feature should be enabled
 +(defaults to 'never'):
 ++
  --
  `always`;;
        always show in columns
        never show in columns
  `auto`;;
        show in columns if the output is to the terminal
 +--
 ++
 +These options control layout (defaults to 'column').  Setting any
 +of these implies 'always' if none of 'always', 'never', or 'auto' are
 +specified.
 ++
 +--
  `column`;;
 -      fill columns before rows (default)
 +      fill columns before rows
  `row`;;
        fill rows before columns
  `plain`;;
        show in one column
 +--
 ++
 +Finally, these options can be combined with a layout option (defaults
 +to 'nodense'):
 ++
 +--
  `dense`;;
        make unequal size columns to utilize more space
  `nodense`;;
        make equal size columns
  --
 -+
 -This option defaults to 'never'.
  
  column.branch::
        Specify whether to output branch listing in `git branch` in columns.
        See `column.ui` for details.
  
+ column.clean::
+       Specify the layout when list items in `git clean -i`, which always
+       shows files and directories in columns. See `column.ui` for details.
  column.status::
        Specify whether to output untracked files in `git status` in columns.
        See `column.ui` for details.
@@@ -1844,59 -1831,39 +1849,59 @@@ pull.twohead:
        The default merge strategy to use when pulling a single branch.
  
  push.default::
 -      Defines the action `git push` should take if no refspec is given
 -      on the command line, no refspec is configured in the remote, and
 -      no refspec is implied by any of the options given on the command
 -      line. Possible values are:
 +      Defines the action `git push` should take if no refspec is
 +      explicitly given.  Different values are well-suited for
 +      specific workflows; for instance, in a purely central workflow
 +      (i.e. the fetch source is equal to the push destination),
 +      `upstream` is probably what you want.  Possible values are:
  +
  --
 -* `nothing` - do not push anything.
 -* `matching` - push all branches having the same name in both ends.
 -  This is for those who prepare all the branches into a publishable
 -  shape and then push them out with a single command.  It is not
 -  appropriate for pushing into a repository shared by multiple users,
 -  since locally stalled branches will attempt a non-fast forward push
 -  if other users updated the branch.
 -  +
 -  This is currently the default, but Git 2.0 will change the default
 -  to `simple`.
 -* `upstream` - push the current branch to its upstream branch
 -  (`tracking` is a deprecated synonym for this).
 -  With this, `git push` will update the same remote ref as the one which
 -  is merged by `git pull`, making `push` and `pull` symmetrical.
 -  See "branch.<name>.merge" for how to configure the upstream branch.
 -* `simple` - like `upstream`, but refuses to push if the upstream
 -  branch's name is different from the local one. This is the safest
 -  option and is well-suited for beginners. It will become the default
 -  in Git 2.0.
 -* `current` - push the current branch to a branch of the same name.
 ---
 +
 +* `nothing` - do not push anything (error out) unless a refspec is
 +  explicitly given. This is primarily meant for people who want to
 +  avoid mistakes by always being explicit.
 +
 +* `current` - push the current branch to update a branch with the same
 +  name on the receiving end.  Works in both central and non-central
 +  workflows.
 +
 +* `upstream` - push the current branch back to the branch whose
 +  changes are usually integrated into the current branch (which is
 +  called `@{upstream}`).  This mode only makes sense if you are
 +  pushing to the same repository you would normally pull from
 +  (i.e. central workflow).
 +
 +* `simple` - in centralized workflow, work like `upstream` with an
 +  added safety to refuse to push if the upstream branch's name is
 +  different from the local one.
 ++
 +When pushing to a remote that is different from the remote you normally
 +pull from, work as `current`.  This is the safest option and is suited
 +for beginners.
 ++
 +This mode will become the default in Git 2.0.
 +
 +* `matching` - push all branches having the same name on both ends.
 +  This makes the repository you are pushing to remember the set of
 +  branches that will be pushed out (e.g. if you always push 'maint'
 +  and 'master' there and no other branches, the repository you push
 +  to will have these two branches, and your local 'maint' and
 +  'master' will be pushed there).
 ++
 +To use this mode effectively, you have to make sure _all_ the
 +branches you would push out are ready to be pushed out before
 +running 'git push', as the whole point of this mode is to allow you
 +to push all of the branches in one go.  If you usually finish work
 +on only one branch and push out the result, while other branches are
 +unfinished, this mode is not for you.  Also this mode is not
 +suitable for pushing into a shared central repository, as other
 +people may add new branches there, or update the tip of existing
 +branches outside your control.
  +
 -The `simple`, `current` and `upstream` modes are for those who want to
 -push out a single branch after finishing work, even when the other
 -branches are not yet ready to be pushed out. If you are working with
 -other people to push into the same shared repository, you would want
 -to use one of these.
 +This is currently the default, but Git 2.0 will change the default
 +to `simple`.
 +
 +--
  
  rebase.stat::
        Whether to show a diffstat of what changed upstream since the last
  rebase.autosquash::
        If set to true enable '--autosquash' option by default.
  
 +rebase.autostash::
 +      When set to true, automatically create a temporary stash
 +      before the operation begins, and apply it after the operation
 +      ends.  This means that you can run rebase on a dirty worktree.
 +      However, use with care: the final stash application after a
 +      successful rebase might result in non-trivial conflicts.
 +      Defaults to false.
 +
  receive.autogc::
        By default, git-receive-pack will run "git-gc --auto" after
        receiving data from git-push and updating refs.  You can stop
@@@ -2112,14 -2071,6 +2117,14 @@@ status.relativePaths:
        relative to the repository root (this was the default for Git
        prior to v1.5.4).
  
 +status.short::
 +      Set to true to enable --short by default in linkgit:git-status[1].
 +      The option --no-short takes precedence over this variable.
 +
 +status.branch::
 +      Set to true to enable --branch by default in linkgit:git-status[1].
 +      The option --no-branch takes precedence over this variable.
 +
  status.showUntrackedFiles::
        By default, linkgit:git-status[1] and linkgit:git-commit[1] show
        files which are not currently tracked by Git. Directories which
diff --combined builtin/clean.c
index e344137ef2568efb29964d4758c60b0abf35f743,bf03acfe889e1ff729d22195e7fcbf3fdb8d5a8d..dba8387747b0fa5129b9270ce5140ba847a7a139
  #include "refs.h"
  #include "string-list.h"
  #include "quote.h"
+ #include "column.h"
+ #include "color.h"
  
  static int force = -1; /* unset */
+ static int interactive;
+ static struct string_list del_list = STRING_LIST_INIT_DUP;
+ static unsigned int colopts;
  
  static const char *const builtin_clean_usage[] = {
-       N_("git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."),
+       N_("git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."),
        NULL
  };
  
@@@ -27,11 -32,112 +32,112 @@@ static const char *msg_skip_git_dir = N
  static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n");
  static const char *msg_warn_remove_failed = N_("failed to remove %s");
  
+ static int clean_use_color = -1;
+ static char clean_colors[][COLOR_MAXLEN] = {
+       GIT_COLOR_RESET,
+       GIT_COLOR_NORMAL,       /* PLAIN */
+       GIT_COLOR_BOLD_BLUE,    /* PROMPT */
+       GIT_COLOR_BOLD,         /* HEADER */
+       GIT_COLOR_BOLD_RED,     /* HELP */
+       GIT_COLOR_BOLD_RED,     /* ERROR */
+ };
+ enum color_clean {
+       CLEAN_COLOR_RESET = 0,
+       CLEAN_COLOR_PLAIN = 1,
+       CLEAN_COLOR_PROMPT = 2,
+       CLEAN_COLOR_HEADER = 3,
+       CLEAN_COLOR_HELP = 4,
+       CLEAN_COLOR_ERROR = 5,
+ };
+ #define MENU_OPTS_SINGLETON           01
+ #define MENU_OPTS_IMMEDIATE           02
+ #define MENU_OPTS_LIST_ONLY           04
+ struct menu_opts {
+       const char *header;
+       const char *prompt;
+       int flags;
+ };
+ #define MENU_RETURN_NO_LOOP           10
+ struct menu_item {
+       char hotkey;
+       const char *title;
+       int selected;
+       int (*fn)();
+ };
+ enum menu_stuff_type {
+       MENU_STUFF_TYPE_STRING_LIST = 1,
+       MENU_STUFF_TYPE_MENU_ITEM
+ };
+ struct menu_stuff {
+       enum menu_stuff_type type;
+       int nr;
+       void *stuff;
+ };
+ static int parse_clean_color_slot(const char *var)
+ {
+       if (!strcasecmp(var, "reset"))
+               return CLEAN_COLOR_RESET;
+       if (!strcasecmp(var, "plain"))
+               return CLEAN_COLOR_PLAIN;
+       if (!strcasecmp(var, "prompt"))
+               return CLEAN_COLOR_PROMPT;
+       if (!strcasecmp(var, "header"))
+               return CLEAN_COLOR_HEADER;
+       if (!strcasecmp(var, "help"))
+               return CLEAN_COLOR_HELP;
+       if (!strcasecmp(var, "error"))
+               return CLEAN_COLOR_ERROR;
+       return -1;
+ }
  static int git_clean_config(const char *var, const char *value, void *cb)
  {
-       if (!strcmp(var, "clean.requireforce"))
+       if (!prefixcmp(var, "column."))
+               return git_column_config(var, value, "clean", &colopts);
+       /* honors the color.interactive* config variables which also
+          applied in git-add--interactive and git-stash */
+       if (!strcmp(var, "color.interactive")) {
+               clean_use_color = git_config_colorbool(var, value);
+               return 0;
+       }
+       if (!prefixcmp(var, "color.interactive.")) {
+               int slot = parse_clean_color_slot(var +
+                                                 strlen("color.interactive."));
+               if (slot < 0)
+                       return 0;
+               if (!value)
+                       return config_error_nonbool(var);
+               color_parse(value, var, clean_colors[slot]);
+               return 0;
+       }
+       if (!strcmp(var, "clean.requireforce")) {
                force = !git_config_bool(var, value);
-       return git_default_config(var, value, cb);
+               return 0;
+       }
+       /* inspect the color.ui config variable and others */
+       return git_color_default_config(var, value, cb);
+ }
+ static const char *clean_get_color(enum color_clean ix)
+ {
+       if (want_color(clean_use_color))
+               return clean_colors[ix];
+       return "";
+ }
+ static void clean_print_color(enum color_clean ix)
+ {
+       printf("%s", clean_get_color(ix));
  }
  
  static int exclude_cb(const struct option *opt, const char *arg, int unset)
@@@ -56,7 -162,7 +162,7 @@@ static int remove_dirs(struct strbuf *p
        if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
                        !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) {
                if (!quiet) {
-                       quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+                       quote_path_relative(path->buf, prefix, &quoted);
                        printf(dry_run ?  _(msg_would_skip_git_dir) : _(msg_skip_git_dir),
                                        quoted.buf);
                }
                /* an empty dir could be removed even if it is unreadble */
                res = dry_run ? 0 : rmdir(path->buf);
                if (res) {
-                       quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+                       quote_path_relative(path->buf, prefix, &quoted);
                        warning(_(msg_warn_remove_failed), quoted.buf);
                        *dir_gone = 0;
                }
                        if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone))
                                ret = 1;
                        if (gone) {
-                               quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+                               quote_path_relative(path->buf, prefix, &quoted);
                                string_list_append(&dels, quoted.buf);
                        } else
                                *dir_gone = 0;
                } else {
                        res = dry_run ? 0 : unlink(path->buf);
                        if (!res) {
-                               quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+                               quote_path_relative(path->buf, prefix, &quoted);
                                string_list_append(&dels, quoted.buf);
                        } else {
-                               quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+                               quote_path_relative(path->buf, prefix, &quoted);
                                warning(_(msg_warn_remove_failed), quoted.buf);
                                *dir_gone = 0;
                                ret = 1;
                if (!res)
                        *dir_gone = 1;
                else {
-                       quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
+                       quote_path_relative(path->buf, prefix, &quoted);
                        warning(_(msg_warn_remove_failed), quoted.buf);
                        *dir_gone = 0;
                        ret = 1;
        return ret;
  }
  
+ static void pretty_print_dels(void)
+ {
+       struct string_list list = STRING_LIST_INIT_DUP;
+       struct string_list_item *item;
+       struct strbuf buf = STRBUF_INIT;
+       const char *qname;
+       struct column_options copts;
+       for_each_string_list_item(item, &del_list) {
+               qname = quote_path_relative(item->string, NULL, &buf);
+               string_list_append(&list, qname);
+       }
+       /*
+        * always enable column display, we only consult column.*
+        * about layout strategy and stuff
+        */
+       colopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
+       memset(&copts, 0, sizeof(copts));
+       copts.indent = "  ";
+       copts.padding = 2;
+       print_columns(&list, colopts, &copts);
+       strbuf_release(&buf);
+       string_list_clear(&list, 0);
+ }
+ static void pretty_print_menus(struct string_list *menu_list)
+ {
+       unsigned int local_colopts = 0;
+       struct column_options copts;
+       local_colopts = COL_ENABLED | COL_ROW;
+       memset(&copts, 0, sizeof(copts));
+       copts.indent = "  ";
+       copts.padding = 2;
+       print_columns(menu_list, local_colopts, &copts);
+ }
+ static void prompt_help_cmd(int singleton)
+ {
+       clean_print_color(CLEAN_COLOR_HELP);
+       printf_ln(singleton ?
+                 _("Prompt help:\n"
+                   "1          - select a numbered item\n"
+                   "foo        - select item based on unique prefix\n"
+                   "           - (empty) select nothing") :
+                 _("Prompt help:\n"
+                   "1          - select a single item\n"
+                   "3-5        - select a range of items\n"
+                   "2-3,6-9    - select multiple ranges\n"
+                   "foo        - select item based on unique prefix\n"
+                   "-...       - unselect specified items\n"
+                   "*          - choose all items\n"
+                   "           - (empty) finish selecting"));
+       clean_print_color(CLEAN_COLOR_RESET);
+ }
+ /*
+  * display menu stuff with number prefix and hotkey highlight
+  */
+ static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen)
+ {
+       struct string_list menu_list = STRING_LIST_INIT_DUP;
+       struct strbuf menu = STRBUF_INIT;
+       struct strbuf buf = STRBUF_INIT;
+       struct menu_item *menu_item;
+       struct string_list_item *string_list_item;
+       int i;
+       switch (stuff->type) {
+       default:
+               die("Bad type of menu_staff when print menu");
+       case MENU_STUFF_TYPE_MENU_ITEM:
+               menu_item = (struct menu_item *)stuff->stuff;
+               for (i = 0; i < stuff->nr; i++, menu_item++) {
+                       const char *p;
+                       int highlighted = 0;
+                       p = menu_item->title;
+                       if ((*chosen)[i] < 0)
+                               (*chosen)[i] = menu_item->selected ? 1 : 0;
+                       strbuf_addf(&menu, "%s%2d: ", (*chosen)[i] ? "*" : " ", i+1);
+                       for (; *p; p++) {
+                               if (!highlighted && *p == menu_item->hotkey) {
+                                       strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_PROMPT));
+                                       strbuf_addch(&menu, *p);
+                                       strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_RESET));
+                                       highlighted = 1;
+                               } else {
+                                       strbuf_addch(&menu, *p);
+                               }
+                       }
+                       string_list_append(&menu_list, menu.buf);
+                       strbuf_reset(&menu);
+               }
+               break;
+       case MENU_STUFF_TYPE_STRING_LIST:
+               i = 0;
+               for_each_string_list_item(string_list_item, (struct string_list *)stuff->stuff) {
+                       if ((*chosen)[i] < 0)
+                               (*chosen)[i] = 0;
+                       strbuf_addf(&menu, "%s%2d: %s",
+                                   (*chosen)[i] ? "*" : " ", i+1, string_list_item->string);
+                       string_list_append(&menu_list, menu.buf);
+                       strbuf_reset(&menu);
+                       i++;
+               }
+               break;
+       }
+       pretty_print_menus(&menu_list);
+       strbuf_release(&menu);
+       strbuf_release(&buf);
+       string_list_clear(&menu_list, 0);
+ }
+ /*
+  * Parse user input, and return choice(s) for menu (menu_stuff).
+  *
+  * Input
+  *     (for single choice)
+  *         1          - select a numbered item
+  *         foo        - select item based on menu title
+  *                    - (empty) select nothing
+  *
+  *     (for multiple choice)
+  *         1          - select a single item
+  *         3-5        - select a range of items
+  *         2-3,6-9    - select multiple ranges
+  *         foo        - select item based on menu title
+  *         -...       - unselect specified items
+  *         *          - choose all items
+  *                    - (empty) finish selecting
+  *
+  * The parse result will be saved in array **chosen, and
+  * return number of total selections.
+  */
+ static int parse_choice(struct menu_stuff *menu_stuff,
+                       int is_single,
+                       struct strbuf input,
+                       int **chosen)
+ {
+       struct strbuf **choice_list, **ptr;
+       struct menu_item *menu_item;
+       struct string_list_item *string_list_item;
+       int nr = 0;
+       int i;
+       if (is_single) {
+               choice_list = strbuf_split_max(&input, '\n', 0);
+       } else {
+               char *p = input.buf;
+               do {
+                       if (*p == ',')
+                               *p = ' ';
+               } while (*p++);
+               choice_list = strbuf_split_max(&input, ' ', 0);
+       }
+       for (ptr = choice_list; *ptr; ptr++) {
+               char *p;
+               int choose = 1;
+               int bottom = 0, top = 0;
+               int is_range, is_number;
+               strbuf_trim(*ptr);
+               if (!(*ptr)->len)
+                       continue;
+               /* Input that begins with '-'; unchoose */
+               if (*(*ptr)->buf == '-') {
+                       choose = 0;
+                       strbuf_remove((*ptr), 0, 1);
+               }
+               is_range = 0;
+               is_number = 1;
+               for (p = (*ptr)->buf; *p; p++) {
+                       if ('-' == *p) {
+                               if (!is_range) {
+                                       is_range = 1;
+                                       is_number = 0;
+                               } else {
+                                       is_number = 0;
+                                       is_range = 0;
+                                       break;
+                               }
+                       } else if (!isdigit(*p)) {
+                               is_number = 0;
+                               is_range = 0;
+                               break;
+                       }
+               }
+               if (is_number) {
+                       bottom = atoi((*ptr)->buf);
+                       top = bottom;
+               } else if (is_range) {
+                       bottom = atoi((*ptr)->buf);
+                       /* a range can be specified like 5-7 or 5- */
+                       if (!*(strchr((*ptr)->buf, '-') + 1))
+                               top = menu_stuff->nr;
+                       else
+                               top = atoi(strchr((*ptr)->buf, '-') + 1);
+               } else if (!strcmp((*ptr)->buf, "*")) {
+                       bottom = 1;
+                       top = menu_stuff->nr;
+               } else {
+                       switch (menu_stuff->type) {
+                       default:
+                               die("Bad type of menu_stuff when parse choice");
+                       case MENU_STUFF_TYPE_MENU_ITEM:
+                               menu_item = (struct menu_item *)menu_stuff->stuff;
+                               for (i = 0; i < menu_stuff->nr; i++, menu_item++) {
+                                       if (((*ptr)->len == 1 &&
+                                            *(*ptr)->buf == menu_item->hotkey) ||
+                                           !strcasecmp((*ptr)->buf, menu_item->title)) {
+                                               bottom = i + 1;
+                                               top = bottom;
+                                               break;
+                                       }
+                               }
+                               break;
+                       case MENU_STUFF_TYPE_STRING_LIST:
+                               string_list_item = ((struct string_list *)menu_stuff->stuff)->items;
+                               for (i = 0; i < menu_stuff->nr; i++, string_list_item++) {
+                                       if (!strcasecmp((*ptr)->buf, string_list_item->string)) {
+                                               bottom = i + 1;
+                                               top = bottom;
+                                               break;
+                                       }
+                               }
+                               break;
+                       }
+               }
+               if (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top ||
+                   (is_single && bottom != top)) {
+                       clean_print_color(CLEAN_COLOR_ERROR);
+                       printf_ln(_("Huh (%s)?"), (*ptr)->buf);
+                       clean_print_color(CLEAN_COLOR_RESET);
+                       continue;
+               }
+               for (i = bottom; i <= top; i++)
+                       (*chosen)[i-1] = choose;
+       }
+       strbuf_list_free(choice_list);
+       for (i = 0; i < menu_stuff->nr; i++)
+               nr += (*chosen)[i];
+       return nr;
+ }
+ /*
+  * Implement a git-add-interactive compatible UI, which is borrowed
+  * from git-add--interactive.perl.
+  *
+  * Return value:
+  *
+  *   - Return an array of integers
+  *   - , and it is up to you to free the allocated memory.
+  *   - The array ends with EOF.
+  *   - If user pressed CTRL-D (i.e. EOF), no selection returned.
+  */
+ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)
+ {
+       struct strbuf choice = STRBUF_INIT;
+       int *chosen, *result;
+       int nr = 0;
+       int eof = 0;
+       int i;
+       chosen = xmalloc(sizeof(int) * stuff->nr);
+       /* set chosen as uninitialized */
+       for (i = 0; i < stuff->nr; i++)
+               chosen[i] = -1;
+       for (;;) {
+               if (opts->header) {
+                       printf_ln("%s%s%s",
+                                 clean_get_color(CLEAN_COLOR_HEADER),
+                                 _(opts->header),
+                                 clean_get_color(CLEAN_COLOR_RESET));
+               }
+               /* chosen will be initialized by print_highlight_menu_stuff */
+               print_highlight_menu_stuff(stuff, &chosen);
+               if (opts->flags & MENU_OPTS_LIST_ONLY)
+                       break;
+               if (opts->prompt) {
+                       printf("%s%s%s%s",
+                              clean_get_color(CLEAN_COLOR_PROMPT),
+                              _(opts->prompt),
+                              opts->flags & MENU_OPTS_SINGLETON ? "> " : ">> ",
+                              clean_get_color(CLEAN_COLOR_RESET));
+               }
+               if (strbuf_getline(&choice, stdin, '\n') != EOF) {
+                       strbuf_trim(&choice);
+               } else {
+                       eof = 1;
+                       break;
+               }
+               /* help for prompt */
+               if (!strcmp(choice.buf, "?")) {
+                       prompt_help_cmd(opts->flags & MENU_OPTS_SINGLETON);
+                       continue;
+               }
+               /* for a multiple-choice menu, press ENTER (empty) will return back */
+               if (!(opts->flags & MENU_OPTS_SINGLETON) && !choice.len)
+                       break;
+               nr = parse_choice(stuff,
+                                 opts->flags & MENU_OPTS_SINGLETON,
+                                 choice,
+                                 &chosen);
+               if (opts->flags & MENU_OPTS_SINGLETON) {
+                       if (nr)
+                               break;
+               } else if (opts->flags & MENU_OPTS_IMMEDIATE) {
+                       break;
+               }
+       }
+       if (eof) {
+               result = xmalloc(sizeof(int));
+               *result = EOF;
+       } else {
+               int j = 0;
+               /*
+                * recalculate nr, if return back from menu directly with
+                * default selections.
+                */
+               if (!nr) {
+                       for (i = 0; i < stuff->nr; i++)
+                               nr += chosen[i];
+               }
+               result = xmalloc(sizeof(int) * (nr + 1));
+               memset(result, 0, sizeof(int) * (nr + 1));
+               for (i = 0; i < stuff->nr && j < nr; i++) {
+                       if (chosen[i])
+                               result[j++] = i;
+               }
+               result[j] = EOF;
+       }
+       free(chosen);
+       strbuf_release(&choice);
+       return result;
+ }
+ static int clean_cmd(void)
+ {
+       return MENU_RETURN_NO_LOOP;
+ }
+ static int filter_by_patterns_cmd(void)
+ {
+       struct dir_struct dir;
+       struct strbuf confirm = STRBUF_INIT;
+       struct strbuf **ignore_list;
+       struct string_list_item *item;
+       struct exclude_list *el;
+       int changed = -1, i;
+       for (;;) {
+               if (!del_list.nr)
+                       break;
+               if (changed)
+                       pretty_print_dels();
+               clean_print_color(CLEAN_COLOR_PROMPT);
+               printf(_("Input ignore patterns>> "));
+               clean_print_color(CLEAN_COLOR_RESET);
+               if (strbuf_getline(&confirm, stdin, '\n') != EOF)
+                       strbuf_trim(&confirm);
+               else
+                       putchar('\n');
+               /* quit filter_by_pattern mode if press ENTER or Ctrl-D */
+               if (!confirm.len)
+                       break;
+               memset(&dir, 0, sizeof(dir));
+               el = add_exclude_list(&dir, EXC_CMDL, "manual exclude");
+               ignore_list = strbuf_split_max(&confirm, ' ', 0);
+               for (i = 0; ignore_list[i]; i++) {
+                       strbuf_trim(ignore_list[i]);
+                       if (!ignore_list[i]->len)
+                               continue;
+                       add_exclude(ignore_list[i]->buf, "", 0, el, -(i+1));
+               }
+               changed = 0;
+               for_each_string_list_item(item, &del_list) {
+                       int dtype = DT_UNKNOWN;
+                       if (is_excluded(&dir, item->string, &dtype)) {
+                               *item->string = '\0';
+                               changed++;
+                       }
+               }
+               if (changed) {
+                       string_list_remove_empty_items(&del_list, 0);
+               } else {
+                       clean_print_color(CLEAN_COLOR_ERROR);
+                       printf_ln(_("WARNING: Cannot find items matched by: %s"), confirm.buf);
+                       clean_print_color(CLEAN_COLOR_RESET);
+               }
+               strbuf_list_free(ignore_list);
+               clear_directory(&dir);
+       }
+       strbuf_release(&confirm);
+       return 0;
+ }
+ static int select_by_numbers_cmd(void)
+ {
+       struct menu_opts menu_opts;
+       struct menu_stuff menu_stuff;
+       struct string_list_item *items;
+       int *chosen;
+       int i, j;
+       menu_opts.header = NULL;
+       menu_opts.prompt = N_("Select items to delete");
+       menu_opts.flags = 0;
+       menu_stuff.type = MENU_STUFF_TYPE_STRING_LIST;
+       menu_stuff.stuff = &del_list;
+       menu_stuff.nr = del_list.nr;
+       chosen = list_and_choose(&menu_opts, &menu_stuff);
+       items = del_list.items;
+       for (i = 0, j = 0; i < del_list.nr; i++) {
+               if (i < chosen[j]) {
+                       *(items[i].string) = '\0';
+               } else if (i == chosen[j]) {
+                       /* delete selected item */
+                       j++;
+                       continue;
+               } else {
+                       /* end of chosen (chosen[j] == EOF), won't delete */
+                       *(items[i].string) = '\0';
+               }
+       }
+       string_list_remove_empty_items(&del_list, 0);
+       free(chosen);
+       return 0;
+ }
+ static int ask_each_cmd(void)
+ {
+       struct strbuf confirm = STRBUF_INIT;
+       struct strbuf buf = STRBUF_INIT;
+       struct string_list_item *item;
+       const char *qname;
+       int changed = 0, eof = 0;
+       for_each_string_list_item(item, &del_list) {
+               /* Ctrl-D should stop removing files */
+               if (!eof) {
+                       qname = quote_path_relative(item->string, NULL, &buf);
+                       printf(_("remove %s? "), qname);
+                       if (strbuf_getline(&confirm, stdin, '\n') != EOF) {
+                               strbuf_trim(&confirm);
+                       } else {
+                               putchar('\n');
+                               eof = 1;
+                       }
+               }
+               if (!confirm.len || strncasecmp(confirm.buf, "yes", confirm.len)) {
+                       *item->string = '\0';
+                       changed++;
+               }
+       }
+       if (changed)
+               string_list_remove_empty_items(&del_list, 0);
+       strbuf_release(&buf);
+       strbuf_release(&confirm);
+       return MENU_RETURN_NO_LOOP;
+ }
+ static int quit_cmd(void)
+ {
+       string_list_clear(&del_list, 0);
+       printf_ln(_("Bye."));
+       return MENU_RETURN_NO_LOOP;
+ }
+ static int help_cmd(void)
+ {
+       clean_print_color(CLEAN_COLOR_HELP);
+       printf_ln(_(
+                   "clean               - start cleaning\n"
+                   "filter by pattern   - exclude items from deletion\n"
+                   "select by numbers   - select items to be deleted by numbers\n"
+                   "ask each            - confirm each deletion (like \"rm -i\")\n"
+                   "quit                - stop cleaning\n"
+                   "help                - this screen\n"
+                   "?                   - help for prompt selection"
+                  ));
+       clean_print_color(CLEAN_COLOR_RESET);
+       return 0;
+ }
+ static void interactive_main_loop(void)
+ {
+       while (del_list.nr) {
+               struct menu_opts menu_opts;
+               struct menu_stuff menu_stuff;
+               struct menu_item menus[] = {
+                       {'c', "clean",                  0, clean_cmd},
+                       {'f', "filter by pattern",      0, filter_by_patterns_cmd},
+                       {'s', "select by numbers",      0, select_by_numbers_cmd},
+                       {'a', "ask each",               0, ask_each_cmd},
+                       {'q', "quit",                   0, quit_cmd},
+                       {'h', "help",                   0, help_cmd},
+               };
+               int *chosen;
+               menu_opts.header = N_("*** Commands ***");
+               menu_opts.prompt = N_("What now");
+               menu_opts.flags = MENU_OPTS_SINGLETON;
+               menu_stuff.type = MENU_STUFF_TYPE_MENU_ITEM;
+               menu_stuff.stuff = menus;
+               menu_stuff.nr = sizeof(menus) / sizeof(struct menu_item);
+               clean_print_color(CLEAN_COLOR_HEADER);
+               printf_ln(Q_("Would remove the following item:",
+                            "Would remove the following items:",
+                            del_list.nr));
+               clean_print_color(CLEAN_COLOR_RESET);
+               pretty_print_dels();
+               chosen = list_and_choose(&menu_opts, &menu_stuff);
+               if (*chosen != EOF) {
+                       int ret;
+                       ret = menus[*chosen].fn();
+                       if (ret != MENU_RETURN_NO_LOOP) {
+                               free(chosen);
+                               chosen = NULL;
+                               if (!del_list.nr) {
+                                       clean_print_color(CLEAN_COLOR_ERROR);
+                                       printf_ln(_("No more files to clean, exiting."));
+                                       clean_print_color(CLEAN_COLOR_RESET);
+                                       break;
+                               }
+                               continue;
+                       }
+               } else {
+                       quit_cmd();
+               }
+               free(chosen);
+               chosen = NULL;
+               break;
+       }
+ }
  int cmd_clean(int argc, const char **argv, const char *prefix)
  {
        int i, res;
        int dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0;
        int ignored_only = 0, config_set = 0, errors = 0, gone = 1;
        int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
-       struct strbuf directory = STRBUF_INIT;
+       struct strbuf abs_path = STRBUF_INIT;
        struct dir_struct dir;
        static const char **pathspec;
        struct strbuf buf = STRBUF_INIT;
        struct string_list exclude_list = STRING_LIST_INIT_NODUP;
        struct exclude_list *el;
+       struct string_list_item *item;
        const char *qname;
        char *seen = NULL;
        struct option options[] = {
                OPT__QUIET(&quiet, N_("do not print names of files removed")),
                OPT__DRY_RUN(&dry_run, N_("dry run")),
                OPT__FORCE(&force, N_("force")),
+               OPT_BOOL('i', "interactive", &interactive, N_("interactive cleaning")),
                OPT_BOOLEAN('d', NULL, &remove_directories,
                                N_("remove whole directories")),
                { OPTION_CALLBACK, 'e', "exclude", &exclude_list, N_("pattern"),
        if (ignored && ignored_only)
                die(_("-x and -X cannot be used together"));
  
-       if (!dry_run && !force) {
+       if (!interactive && !dry_run && !force) {
                if (config_set)
-                       die(_("clean.requireForce set to true and neither -n nor -f given; "
+                       die(_("clean.requireForce set to true and neither -i, -n nor -f given; "
                                  "refusing to clean"));
                else
-                       die(_("clean.requireForce defaults to true and neither -n nor -f given; "
+                       die(_("clean.requireForce defaults to true and neither -i, -n nor -f given; "
                                  "refusing to clean"));
        }
  
                struct dir_entry *ent = dir.entries[i];
                int len, pos;
                int matches = 0;
 -              struct cache_entry *ce;
 +              const struct cache_entry *ce;
                struct stat st;
+               const char *rel;
  
                /*
                 * Remove the '/' at the end that directory
                                continue; /* Yup, this one exists unmerged */
                }
  
-               /*
-                * we might have removed this as part of earlier
-                * recursive directory removal, so lstat() here could
-                * fail with ENOENT.
-                */
                if (lstat(ent->name, &st))
-                       continue;
+                       die_errno("Cannot lstat '%s'", ent->name);
  
                if (pathspec) {
                        memset(seen, 0, argc > 0 ? argc : 1);
                }
  
                if (S_ISDIR(st.st_mode)) {
-                       strbuf_addstr(&directory, ent->name);
                        if (remove_directories || (matches == MATCHED_EXACTLY)) {
-                               if (remove_dirs(&directory, prefix, rm_flags, dry_run, quiet, &gone))
-                                       errors++;
-                               if (gone && !quiet) {
-                                       qname = quote_path_relative(directory.buf, directory.len, &buf, prefix);
-                                       printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
-                               }
+                               rel = relative_path(ent->name, prefix, &buf);
+                               string_list_append(&del_list, rel);
                        }
-                       strbuf_reset(&directory);
                } else {
                        if (pathspec && !matches)
                                continue;
-                       res = dry_run ? 0 : unlink(ent->name);
+                       rel = relative_path(ent->name, prefix, &buf);
+                       string_list_append(&del_list, rel);
+               }
+       }
+       if (interactive && del_list.nr > 0)
+               interactive_main_loop();
+       for_each_string_list_item(item, &del_list) {
+               struct stat st;
+               if (prefix)
+                       strbuf_addstr(&abs_path, prefix);
+               strbuf_addstr(&abs_path, item->string);
+               /*
+                * we might have removed this as part of earlier
+                * recursive directory removal, so lstat() here could
+                * fail with ENOENT.
+                */
+               if (lstat(abs_path.buf, &st))
+                       continue;
+               if (S_ISDIR(st.st_mode)) {
+                       if (remove_dirs(&abs_path, prefix, rm_flags, dry_run, quiet, &gone))
+                               errors++;
+                       if (gone && !quiet) {
+                               qname = quote_path_relative(item->string, NULL, &buf);
+                               printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
+                       }
+               } else {
+                       res = dry_run ? 0 : unlink(abs_path.buf);
                        if (res) {
-                               qname = quote_path_relative(ent->name, -1, &buf, prefix);
+                               qname = quote_path_relative(item->string, NULL, &buf);
                                warning(_(msg_warn_remove_failed), qname);
                                errors++;
                        } else if (!quiet) {
-                               qname = quote_path_relative(ent->name, -1, &buf, prefix);
+                               qname = quote_path_relative(item->string, NULL, &buf);
                                printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
                        }
                }
+               strbuf_reset(&abs_path);
        }
        free(seen);
  
-       strbuf_release(&directory);
+       strbuf_release(&abs_path);
+       strbuf_release(&buf);
+       string_list_clear(&del_list, 0);
        string_list_clear(&exclude_list, 0);
        return (errors != 0);
  }
diff --combined builtin/grep.c
index 0223d701a6cb2824733f72fc04d298a9c8c87020,a419cda729c24453427067dd9e14a8850d9e7075..d3b3b1db1121b37d5a8d0e440e03273526003774
@@@ -286,8 -286,7 +286,7 @@@ static int grep_sha1(struct grep_opt *o
        struct strbuf pathbuf = STRBUF_INIT;
  
        if (opt->relative && opt->prefix_length) {
-               quote_path_relative(filename + tree_name_len, -1, &pathbuf,
-                                   opt->prefix);
+               quote_path_relative(filename + tree_name_len, opt->prefix, &pathbuf);
                strbuf_insert(&pathbuf, 0, filename, tree_name_len);
        } else {
                strbuf_addstr(&pathbuf, filename);
@@@ -318,7 -317,7 +317,7 @@@ static int grep_file(struct grep_opt *o
        struct strbuf buf = STRBUF_INIT;
  
        if (opt->relative && opt->prefix_length)
-               quote_path_relative(filename, -1, &buf, opt->prefix);
+               quote_path_relative(filename, opt->prefix, &buf);
        else
                strbuf_addstr(&buf, filename);
  
@@@ -376,7 -375,7 +375,7 @@@ static int grep_cache(struct grep_opt *
        read_cache();
  
        for (nr = 0; nr < active_nr; nr++) {
 -              struct cache_entry *ce = active_cache[nr];
 +              const struct cache_entry *ce = active_cache[nr];
                if (!S_ISREG(ce->ce_mode))
                        continue;
                if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL))
diff --combined builtin/ls-files.c
index 80fff23ad959ec7d6ed3c3fcc34faf77c3816459,29d45b4f5a974e9fe7c6055e36d7322de16f193c..5cf3e313700e4cbcd26ae1a9f3f99f2819e40702
@@@ -46,10 -46,14 +46,14 @@@ static const char *tag_modified = ""
  static const char *tag_skip_worktree = "";
  static const char *tag_resolve_undo = "";
  
- static void write_name(const char* name, size_t len)
+ static void write_name(const char *name)
  {
-       write_name_quoted_relative(name, len, prefix, prefix_len, stdout,
-                       line_terminator);
+       /*
+        * With "--full-name", prefix_len=0; this caller needs to pass
+        * an empty string in that case (a NULL is good for "").
+        */
+       write_name_quoted_relative(name, prefix_len ? prefix : NULL,
+                                  stdout, line_terminator);
  }
  
  static void show_dir_entry(const char *tag, struct dir_entry *ent)
@@@ -63,7 -67,7 +67,7 @@@
                return;
  
        fputs(tag, stdout);
-       write_name(ent->name, ent->len);
+       write_name(ent->name);
  }
  
  static void show_other_files(struct dir_struct *dir)
@@@ -127,7 -131,7 +131,7 @@@ static void show_killed_files(struct di
        }
  }
  
 -static void show_ce_entry(const char *tag, struct cache_entry *ce)
 +static void show_ce_entry(const char *tag, const struct cache_entry *ce)
  {
        int len = max_prefix_len;
  
                       find_unique_abbrev(ce->sha1,abbrev),
                       ce_stage(ce));
        }
-       write_name(ce->name, ce_namelen(ce));
+       write_name(ce->name);
        if (debug_mode) {
 -              printf("  ctime: %d:%d\n", ce->ce_ctime.sec, ce->ce_ctime.nsec);
 -              printf("  mtime: %d:%d\n", ce->ce_mtime.sec, ce->ce_mtime.nsec);
 -              printf("  dev: %d\tino: %d\n", ce->ce_dev, ce->ce_ino);
 -              printf("  uid: %d\tgid: %d\n", ce->ce_uid, ce->ce_gid);
 -              printf("  size: %d\tflags: %x\n", ce->ce_size, ce->ce_flags);
 +              const struct stat_data *sd = &ce->ce_stat_data;
 +
 +              printf("  ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec);
 +              printf("  mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec);
 +              printf("  dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino);
 +              printf("  uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid);
 +              printf("  size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags);
        }
  }
  
@@@ -198,12 -200,12 +202,12 @@@ static void show_ru_info(void
                        printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i],
                               find_unique_abbrev(ui->sha1[i], abbrev),
                               i + 1);
-                       write_name(path, len);
+                       write_name(path);
                }
        }
  }
  
 -static int ce_excluded(struct dir_struct *dir, struct cache_entry *ce)
 +static int ce_excluded(struct dir_struct *dir, const struct cache_entry *ce)
  {
        int dtype = ce_to_dtype(ce);
        return is_excluded(dir, ce->name, &dtype);
@@@ -221,9 -223,9 +225,9 @@@ static void show_files(struct dir_struc
                if (show_killed)
                        show_killed_files(dir);
        }
 -      if (show_cached | show_stage) {
 +      if (show_cached || show_stage) {
                for (i = 0; i < active_nr; i++) {
 -                      struct cache_entry *ce = active_cache[i];
 +                      const struct cache_entry *ce = active_cache[i];
                        if ((dir->flags & DIR_SHOW_IGNORED) &&
                            !ce_excluded(dir, ce))
                                continue;
                                (ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached), ce);
                }
        }
 -      if (show_deleted | show_modified) {
 +      if (show_deleted || show_modified) {
                for (i = 0; i < active_nr; i++) {
 -                      struct cache_entry *ce = active_cache[i];
 +                      const struct cache_entry *ce = active_cache[i];
                        struct stat st;
                        int err;
                        if ((dir->flags & DIR_SHOW_IGNORED) &&
@@@ -273,7 -275,7 +277,7 @@@ static void prune_cache(const char *pre
        last = active_nr;
        while (last > first) {
                int next = (last + first) >> 1;
 -              struct cache_entry *ce = active_cache[next];
 +              const struct cache_entry *ce = active_cache[next];
                if (!strncmp(ce->name, prefix, max_prefix_len)) {
                        first = next+1;
                        continue;
@@@ -391,7 -393,7 +395,7 @@@ int report_path_error(const char *ps_ma
                if (found_dup)
                        continue;
  
-               name = quote_path_relative(pathspec[num], -1, &sb, prefix);
+               name = quote_path_relative(pathspec[num], prefix, &sb);
                error("pathspec '%s' did not match any file(s) known to git.",
                      name);
                errors++;
@@@ -573,8 -575,8 +577,8 @@@ int cmd_ls_files(int argc, const char *
                die("ls-files --ignored needs some exclude pattern");
  
        /* With no flags, we default to showing the cached files */
 -      if (!(show_stage | show_deleted | show_others | show_unmerged |
 -            show_killed | show_modified | show_resolve_undo))
 +      if (!(show_stage || show_deleted || show_others || show_unmerged ||
 +            show_killed || show_modified || show_resolve_undo))
                show_cached = 1;
  
        if (max_prefix)
diff --combined cache.h
index 3142b6c372361e93d1d16ce080f68aaf0bb51267,8e42256942bdb3b1170938094963970a8e4fad66..4c606ce28bcd59956a47b2881436047bc7a5ed90
+++ b/cache.h
@@@ -119,19 -119,15 +119,19 @@@ struct cache_time 
        unsigned int nsec;
  };
  
 +struct stat_data {
 +      struct cache_time sd_ctime;
 +      struct cache_time sd_mtime;
 +      unsigned int sd_dev;
 +      unsigned int sd_ino;
 +      unsigned int sd_uid;
 +      unsigned int sd_gid;
 +      unsigned int sd_size;
 +};
 +
  struct cache_entry {
 -      struct cache_time ce_ctime;
 -      struct cache_time ce_mtime;
 -      unsigned int ce_dev;
 -      unsigned int ce_ino;
 +      struct stat_data ce_stat_data;
        unsigned int ce_mode;
 -      unsigned int ce_uid;
 -      unsigned int ce_gid;
 -      unsigned int ce_size;
        unsigned int ce_flags;
        unsigned int ce_namelen;
        unsigned char sha1[20];
   * another. But we never change the name, or the hash state!
   */
  #define CE_STATE_MASK (CE_HASHED | CE_UNHASHED)
 -static inline void copy_cache_entry(struct cache_entry *dst, struct cache_entry *src)
 +static inline void copy_cache_entry(struct cache_entry *dst,
 +                                  const struct cache_entry *src)
  {
        unsigned int state = dst->ce_flags & CE_STATE_MASK;
  
@@@ -227,8 -222,7 +227,8 @@@ static inline unsigned int create_ce_mo
                return S_IFGITLINK;
        return S_IFREG | ce_permissions(mode);
  }
 -static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned int mode)
 +static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,
 +                                           unsigned int mode)
  {
        extern int trust_executable_bit, has_symlinks;
        if (!has_symlinks && S_ISREG(mode) &&
@@@ -425,8 -419,6 +425,8 @@@ extern int path_inside_repo(const char 
  extern int set_git_dir_init(const char *git_dir, const char *real_git_dir, int);
  extern int init_db(const char *template_dir, unsigned int flags);
  
 +extern void sanitize_stdfds(void);
 +
  #define alloc_nr(x) (((x)+16)*3/2)
  
  /*
@@@ -478,7 -470,7 +478,7 @@@ extern int remove_file_from_index(struc
  extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags);
  extern int add_file_to_index(struct index_state *, const char *path, int flags);
  extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh);
 -extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
 +extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);
  extern int index_name_is_other(const struct index_state *, const char *, int);
  extern void *read_blob_data_from_index(struct index_state *, const char *, unsigned long *);
  
  #define CE_MATCH_RACY_IS_DIRTY                02
  /* do stat comparison even if CE_SKIP_WORKTREE is true */
  #define CE_MATCH_IGNORE_SKIP_WORKTREE 04
 -extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 -extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 +extern int ie_match_stat(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
 +extern int ie_modified(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
  
  #define PATHSPEC_ONESTAR 1    /* the pathspec pattern sastisfies GFNM_ONESTAR */
  
@@@ -517,21 -509,6 +517,21 @@@ extern int limit_pathspec_to_literal(vo
  #define HASH_FORMAT_CHECK 2
  extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
  extern int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags);
 +
 +/*
 + * Record to sd the data from st that we use to check whether a file
 + * might have changed.
 + */
 +extern void fill_stat_data(struct stat_data *sd, struct stat *st);
 +
 +/*
 + * Return 0 if st is consistent with a file not having been changed
 + * since sd was filled.  If there are differences, return a
 + * combination of MTIME_CHANGED, CTIME_CHANGED, OWNER_CHANGED,
 + * INODE_CHANGED, and DATA_CHANGED.
 + */
 +extern int match_stat_data(const struct stat_data *sd, struct stat *st);
 +
  extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
  
  #define REFRESH_REALLY                0x0001  /* ignore_valid */
@@@ -760,7 -737,7 +760,7 @@@ int is_directory(const char *)
  const char *real_path(const char *path);
  const char *real_path_if_valid(const char *path);
  const char *absolute_path(const char *path);
- const char *relative_path(const char *abs, const char *base);
+ const char *relative_path(const char *in, const char *prefix, struct strbuf *sb);
  int normalize_path_copy(char *dst, const char *src);
  int longest_ancestor_length(const char *path, struct string_list *prefixes);
  char *strip_path_suffix(const char *path, const char *suffix);
@@@ -795,6 -772,9 +795,6 @@@ extern int parse_sha1_header(const cha
  /* global flag to enable extra checks when accessing packed objects */
  extern int do_check_packed_object_crc;
  
 -/* for development: log offset of pack access */
 -extern const char *log_pack_access;
 -
  extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
  
  extern int move_temp_to_file(const char *tmpfile, const char *filename);
@@@ -930,7 -910,6 +930,7 @@@ void show_date_relative(unsigned long t
                        struct strbuf *timebuf);
  int parse_date(const char *date, char *buf, int bufsize);
  int parse_date_basic(const char *date, unsigned long *timestamp, int *offset);
 +int parse_expiry_date(const char *date, unsigned long *timestamp);
  void datestamp(char *buf, int bufsize);
  #define approxidate(s) approxidate_careful((s), NULL)
  unsigned long approxidate_careful(const char *, int *);
@@@ -1045,21 -1024,9 +1045,21 @@@ struct ref 
        unsigned int
                force:1,
                forced_update:1,
 -              merge:1,
                deletion:1,
                matched:1;
 +
 +      /*
 +       * Order is important here, as we write to FETCH_HEAD
 +       * in numeric order. And the default NOT_FOR_MERGE
 +       * should be 0, so that xcalloc'd structures get it
 +       * by default.
 +       */
 +      enum {
 +              FETCH_HEAD_MERGE = -1,
 +              FETCH_HEAD_NOT_FOR_MERGE = 0,
 +              FETCH_HEAD_IGNORE = 1
 +      } fetch_head_status;
 +
        enum {
                REF_STATUS_NONE = 0,
                REF_STATUS_OK,
@@@ -1132,7 -1099,6 +1132,7 @@@ extern int unpack_object_header(struct 
  struct object_info {
        /* Request */
        unsigned long *sizep;
 +      unsigned long *disk_sizep;
  
        /* Response */
        enum {
@@@ -1176,15 -1142,11 +1176,15 @@@ extern int update_server_info(int)
  typedef int (*config_fn_t)(const char *, const char *, void *);
  extern int git_default_config(const char *, const char *, void *);
  extern int git_config_from_file(config_fn_t fn, const char *, void *);
 +extern int git_config_from_buf(config_fn_t fn, const char *name,
 +                             const char *buf, size_t len, void *data);
  extern void git_config_push_parameter(const char *text);
  extern int git_config_from_parameters(config_fn_t fn, void *data);
  extern int git_config(config_fn_t fn, void *);
  extern int git_config_with_options(config_fn_t fn, void *,
 -                                 const char *filename, int respect_includes);
 +                                 const char *filename,
 +                                 const char *blob_ref,
 +                                 int respect_includes);
  extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
  extern int git_parse_ulong(const char *, unsigned long *);
  extern int git_config_int(const char *, const char *);
@@@ -1364,31 -1326,4 +1364,31 @@@ int checkout_fast_forward(const unsigne
  
  int sane_execvp(const char *file, char *const argv[]);
  
 +/*
 + * A struct to encapsulate the concept of whether a file has changed
 + * since we last checked it. This uses criteria similar to those used
 + * for the index.
 + */
 +struct stat_validity {
 +      struct stat_data *sd;
 +};
 +
 +void stat_validity_clear(struct stat_validity *sv);
 +
 +/*
 + * Returns 1 if the path is a regular file (or a symlink to a regular
 + * file) and matches the saved stat_validity, 0 otherwise.  A missing
 + * or inaccessible file is considered a match if the struct was just
 + * initialized, or if the previous update found an inaccessible file.
 + */
 +int stat_validity_check(struct stat_validity *sv, const char *path);
 +
 +/*
 + * Update the stat_validity from a file opened at descriptor fd. If
 + * the file is missing, inaccessible, or not a regular file, then
 + * future calls to stat_validity_check will match iff one of those
 + * conditions continues to be true.
 + */
 +void stat_validity_update(struct stat_validity *sv, int fd);
 +
  #endif /* CACHE_H */
diff --combined setup.c
index 88aab94f1595de5a52c8c99ae8b8c4b9322b2fcf,0d9ea6239f2512760b4104440362d2e3758b3f09..5262319be69b3bf3fd2492dea3d30e7353d66a23
+++ b/setup.c
@@@ -360,6 -360,7 +360,7 @@@ int is_inside_work_tree(void
  
  void setup_work_tree(void)
  {
+       struct strbuf sb = STRBUF_INIT;
        const char *work_tree, *git_dir;
        static int initialized = 0;
  
        if (getenv(GIT_WORK_TREE_ENVIRONMENT))
                setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1);
  
-       set_git_dir(relative_path(git_dir, work_tree));
+       set_git_dir(relative_path(git_dir, work_tree, &sb));
        initialized = 1;
+       strbuf_release(&sb);
  }
  
  static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
@@@ -908,15 -911,3 +911,15 @@@ const char *resolve_gitdir(const char *
                return suspect;
        return read_gitfile(suspect);
  }
 +
 +/* if any standard file descriptor is missing open it to /dev/null */
 +void sanitize_stdfds(void)
 +{
 +      int fd = open("/dev/null", O_RDWR, 0);
 +      while (fd != -1 && fd < 2)
 +              fd = dup(fd);
 +      if (fd == -1)
 +              die_errno("open /dev/null or dup failed");
 +      if (fd > 2)
 +              close(fd);
 +}
diff --combined wt-status.c
index bf1554cc4cfff1b11ddf0845f8d81d8376c8b049,ef0fc4bb49009dc2cf6215734a89e1b8eb2f3849..cb24f1fa9b9f9632e5fd92bbcceb44008619fa14
@@@ -127,7 -127,6 +127,7 @@@ void wt_status_prepare(struct wt_statu
        s->change.strdup_strings = 1;
        s->untracked.strdup_strings = 1;
        s->ignored.strdup_strings = 1;
 +      s->show_branch = -1;  /* unspecified */
  }
  
  static void wt_status_print_unmerged_header(struct wt_status *s)
@@@ -244,7 -243,7 +244,7 @@@ static void wt_status_print_unmerged_da
        struct strbuf onebuf = STRBUF_INIT;
        const char *one, *how = _("bug");
  
-       one = quote_path(it->string, -1, &onebuf, s->prefix);
+       one = quote_path(it->string, s->prefix, &onebuf);
        status_printf(s, color(WT_STATUS_HEADER, s), "\t");
        switch (d->stagemask) {
        case 1: how = _("both deleted:"); break;
@@@ -298,8 -297,8 +298,8 @@@ static void wt_status_print_change_data
                    change_type);
        }
  
-       one = quote_path(one_name, -1, &onebuf, s->prefix);
-       two = quote_path(two_name, -1, &twobuf, s->prefix);
+       one = quote_path(one_name, s->prefix, &onebuf);
+       two = quote_path(two_name, s->prefix, &twobuf);
  
        status_printf(s, color(WT_STATUS_HEADER, s), "\t");
        switch (status) {
@@@ -372,7 -371,7 +372,7 @@@ static void wt_status_collect_changed_c
  static int unmerged_mask(const char *path)
  {
        int pos, mask;
 -      struct cache_entry *ce;
 +      const struct cache_entry *ce;
  
        pos = cache_name_pos(path, strlen(path));
        if (0 <= pos)
@@@ -476,7 -475,7 +476,7 @@@ static void wt_status_collect_changes_i
        for (i = 0; i < active_nr; i++) {
                struct string_list_item *it;
                struct wt_status_change_data *d;
 -              struct cache_entry *ce = active_cache[i];
 +              const struct cache_entry *ce = active_cache[i];
  
                if (!ce_path_match(ce, &pathspec))
                        continue;
@@@ -707,8 -706,7 +707,7 @@@ static void wt_status_print_other(struc
                struct string_list_item *it;
                const char *path;
                it = &(l->items[i]);
-               path = quote_path(it->string, strlen(it->string),
-                                 &buf, s->prefix);
+               path = quote_path(it->string, s->prefix, &buf);
                if (column_active(s->colopts)) {
                        string_list_append(&output, path);
                        continue;
@@@ -827,7 -825,7 +826,7 @@@ static void show_am_in_progress(struct 
        if (advice_status_hints) {
                if (!state->am_empty_patch)
                        status_printf_ln(s, color,
 -                              _("  (fix conflicts and then run \"git am --resolved\")"));
 +                              _("  (fix conflicts and then run \"git am --continue\")"));
                status_printf_ln(s, color,
                        _("  (use \"git am --skip\" to skip this patch)"));
                status_printf_ln(s, color,
@@@ -956,12 -954,10 +955,12 @@@ static void show_cherry_pick_in_progres
        if (advice_status_hints) {
                if (has_unmerged(s))
                        status_printf_ln(s, color,
 -                              _("  (fix conflicts and run \"git commit\")"));
 +                              _("  (fix conflicts and run \"git cherry-pick --continue\")"));
                else
                        status_printf_ln(s, color,
 -                              _("  (all conflicts fixed: run \"git commit\")"));
 +                              _("  (all conflicts fixed: run \"git cherry-pick --continue\")"));
 +              status_printf_ln(s, color,
 +                      _("  (use \"git cherry-pick --abort\" to cancel the cherry-pick operation)"));
        }
        wt_status_print_trailer(s);
  }
@@@ -1038,6 -1034,7 +1037,6 @@@ got_nothing
  }
  
  struct grab_1st_switch_cbdata {
 -      int found;
        struct strbuf buf;
        unsigned char nsha1[20];
  };
@@@ -1061,6 -1058,7 +1060,6 @@@ static int grab_1st_switch(unsigned cha
        for (end = target; *end && *end != '\n'; end++)
                ;
        strbuf_add(&cb->buf, target, end - target);
 -      cb->found = 1;
        return 1;
  }
  
@@@ -1177,10 -1175,7 +1176,10 @@@ void wt_status_print(struct wt_status *
                        branch_name += 11;
                else if (!strcmp(branch_name, "HEAD")) {
                        branch_status_color = color(WT_STATUS_NOBRANCH, s);
 -                      if (state.detached_from) {
 +                      if (state.rebase_in_progress || state.rebase_interactive_in_progress) {
 +                              on_what = _("rebase in progress; onto ");
 +                              branch_name = state.onto;
 +                      } else if (state.detached_from) {
                                unsigned char sha1[20];
                                branch_name = state.detached_from;
                                if (!get_sha1("HEAD", sha1) &&
@@@ -1293,7 -1288,7 +1292,7 @@@ static void wt_shortstatus_unmerged(str
        } else {
                struct strbuf onebuf = STRBUF_INIT;
                const char *one;
-               one = quote_path(it->string, -1, &onebuf, s->prefix);
+               one = quote_path(it->string, s->prefix, &onebuf);
                printf(" %s\n", one);
                strbuf_release(&onebuf);
        }
@@@ -1321,7 -1316,7 +1320,7 @@@ static void wt_shortstatus_status(struc
                struct strbuf onebuf = STRBUF_INIT;
                const char *one;
                if (d->head_path) {
-                       one = quote_path(d->head_path, -1, &onebuf, s->prefix);
+                       one = quote_path(d->head_path, s->prefix, &onebuf);
                        if (*one != '"' && strchr(one, ' ') != NULL) {
                                putchar('"');
                                strbuf_addch(&onebuf, '"');
                        printf("%s -> ", one);
                        strbuf_release(&onebuf);
                }
-               one = quote_path(it->string, -1, &onebuf, s->prefix);
+               one = quote_path(it->string, s->prefix, &onebuf);
                if (*one != '"' && strchr(one, ' ') != NULL) {
                        putchar('"');
                        strbuf_addch(&onebuf, '"');
@@@ -1349,7 -1344,7 +1348,7 @@@ static void wt_shortstatus_other(struc
        } else {
                struct strbuf onebuf = STRBUF_INIT;
                const char *one;
-               one = quote_path(it->string, -1, &onebuf, s->prefix);
+               one = quote_path(it->string, s->prefix, &onebuf);
                color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign);
                printf(" %s\n", one);
                strbuf_release(&onebuf);