Merge branch 'kh/commit'
authorJunio C Hamano <gitster@pobox.com>
Wed, 5 Dec 2007 01:16:33 +0000 (17:16 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 5 Dec 2007 01:16:33 +0000 (17:16 -0800)
* kh/commit: (33 commits)
git-commit --allow-empty
git-commit: Allow to amend a merge commit that does not change the tree
quote_path: fix collapsing of relative paths
Make git status usage say git status instead of git commit
Fix --signoff in builtin-commit differently.
git-commit: clean up die messages
Do not generate full commit log message if it is not going to be used
Remove git-status from list of scripts as it is builtin
Fix off-by-one error when truncating the diff out of the commit message.
builtin-commit.c: export GIT_INDEX_FILE for launch_editor as well.
Add a few more tests for git-commit
builtin-commit: Include the diff in the commit message when verbose.
builtin-commit: fix partial-commit support
Fix add_files_to_cache() to take pathspec, not user specified list of files
Export three helper functions from ls-files
builtin-commit: run commit-msg hook with correct message file
builtin-commit: do not color status output shown in the message template
file_exists(): dangling symlinks do exist
Replace "runstatus" with "status" in the tests
t7501-commit: Add test for git commit <file> with dirty index.
...

12 files changed:
1  2 
.gitignore
Makefile
builtin-tag.c
builtin.h
cache.h
contrib/examples/git-commit.sh
dir.c
git.c
ident.c
t/t4001-diff-rename.sh
t/t7501-commit.sh
wt-status.c
diff --combined .gitignore
index 916fabc6c5485525df37832db38b007e3c7203ef,bbd7f558e71ef05f2a99df1e7a38a06b10c0d03c..bac60ce31ae3b8d74619df7dd93356c49f21d081
@@@ -35,7 -35,6 +35,7 @@@ git-diff-file
  git-diff-index
  git-diff-tree
  git-describe
 +git-fast-export
  git-fast-import
  git-fetch
  git-fetch--tool
@@@ -110,7 -109,6 +110,6 @@@ git-rev-lis
  git-rev-parse
  git-revert
  git-rm
- git-runstatus
  git-send-email
  git-send-pack
  git-sh-setup
diff --combined Makefile
index ed0c5d113c4e169aa1342b32b8cd87a798559daf,625b9c337c4afd66cb5e5cd9171c682d8a3bce4a..eb2dd5def3458bf11e084bf1a46624511393ec67
+++ b/Makefile
@@@ -111,7 -111,7 +111,7 @@@ all:
  # times (my ext3 doesn't).
  #
  # Define USE_STDEV below if you want git to care about the underlying device
 -# change being considered an inode change from the update-cache perspective.
 +# change being considered an inode change from the update-index perspective.
  #
  # Define ASCIIDOC8 if you want to format documentation with AsciiDoc 8
  #
@@@ -213,7 -213,8 +213,7 @@@ BASIC_LDFLAGS 
  
  SCRIPT_SH = \
        git-bisect.sh git-checkout.sh \
-       git-clone.sh git-commit.sh \
 -      git-clean.sh git-clone.sh \
 -      git-ls-remote.sh \
++      git-clone.sh \
        git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
        git-pull.sh git-rebase.sh git-rebase--interactive.sh \
        git-repack.sh git-request-pull.sh \
@@@ -233,7 -234,7 +233,7 @@@ SCRIPT_PERL = 
  
  SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.perl,%,$(SCRIPT_PERL)) \
-         git-status git-instaweb
+         git-instaweb
  
  # ... and all the rest that could be moved out of bindir to gitexecdir
  PROGRAMS = \
        git-fast-import$X \
        git-daemon$X \
        git-merge-index$X git-mktag$X git-mktree$X git-patch-id$X \
 -      git-peek-remote$X git-receive-pack$X \
 +      git-receive-pack$X \
        git-send-pack$X git-shell$X \
        git-show-index$X \
        git-unpack-file$X \
@@@ -259,7 -260,7 +259,7 @@@ EXTRA_PROGRAMS 
  BUILT_INS = \
        git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \
        git-get-tar-commit-id$X git-init$X git-repo-config$X \
-       git-fsck-objects$X git-cherry-pick$X git-peek-remote$X \
 -      git-fsck-objects$X git-cherry-pick$X git-status$X\
++      git-fsck-objects$X git-cherry-pick$X git-peek-remote$X git-status$X \
        $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
  
  # what 'all' will build and 'install' will install, in gitexecdir
@@@ -269,6 -270,9 +269,6 @@@ ALL_PROGRAMS += git-merge-subtree$
  
  # what 'all' will build but not install in gitexecdir
  OTHER_PROGRAMS = git$X gitweb/gitweb.cgi
 -ifndef NO_TCLTK
 -OTHER_PROGRAMS += gitk-wish
 -endif
  
  # Set paths to tools early so that they can be used for version tests.
  ifndef SHELL_PATH
@@@ -326,7 -330,7 +326,8 @@@ BUILTIN_OBJS = 
        builtin-check-attr.o \
        builtin-checkout-index.o \
        builtin-check-ref-format.o \
 +      builtin-clean.o \
+       builtin-commit.o \
        builtin-commit-tree.o \
        builtin-count-objects.o \
        builtin-describe.o \
        builtin-diff-files.o \
        builtin-diff-index.o \
        builtin-diff-tree.o \
 +      builtin-fast-export.o \
        builtin-fetch.o \
        builtin-fetch-pack.o \
        builtin-fetch--tool.o \
        builtin-log.o \
        builtin-ls-files.o \
        builtin-ls-tree.o \
 +      builtin-ls-remote.o \
        builtin-mailinfo.o \
        builtin-mailsplit.o \
        builtin-merge-base.o \
        builtin-push.o \
        builtin-read-tree.o \
        builtin-reflog.o \
 +      builtin-send-pack.o \
        builtin-config.o \
        builtin-rerere.o \
        builtin-reset.o \
        builtin-rev-parse.o \
        builtin-revert.o \
        builtin-rm.o \
-       builtin-runstatus.o \
        builtin-shortlog.o \
        builtin-show-branch.o \
        builtin-stripspace.o \
@@@ -754,7 -754,7 +754,7 @@@ TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_
  LIBS = $(GITLIBS) $(EXTLIBS)
  
  BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' \
 -      -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' $(COMPAT_CFLAGS)
 +      $(COMPAT_CFLAGS)
  LIB_OBJS += $(COMPAT_OBJS)
  
  ALL_CFLAGS += $(BASIC_CFLAGS)
@@@ -773,7 -773,6 +773,7 @@@ endi
  all::
  ifndef NO_TCLTK
        $(QUIET_SUBDIR0)git-gui $(QUIET_SUBDIR1) all
 +      $(QUIET_SUBDIR0)gitk-git $(QUIET_SUBDIR1) all
  endif
        $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
        $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1)
  strip: $(PROGRAMS) git$X
        $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X
  
 -gitk-wish: gitk GIT-GUI-VARS
 -      $(QUIET_GEN)$(RM) $@ $@+ && \
 -      sed -e '1,3s|^exec .* "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' <gitk >$@+ && \
 -      chmod +x $@+ && \
 -      mv -f $@+ $@
 -
  git.o: git.c common-cmds.h GIT-CFLAGS
        $(QUIET_CC)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
                $(ALL_CFLAGS) -c $(filter %.c,$^)
@@@ -797,7 -802,7 +797,7 @@@ git-merge-subtree$X: git-merge-recursiv
  $(BUILT_INS): git$X
        $(QUIET_BUILT_IN)$(RM) $@ && ln git$X $@
  
 -common-cmds.h: ./generate-cmdlist.sh
 +common-cmds.h: ./generate-cmdlist.sh command-list.txt
  
  common-cmds.h: $(wildcard Documentation/git-*.txt)
        $(QUIET_GEN)./generate-cmdlist.sh > $@+ && mv $@+ $@
@@@ -833,9 -838,6 +833,6 @@@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % 
        chmod +x $@+ && \
        mv $@+ $@
  
- git-status: git-commit
-       $(QUIET_GEN)cp $< $@+ && mv $@+ $@
  gitweb/gitweb.cgi: gitweb/gitweb.perl
        $(QUIET_GEN)$(RM) $@ $@+ && \
        sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
@@@ -898,9 -900,6 +895,9 @@@ exec_cmd.o: exec_cmd.c GIT-CFLAG
  builtin-init-db.o: builtin-init-db.c GIT-CFLAGS
        $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $<
  
 +config.o: config.c GIT-CFLAGS
 +      $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' $<
 +
  http.o: http.c GIT-CFLAGS
        $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DGIT_USER_AGENT='"git/$(GIT_VERSION)"' $<
  
@@@ -1017,14 -1016,14 +1014,14 @@@ remove-dashes
  ### Installation rules
  
  install: all
 -      $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(bindir_SQ)'
 -      $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(gitexecdir_SQ)'
 +      $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)'
 +      $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexecdir_SQ)'
        $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
        $(INSTALL) git$X '$(DESTDIR_SQ)$(bindir_SQ)'
        $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
        $(MAKE) -C perl prefix='$(prefix_SQ)' install
  ifndef NO_TCLTK
 -      $(INSTALL) gitk-wish '$(DESTDIR_SQ)$(bindir_SQ)'/gitk
 +      $(MAKE) -C gitk-git install
        $(MAKE) -C git-gui install
  endif
        if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \
@@@ -1117,7 -1116,7 +1114,7 @@@ clean
        $(MAKE) -C templates/ clean
        $(MAKE) -C t/ clean
  ifndef NO_TCLTK
 -      $(RM) gitk-wish
 +      $(MAKE) -C gitk-git clean
        $(MAKE) -C git-gui clean
  endif
        $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS
@@@ -1139,7 -1138,7 +1136,7 @@@ check-docs:
                esac ; \
                test -f "Documentation/$$v.txt" || \
                echo "no doc: $$v"; \
 -              sed -e '1,/^__DATA__/d' Documentation/cmd-list.perl | \
 +              sed -e '/^#/d' command-list.txt | \
                grep -q "^$$v[  ]" || \
                case "$$v" in \
                git) ;; \
                esac ; \
        done; \
        ( \
 -              sed -e '1,/^__DATA__/d' \
 +              sed -e '/^#/d' \
                    -e 's/[     ].*//' \
 -                  -e 's/^/listed /' Documentation/cmd-list.perl; \
 +                  -e 's/^/listed /' command-list.txt; \
                ls -1 Documentation/git*txt | \
                sed -e 's|Documentation/|documented |' \
                    -e 's/\.txt//'; \
                case "$$how,$$cmd" in \
                *,git-citool | \
                *,git-gui | \
 +              *,git-help | \
                documented,gitattributes | \
                documented,gitignore | \
                documented,gitmodules | \
diff --combined builtin-tag.c
index 114c684d246e975852bb5446ea2e7ab682ea036c,7626da3f47fad011733b9cc9a87a2e88778e1100..729389bbd6120e4b395fbc6f916f8d13965191d6
  #include "refs.h"
  #include "tag.h"
  #include "run-command.h"
 -
 -static const char builtin_tag_usage[] =
 -  "git-tag [-n [<num>]] -l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d | -v] [-m <msg> | -F <file>] <tagname> [<head>]";
 +#include "parse-options.h"
 +
 +static const char * const git_tag_usage[] = {
 +      "git-tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
 +      "git-tag -d <tagname>...",
 +      "git-tag [-n [<num>]] -l [<pattern>]",
 +      "git-tag -v <tagname>...",
 +      NULL
 +};
  
  static char signingkey[1000];
  
static void launch_editor(const char *path, struct strbuf *buffer)
void launch_editor(const char *path, struct strbuf *buffer, const char *const *env)
  {
        const char *editor, *terminal;
-       struct child_process child;
-       const char *args[3];
  
        editor = getenv("GIT_EDITOR");
        if (!editor && editor_program)
        if (!editor)
                editor = "vi";
  
-       memset(&child, 0, sizeof(child));
-       child.argv = args;
-       args[0] = editor;
-       args[1] = path;
-       args[2] = NULL;
+       if (strcmp(editor, ":")) {
+               const char *args[] = { editor, path, NULL };
  
-       if (run_command(&child))
-               die("There was a problem with the editor %s.", editor);
+               if (run_command_v_opt_cd_env(args, 0, NULL, env))
+                       die("There was a problem with the editor %s.", editor);
+       }
  
        if (strbuf_read_file(buffer, path, 0) < 0)
                die("could not read message file '%s': %s",
@@@ -282,7 -272,7 +278,7 @@@ static void write_tag_body(int fd, cons
  
  static void create_tag(const unsigned char *object, const char *tag,
                       struct strbuf *buf, int message, int sign,
 -                         unsigned char *prev, unsigned char *result)
 +                     unsigned char *prev, unsigned char *result)
  {
        enum object_type type;
        char header_buf[1024];
                        write_or_die(fd, tag_template, strlen(tag_template));
                close(fd);
  
-               launch_editor(path, buf);
+               launch_editor(path, buf, NULL);
  
                unlink(path);
                free(path);
                die("unable to write tag file");
  }
  
 +struct msg_arg {
 +      int given;
 +      struct strbuf buf;
 +};
 +
 +static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
 +{
 +      struct msg_arg *msg = opt->value;
 +
 +      if (!arg)
 +              return -1;
 +      if (msg->buf.len)
 +              strbuf_addstr(&(msg->buf), "\n\n");
 +      strbuf_addstr(&(msg->buf), arg);
 +      msg->given = 1;
 +      return 0;
 +}
 +
  int cmd_tag(int argc, const char **argv, const char *prefix)
  {
        struct strbuf buf;
        unsigned char object[20], prev[20];
 -      int annotate = 0, sign = 0, force = 0, lines = 0, message = 0;
        char ref[PATH_MAX];
        const char *object_ref, *tag;
 -      int i;
        struct ref_lock *lock;
  
 +      int annotate = 0, sign = 0, force = 0, lines = 0,
 +                                      delete = 0, verify = 0;
 +      char *list = NULL, *msgfile = NULL, *keyid = NULL;
 +      const char *no_pattern = "NO_PATTERN";
 +      struct msg_arg msg = { 0, STRBUF_INIT };
 +      struct option options[] = {
 +              { OPTION_STRING, 'l', NULL, &list, "pattern", "list tag names",
 +                      PARSE_OPT_OPTARG, NULL, (intptr_t) no_pattern },
 +              { OPTION_INTEGER, 'n', NULL, &lines, NULL,
 +                              "print n lines of each tag message",
 +                              PARSE_OPT_OPTARG, NULL, 1 },
 +              OPT_BOOLEAN('d', NULL, &delete, "delete tags"),
 +              OPT_BOOLEAN('v', NULL, &verify, "verify tags"),
 +
 +              OPT_GROUP("Tag creation options"),
 +              OPT_BOOLEAN('a', NULL, &annotate,
 +                                      "annotated tag, needs a message"),
 +              OPT_CALLBACK('m', NULL, &msg, "msg",
 +                           "message for the tag", parse_msg_arg),
 +              OPT_STRING('F', NULL, &msgfile, "file", "message in a file"),
 +              OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"),
 +              OPT_STRING('u', NULL, &keyid, "key-id",
 +                                      "use another key to sign the tag"),
 +              OPT_BOOLEAN('f', NULL, &force, "replace the tag if exists"),
 +              OPT_END()
 +      };
 +
        git_config(git_tag_config);
 -      strbuf_init(&buf, 0);
  
 -      for (i = 1; i < argc; i++) {
 -              const char *arg = argv[i];
 +      argc = parse_options(argc, argv, options, git_tag_usage, 0);
  
 -              if (arg[0] != '-')
 -                      break;
 -              if (!strcmp(arg, "-a")) {
 -                      annotate = 1;
 -                      continue;
 -              }
 -              if (!strcmp(arg, "-s")) {
 -                      annotate = 1;
 -                      sign = 1;
 -                      continue;
 -              }
 -              if (!strcmp(arg, "-f")) {
 -                      force = 1;
 -                      continue;
 -              }
 -              if (!strcmp(arg, "-n")) {
 -                      if (i + 1 == argc || *argv[i + 1] == '-')
 -                              /* no argument */
 -                              lines = 1;
 -                      else
 -                              lines = isdigit(*argv[++i]) ?
 -                                      atoi(argv[i]) : 1;
 -                      continue;
 -              }
 -              if (!strcmp(arg, "-m")) {
 -                      annotate = 1;
 -                      i++;
 -                      if (i == argc)
 -                              die("option -m needs an argument.");
 -                      if (message)
 -                              die("only one -F or -m option is allowed.");
 -                      strbuf_addstr(&buf, argv[i]);
 -                      message = 1;
 -                      continue;
 -              }
 -              if (!strcmp(arg, "-F")) {
 -                      annotate = 1;
 -                      i++;
 -                      if (i == argc)
 -                              die("option -F needs an argument.");
 -                      if (message)
 -                              die("only one -F or -m option is allowed.");
 -
 -                      if (!strcmp(argv[i], "-")) {
 +      if (sign)
 +              annotate = 1;
 +
 +      if (list)
 +              return list_tags(list == no_pattern ? NULL : list, lines);
 +      if (delete)
 +              return for_each_tag_name(argv, delete_tag);
 +      if (verify)
 +              return for_each_tag_name(argv, verify_tag);
 +
 +      strbuf_init(&buf, 0);
 +      if (msg.given || msgfile) {
 +              if (msg.given && msgfile)
 +                      die("only one -F or -m option is allowed.");
 +              annotate = 1;
 +              if (msg.given)
 +                      strbuf_addbuf(&buf, &(msg.buf));
 +              else {
 +                      if (!strcmp(msgfile, "-")) {
                                if (strbuf_read(&buf, 0, 1024) < 0)
 -                                      die("cannot read %s", argv[i]);
 +                                      die("cannot read %s", msgfile);
                        } else {
 -                              if (strbuf_read_file(&buf, argv[i], 1024) < 0)
 +                              if (strbuf_read_file(&buf, msgfile, 1024) < 0)
                                        die("could not open or read '%s': %s",
 -                                              argv[i], strerror(errno));
 +                                              msgfile, strerror(errno));
                        }
 -                      message = 1;
 -                      continue;
 -              }
 -              if (!strcmp(arg, "-u")) {
 -                      annotate = 1;
 -                      sign = 1;
 -                      i++;
 -                      if (i == argc)
 -                              die("option -u needs an argument.");
 -                      if (strlcpy(signingkey, argv[i], sizeof(signingkey))
 -                                                      >= sizeof(signingkey))
 -                              die("argument to option -u too long");
 -                      continue;
                }
 -              if (!strcmp(arg, "-l"))
 -                      return list_tags(argv[i + 1], lines);
 -              if (!strcmp(arg, "-d"))
 -                      return for_each_tag_name(argv + i + 1, delete_tag);
 -              if (!strcmp(arg, "-v"))
 -                      return for_each_tag_name(argv + i + 1, verify_tag);
 -              usage(builtin_tag_usage);
        }
  
 -      if (i == argc) {
 +      if (argc == 0) {
                if (annotate)
 -                      usage(builtin_tag_usage);
 +                      usage_with_options(git_tag_usage, options);
                return list_tags(NULL, lines);
        }
 -      tag = argv[i++];
 +      tag = argv[0];
  
 -      object_ref = i < argc ? argv[i] : "HEAD";
 -      if (i + 1 < argc)
 +      object_ref = argc == 2 ? argv[1] : "HEAD";
 +      if (argc > 2)
                die("too many params");
  
        if (get_sha1(object_ref, object))
                die("tag '%s' already exists", tag);
  
        if (annotate)
 -              create_tag(object, tag, &buf, message, sign, prev, object);
 +              create_tag(object, tag, &buf, msg.given || msgfile,
 +                         sign, prev, object);
  
        lock = lock_any_ref_for_update(ref, prev, 0);
        if (!lock)
diff --combined builtin.h
index 3c1bf38d83c9a1ebf4a1036114a158b58a6c1b75,caea1a94e40f2e103b75694fa14d17f628d29935..cb675c4d7a80b99a06d6d04156e767ea025eb512
+++ b/builtin.h
@@@ -24,7 -24,7 +24,8 @@@ extern int cmd_check_attr(int argc, con
  extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
  extern int cmd_cherry(int argc, const char **argv, const char *prefix);
  extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
 +extern int cmd_clean(int argc, const char **argv, const char *prefix);
+ extern int cmd_commit(int argc, const char **argv, const char *prefix);
  extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
  extern int cmd_count_objects(int argc, const char **argv, const char *prefix);
  extern int cmd_describe(int argc, const char **argv, const char *prefix);
@@@ -32,7 -32,6 +33,7 @@@ extern int cmd_diff_files(int argc, con
  extern int cmd_diff_index(int argc, const char **argv, const char *prefix);
  extern int cmd_diff(int argc, const char **argv, const char *prefix);
  extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
 +extern int cmd_fast_export(int argc, const char **argv, const char *prefix);
  extern int cmd_fetch(int argc, const char **argv, const char *prefix);
  extern int cmd_fetch_pack(int argc, const char **argv, const char *prefix);
  extern int cmd_fetch__tool(int argc, const char **argv, const char *prefix);
@@@ -50,7 -49,6 +51,7 @@@ extern int cmd_log(int argc, const cha
  extern int cmd_log_reflog(int argc, const char **argv, const char *prefix);
  extern int cmd_ls_files(int argc, const char **argv, const char *prefix);
  extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
 +extern int cmd_ls_remote(int argc, const char **argv, const char *prefix);
  extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
  extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
  extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
@@@ -72,11 -70,10 +73,11 @@@ extern int cmd_rev_list(int argc, cons
  extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
  extern int cmd_revert(int argc, const char **argv, const char *prefix);
  extern int cmd_rm(int argc, const char **argv, const char *prefix);
- extern int cmd_runstatus(int argc, const char **argv, const char *prefix);
 +extern int cmd_send_pack(int argc, const char **argv, const char *prefix);
  extern int cmd_shortlog(int argc, const char **argv, const char *prefix);
  extern int cmd_show(int argc, const char **argv, const char *prefix);
  extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
+ extern int cmd_status(int argc, const char **argv, const char *prefix);
  extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
  extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
  extern int cmd_tag(int argc, const char **argv, const char *prefix);
diff --combined cache.h
index ff2a1c0778253083bced51f4e4916c2c30caa4b1,43cfebb0cfd806b406b11d919f9a77607ff3aa2b..406befb705a0a99119476bbed3be12dd6059d932
+++ b/cache.h
@@@ -192,13 -192,6 +192,13 @@@ enum object_type 
        OBJ_MAX,
  };
  
 +static inline enum object_type object_type(unsigned int mode)
 +{
 +      return S_ISDIR(mode) ? OBJ_TREE :
 +              S_ISGITLINK(mode) ? OBJ_COMMIT :
 +              OBJ_BLOB;
 +}
 +
  #define GIT_DIR_ENVIRONMENT "GIT_DIR"
  #define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
  #define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
@@@ -297,7 -290,6 +297,7 @@@ extern int refresh_index(struct index_s
  
  struct lock_file {
        struct lock_file *next;
 +      int fd;
        pid_t owner;
        char on_list;
        char filename[PATH_MAX];
@@@ -423,10 -415,6 +423,10 @@@ extern const char *resolve_ref(const ch
  extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
  extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
  
 +extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules);
 +extern const char *ref_rev_parse_rules[];
 +extern const char *ref_fetch_rules[];
 +
  extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg);
  extern int validate_headref(const char *ref);
  
@@@ -456,6 -444,7 +456,7 @@@ enum date_mode parse_date_format(const 
  extern const char *git_author_info(int);
  extern const char *git_committer_info(int);
  extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int);
+ extern const char *fmt_name(const char *name, const char *email);
  
  struct checkout {
        const char *base_dir;
@@@ -511,20 -500,8 +512,20 @@@ struct ref 
        struct ref *next;
        unsigned char old_sha1[20];
        unsigned char new_sha1[20];
 -      unsigned char force;
 -      unsigned char merge;
 +      unsigned int force:1,
 +              merge:1,
 +              nonfastforward:1,
 +              deletion:1;
 +      enum {
 +              REF_STATUS_NONE = 0,
 +              REF_STATUS_OK,
 +              REF_STATUS_REJECT_NONFASTFORWARD,
 +              REF_STATUS_REJECT_NODELETE,
 +              REF_STATUS_UPTODATE,
 +              REF_STATUS_REMOTE_REJECT,
 +              REF_STATUS_EXPECTING_REPORT,
 +      } status;
 +      char *remote_status;
        struct ref *peer_ref; /* when renaming */
        char name[FLEX_ARRAY]; /* more */
  };
  #define REF_HEADS     (1u << 1)
  #define REF_TAGS      (1u << 2)
  
 +extern struct ref *find_ref_by_name(struct ref *list, const char *name);
 +
  #define CONNECT_VERBOSE       (1u << 0)
 -extern struct child_process *git_connect(int fd[2], char *url, const char *prog, int flags);
 +extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
  extern int finish_connect(struct child_process *conn);
  extern int path_match(const char *path, int nr, char **match);
  extern int get_ack(int fd, unsigned char *result_sha1);
@@@ -582,7 -557,6 +583,7 @@@ extern int git_config_bool(const char *
  extern int git_config_set(const char *, const char *);
  extern int git_config_set_multivar(const char *, const char *, const char *, int);
  extern int git_config_rename_section(const char *, const char *);
 +extern const char *git_etc_gitconfig(void);
  extern int check_repository_format_version(const char *var, const char *value);
  
  #define MAX_GITNAME (1000)
@@@ -624,17 -598,25 +625,25 @@@ extern void alloc_report(void)
  
  /* trace.c */
  extern void trace_printf(const char *format, ...);
 -extern void trace_argv_printf(const char **argv, int count, const char *format, ...);
 +extern void trace_argv_printf(const char **argv, const char *format, ...);
  
  /* convert.c */
  /* returns 1 if *dst was used */
  extern int convert_to_git(const char *path, const char *src, size_t len, struct strbuf *dst);
  extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst);
  
+ /* add */
+ void add_files_to_cache(int verbose, const char *prefix, const char **pathspec);
  /* diff.c */
  extern int diff_auto_refresh_index;
  
  /* match-trees.c */
  void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int);
  
+ /* ls-files */
+ int pathspec_match(const char **spec, char *matched, const char *filename, int skiplen);
+ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset);
+ void overlay_tree_on_cache(const char *tree_name, const char *prefix);
  #endif /* CACHE_H */
index 0000000000000000000000000000000000000000,485339754ca3567c86b824af656700654c68e173..2c4a4062a5317c51601fc4c644c96a7f75e1ef2c
mode 000000,100755..100755
--- /dev/null
@@@ -1,0 -1,629 +1,639 @@@
 -if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" ]
 -then
+ #!/bin/sh
+ #
+ # Copyright (c) 2005 Linus Torvalds
+ # Copyright (c) 2006 Junio C Hamano
+ USAGE='[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [--template <file>] [[-i | -o] <path>...]'
+ SUBDIRECTORY_OK=Yes
+ OPTIONS_SPEC=
+ . git-sh-setup
+ require_work_tree
+ git rev-parse --verify HEAD >/dev/null 2>&1 || initial_commit=t
+ case "$0" in
+ *status)
+       status_only=t
+       ;;
+ *commit)
+       status_only=
+       ;;
+ esac
+ refuse_partial () {
+       echo >&2 "$1"
+       echo >&2 "You might have meant to say 'git commit -i paths...', perhaps?"
+       exit 1
+ }
+ TMP_INDEX=
+ THIS_INDEX="${GIT_INDEX_FILE:-$GIT_DIR/index}"
+ NEXT_INDEX="$GIT_DIR/next-index$$"
+ rm -f "$NEXT_INDEX"
+ save_index () {
+       cp -p "$THIS_INDEX" "$NEXT_INDEX"
+ }
+ run_status () {
+       # If TMP_INDEX is defined, that means we are doing
+       # "--only" partial commit, and that index file is used
+       # to build the tree for the commit.  Otherwise, if
+       # NEXT_INDEX exists, that is the index file used to
+       # make the commit.  Otherwise we are using as-is commit
+       # so the regular index file is what we use to compare.
+       if test '' != "$TMP_INDEX"
+       then
+               GIT_INDEX_FILE="$TMP_INDEX"
+               export GIT_INDEX_FILE
+       elif test -f "$NEXT_INDEX"
+       then
+               GIT_INDEX_FILE="$NEXT_INDEX"
+               export GIT_INDEX_FILE
+       fi
+       if test "$status_only" = "t" -o "$use_status_color" = "t"; then
+               color=
+       else
+               color=--nocolor
+       fi
+       git runstatus ${color} \
+               ${verbose:+--verbose} \
+               ${amend:+--amend} \
+               ${untracked_files:+--untracked}
+ }
+ trap '
+       test -z "$TMP_INDEX" || {
+               test -f "$TMP_INDEX" && rm -f "$TMP_INDEX"
+       }
+       rm -f "$NEXT_INDEX"
+ ' 0
+ ################################################################
+ # Command line argument parsing and sanity checking
+ all=
+ also=
++allow_empty=f
+ interactive=
+ only=
+ logfile=
+ use_commit=
+ amend=
+ edit_flag=
+ no_edit=
+ log_given=
+ log_message=
+ verify=t
+ quiet=
+ verbose=
+ signoff=
+ force_author=
+ only_include_assumed=
+ untracked_files=
+ templatefile="`git config commit.template`"
+ while test $# != 0
+ do
+       case "$1" in
+       -F|--F|-f|--f|--fi|--fil|--file)
+               case "$#" in 1) usage ;; esac
+               shift
+               no_edit=t
+               log_given=t$log_given
+               logfile="$1"
+               ;;
+       -F*|-f*)
+               no_edit=t
+               log_given=t$log_given
+               logfile="${1#-[Ff]}"
+               ;;
+       --F=*|--f=*|--fi=*|--fil=*|--file=*)
+               no_edit=t
+               log_given=t$log_given
+               logfile="${1#*=}"
+               ;;
+       -a|--a|--al|--all)
+               all=t
+               ;;
++      --allo|--allow|--allow-|--allow-e|--allow-em|--allow-emp|\
++      --allow-empt|--allow-empty)
++              allow_empty=t
++              ;;
+       --au=*|--aut=*|--auth=*|--autho=*|--author=*)
+               force_author="${1#*=}"
+               ;;
+       --au|--aut|--auth|--autho|--author)
+               case "$#" in 1) usage ;; esac
+               shift
+               force_author="$1"
+               ;;
+       -e|--e|--ed|--edi|--edit)
+               edit_flag=t
+               ;;
+       -i|--i|--in|--inc|--incl|--inclu|--includ|--include)
+               also=t
+               ;;
+       --int|--inte|--inter|--intera|--interac|--interact|--interacti|\
+       --interactiv|--interactive)
+               interactive=t
+               ;;
+       -o|--o|--on|--onl|--only)
+               only=t
+               ;;
+       -m|--m|--me|--mes|--mess|--messa|--messag|--message)
+               case "$#" in 1) usage ;; esac
+               shift
+               log_given=m$log_given
+               log_message="${log_message:+${log_message}
+ }$1"
+               no_edit=t
+               ;;
+       -m*)
+               log_given=m$log_given
+               log_message="${log_message:+${log_message}
+ }${1#-m}"
+               no_edit=t
+               ;;
+       --m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
+               log_given=m$log_given
+               log_message="${log_message:+${log_message}
+ }${1#*=}"
+               no_edit=t
+               ;;
+       -n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|\
+       --no-verify)
+               verify=
+               ;;
+       --a|--am|--ame|--amen|--amend)
+               amend=t
+               use_commit=HEAD
+               ;;
+       -c)
+               case "$#" in 1) usage ;; esac
+               shift
+               log_given=t$log_given
+               use_commit="$1"
+               no_edit=
+               ;;
+       --ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\
+       --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\
+       --reedit-messag=*|--reedit-message=*)
+               log_given=t$log_given
+               use_commit="${1#*=}"
+               no_edit=
+               ;;
+       --ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\
+       --reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|\
+       --reedit-message)
+               case "$#" in 1) usage ;; esac
+               shift
+               log_given=t$log_given
+               use_commit="$1"
+               no_edit=
+               ;;
+       -C)
+               case "$#" in 1) usage ;; esac
+               shift
+               log_given=t$log_given
+               use_commit="$1"
+               no_edit=t
+               ;;
+       --reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\
+       --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\
+       --reuse-message=*)
+               log_given=t$log_given
+               use_commit="${1#*=}"
+               no_edit=t
+               ;;
+       --reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\
+       --reuse-mess|--reuse-messa|--reuse-messag|--reuse-message)
+               case "$#" in 1) usage ;; esac
+               shift
+               log_given=t$log_given
+               use_commit="$1"
+               no_edit=t
+               ;;
+       -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
+               signoff=t
+               ;;
+       -t|--t|--te|--tem|--temp|--templ|--templa|--templat|--template)
+               case "$#" in 1) usage ;; esac
+               shift
+               templatefile="$1"
+               no_edit=
+               ;;
+       -q|--q|--qu|--qui|--quie|--quiet)
+               quiet=t
+               ;;
+       -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
+               verbose=t
+               ;;
+       -u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|\
+       --untracked|--untracked-|--untracked-f|--untracked-fi|--untracked-fil|\
+       --untracked-file|--untracked-files)
+               untracked_files=t
+               ;;
+       --)
+               shift
+               break
+               ;;
+       -*)
+               usage
+               ;;
+       *)
+               break
+               ;;
+       esac
+       shift
+ done
+ case "$edit_flag" in t) no_edit= ;; esac
+ ################################################################
+ # Sanity check options
+ case "$amend,$initial_commit" in
+ t,t)
+       die "You do not have anything to amend." ;;
+ t,)
+       if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
+               die "You are in the middle of a merge -- cannot amend."
+       fi ;;
+ esac
+ case "$log_given" in
+ tt*)
+       die "Only one of -c/-C/-F can be used." ;;
+ *tm*|*mt*)
+       die "Option -m cannot be combined with -c/-C/-F." ;;
+ esac
+ case "$#,$also,$only,$amend" in
+ *,t,t,*)
+       die "Only one of --include/--only can be used." ;;
+ 0,t,,* | 0,,t,)
+       die "No paths with --include/--only does not make sense." ;;
+ 0,,t,t)
+       only_include_assumed="# Clever... amending the last one with dirty index." ;;
+ 0,,,*)
+       ;;
+ *,,,*)
+       only_include_assumed="# Explicit paths specified without -i nor -o; assuming --only paths..."
+       also=
+       ;;
+ esac
+ unset only
+ case "$all,$interactive,$also,$#" in
+ *t,*t,*)
+       die "Cannot use -a, --interactive or -i at the same time." ;;
+ t,,,[1-9]*)
+       die "Paths with -a does not make sense." ;;
+ ,t,,[1-9]*)
+       die "Paths with --interactive does not make sense." ;;
+ ,,t,0)
+       die "No paths with -i does not make sense." ;;
+ esac
+ if test ! -z "$templatefile" -a -z "$log_given"
+ then
+       if test ! -f "$templatefile"
+       then
+               die "Commit template file does not exist."
+       fi
+ fi
+ ################################################################
+ # Prepare index to have a tree to be committed
+ case "$all,$also" in
+ t,)
+       if test ! -f "$THIS_INDEX"
+       then
+               die 'nothing to commit (use "git add file1 file2" to include for commit)'
+       fi
+       save_index &&
+       (
+               cd_to_toplevel &&
+               GIT_INDEX_FILE="$NEXT_INDEX" &&
+               export GIT_INDEX_FILE &&
+               git diff-files --name-only -z |
+               git update-index --remove -z --stdin
+       ) || exit
+       ;;
+ ,t)
+       save_index &&
+       git ls-files --error-unmatch -- "$@" >/dev/null || exit
+       git diff-files --name-only -z -- "$@"  |
+       (
+               cd_to_toplevel &&
+               GIT_INDEX_FILE="$NEXT_INDEX" &&
+               export GIT_INDEX_FILE &&
+               git update-index --remove -z --stdin
+       ) || exit
+       ;;
+ ,)
+       if test "$interactive" = t; then
+               git add --interactive || exit
+       fi
+       case "$#" in
+       0)
+               ;; # commit as-is
+       *)
+               if test -f "$GIT_DIR/MERGE_HEAD"
+               then
+                       refuse_partial "Cannot do a partial commit during a merge."
+               fi
+               TMP_INDEX="$GIT_DIR/tmp-index$$"
+               W=
+               test -z "$initial_commit" && W=--with-tree=HEAD
+               commit_only=`git ls-files --error-unmatch $W -- "$@"` || exit
+               # Build a temporary index and update the real index
+               # the same way.
+               if test -z "$initial_commit"
+               then
+                       GIT_INDEX_FILE="$THIS_INDEX" \
+                       git read-tree --index-output="$TMP_INDEX" -i -m HEAD
+               else
+                       rm -f "$TMP_INDEX"
+               fi || exit
+               printf '%s\n' "$commit_only" |
+               GIT_INDEX_FILE="$TMP_INDEX" \
+               git update-index --add --remove --stdin &&
+               save_index &&
+               printf '%s\n' "$commit_only" |
+               (
+                       GIT_INDEX_FILE="$NEXT_INDEX"
+                       export GIT_INDEX_FILE
+                       git update-index --add --remove --stdin
+               ) || exit
+               ;;
+       esac
+       ;;
+ esac
+ ################################################################
+ # If we do as-is commit, the index file will be THIS_INDEX,
+ # otherwise NEXT_INDEX after we make this commit.  We leave
+ # the index as is if we abort.
+ if test -f "$NEXT_INDEX"
+ then
+       USE_INDEX="$NEXT_INDEX"
+ else
+       USE_INDEX="$THIS_INDEX"
+ fi
+ case "$status_only" in
+ t)
+       # This will silently fail in a read-only repository, which is
+       # what we want.
+       GIT_INDEX_FILE="$USE_INDEX" git update-index -q --unmerged --refresh
+       run_status
+       exit $?
+       ;;
+ '')
+       GIT_INDEX_FILE="$USE_INDEX" git update-index -q --refresh || exit
+       ;;
+ esac
+ ################################################################
+ # Grab commit message, write out tree and make commit.
+ if test t = "$verify" && test -x "$GIT_DIR"/hooks/pre-commit
+ then
+     GIT_INDEX_FILE="${TMP_INDEX:-${USE_INDEX}}" "$GIT_DIR"/hooks/pre-commit \
+     || exit
+ fi
+ if test "$log_message" != ''
+ then
+       printf '%s\n' "$log_message"
+ elif test "$logfile" != ""
+ then
+       if test "$logfile" = -
+       then
+               test -t 0 &&
+               echo >&2 "(reading log message from standard input)"
+               cat
+       else
+               cat <"$logfile"
+       fi
+ elif test "$use_commit" != ""
+ then
+       encoding=$(git config i18n.commitencoding || echo UTF-8)
+       git show -s --pretty=raw --encoding="$encoding" "$use_commit" |
+       sed -e '1,/^$/d' -e 's/^    //'
+ elif test -f "$GIT_DIR/MERGE_MSG"
+ then
+       cat "$GIT_DIR/MERGE_MSG"
+ elif test -f "$GIT_DIR/SQUASH_MSG"
+ then
+       cat "$GIT_DIR/SQUASH_MSG"
+ elif test "$templatefile" != ""
+ then
+       cat "$templatefile"
+ fi | git stripspace >"$GIT_DIR"/COMMIT_EDITMSG
+ case "$signoff" in
+ t)
+       sign=$(git-var GIT_COMMITTER_IDENT | sed -e '
+               s/>.*/>/
+               s/^/Signed-off-by: /
+               ')
+       blank_before_signoff=
+       tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG |
+       grep 'Signed-off-by:' >/dev/null || blank_before_signoff='
+ '
+       tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG |
+       grep "$sign"$ >/dev/null ||
+       printf '%s%s\n' "$blank_before_signoff" "$sign" \
+               >>"$GIT_DIR"/COMMIT_EDITMSG
+       ;;
+ esac
+ if test -f "$GIT_DIR/MERGE_HEAD" && test -z "$no_edit"; then
+       echo "#"
+       echo "# It looks like you may be committing a MERGE."
+       echo "# If this is not correct, please remove the file"
+       printf '%s\n' "#        $GIT_DIR/MERGE_HEAD"
+       echo "# and try again"
+       echo "#"
+ fi >>"$GIT_DIR"/COMMIT_EDITMSG
+ # Author
+ if test '' != "$use_commit"
+ then
+       eval "$(get_author_ident_from_commit "$use_commit")"
+       export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
+ fi
+ if test '' != "$force_author"
+ then
+       GIT_AUTHOR_NAME=`expr "z$force_author" : 'z\(.*[^ ]\) *<.*'` &&
+       GIT_AUTHOR_EMAIL=`expr "z$force_author" : '.*\(<.*\)'` &&
+       test '' != "$GIT_AUTHOR_NAME" &&
+       test '' != "$GIT_AUTHOR_EMAIL" ||
+       die "malformed --author parameter"
+       export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
+ fi
+ PARENTS="-p HEAD"
+ if test -z "$initial_commit"
+ then
+       rloga='commit'
+       if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
+               rloga='commit (merge)'
+               PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"`
+       elif test -n "$amend"; then
+               rloga='commit (amend)'
+               PARENTS=$(git cat-file commit HEAD |
+                       sed -n -e '/^$/q' -e 's/^parent /-p /p')
+       fi
+       current="$(git rev-parse --verify HEAD)"
+ else
+       if [ -z "$(git ls-files)" ]; then
+               echo >&2 'nothing to commit (use "git add file1 file2" to include for commit)'
+               exit 1
+       fi
+       PARENTS=""
+       rloga='commit (initial)'
+       current=''
+ fi
+ set_reflog_action "$rloga"
+ if test -z "$no_edit"
+ then
+       {
+               echo ""
+               echo "# Please enter the commit message for your changes."
+               echo "# (Comment lines starting with '#' will not be included)"
+               test -z "$only_include_assumed" || echo "$only_include_assumed"
+               run_status
+       } >>"$GIT_DIR"/COMMIT_EDITMSG
+ else
+       # we need to check if there is anything to commit
+       run_status >/dev/null
+ fi
 -fi
++case "$allow_empty,$?,$PARENTS" in
++t,* | ?,0,* | ?,*,-p' '?*-p' '?*)
++      # an explicit --allow-empty, or a merge commit can record the
++      # same tree as its parent.  Otherwise having commitable paths
++      # is required.
++      ;;
++*)
+       rm -f "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
+       use_status_color=t
+       run_status
+       exit 1
++esac
+ case "$no_edit" in
+ '')
+       git-var GIT_AUTHOR_IDENT > /dev/null  || die
+       git-var GIT_COMMITTER_IDENT > /dev/null  || die
+       git_editor "$GIT_DIR/COMMIT_EDITMSG"
+       ;;
+ esac
+ case "$verify" in
+ t)
+       if test -x "$GIT_DIR"/hooks/commit-msg
+       then
+               "$GIT_DIR"/hooks/commit-msg "$GIT_DIR"/COMMIT_EDITMSG || exit
+       fi
+ esac
+ if test -z "$no_edit"
+ then
+     sed -e '
+         /^diff --git a\/.*/{
+           s///
+           q
+       }
+       /^#/d
+     ' "$GIT_DIR"/COMMIT_EDITMSG
+ else
+     cat "$GIT_DIR"/COMMIT_EDITMSG
+ fi |
+ git stripspace >"$GIT_DIR"/COMMIT_MSG
+ # Test whether the commit message has any content we didn't supply.
+ have_commitmsg=
+ grep -v -i '^Signed-off-by' "$GIT_DIR"/COMMIT_MSG |
+       git stripspace > "$GIT_DIR"/COMMIT_BAREMSG
+ # Is the commit message totally empty?
+ if test -s "$GIT_DIR"/COMMIT_BAREMSG
+ then
+       if test "$templatefile" != ""
+       then
+               # Test whether this is just the unaltered template.
+               if cnt=`sed -e '/^#/d' < "$templatefile" |
+                       git stripspace |
+                       diff "$GIT_DIR"/COMMIT_BAREMSG - |
+                       wc -l` &&
+                  test 0 -lt $cnt
+               then
+                       have_commitmsg=t
+               fi
+       else
+               # No template, so the content in the commit message must
+               # have come from the user.
+               have_commitmsg=t
+       fi
+ fi
+ rm -f "$GIT_DIR"/COMMIT_BAREMSG
+ if test "$have_commitmsg" = "t"
+ then
+       if test -z "$TMP_INDEX"
+       then
+               tree=$(GIT_INDEX_FILE="$USE_INDEX" git write-tree)
+       else
+               tree=$(GIT_INDEX_FILE="$TMP_INDEX" git write-tree) &&
+               rm -f "$TMP_INDEX"
+       fi &&
+       commit=$(git commit-tree $tree $PARENTS <"$GIT_DIR/COMMIT_MSG") &&
+       rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) &&
+       git update-ref -m "$GIT_REFLOG_ACTION: $rlogm" HEAD $commit "$current" &&
+       rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" &&
+       if test -f "$NEXT_INDEX"
+       then
+               mv "$NEXT_INDEX" "$THIS_INDEX"
+       else
+               : ;# happy
+       fi
+ else
+       echo >&2 "* no commit message?  aborting commit."
+       false
+ fi
+ ret="$?"
+ rm -f "$GIT_DIR/COMMIT_MSG" "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
+ cd_to_toplevel
+ git rerere
+ if test "$ret" = 0
+ then
+       git gc --auto
+       if test -x "$GIT_DIR"/hooks/post-commit
+       then
+               "$GIT_DIR"/hooks/post-commit
+       fi
+       if test -z "$quiet"
+       then
+               commit=`git diff-tree --always --shortstat --pretty="format:%h: %s"\
+                      --summary --root HEAD --`
+               echo "Created${initial_commit:+ initial} commit $commit"
+       fi
+ fi
+ exit "$ret"
diff --combined dir.c
index d448902909a7da216fbd49cecc505e68c0ba5e5f,11a4cf3e16f840ca826b927ce504535eec8c71db..6b3273d1d18298dc6cb8c08b42948af05c775221
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -144,14 -144,17 +144,14 @@@ void add_exclude(const char *string, co
                x->flags |= EXC_FLAG_NOWILDCARD;
        if (*string == '*' && no_wildcard(string+1))
                x->flags |= EXC_FLAG_ENDSWITH;
 -      if (which->nr == which->alloc) {
 -              which->alloc = alloc_nr(which->alloc);
 -              which->excludes = xrealloc(which->excludes,
 -                                         which->alloc * sizeof(x));
 -      }
 +      ALLOC_GROW(which->excludes, which->nr + 1, which->alloc);
        which->excludes[which->nr++] = x;
  }
  
  static int add_excludes_from_file_1(const char *fname,
                                    const char *base,
                                    int baselen,
 +                                  char **buf_p,
                                    struct exclude_list *which)
  {
        struct stat st;
                goto err;
        close(fd);
  
 +      if (buf_p)
 +              *buf_p = buf;
        buf[size++] = '\n';
        entry = buf;
        for (i = 0; i < size; i++) {
  
  void add_excludes_from_file(struct dir_struct *dir, const char *fname)
  {
 -      if (add_excludes_from_file_1(fname, "", 0,
 +      if (add_excludes_from_file_1(fname, "", 0, NULL,
                                     &dir->exclude_list[EXC_FILE]) < 0)
                die("cannot use %s as an exclude file", fname);
  }
  
 -int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen)
 +static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
  {
 -      char exclude_file[PATH_MAX];
 -      struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
 -      int current_nr = el->nr;
 -
 -      if (dir->exclude_per_dir) {
 -              memcpy(exclude_file, base, baselen);
 -              strcpy(exclude_file + baselen, dir->exclude_per_dir);
 -              add_excludes_from_file_1(exclude_file, base, baselen, el);
 +      struct exclude_list *el;
 +      struct exclude_stack *stk = NULL;
 +      int current;
 +
 +      if ((!dir->exclude_per_dir) ||
 +          (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX))
 +              return; /* too long a path -- ignore */
 +
 +      /* Pop the ones that are not the prefix of the path being checked. */
 +      el = &dir->exclude_list[EXC_DIRS];
 +      while ((stk = dir->exclude_stack) != NULL) {
 +              if (stk->baselen <= baselen &&
 +                  !strncmp(dir->basebuf, base, stk->baselen))
 +                      break;
 +              dir->exclude_stack = stk->prev;
 +              while (stk->exclude_ix < el->nr)
 +                      free(el->excludes[--el->nr]);
 +              free(stk->filebuf);
 +              free(stk);
        }
 -      return current_nr;
 -}
  
 -void pop_exclude_per_directory(struct dir_struct *dir, int stk)
 -{
 -      struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
 +      /* Read from the parent directories and push them down. */
 +      current = stk ? stk->baselen : -1;
 +      while (current < baselen) {
 +              struct exclude_stack *stk = xcalloc(1, sizeof(*stk));
 +              const char *cp;
  
 -      while (stk < el->nr)
 -              free(el->excludes[--el->nr]);
 +              if (current < 0) {
 +                      cp = base;
 +                      current = 0;
 +              }
 +              else {
 +                      cp = strchr(base + current + 1, '/');
 +                      if (!cp)
 +                              die("oops in prep_exclude");
 +                      cp++;
 +              }
 +              stk->prev = dir->exclude_stack;
 +              stk->baselen = cp - base;
 +              stk->exclude_ix = el->nr;
 +              memcpy(dir->basebuf + current, base + current,
 +                     stk->baselen - current);
 +              strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);
 +              add_excludes_from_file_1(dir->basebuf,
 +                                       dir->basebuf, stk->baselen,
 +                                       &stk->filebuf, el);
 +              dir->exclude_stack = stk;
 +              current = stk->baselen;
 +      }
 +      dir->basebuf[baselen] = '\0';
  }
  
  /* Scan the list and let the last match determines the fate.
@@@ -318,7 -287,6 +318,7 @@@ int excluded(struct dir_struct *dir, co
        const char *basename = strrchr(pathname, '/');
        basename = (basename) ? basename+1 : pathname;
  
 +      prep_exclude(dir, pathname, basename-pathname);
        for (st = EXC_CMDL; st <= EXC_FILE; st++) {
                switch (excluded_1(pathname, pathlen, basename, &dir->exclude_list[st])) {
                case 0:
@@@ -536,10 -504,13 +536,10 @@@ static int read_directory_recursive(str
        int contents = 0;
  
        if (fdir) {
 -              int exclude_stk;
                struct dirent *de;
                char fullname[PATH_MAX + 1];
                memcpy(fullname, base, baselen);
  
 -              exclude_stk = push_exclude_per_directory(dir, base, baselen);
 -
                while ((de = readdir(fdir)) != NULL) {
                        int len, dtype;
                        int exclude;
                }
  exit_early:
                closedir(fdir);
 -
 -              pop_exclude_per_directory(dir, exclude_stk);
        }
  
        return contents;
@@@ -681,9 -654,37 +681,9 @@@ static void free_simplify(struct path_s
  int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen, const char **pathspec)
  {
        struct path_simplify *simplify = create_simplify(pathspec);
 -      char *pp = NULL;
 -
 -      /*
 -       * Make sure to do the per-directory exclude for all the
 -       * directories leading up to our base.
 -       */
 -      if (baselen) {
 -              if (dir->exclude_per_dir) {
 -                      char *p;
 -                      pp = xmalloc(baselen+1);
 -                      memcpy(pp, base, baselen+1);
 -                      p = pp;
 -                      while (1) {
 -                              char save = *p;
 -                              *p = 0;
 -                              push_exclude_per_directory(dir, pp, p-pp);
 -                              *p++ = save;
 -                              if (!save)
 -                                      break;
 -                              p = strchr(p, '/');
 -                              if (p)
 -                                      p++;
 -                              else
 -                                      p = pp + baselen;
 -                      }
 -              }
 -      }
  
        read_directory_recursive(dir, path, base, baselen, 0, simplify);
        free_simplify(simplify);
 -      free(pp);
        qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
        qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);
        return dir->nr;
  int file_exists(const char *f)
  {
        struct stat sb;
-       return stat(f, &sb) == 0;
+       return lstat(f, &sb) == 0;
  }
  
  /*
diff --combined git.c
index c4877a9714c227a1af77cfa84caa2230eb30bf9d,a5adc3487d4ca62a9ff93679ce8711a578a5c3eb..15fec8974ad5361a2308b3c46424e896bc8e3246
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -169,7 -169,7 +169,7 @@@ static int handle_alias(int *argcp, con
  
                                strbuf_init(&buf, PATH_MAX);
                                strbuf_addstr(&buf, alias_string);
 -                              sq_quote_argv(&buf, (*argv) + 1, *argcp - 1, PATH_MAX);
 +                              sq_quote_argv(&buf, (*argv) + 1, PATH_MAX);
                                free(alias_string);
                                alias_string = buf.buf;
                        }
                if (!strcmp(alias_command, new_argv[0]))
                        die("recursive alias: %s", alias_command);
  
 -              trace_argv_printf(new_argv, count,
 +              trace_argv_printf(new_argv,
                                  "trace: alias expansion: %s =>",
                                  alias_command);
  
@@@ -252,11 -252,11 +252,11 @@@ static int run_command(struct cmd_struc
        if (p->option & NEED_WORK_TREE)
                setup_work_tree();
  
 -      trace_argv_printf(argv, argc, "trace: built-in: git");
 +      trace_argv_printf(argv, "trace: built-in: git");
  
        status = p->fn(argc, argv, prefix);
        if (status)
 -              return status;
 +              return status & 0xff;
  
        /* Somebody closed stdout? */
        if (fstat(fileno(stdout), &st))
@@@ -293,7 -293,7 +293,8 @@@ static void handle_internal_command(in
                { "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE },
                { "cherry", cmd_cherry, RUN_SETUP },
                { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
 +              { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
+               { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
                { "commit-tree", cmd_commit_tree, RUN_SETUP },
                { "config", cmd_config },
                { "count-objects", cmd_count_objects, RUN_SETUP },
                { "diff-files", cmd_diff_files },
                { "diff-index", cmd_diff_index, RUN_SETUP },
                { "diff-tree", cmd_diff_tree, RUN_SETUP },
 +              { "fast-export", cmd_fast_export, RUN_SETUP },
                { "fetch", cmd_fetch, RUN_SETUP },
                { "fetch-pack", cmd_fetch_pack, RUN_SETUP },
                { "fetch--tool", cmd_fetch__tool, RUN_SETUP },
                { "log", cmd_log, RUN_SETUP | USE_PAGER },
                { "ls-files", cmd_ls_files, RUN_SETUP },
                { "ls-tree", cmd_ls_tree, RUN_SETUP },
 +              { "ls-remote", cmd_ls_remote },
                { "mailinfo", cmd_mailinfo },
                { "mailsplit", cmd_mailsplit },
                { "merge-base", cmd_merge_base, RUN_SETUP },
                { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
                { "name-rev", cmd_name_rev, RUN_SETUP },
                { "pack-objects", cmd_pack_objects, RUN_SETUP },
 +              { "peek-remote", cmd_ls_remote },
                { "pickaxe", cmd_blame, RUN_SETUP },
                { "prune", cmd_prune, RUN_SETUP },
                { "prune-packed", cmd_prune_packed, RUN_SETUP },
                { "rev-parse", cmd_rev_parse },
                { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
                { "rm", cmd_rm, RUN_SETUP },
-               { "runstatus", cmd_runstatus, RUN_SETUP | NEED_WORK_TREE },
 +              { "send-pack", cmd_send_pack, RUN_SETUP },
                { "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
                { "show-branch", cmd_show_branch, RUN_SETUP },
                { "show", cmd_show, RUN_SETUP | USE_PAGER },
+               { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
                { "stripspace", cmd_stripspace },
                { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
                { "tag", cmd_tag, RUN_SETUP },
diff --combined ident.c
index dbd0f527b27e1fd4139862a452d901d851367fb0,021d79b38ba40af93bd03b39a869be4264e6c426..07b4998f71670f17d4972f8e7940d18091f56921
+++ b/ident.c
@@@ -113,15 -113,25 +113,15 @@@ static int add_raw(char *buf, size_t si
  
  static int crud(unsigned char c)
  {
 -      static char crud_array[256];
 -      static int crud_array_initialized = 0;
 -
 -      if (!crud_array_initialized) {
 -              int k;
 -
 -              for (k = 0; k <= 31; ++k) crud_array[k] = 1;
 -              crud_array[' '] = 1;
 -              crud_array['.'] = 1;
 -              crud_array[','] = 1;
 -              crud_array[':'] = 1;
 -              crud_array[';'] = 1;
 -              crud_array['<'] = 1;
 -              crud_array['>'] = 1;
 -              crud_array['"'] = 1;
 -              crud_array['\''] = 1;
 -              crud_array_initialized = 1;
 -      }
 -      return crud_array[c];
 +      return  c <= 32  ||
 +              c == '.' ||
 +              c == ',' ||
 +              c == ':' ||
 +              c == ';' ||
 +              c == '<' ||
 +              c == '>' ||
 +              c == '"' ||
 +              c == '\'';
  }
  
  /*
@@@ -182,12 -192,14 +182,14 @@@ static const char *env_hint 
  "Omit --global to set the identity only in this repository.\n"
  "\n";
  
const char *fmt_ident(const char *name, const char *email,
-                     const char *date_str, int error_on_no_name)
static const char *fmt_ident_1(const char *name, const char *email,
+                              const char *date_str, int flag)
  {
        static char buffer[1000];
        char date[50];
        int i;
+       int error_on_no_name = !!(flag & 01);
+       int name_addr_only = !!(flag & 02);
  
        setup_ident();
        if (!name)
        }
  
        strcpy(date, git_default_date);
-       if (date_str)
+       if (!name_addr_only && date_str)
                parse_date(date_str, date, sizeof(date));
  
        i = copy(buffer, sizeof(buffer), 0, name);
        i = add_raw(buffer, sizeof(buffer), i, " <");
        i = copy(buffer, sizeof(buffer), i, email);
-       i = add_raw(buffer, sizeof(buffer), i, "> ");
-       i = copy(buffer, sizeof(buffer), i, date);
+       if (!name_addr_only) {
+               i = add_raw(buffer, sizeof(buffer), i,  "> ");
+               i = copy(buffer, sizeof(buffer), i, date);
+       } else {
+               i = add_raw(buffer, sizeof(buffer), i, ">");
+       }
        if (i >= sizeof(buffer))
                die("Impossibly long personal identifier");
        buffer[i] = 0;
        return buffer;
  }
  
+ const char *fmt_ident(const char *name, const char *email,
+                     const char *date_str, int error_on_no_name)
+ {
+       int flag = (error_on_no_name ? 01 : 0);
+       return fmt_ident_1(name, email, date_str, flag);
+ }
+ const char *fmt_name(const char *name, const char *email)
+ {
+       return fmt_ident_1(name, email, NULL, 03);
+ }
  const char *git_author_info(int error_on_no_name)
  {
        return fmt_ident(getenv("GIT_AUTHOR_NAME"),
diff --combined t/t4001-diff-rename.sh
index 2fe50bc7ce050f32affa287c5c6e3ab7b1a66f45,877c1ea5db4b99f088db6f5e38a6833abc3cfef5..a32692417db73444dbdc143e6908b7371be79d42
@@@ -27,7 -27,7 +27,7 @@@ Line 1
  '
  
  test_expect_success \
 -    'update-cache --add a file.' \
 +    'update-index --add a file.' \
      'git update-index --add path0'
  
  test_expect_success \
@@@ -71,10 -71,10 +71,10 @@@ test_expect_success 'favour same basena
        git rm path1 &&
        mkdir subdir &&
        git mv another-path subdir/path1 &&
-       git runstatus | grep "renamed: .*path1 -> subdir/path1"'
+       git status | grep "renamed: .*path1 -> subdir/path1"'
  
  test_expect_success  'favour same basenames even with minor differences' '
        git show HEAD:path1 | sed "s/15/16/" > subdir/path1 &&
-       git runstatus | grep "renamed: .*path1 -> subdir/path1"'
+       git status | grep "renamed: .*path1 -> subdir/path1"'
  
  test_done
diff --combined t/t7501-commit.sh
index 0316ecf5a16792f226725eb9626ad77c369877bb,ce83af3a02427af8c6a5fbea921a46ed5522b8b3..19c4b2c5566dcd6a88b8b557d4d597079df21b4e
@@@ -79,8 -79,7 +79,8 @@@ test_expect_success 
  
  cat >editor <<\EOF
  #!/bin/sh
 -sed -i -e "s/a file/an amend commit/g" $1
 +sed -e "s/a file/an amend commit/g" < $1 > $1-
 +mv $1- $1
  EOF
  chmod 755 editor
  
@@@ -99,8 -98,7 +99,8 @@@ test_expect_success 
  
  cat >editor <<\EOF
  #!/bin/sh
 -sed -i -e "s/amend/older/g" $1
 +sed -e "s/amend/older/g"  < $1 > $1-
 +mv $1- $1
  EOF
  chmod 755 editor
  
@@@ -244,43 -242,29 +244,70 @@@ test_expect_success 'multiple -m' 
  
  '
  
+ author="The Real Author <someguy@his.email.org>"
+ test_expect_success 'amend commit to fix author' '
+       oldtick=$GIT_AUTHOR_DATE &&
+       test_tick &&
+       git reset --hard &&
+       git cat-file -p HEAD |
+       sed -e "s/author.*/author $author $oldtick/" \
+               -e "s/^\(committer.*> \).*$/\1$GIT_COMMITTER_DATE/" > \
+               expected &&
+       git commit --amend --author="$author" &&
+       git cat-file -p HEAD > current &&
+       diff expected current
+ '
+ test_expect_success 'git commit <file> with dirty index' '
+       echo tacocat > elif &&
+       echo tehlulz > chz &&
+       git add chz &&
+       git commit elif -m "tacocat is a palindrome" &&
+       git show --stat | grep elif &&
+       git diff --cached | grep chz
+ '
 +test_expect_success 'same tree (single parent)' '
 +
++      git reset --hard
++
 +      if git commit -m empty
 +      then
 +              echo oops -- should have complained
 +              false
 +      else
 +              : happy
 +      fi
 +
 +'
 +
 +test_expect_success 'same tree (single parent) --allow-empty' '
 +
 +      git commit --allow-empty -m "forced empty" &&
 +      git cat-file commit HEAD | grep forced
 +
 +'
 +
 +test_expect_success 'same tree (merge and amend merge)' '
 +
 +      git checkout -b side HEAD^ &&
 +      echo zero >zero &&
 +      git add zero &&
 +      git commit -m "add zero" &&
 +      git checkout master &&
 +
 +      git merge -s ours side -m "empty ok" &&
 +      git diff HEAD^ HEAD >actual &&
 +      : >expected &&
 +      diff -u expected actual &&
 +
 +      git commit --amend -m "empty really ok" &&
 +      git diff HEAD^ HEAD >actual &&
 +      : >expected &&
 +      diff -u expected actual
 +
 +'
 +
  test_done
diff --combined wt-status.c
index bf2fe8d237b51e87d40dbe60eb70f485273083ff,52ab41ceb6a5f839a3b0c9ffd69983ea5fdd60f7..d35386dae19287a28ef7086d886bdfb3941d18b1
@@@ -81,33 -81,47 +81,47 @@@ static void wt_status_print_trailer(str
        color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#");
  }
  
- static const char *quote_crlf(const char *in, char *buf, size_t sz)
+ static char *quote_path(const char *in, int len,
+                       struct strbuf *out, const char *prefix)
  {
-       const char *scan;
-       char *out;
-       const char *ret = in;
+       if (len < 0)
+               len = strlen(in);
  
-       for (scan = in, out = buf; *scan; scan++) {
-               int ch = *scan;
-               int quoted;
+       strbuf_grow(out, len);
+       strbuf_setlen(out, 0);
+       if (prefix) {
+               int off = 0;
+               while (prefix[off] && off < len && prefix[off] == in[off])
+                       if (prefix[off] == '/') {
+                               prefix += off + 1;
+                               in += off + 1;
+                               len -= off + 1;
+                               off = 0;
+                       } else
+                               off++;
+               for (; *prefix; prefix++)
+                       if (*prefix == '/')
+                               strbuf_addstr(out, "../");
+       }
+       for ( ; len > 0; in++, len--) {
+               int ch = *in;
  
                switch (ch) {
                case '\n':
-                       quoted = 'n';
+                       strbuf_addstr(out, "\\n");
                        break;
                case '\r':
-                       quoted = 'r';
+                       strbuf_addstr(out, "\\r");
                        break;
                default:
-                       *out++ = ch;
+                       strbuf_addch(out, ch);
                        continue;
                }
-               *out++ = '\\';
-               *out++ = quoted;
-               ret = buf;
        }
-       *out = '\0';
-       return ret;
+       return out->buf;
  }
  
  static void wt_status_print_filepair(struct wt_status *s,
  {
        const char *c = color(t);
        const char *one, *two;
-       char onebuf[PATH_MAX], twobuf[PATH_MAX];
+       struct strbuf onebuf, twobuf;
  
-       one = quote_crlf(p->one->path, onebuf, sizeof(onebuf));
-       two = quote_crlf(p->two->path, twobuf, sizeof(twobuf));
+       strbuf_init(&onebuf, 0);
+       strbuf_init(&twobuf, 0);
+       one = quote_path(p->one->path, -1, &onebuf, s->prefix);
+       two = quote_path(p->two->path, -1, &twobuf, s->prefix);
  
        color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
        switch (p->status) {
                die("bug: unhandled diff status %c", p->status);
        }
        fprintf(s->fp, "\n");
+       strbuf_release(&onebuf);
+       strbuf_release(&twobuf);
  }
  
  static void wt_status_print_updated_cb(struct diff_queue_struct *q,
@@@ -204,8 -222,9 +222,9 @@@ static void wt_read_cache(struct wt_sta
  static void wt_status_print_initial(struct wt_status *s)
  {
        int i;
-       char buf[PATH_MAX];
+       struct strbuf buf;
  
+       strbuf_init(&buf, 0);
        wt_read_cache(s);
        if (active_nr) {
                s->commitable = 1;
        for (i = 0; i < active_nr; i++) {
                color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
                color_fprintf_ln(s->fp, color(WT_STATUS_UPDATED), "new file: %s",
-                               quote_crlf(active_cache[i]->name,
-                                          buf, sizeof(buf)));
+                               quote_path(active_cache[i]->name, -1,
+                                          &buf, s->prefix));
        }
        if (active_nr)
                wt_status_print_trailer(s);
+       strbuf_release(&buf);
  }
  
  static void wt_status_print_updated(struct wt_status *s)
        rev.diffopt.format_callback_data = s;
        rev.diffopt.detect_rename = 1;
        rev.diffopt.rename_limit = 100;
 +      rev.diffopt.break_opt = 0;
        wt_read_cache(s);
        run_diff_index(&rev, 1);
  }
@@@ -253,7 -272,9 +273,9 @@@ static void wt_status_print_untracked(s
        struct dir_struct dir;
        int i;
        int shown_header = 0;
+       struct strbuf buf;
  
+       strbuf_init(&buf, 0);
        memset(&dir, 0, sizeof(dir));
  
        if (!s->untracked) {
                        shown_header = 1;
                }
                color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t");
-               color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED), "%.*s",
-                               ent->len, ent->name);
+               color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED), "%s",
+                               quote_path(ent->name, ent->len,
+                                       &buf, s->prefix));
        }
+       strbuf_release(&buf);
  }
  
  static void wt_status_print_verbose(struct wt_status *s)
  {
        struct rev_info rev;
+       int saved_stdout;
+       fflush(s->fp);
+       /* Sigh, the entire diff machinery is hardcoded to output to
+        * stdout.  Do the dup-dance...*/
+       saved_stdout = dup(STDOUT_FILENO);
+       if (saved_stdout < 0 ||dup2(fileno(s->fp), STDOUT_FILENO) < 0)
+               die("couldn't redirect stdout\n");
        init_revisions(&rev, NULL);
        setup_revisions(0, NULL, &rev, s->reference);
        rev.diffopt.output_format |= DIFF_FORMAT_PATCH;
        rev.diffopt.detect_rename = 1;
        wt_read_cache(s);
        run_diff_index(&rev, 1);
+       fflush(stdout);
+       if (dup2(saved_stdout, STDOUT_FILENO) < 0)
+               die("couldn't restore stdout\n");
+       close(saved_stdout);
  }
  
  void wt_status_print(struct wt_status *s)