Merge branch 'jc/docmake-perl'
authorJunio C Hamano <gitster@pobox.com>
Fri, 7 Dec 2007 07:43:42 +0000 (23:43 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 7 Dec 2007 07:43:42 +0000 (23:43 -0800)
* jc/docmake-perl:
Run the specified perl in Documentation/

58 files changed:
.gitignore
Documentation/SubmittingPatches
Documentation/config.txt
Documentation/git-add.txt
Documentation/git-help.txt
Documentation/git-rerere.txt
Documentation/git-send-pack.txt
Documentation/git-status.txt
Documentation/git.txt
Makefile
builtin-add.c
builtin-clean.c
builtin-commit.c [new file with mode: 0644]
builtin-config.c
builtin-fetch.c
builtin-for-each-ref.c
builtin-grep.c
builtin-ls-files.c
builtin-push.c
builtin-runstatus.c
builtin-tag.c
builtin.h
cache.h
commit.h
config.mak.in
configure.ac
contrib/examples/git-commit.sh [new file with mode: 0755]
contrib/hg-to-git/hg-to-git.py
contrib/remotes2config.sh [changed mode: 0644->0755]
dir.c
git-add--interactive.perl
git-am.sh
git-commit.sh [deleted file]
git-submodule.sh
git.c
gitweb/gitweb.perl
ident.c
refs.c
remote.c
setup.c
sha1_name.c
strbuf.h
t/t0001-init.sh
t/t1302-repo-version.sh [new file with mode: 0755]
t/t3001-ls-files-others-exclude.sh
t/t4001-diff-rename.sh
t/t5510-fetch.sh
t/t5516-fetch-push.sh
t/t6300-for-each-ref.sh
t/t7300-clean.sh
t/t7500-commit.sh
t/t7501-commit.sh
t/t7502-commit.sh [new file with mode: 0755]
t/t7502-status.sh [new file with mode: 0755]
t/t9600-cvsimport.sh
transport.c
wt-status.c
wt-status.h
index 916fabc6c5485525df37832db38b007e3c7203ef..bac60ce31ae3b8d74619df7dd93356c49f21d081 100644 (file)
@@ -110,7 +110,6 @@ git-rev-list
 git-rev-parse
 git-revert
 git-rm
-git-runstatus
 git-send-email
 git-send-pack
 git-sh-setup
index 83bf54c7ac153b7010dcecc34d28c05dc68c10b6..de08d094e3e3683913bac49f643bb6b49fbec04d 100644 (file)
@@ -10,7 +10,7 @@ Checklist (and a short version for the impatient):
        - the first line of the commit message should be a short
          description and should skip the full stop
        - if you want your work included in git.git, add a
-         "Signed-off-by: Your Name <your@email.com>" line to the
+         "Signed-off-by: Your Name <you@example.com>" line to the
          commit message (or just use the option "-s" when
          committing) to confirm that you agree to the Developer's
          Certificate of Origin
index 6ae11842e5d71f201f8901922db8d9bdafb2d17b..0e45ec547cdfba88ed660dea96b50be2d925e225 100644 (file)
@@ -359,8 +359,8 @@ clean.requireForce::
 
 color.branch::
        A boolean to enable/disable color in the output of
-       gitlink:git-branch[1]. May be set to `true` (or `always`),
-       `false` (or `never`) or `auto`, in which case colors are used
+       gitlink:git-branch[1]. May be set to `always`,
+       `false` (or `never`) or `auto` (or `true`), in which case colors are used
        only when the output is to a terminal. Defaults to false.
 
 color.branch.<slot>::
@@ -378,9 +378,9 @@ second is the background.  The position of the attribute, if any,
 doesn't matter.
 
 color.diff::
-       When true (or `always`), always use colors in patch.
-       When false (or `never`), never.  When set to `auto`, use
-       colors only when the output is to the terminal.
+       When set to `always`, always use colors in patch.
+       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.diff.<slot>::
        Use customized color for diff colorization.  `<slot>` specifies
@@ -397,8 +397,8 @@ color.pager::
 
 color.status::
        A boolean to enable/disable color in the output of
-       gitlink:git-status[1]. May be set to `true` (or `always`),
-       `false` (or `never`) or `auto`, in which case colors are used
+       gitlink:git-status[1]. May be set to `always`,
+       `false` (or `never`) or `auto` (or `true`), in which case colors are used
        only when the output is to a terminal. Defaults to false.
 
 color.status.<slot>::
@@ -507,7 +507,9 @@ gc.rerereunresolved::
 rerere.enabled::
        Activate recording of resolved conflicts, so that identical
        conflict hunks can be resolved automatically, should they
-       be encountered again.  See gitlink:git-rerere[1].
+       be encountered again.  gitlink:git-rerere[1] command is by
+       default enabled, but can be disabled by setting this option to
+       false.
 
 gitcvs.enabled::
        Whether the CVS server interface is enabled for this repository.
index 63829d93cc827255355aa07a7db061b1a3a9e4d9..bf94cd43bdd8a9e113af37bec5acd568dd1b3930 100644 (file)
@@ -8,7 +8,7 @@ git-add - Add file contents to the index
 SYNOPSIS
 --------
 [verse]
-'git-add' [-n] [-v] [-f] [--interactive | -i] [-u] [--refresh]
+'git-add' [-n] [-v] [-f] [--interactive | -i] [--patch | -p] [-u] [--refresh]
           [--] <filepattern>...
 
 DESCRIPTION
@@ -61,7 +61,14 @@ OPTIONS
 
 -i, \--interactive::
        Add modified contents in the working tree interactively to
-       the index.
+       the index. Optional path arguments may be supplied to limit
+       operation to a subset of the working tree. See ``Interactive
+       mode'' for details.
+
+-p, \--patch:
+       Similar to Interactive mode but the initial command loop is
+       bypassed and the 'patch' subcommand is invoked using each of
+       the specified filepatterns before exiting.
 
 -u::
        Update only files that git already knows about. This is similar
@@ -210,6 +217,8 @@ patch::
        k - do not decide on this hunk now, and view the previous
            undecided hunk
        K - do not decide on this hunk now, and view the previous hunk
+       s - split the current hunk into smaller hunks
+       ? - print help
 +
 After deciding the fate for all hunks, if there is any hunk
 that was chosen, the index is updated with the selected hunks.
index a2255bc55a8241c89de18b22cb22f05b3692cda7..7ddbf467dfdf1d5a7825836d951cd54b336265b0 100644 (file)
@@ -1,5 +1,5 @@
 git-help(1)
-======
+===========
 
 NAME
 ----
index c4d4263238e5b7c6dff705de5d8552a577fc35d6..8ce492c8f2738275aa951f2fd98f1bcca16baee6 100644 (file)
@@ -22,10 +22,6 @@ automerge results and corresponding hand-resolve results on the
 initial manual merge, and later by noticing the same automerge
 results and applying the previously recorded hand resolution.
 
-[NOTE]
-You need to set the config variable rerere.enabled to enable this
-command.
-
 
 COMMANDS
 --------
index 2fa01d4a3ca92ed3a3896e4df416cf8f3933c885..a2d9cb61be1598eb4568c7d03aafbd733e1acd9c 100644 (file)
@@ -85,7 +85,9 @@ Each pattern pair consists of the source side (before the colon)
 and the destination side (after the colon).  The ref to be
 pushed is determined by finding a match that matches the source
 side, and where it is pushed is determined by using the
-destination side.
+destination side. The rules used to match a ref are the same
+rules used by gitlink:git-rev-parse[1] to resolve a symbolic ref
+name.
 
  - It is an error if <src> does not match exactly one of the
    local refs.
index 8fd0fc62368611bee25b4bbc3becef853937ff2c..b0cb6bc8b74158f3c9012b6f2d0f10f8f9e5bae9 100644 (file)
@@ -40,6 +40,10 @@ OUTPUT
 The output from this command is designed to be used as a commit
 template comments, and all the output lines are prefixed with '#'.
 
+The paths mentioned in the output, unlike many other git commands, are
+made relative to the current directory, if you are working in a
+subdirectory (this is on purpose, to help cutting and pasting).
+
 
 CONFIGURATION
 -------------
index c4e6fc64949ce931d71e49a83d8cacfb1ffad01d..1574ecd77eccc586c62b745465bffb80baf67ce7 100644 (file)
@@ -536,7 +536,7 @@ Authors
 -------
 * git's founding father is Linus Torvalds <torvalds@osdl.org>.
 * The current git nurse is Junio C Hamano <gitster@pobox.com>.
-* The git potty was written by Andres Ericsson <ae@op5.se>.
+* The git potty was written by Andreas Ericsson <ae@op5.se>.
 * General upbringing is handled by the git-list <git@vger.kernel.org>.
 
 Documentation
index ed0c5d113c4e169aa1342b32b8cd87a798559daf..4dda3405bb6d456e8f4910e834affc6a82827f77 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -213,7 +213,7 @@ BASIC_LDFLAGS =
 
 SCRIPT_SH = \
        git-bisect.sh git-checkout.sh \
-       git-clone.sh git-commit.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 +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 = \
@@ -259,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-peek-remote$X git-status$X \
        $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
 
 # what 'all' will build and 'install' will install, in gitexecdir
@@ -327,6 +327,7 @@ BUILTIN_OBJS = \
        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 \
@@ -369,7 +370,6 @@ BUILTIN_OBJS = \
        builtin-rev-parse.o \
        builtin-revert.o \
        builtin-rm.o \
-       builtin-runstatus.o \
        builtin-shortlog.o \
        builtin-show-branch.o \
        builtin-stripspace.o \
@@ -406,7 +406,9 @@ endif
 ifeq ($(uname_S),Darwin)
        NEEDS_SSL_WITH_CRYPTO = YesPlease
        NEEDS_LIBICONV = YesPlease
-       OLD_ICONV = UnfortunatelyYes
+       ifneq ($(shell expr "$(uname_R)" : '9\.'),2)
+               OLD_ICONV = UnfortunatelyYes
+       endif
        NO_STRLCPY = YesPlease
        NO_MEMMEM = YesPlease
 endif
@@ -444,6 +446,7 @@ ifeq ($(uname_O),Cygwin)
        NEEDS_LIBICONV = YesPlease
        NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes
        NO_TRUSTABLE_FILEMODE = UnfortunatelyYes
+       OLD_ICONV = UnfortunatelyYes
        # There are conflicting reports about this.
        # On some boxes NO_MMAP is needed, and not so elsewhere.
        # Try commenting this out if you suspect MMAP is more efficient
@@ -833,9 +836,6 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.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)|' \
index cf815a0b8ef0e588cfaf25f71747ba7248d19d70..5c29cc2f3f680e4dee19b040e647dd8b014cf10d 100644 (file)
@@ -19,7 +19,7 @@ static const char * const builtin_add_usage[] = {
        "git-add [options] [--] <filepattern>...",
        NULL
 };
-
+static int patch_interactive = 0, add_interactive = 0;
 static int take_worktree_changes;
 
 static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
@@ -105,12 +105,12 @@ static void update_callback(struct diff_queue_struct *q,
        }
 }
 
-void add_files_to_cache(int verbose, const char *prefix, const char **files)
+void add_files_to_cache(int verbose, const char *prefix, const char **pathspec)
 {
        struct rev_info rev;
        init_revisions(&rev, prefix);
        setup_revisions(0, NULL, &rev, NULL);
-       rev.prune_data = get_pathspec(prefix, files);
+       rev.prune_data = pathspec;
        rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
        rev.diffopt.format_callback = update_callback;
        rev.diffopt.format_callback_data = &verbose;
@@ -135,11 +135,40 @@ static void refresh(int verbose, const char **pathspec)
         free(seen);
 }
 
-int interactive_add(void)
+static const char **validate_pathspec(int argc, const char **argv, const char *prefix)
 {
-       const char *argv[2] = { "add--interactive", NULL };
+       const char **pathspec = get_pathspec(prefix, argv);
 
-       return run_command_v_opt(argv, RUN_GIT_CMD);
+       return pathspec;
+}
+
+int interactive_add(int argc, const char **argv, const char *prefix)
+{
+       int status, ac;
+       const char **args;
+       const char **pathspec = NULL;
+
+       if (argc) {
+               pathspec = validate_pathspec(argc, argv, prefix);
+               if (!pathspec)
+                       return -1;
+       }
+
+       args = xcalloc(sizeof(const char *), (argc + 4));
+       ac = 0;
+       args[ac++] = "add--interactive";
+       if (patch_interactive)
+               args[ac++] = "--patch";
+       args[ac++] = "--";
+       if (argc) {
+               memcpy(&(args[ac]), pathspec, sizeof(const char *) * argc);
+               ac += argc;
+       }
+       args[ac] = NULL;
+
+       status = run_command_v_opt(args, RUN_GIT_CMD);
+       free(args);
+       return status;
 }
 
 static struct lock_file lock_file;
@@ -148,13 +177,13 @@ static const char ignore_error[] =
 "The following paths are ignored by one of your .gitignore files:\n";
 
 static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0;
-static int add_interactive = 0;
 
 static struct option builtin_add_options[] = {
        OPT__DRY_RUN(&show_only),
        OPT__VERBOSE(&verbose),
        OPT_GROUP(""),
        OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"),
+       OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"),
        OPT_BOOLEAN('f', NULL, &ignored_too, "allow adding otherwise ignored files"),
        OPT_BOOLEAN('u', NULL, &take_worktree_changes, "update tracked files"),
        OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"),
@@ -163,26 +192,27 @@ static struct option builtin_add_options[] = {
 
 int cmd_add(int argc, const char **argv, const char *prefix)
 {
-       int i, newfd, orig_argc = argc;
+       int i, newfd;
        const char **pathspec;
        struct dir_struct dir;
 
        argc = parse_options(argc, argv, builtin_add_options,
                          builtin_add_usage, 0);
-       if (add_interactive) {
-               if (add_interactive != 1 || orig_argc != 2)
-                       die("add --interactive does not take any parameters");
-               exit(interactive_add());
-       }
+       if (patch_interactive)
+               add_interactive = 1;
+       if (add_interactive)
+               exit(interactive_add(argc, argv, prefix));
 
        git_config(git_default_config);
 
        newfd = hold_locked_index(&lock_file, 1);
 
        if (take_worktree_changes) {
+               const char **pathspec;
                if (read_cache() < 0)
                        die("index file corrupt");
-               add_files_to_cache(verbose, prefix, argv);
+               pathspec = get_pathspec(prefix, argv);
+               add_files_to_cache(verbose, prefix, pathspec);
                goto finish;
        }
 
index 56ae4eb9bb4efad179f7d664880457535bd6048f..ae30d4e76c3e758689464f41a63dda0605d60e9f 100644 (file)
@@ -27,13 +27,14 @@ static int git_clean_config(const char *var, const char *value)
 
 int cmd_clean(int argc, const char **argv, const char *prefix)
 {
-       int j;
+       int i;
        int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0;
        int ignored_only = 0, baselen = 0, config_set = 0;
        struct strbuf directory;
        struct dir_struct dir;
        const char *path, *base;
        static const char **pathspec;
+       char *seen = NULL;
        struct option options[] = {
                OPT__QUIET(&quiet),
                OPT__DRY_RUN(&show_only),
@@ -85,12 +86,14 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
        read_directory(&dir, path, base, baselen, pathspec);
        strbuf_init(&directory, 0);
 
-       for (j = 0; j < dir.nr; ++j) {
-               struct dir_entry *ent = dir.entries[j];
-               int len, pos, specs;
+       if (pathspec)
+               seen = xmalloc(argc);
+
+       for (i = 0; i < dir.nr; i++) {
+               struct dir_entry *ent = dir.entries[i];
+               int len, pos, matches;
                struct cache_entry *ce;
                struct stat st;
-               char *seen;
 
                /*
                 * Remove the '/' at the end that directory
@@ -110,25 +113,29 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
                                continue; /* Yup, this one exists unmerged */
                }
 
-               if (!lstat(ent->name, &st) && (S_ISDIR(st.st_mode))) {
-                       int matched_path = 0;
+               /*
+                * 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;
+
+               if (pathspec) {
+                       memset(seen, 0, argc);
+                       matches = match_pathspec(pathspec, ent->name, ent->len,
+                                                baselen, seen);
+               } else {
+                       matches = 0;
+               }
+
+               if (S_ISDIR(st.st_mode)) {
                        strbuf_addstr(&directory, ent->name);
-                       if (pathspec) {
-                               for (specs =0; pathspec[specs]; ++specs)
-                                       /* nothing */;
-                               seen = xcalloc(specs, 1);
-                               /* Check if directory was explictly passed as
-                                * pathspec.  If so we want to remove it */
-                               if (match_pathspec(pathspec, ent->name, ent->len,
-                                                  baselen, seen))
-                                       matched_path = 1;
-                               free(seen);
-                       }
-                       if (show_only && (remove_directories || matched_path)) {
+                       if (show_only && (remove_directories || matches)) {
                                printf("Would remove %s\n", directory.buf);
-                       } else if (quiet && (remove_directories || matched_path)) {
+                       } else if (quiet && (remove_directories || matches)) {
                                remove_dir_recursively(&directory, 0);
-                       } else if (remove_directories || matched_path) {
+                       } else if (remove_directories || matches) {
                                printf("Removing %s\n", directory.buf);
                                remove_dir_recursively(&directory, 0);
                        } else if (show_only) {
@@ -138,6 +145,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
                        }
                        strbuf_reset(&directory);
                } else {
+                       if (pathspec && !matches)
+                               continue;
                        if (show_only) {
                                printf("Would remove %s\n", ent->name);
                                continue;
@@ -147,6 +156,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
                        unlink(ent->name);
                }
        }
+       free(seen);
 
        strbuf_release(&directory);
        return 0;
diff --git a/builtin-commit.c b/builtin-commit.c
new file mode 100644 (file)
index 0000000..2ec8223
--- /dev/null
@@ -0,0 +1,851 @@
+/*
+ * Builtin "git commit"
+ *
+ * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>
+ * Based on git-commit.sh by Junio C Hamano and Linus Torvalds
+ */
+
+#include "cache.h"
+#include "cache-tree.h"
+#include "dir.h"
+#include "builtin.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "commit.h"
+#include "revision.h"
+#include "wt-status.h"
+#include "run-command.h"
+#include "refs.h"
+#include "log-tree.h"
+#include "strbuf.h"
+#include "utf8.h"
+#include "parse-options.h"
+#include "path-list.h"
+
+static const char * const builtin_commit_usage[] = {
+       "git-commit [options] [--] <filepattern>...",
+       NULL
+};
+
+static const char * const builtin_status_usage[] = {
+       "git-status [options] [--] <filepattern>...",
+       NULL
+};
+
+static unsigned char head_sha1[20], merge_head_sha1[20];
+static char *use_message_buffer;
+static const char commit_editmsg[] = "COMMIT_EDITMSG";
+static struct lock_file index_lock; /* real index */
+static struct lock_file false_lock; /* used only for partial commits */
+static enum {
+       COMMIT_AS_IS = 1,
+       COMMIT_NORMAL,
+       COMMIT_PARTIAL,
+} commit_style;
+
+static char *logfile, *force_author, *template_file;
+static char *edit_message, *use_message;
+static int all, edit_flag, also, interactive, only, amend, signoff;
+static int quiet, verbose, untracked_files, no_verify, allow_empty;
+
+static int no_edit, initial_commit, in_merge;
+const char *only_include_assumed;
+struct strbuf message;
+
+static int opt_parse_m(const struct option *opt, const char *arg, int unset)
+{
+       struct strbuf *buf = opt->value;
+       if (unset)
+               strbuf_setlen(buf, 0);
+       else {
+               strbuf_addstr(buf, arg);
+               strbuf_addch(buf, '\n');
+               strbuf_addch(buf, '\n');
+       }
+       return 0;
+}
+
+static struct option builtin_commit_options[] = {
+       OPT__QUIET(&quiet),
+       OPT__VERBOSE(&verbose),
+       OPT_GROUP("Commit message options"),
+
+       OPT_STRING('F', "file", &logfile, "FILE", "read log from file"),
+       OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"),
+       OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m),
+       OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit "),
+       OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"),
+       OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by: header"),
+       OPT_STRING('t', "template", &template_file, "FILE", "use specified template file"),
+       OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
+
+       OPT_GROUP("Commit contents options"),
+       OPT_BOOLEAN('a', "all", &all, "commit all changed files"),
+       OPT_BOOLEAN('i', "include", &also, "add specified files to index for commit"),
+       OPT_BOOLEAN(0, "interactive", &interactive, "interactively add files"),
+       OPT_BOOLEAN('o', "only", &only, ""),
+       OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"),
+       OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
+       OPT_BOOLEAN(0, "untracked-files", &untracked_files, "show all untracked files"),
+       OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
+
+       OPT_END()
+};
+
+static void rollback_index_files(void)
+{
+       switch (commit_style) {
+       case COMMIT_AS_IS:
+               break; /* nothing to do */
+       case COMMIT_NORMAL:
+               rollback_lock_file(&index_lock);
+               break;
+       case COMMIT_PARTIAL:
+               rollback_lock_file(&index_lock);
+               rollback_lock_file(&false_lock);
+               break;
+       }
+}
+
+static void commit_index_files(void)
+{
+       switch (commit_style) {
+       case COMMIT_AS_IS:
+               break; /* nothing to do */
+       case COMMIT_NORMAL:
+               commit_lock_file(&index_lock);
+               break;
+       case COMMIT_PARTIAL:
+               commit_lock_file(&index_lock);
+               rollback_lock_file(&false_lock);
+               break;
+       }
+}
+
+/*
+ * Take a union of paths in the index and the named tree (typically, "HEAD"),
+ * and return the paths that match the given pattern in list.
+ */
+static int list_paths(struct path_list *list, const char *with_tree,
+                     const char *prefix, const char **pattern)
+{
+       int i;
+       char *m;
+
+       for (i = 0; pattern[i]; i++)
+               ;
+       m = xcalloc(1, i);
+
+       if (with_tree)
+               overlay_tree_on_cache(with_tree, prefix);
+
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (ce->ce_flags & htons(CE_UPDATE))
+                       continue;
+               if (!pathspec_match(pattern, m, ce->name, 0))
+                       continue;
+               path_list_insert(ce->name, list);
+       }
+
+       return report_path_error(m, pattern, prefix ? strlen(prefix) : 0);
+}
+
+static void add_remove_files(struct path_list *list)
+{
+       int i;
+       for (i = 0; i < list->nr; i++) {
+               struct path_list_item *p = &(list->items[i]);
+               if (file_exists(p->path))
+                       add_file_to_cache(p->path, 0);
+               else
+                       remove_file_from_cache(p->path);
+       }
+}
+
+static char *prepare_index(int argc, const char **argv, const char *prefix)
+{
+       int fd;
+       struct tree *tree;
+       struct path_list partial;
+       const char **pathspec = NULL;
+
+       if (interactive) {
+               interactive_add(argc, argv, prefix);
+               commit_style = COMMIT_AS_IS;
+               return get_index_file();
+       }
+
+       if (read_cache() < 0)
+               die("index file corrupt");
+
+       if (*argv)
+               pathspec = get_pathspec(prefix, argv);
+
+       /*
+        * Non partial, non as-is commit.
+        *
+        * (1) get the real index;
+        * (2) update the_index as necessary;
+        * (3) write the_index out to the real index (still locked);
+        * (4) return the name of the locked index file.
+        *
+        * The caller should run hooks on the locked real index, and
+        * (A) if all goes well, commit the real index;
+        * (B) on failure, rollback the real index.
+        */
+       if (all || (also && pathspec && *pathspec)) {
+               int fd = hold_locked_index(&index_lock, 1);
+               add_files_to_cache(0, also ? prefix : NULL, pathspec);
+               refresh_cache(REFRESH_QUIET);
+               if (write_cache(fd, active_cache, active_nr) || close(fd))
+                       die("unable to write new_index file");
+               commit_style = COMMIT_NORMAL;
+               return index_lock.filename;
+       }
+
+       /*
+        * As-is commit.
+        *
+        * (1) return the name of the real index file.
+        *
+        * The caller should run hooks on the real index, and run
+        * hooks on the real index, and create commit from the_index.
+        * We still need to refresh the index here.
+        */
+       if (!pathspec || !*pathspec) {
+               fd = hold_locked_index(&index_lock, 1);
+               refresh_cache(REFRESH_QUIET);
+               if (write_cache(fd, active_cache, active_nr) ||
+                   close(fd) || commit_locked_index(&index_lock))
+                       die("unable to write new_index file");
+               commit_style = COMMIT_AS_IS;
+               return get_index_file();
+       }
+
+       /*
+        * A partial commit.
+        *
+        * (0) find the set of affected paths;
+        * (1) get lock on the real index file;
+        * (2) update the_index with the given paths;
+        * (3) write the_index out to the real index (still locked);
+        * (4) get lock on the false index file;
+        * (5) reset the_index from HEAD;
+        * (6) update the_index the same way as (2);
+        * (7) write the_index out to the false index file;
+        * (8) return the name of the false index file (still locked);
+        *
+        * The caller should run hooks on the locked false index, and
+        * create commit from it.  Then
+        * (A) if all goes well, commit the real index;
+        * (B) on failure, rollback the real index;
+        * In either case, rollback the false index.
+        */
+       commit_style = COMMIT_PARTIAL;
+
+       if (file_exists(git_path("MERGE_HEAD")))
+               die("cannot do a partial commit during a merge.");
+
+       memset(&partial, 0, sizeof(partial));
+       partial.strdup_paths = 1;
+       if (list_paths(&partial, initial_commit ? NULL : "HEAD", prefix, pathspec))
+               exit(1);
+
+       discard_cache();
+       if (read_cache() < 0)
+               die("cannot read the index");
+
+       fd = hold_locked_index(&index_lock, 1);
+       add_remove_files(&partial);
+       refresh_cache(REFRESH_QUIET);
+       if (write_cache(fd, active_cache, active_nr) || close(fd))
+               die("unable to write new_index file");
+
+       fd = hold_lock_file_for_update(&false_lock,
+                                      git_path("next-index-%d", getpid()), 1);
+       discard_cache();
+       if (!initial_commit) {
+               tree = parse_tree_indirect(head_sha1);
+               if (!tree)
+                       die("failed to unpack HEAD tree object");
+               if (read_tree(tree, 0, NULL))
+                       die("failed to read HEAD tree object");
+       }
+       add_remove_files(&partial);
+       refresh_cache(REFRESH_QUIET);
+
+       if (write_cache(fd, active_cache, active_nr) || close(fd))
+               die("unable to write temporary index file");
+       return false_lock.filename;
+}
+
+static int run_status(FILE *fp, const char *index_file, const char *prefix)
+{
+       struct wt_status s;
+
+       wt_status_prepare(&s);
+       s.prefix = prefix;
+
+       if (amend) {
+               s.amend = 1;
+               s.reference = "HEAD^1";
+       }
+       s.verbose = verbose;
+       s.untracked = untracked_files;
+       s.index_file = index_file;
+       s.fp = fp;
+
+       wt_status_print(&s);
+
+       return s.commitable;
+}
+
+static const char sign_off_header[] = "Signed-off-by: ";
+
+static int prepare_log_message(const char *index_file, const char *prefix)
+{
+       struct stat statbuf;
+       int commitable, saved_color_setting;
+       struct strbuf sb;
+       char *buffer;
+       FILE *fp;
+
+       strbuf_init(&sb, 0);
+       if (message.len) {
+               strbuf_addbuf(&sb, &message);
+       } else if (logfile && !strcmp(logfile, "-")) {
+               if (isatty(0))
+                       fprintf(stderr, "(reading log message from standard input)\n");
+               if (strbuf_read(&sb, 0, 0) < 0)
+                       die("could not read log from standard input");
+       } else if (logfile) {
+               if (strbuf_read_file(&sb, logfile, 0) < 0)
+                       die("could not read log file '%s': %s",
+                           logfile, strerror(errno));
+       } else if (use_message) {
+               buffer = strstr(use_message_buffer, "\n\n");
+               if (!buffer || buffer[2] == '\0')
+                       die("commit has empty message");
+               strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
+       } else if (!stat(git_path("MERGE_MSG"), &statbuf)) {
+               if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0)
+                       die("could not read MERGE_MSG: %s", strerror(errno));
+       } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) {
+               if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0)
+                       die("could not read SQUASH_MSG: %s", strerror(errno));
+       } else if (template_file && !stat(template_file, &statbuf)) {
+               if (strbuf_read_file(&sb, template_file, 0) < 0)
+                       die("could not read %s: %s",
+                           template_file, strerror(errno));
+       }
+
+       fp = fopen(git_path(commit_editmsg), "w");
+       if (fp == NULL)
+               die("could not open %s", git_path(commit_editmsg));
+
+       stripspace(&sb, 0);
+
+       if (signoff) {
+               struct strbuf sob;
+               int i;
+
+               strbuf_init(&sob, 0);
+               strbuf_addstr(&sob, sign_off_header);
+               strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"),
+                                            getenv("GIT_COMMITTER_EMAIL")));
+               strbuf_addch(&sob, '\n');
+               for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--)
+                       ; /* do nothing */
+               if (prefixcmp(sb.buf + i, sob.buf)) {
+                       if (prefixcmp(sb.buf + i, sign_off_header))
+                               strbuf_addch(&sb, '\n');
+                       strbuf_addbuf(&sb, &sob);
+               }
+               strbuf_release(&sob);
+       }
+
+       if (fwrite(sb.buf, 1, sb.len, fp) < sb.len)
+               die("could not write commit template: %s", strerror(errno));
+
+       strbuf_release(&sb);
+
+       if (no_edit) {
+               struct rev_info rev;
+               unsigned char sha1[40];
+
+               fclose(fp);
+
+               if (!active_nr && read_cache() < 0)
+                       die("Cannot read index");
+
+               if (get_sha1("HEAD", sha1) != 0)
+                       return !!active_nr;
+
+               init_revisions(&rev, "");
+               rev.abbrev = 0;
+               setup_revisions(0, NULL, &rev, "HEAD");
+               DIFF_OPT_SET(&rev.diffopt, QUIET);
+               DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
+               run_diff_index(&rev, 1 /* cached */);
+
+               return !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES);
+       }
+
+       if (in_merge && !no_edit)
+               fprintf(fp,
+                       "#\n"
+                       "# It looks like you may be committing a MERGE.\n"
+                       "# If this is not correct, please remove the file\n"
+                       "#      %s\n"
+                       "# and try again.\n"
+                       "#\n",
+                       git_path("MERGE_HEAD"));
+
+       fprintf(fp,
+               "\n"
+               "# Please enter the commit message for your changes.\n"
+               "# (Comment lines starting with '#' will not be included)\n");
+       if (only_include_assumed)
+               fprintf(fp, "# %s\n", only_include_assumed);
+
+       saved_color_setting = wt_status_use_color;
+       wt_status_use_color = 0;
+       commitable = run_status(fp, index_file, prefix);
+       wt_status_use_color = saved_color_setting;
+
+       fclose(fp);
+
+       return commitable;
+}
+
+/*
+ * Find out if the message starting at position 'start' in the strbuf
+ * contains only whitespace and Signed-off-by lines.
+ */
+static int message_is_empty(struct strbuf *sb, int start)
+{
+       struct strbuf tmpl;
+       const char *nl;
+       int eol, i;
+
+       /* See if the template is just a prefix of the message. */
+       strbuf_init(&tmpl, 0);
+       if (template_file && strbuf_read_file(&tmpl, template_file, 0) > 0) {
+               stripspace(&tmpl, 1);
+               if (start + tmpl.len <= sb->len &&
+                   memcmp(tmpl.buf, sb->buf + start, tmpl.len) == 0)
+                       start += tmpl.len;
+       }
+       strbuf_release(&tmpl);
+
+       /* Check if the rest is just whitespace and Signed-of-by's. */
+       for (i = start; i < sb->len; i++) {
+               nl = memchr(sb->buf + i, '\n', sb->len - i);
+               if (nl)
+                       eol = nl - sb->buf;
+               else
+                       eol = sb->len;
+
+               if (strlen(sign_off_header) <= eol - i &&
+                   !prefixcmp(sb->buf + i, sign_off_header)) {
+                       i = eol;
+                       continue;
+               }
+               while (i < eol)
+                       if (!isspace(sb->buf[i++]))
+                               return 0;
+       }
+
+       return 1;
+}
+
+static void determine_author_info(struct strbuf *sb)
+{
+       char *name, *email, *date;
+
+       name = getenv("GIT_AUTHOR_NAME");
+       email = getenv("GIT_AUTHOR_EMAIL");
+       date = getenv("GIT_AUTHOR_DATE");
+
+       if (use_message) {
+               const char *a, *lb, *rb, *eol;
+
+               a = strstr(use_message_buffer, "\nauthor ");
+               if (!a)
+                       die("invalid commit: %s", use_message);
+
+               lb = strstr(a + 8, " <");
+               rb = strstr(a + 8, "> ");
+               eol = strchr(a + 8, '\n');
+               if (!lb || !rb || !eol)
+                       die("invalid commit: %s", use_message);
+
+               name = xstrndup(a + 8, lb - (a + 8));
+               email = xstrndup(lb + 2, rb - (lb + 2));
+               date = xstrndup(rb + 2, eol - (rb + 2));
+       }
+
+       if (force_author) {
+               const char *lb = strstr(force_author, " <");
+               const char *rb = strchr(force_author, '>');
+
+               if (!lb || !rb)
+                       die("malformed --author parameter");
+               name = xstrndup(force_author, lb - force_author);
+               email = xstrndup(lb + 2, rb - (lb + 2));
+       }
+
+       strbuf_addf(sb, "author %s\n", fmt_ident(name, email, date, 1));
+}
+
+static int parse_and_validate_options(int argc, const char *argv[],
+                                     const char * const usage[])
+{
+       int f = 0;
+
+       argc = parse_options(argc, argv, builtin_commit_options, usage, 0);
+
+       if (logfile || message.len || use_message)
+               no_edit = 1;
+       if (edit_flag)
+               no_edit = 0;
+
+       if (get_sha1("HEAD", head_sha1))
+               initial_commit = 1;
+
+       if (!get_sha1("MERGE_HEAD", merge_head_sha1))
+               in_merge = 1;
+
+       /* Sanity check options */
+       if (amend && initial_commit)
+               die("You have nothing to amend.");
+       if (amend && in_merge)
+               die("You are in the middle of a merge -- cannot amend.");
+
+       if (use_message)
+               f++;
+       if (edit_message)
+               f++;
+       if (logfile)
+               f++;
+       if (f > 1)
+               die("Only one of -c/-C/-F can be used.");
+       if (message.len && f > 0)
+               die("Option -m cannot be combined with -c/-C/-F.");
+       if (edit_message)
+               use_message = edit_message;
+       if (amend)
+               use_message = "HEAD";
+       if (use_message) {
+               unsigned char sha1[20];
+               static char utf8[] = "UTF-8";
+               const char *out_enc;
+               char *enc, *end;
+               struct commit *commit;
+
+               if (get_sha1(use_message, sha1))
+                       die("could not lookup commit %s", use_message);
+               commit = lookup_commit(sha1);
+               if (!commit || parse_commit(commit))
+                       die("could not parse commit %s", use_message);
+
+               enc = strstr(commit->buffer, "\nencoding");
+               if (enc) {
+                       end = strchr(enc + 10, '\n');
+                       enc = xstrndup(enc + 10, end - (enc + 10));
+               } else {
+                       enc = utf8;
+               }
+               out_enc = git_commit_encoding ? git_commit_encoding : utf8;
+
+               if (strcmp(out_enc, enc))
+                       use_message_buffer =
+                               reencode_string(commit->buffer, out_enc, enc);
+
+               /*
+                * If we failed to reencode the buffer, just copy it
+                * byte for byte so the user can try to fix it up.
+                * This also handles the case where input and output
+                * encodings are identical.
+                */
+               if (use_message_buffer == NULL)
+                       use_message_buffer = xstrdup(commit->buffer);
+               if (enc != utf8)
+                       free(enc);
+       }
+
+       if (!!also + !!only + !!all + !!interactive > 1)
+               die("Only one of --include/--only/--all/--interactive can be used.");
+       if (argc == 0 && (also || (only && !amend)))
+               die("No paths with --include/--only does not make sense.");
+       if (argc == 0 && only && amend)
+               only_include_assumed = "Clever... amending the last one with dirty index.";
+       if (argc > 0 && !also && !only) {
+               only_include_assumed = "Explicit paths specified without -i nor -o; assuming --only paths...";
+               also = 0;
+       }
+
+       if (all && argc > 0)
+               die("Paths with -a does not make sense.");
+       else if (interactive && argc > 0)
+               die("Paths with --interactive does not make sense.");
+
+       return argc;
+}
+
+int cmd_status(int argc, const char **argv, const char *prefix)
+{
+       const char *index_file;
+       int commitable;
+
+       git_config(git_status_config);
+
+       argc = parse_and_validate_options(argc, argv, builtin_status_usage);
+
+       index_file = prepare_index(argc, argv, prefix);
+
+       commitable = run_status(stdout, index_file, prefix);
+
+       rollback_index_files();
+
+       return commitable ? 0 : 1;
+}
+
+static int run_hook(const char *index_file, const char *name, const char *arg)
+{
+       struct child_process hook;
+       const char *argv[3], *env[2];
+       char index[PATH_MAX];
+
+       argv[0] = git_path("hooks/%s", name);
+       argv[1] = arg;
+       argv[2] = NULL;
+       snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
+       env[0] = index;
+       env[1] = NULL;
+
+       if (access(argv[0], X_OK) < 0)
+               return 0;
+
+       memset(&hook, 0, sizeof(hook));
+       hook.argv = argv;
+       hook.no_stdin = 1;
+       hook.stdout_to_stderr = 1;
+       hook.env = env;
+
+       return run_command(&hook);
+}
+
+static void print_summary(const char *prefix, const unsigned char *sha1)
+{
+       struct rev_info rev;
+       struct commit *commit;
+
+       commit = lookup_commit(sha1);
+       if (!commit)
+               die("couldn't look up newly created commit");
+       if (!commit || parse_commit(commit))
+               die("could not parse newly created commit");
+
+       init_revisions(&rev, prefix);
+       setup_revisions(0, NULL, &rev, NULL);
+
+       rev.abbrev = 0;
+       rev.diff = 1;
+       rev.diffopt.output_format =
+               DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY;
+
+       rev.verbose_header = 1;
+       rev.show_root_diff = 1;
+       rev.commit_format = get_commit_format("format:%h: %s");
+       rev.always_show_header = 1;
+
+       printf("Created %scommit ", initial_commit ? "initial " : "");
+
+       log_tree_commit(&rev, commit);
+       printf("\n");
+}
+
+int git_commit_config(const char *k, const char *v)
+{
+       if (!strcmp(k, "commit.template")) {
+               template_file = xstrdup(v);
+               return 0;
+       }
+
+       return git_status_config(k, v);
+}
+
+static int is_a_merge(const unsigned char *sha1)
+{
+       struct commit *commit = lookup_commit(sha1);
+       if (!commit || parse_commit(commit))
+               die("could not parse HEAD commit");
+       return !!(commit->parents && commit->parents->next);
+}
+
+static const char commit_utf8_warn[] =
+"Warning: commit message does not conform to UTF-8.\n"
+"You may want to amend it after fixing the message, or set the config\n"
+"variable i18n.commitencoding to the encoding your project uses.\n";
+
+int cmd_commit(int argc, const char **argv, const char *prefix)
+{
+       int header_len;
+       struct strbuf sb;
+       const char *index_file, *reflog_msg;
+       char *nl, *p;
+       unsigned char commit_sha1[20];
+       struct ref_lock *ref_lock;
+
+       git_config(git_commit_config);
+
+       argc = parse_and_validate_options(argc, argv, builtin_commit_usage);
+
+       index_file = prepare_index(argc, argv, prefix);
+
+       if (!no_verify && run_hook(index_file, "pre-commit", NULL)) {
+               rollback_index_files();
+               return 1;
+       }
+
+       if (!prepare_log_message(index_file, prefix) && !in_merge &&
+           !allow_empty && !(amend && is_a_merge(head_sha1))) {
+               run_status(stdout, index_file, prefix);
+               rollback_index_files();
+               unlink(commit_editmsg);
+               return 1;
+       }
+
+       /*
+        * Re-read the index as pre-commit hook could have updated it,
+        * and write it out as a tree.
+        */
+       discard_cache();
+       read_cache_from(index_file);
+       if (!active_cache_tree)
+               active_cache_tree = cache_tree();
+       if (cache_tree_update(active_cache_tree,
+                             active_cache, active_nr, 0, 0) < 0) {
+               rollback_index_files();
+               die("Error building trees");
+       }
+
+       /*
+        * The commit object
+        */
+       strbuf_init(&sb, 0);
+       strbuf_addf(&sb, "tree %s\n",
+                   sha1_to_hex(active_cache_tree->sha1));
+
+       /* Determine parents */
+       if (initial_commit) {
+               reflog_msg = "commit (initial)";
+       } else if (amend) {
+               struct commit_list *c;
+               struct commit *commit;
+
+               reflog_msg = "commit (amend)";
+               commit = lookup_commit(head_sha1);
+               if (!commit || parse_commit(commit))
+                       die("could not parse HEAD commit");
+
+               for (c = commit->parents; c; c = c->next)
+                       strbuf_addf(&sb, "parent %s\n",
+                                     sha1_to_hex(c->item->object.sha1));
+       } else if (in_merge) {
+               struct strbuf m;
+               FILE *fp;
+
+               reflog_msg = "commit (merge)";
+               strbuf_addf(&sb, "parent %s\n", sha1_to_hex(head_sha1));
+               strbuf_init(&m, 0);
+               fp = fopen(git_path("MERGE_HEAD"), "r");
+               if (fp == NULL)
+                       die("could not open %s for reading: %s",
+                           git_path("MERGE_HEAD"), strerror(errno));
+               while (strbuf_getline(&m, fp, '\n') != EOF)
+                       strbuf_addf(&sb, "parent %s\n", m.buf);
+               fclose(fp);
+               strbuf_release(&m);
+       } else {
+               reflog_msg = "commit";
+               strbuf_addf(&sb, "parent %s\n", sha1_to_hex(head_sha1));
+       }
+
+       determine_author_info(&sb);
+       strbuf_addf(&sb, "committer %s\n", git_committer_info(1));
+       if (!is_encoding_utf8(git_commit_encoding))
+               strbuf_addf(&sb, "encoding %s\n", git_commit_encoding);
+       strbuf_addch(&sb, '\n');
+
+       /* Get the commit message and validate it */
+       header_len = sb.len;
+       if (!no_edit) {
+               char index[PATH_MAX];
+               const char *env[2] = { index, NULL };
+               snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
+               launch_editor(git_path(commit_editmsg), &sb, env);
+       } else if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) {
+               rollback_index_files();
+               die("could not read commit message");
+       }
+       if (run_hook(index_file, "commit-msg", git_path(commit_editmsg))) {
+               rollback_index_files();
+               exit(1);
+       }
+
+       /* Truncate the message just before the diff, if any. */
+       p = strstr(sb.buf, "\ndiff --git a/");
+       if (p != NULL)
+               strbuf_setlen(&sb, p - sb.buf + 1);
+
+       stripspace(&sb, 1);
+       if (sb.len < header_len || message_is_empty(&sb, header_len)) {
+               rollback_index_files();
+               die("no commit message?  aborting commit.");
+       }
+       strbuf_addch(&sb, '\0');
+       if (is_encoding_utf8(git_commit_encoding) && !is_utf8(sb.buf))
+               fprintf(stderr, commit_utf8_warn);
+
+       if (write_sha1_file(sb.buf, sb.len - 1, commit_type, commit_sha1)) {
+               rollback_index_files();
+               die("failed to write commit object");
+       }
+
+       ref_lock = lock_any_ref_for_update("HEAD",
+                                          initial_commit ? NULL : head_sha1,
+                                          0);
+
+       nl = strchr(sb.buf + header_len, '\n');
+       if (nl)
+               strbuf_setlen(&sb, nl + 1 - sb.buf);
+       else
+               strbuf_addch(&sb, '\n');
+       strbuf_remove(&sb, 0, header_len);
+       strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));
+       strbuf_insert(&sb, strlen(reflog_msg), ": ", 2);
+
+       if (!ref_lock) {
+               rollback_index_files();
+               die("cannot lock HEAD ref");
+       }
+       if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) {
+               rollback_index_files();
+               die("cannot update HEAD ref");
+       }
+
+       unlink(git_path("MERGE_HEAD"));
+       unlink(git_path("MERGE_MSG"));
+
+       commit_index_files();
+
+       rerere();
+       run_hook(get_index_file(), "post-commit", NULL);
+       if (!quiet)
+               print_summary(prefix, commit_sha1);
+
+       return 0;
+}
index 4c9ded3b1a146c20d24559bbbee267e9be743fd2..6175dc37382833ed1578f352dbeda004f540be0c 100644 (file)
@@ -38,8 +38,7 @@ static int show_config(const char* key_, const char* value_)
        if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))
                return 0;
        if (regexp != NULL &&
-                        (do_not_match ^
-                         regexec(regexp, (value_?value_:""), 0, NULL, 0)))
+           (do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0)))
                return 0;
 
        if (show_keys) {
index de9947e7ac2cc1236909fab813198b3df93bd39e..f6d16fe9662c01f44795837606d48ddf7c9d414e 100644 (file)
@@ -14,7 +14,7 @@ static const char fetch_usage[] = "git-fetch [-a | --append] [--upload-pack <upl
 
 static int append, force, tags, no_tags, update_head_ok, verbose, quiet;
 static const char *depth;
-static char *default_rla = NULL;
+static struct strbuf default_rla = STRBUF_INIT;
 static struct transport *transport;
 
 static void unlock_pack(void)
@@ -142,7 +142,7 @@ static int s_update_ref(const char *action,
        static struct ref_lock *lock;
 
        if (!rla)
-               rla = default_rla;
+               rla = default_rla.buf;
        snprintf(msg, sizeof(msg), "%s: %s", rla, action);
        lock = lock_any_ref_for_update(ref->name,
                                       check_old ? ref->old_sha1 : NULL, 0);
@@ -543,16 +543,19 @@ static void set_option(const char *name, const char *value)
 int cmd_fetch(int argc, const char **argv, const char *prefix)
 {
        struct remote *remote;
-       int i, j, rla_offset;
+       int i;
        static const char **refs = NULL;
        int ref_nr = 0;
-       int cmd_len = 0;
        const char *upload_pack = NULL;
        int keep = 0;
 
+       /* Record the command line for the reflog */
+       strbuf_addstr(&default_rla, "fetch");
+       for (i = 1; i < argc; i++)
+               strbuf_addf(&default_rla, " %s", argv[i]);
+
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
-               cmd_len += strlen(arg);
 
                if (arg[0] != '-')
                        break;
@@ -613,17 +616,6 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
                usage(fetch_usage);
        }
 
-       for (j = i; j < argc; j++)
-               cmd_len += strlen(argv[j]);
-
-       default_rla = xmalloc(cmd_len + 5 + argc + 1);
-       sprintf(default_rla, "fetch");
-       rla_offset = strlen(default_rla);
-       for (j = 1; j < argc; j++) {
-               sprintf(default_rla + rla_offset, " %s", argv[j]);
-               rla_offset += strlen(argv[j]) + 1;
-       }
-
        if (i == argc)
                remote = remote_get(NULL);
        else
index daf3a081650b6b39fe18f0fab61fa8b3f6c8be0f..f36a43c26459bd386618e551e2e93743cd8030aa 100644 (file)
@@ -13,8 +13,8 @@
 #define QUOTE_NONE 0
 #define QUOTE_SHELL 1
 #define QUOTE_PERL 2
-#define QUOTE_PYTHON 3
-#define QUOTE_TCL 4
+#define QUOTE_PYTHON 4
+#define QUOTE_TCL 8
 
 typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
 
@@ -861,7 +861,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
                usage_with_options(for_each_ref_usage, opts);
        }
        if (HAS_MULTI_BITS(quote_style)) {
-               error("more than one quoting style ?");
+               error("more than one quoting style?");
                usage_with_options(for_each_ref_usage, opts);
        }
        if (verify_format(format))
index bbf747fc7b66f6b4f19d9dc62a9ba10965199917..f1ff8dc556ce6caa1cafe77be41396dd9b7c9ee6 100644 (file)
@@ -343,12 +343,12 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
                        memcpy(name + 2, ce->name, len + 1);
                }
                argv[argc++] = name;
-               if (argc < MAXARGS)
-                       continue;
-               status = flush_grep(opt, argc, nr, argv, &kept);
-               if (0 < status)
-                       hit = 1;
-               argc = nr + kept;
+               if (MAXARGS <= argc) {
+                       status = flush_grep(opt, argc, nr, argv, &kept);
+                       if (0 < status)
+                               hit = 1;
+                       argc = nr + kept;
+               }
                if (ce_stage(ce)) {
                        do {
                                i++;
index 7f607098305fcf14c3e2a7634b6a5dc118445306..0f0ab2da167c57402efad9dc09e58964759c23cd 100644 (file)
@@ -38,28 +38,28 @@ static const char *tag_modified = "";
 
 
 /*
- * Match a pathspec against a filename. The first "len" characters
+ * Match a pathspec against a filename. The first "skiplen" characters
  * are the common prefix
  */
-static int match(const char **spec, char *ps_matched,
-                const char *filename, int len)
+int pathspec_match(const char **spec, char *ps_matched,
+                  const char *filename, int skiplen)
 {
        const char *m;
 
        while ((m = *spec++) != NULL) {
-               int matchlen = strlen(m + len);
+               int matchlen = strlen(m + skiplen);
 
                if (!matchlen)
                        goto matched;
-               if (!strncmp(m + len, filename + len, matchlen)) {
-                       if (m[len + matchlen - 1] == '/')
+               if (!strncmp(m + skiplen, filename + skiplen, matchlen)) {
+                       if (m[skiplen + matchlen - 1] == '/')
                                goto matched;
-                       switch (filename[len + matchlen]) {
+                       switch (filename[skiplen + matchlen]) {
                        case '/': case '\0':
                                goto matched;
                        }
                }
-               if (!fnmatch(m + len, filename + len, 0))
+               if (!fnmatch(m + skiplen, filename + skiplen, 0))
                        goto matched;
                if (ps_matched)
                        ps_matched++;
@@ -80,7 +80,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent)
        if (len >= ent->len)
                die("git-ls-files: internal error - directory entry not superset of prefix");
 
-       if (pathspec && !match(pathspec, ps_matched, ent->name, len))
+       if (pathspec && !pathspec_match(pathspec, ps_matched, ent->name, len))
                return;
 
        fputs(tag, stdout);
@@ -185,7 +185,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce)
        if (len >= ce_namelen(ce))
                die("git-ls-files: internal error - cache entry not superset of prefix");
 
-       if (pathspec && !match(pathspec, ps_matched, ce->name, len))
+       if (pathspec && !pathspec_match(pathspec, ps_matched, ce->name, len))
                return;
 
        if (tag && *tag && show_valid_bit &&
@@ -331,7 +331,7 @@ static const char *verify_pathspec(const char *prefix)
  * that were given from the command line.  We are not
  * going to write this index out.
  */
-static void overlay_tree(const char *tree_name, const char *prefix)
+void overlay_tree_on_cache(const char *tree_name, const char *prefix)
 {
        struct tree *tree;
        unsigned char sha1[20];
@@ -384,6 +384,42 @@ static void overlay_tree(const char *tree_name, const char *prefix)
        }
 }
 
+int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset)
+{
+       /*
+        * Make sure all pathspec matched; otherwise it is an error.
+        */
+       int num, errors = 0;
+       for (num = 0; pathspec[num]; num++) {
+               int other, found_dup;
+
+               if (ps_matched[num])
+                       continue;
+               /*
+                * The caller might have fed identical pathspec
+                * twice.  Do not barf on such a mistake.
+                */
+               for (found_dup = other = 0;
+                    !found_dup && pathspec[other];
+                    other++) {
+                       if (other == num || !ps_matched[other])
+                               continue;
+                       if (!strcmp(pathspec[other], pathspec[num]))
+                               /*
+                                * Ok, we have a match already.
+                                */
+                               found_dup = 1;
+               }
+               if (found_dup)
+                       continue;
+
+               error("pathspec '%s' did not match any file(s) known to git.",
+                     pathspec[num] + prefix_offset);
+               errors++;
+       }
+       return errors;
+}
+
 static const char ls_files_usage[] =
        "git-ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
        "[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
@@ -568,47 +604,17 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
                 */
                if (show_stage || show_unmerged)
                        die("ls-files --with-tree is incompatible with -s or -u");
-               overlay_tree(with_tree, prefix);
+               overlay_tree_on_cache(with_tree, prefix);
        }
        show_files(&dir, prefix);
 
        if (ps_matched) {
-               /* We need to make sure all pathspec matched otherwise
-                * it is an error.
-                */
-               int num, errors = 0;
-               for (num = 0; pathspec[num]; num++) {
-                       int other, found_dup;
-
-                       if (ps_matched[num])
-                               continue;
-                       /*
-                        * The caller might have fed identical pathspec
-                        * twice.  Do not barf on such a mistake.
-                        */
-                       for (found_dup = other = 0;
-                            !found_dup && pathspec[other];
-                            other++) {
-                               if (other == num || !ps_matched[other])
-                                       continue;
-                               if (!strcmp(pathspec[other], pathspec[num]))
-                                       /*
-                                        * Ok, we have a match already.
-                                        */
-                                       found_dup = 1;
-                       }
-                       if (found_dup)
-                               continue;
-
-                       error("pathspec '%s' did not match any file(s) known to git.",
-                             pathspec[num] + prefix_offset);
-                       errors++;
-               }
-
-               if (errors)
+               int bad;
+               bad = report_path_error(ps_matched, pathspec, prefix_offset);
+               if (bad)
                        fprintf(stderr, "Did you forget to 'git add'?\n");
 
-               return errors ? 1 : 0;
+               return bad ? 1 : 0;
        }
 
        return 0;
index 41df717f847ecc41bf0a695fa09fbb926b42f720..c8cb63e23840915ecd7445d1fe4a18f9c6cb694d 100644 (file)
@@ -44,6 +44,15 @@ static void set_refspecs(const char **refs, int nr)
                        strcat(tag, refs[i]);
                        ref = tag;
                }
+               if (!strcmp("HEAD", ref)) {
+                       unsigned char sha1_dummy[20];
+                       ref = resolve_ref(ref, sha1_dummy, 1, NULL);
+                       if (!ref)
+                               die("HEAD cannot be resolved.");
+                       if (prefixcmp(ref, "refs/heads/"))
+                               die("HEAD cannot be resolved to branch.");
+                       ref = xstrdup(ref + 11);
+               }
                add_refspec(ref);
        }
 }
index 2db25c88bf648e4d5f2f41502fd29c5f7324e56b..8d167a9674e70a36662528c98165bd3be8a9f431 100644 (file)
@@ -14,6 +14,7 @@ int cmd_runstatus(int argc, const char **argv, const char *prefix)
 
        git_config(git_status_config);
        wt_status_prepare(&s);
+       s.prefix = prefix;
 
        for (i = 1; i < argc; i++) {
                if (!strcmp(argv[i], "--color"))
index 114c684d246e975852bb5446ea2e7ab682ea036c..729389bbd6120e4b395fbc6f916f8d13965191d6 100644 (file)
@@ -23,11 +23,9 @@ static const char * const git_tag_usage[] = {
 
 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)
@@ -48,14 +46,12 @@ static void launch_editor(const char *path, struct strbuf *buffer)
        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",
@@ -322,7 +318,7 @@ static void create_tag(const unsigned char *object, const char *tag,
                        write_or_die(fd, tag_template, strlen(tag_template));
                close(fd);
 
-               launch_editor(path, buf);
+               launch_editor(path, buf, NULL);
 
                unlink(path);
                free(path);
index 3c1bf38d83c9a1ebf4a1036114a158b58a6c1b75..cb675c4d7a80b99a06d6d04156e767ea025eb512 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -25,6 +25,7 @@ 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);
@@ -72,11 +73,11 @@ extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
 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 --git a/cache.h b/cache.h
index ed8be062990410875b6a1a4cf8663bd5b8a69879..406befb705a0a99119476bbed3be12dd6059d932 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -423,6 +423,10 @@ extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *
 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);
 
@@ -452,6 +456,7 @@ enum date_mode parse_date_format(const char *format);
 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;
@@ -627,10 +632,18 @@ extern void trace_argv_printf(const char **argv, const char *format, ...);
 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 aa679867a9376496febd5105121b1f49f3ff96a4..10e2b5d4cfdc7ac129ead711421ccc51d2667f02 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -113,8 +113,7 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads,
 
 int in_merge_bases(struct commit *, struct commit **, int);
 
-extern int interactive_add(void);
-extern void add_files_to_cache(int verbose, const char *prefix, const char **files);
+extern int interactive_add(int argc, const char **argv, const char *prefix);
 extern int rerere(void);
 
 static inline int single_parent(struct commit *commit)
index 11d256e9cf5b51154a3bf0084f9e3bcea5702352..7d5df9bf3c5dc898a68b538bae55f3d44dbc01ff 100644 (file)
@@ -41,4 +41,5 @@ NO_STRTOUMAX=@NO_STRTOUMAX@
 NO_SETENV=@NO_SETENV@
 NO_MKDTEMP=@NO_MKDTEMP@
 NO_ICONV=@NO_ICONV@
+OLD_ICONV=@OLD_ICONV@
 NO_DEFLATE_BOUND=@NO_DEFLATE_BOUND@
index 5f8a15b9f9580bea6f350e8af468f81cf2535c9e..dd4b4eb9822f131f0efc746013a803c4adba02d2 100644 (file)
@@ -212,6 +212,28 @@ test -n "$NEEDS_SOCKET" && LIBS="$LIBS -lsocket"
 
 
 ## Checks for header files.
+AC_MSG_NOTICE([CHECKS for header files])
+#
+# Define OLD_ICONV if your library has an old iconv(), where the second
+# (input buffer pointer) parameter is declared with type (const char **).
+AC_DEFUN([OLDICONVTEST_SRC], [[
+#include <iconv.h>
+
+extern size_t iconv(iconv_t cd,
+                   char **inbuf, size_t *inbytesleft,
+                   char **outbuf, size_t *outbytesleft);
+
+int main(void)
+{
+       return 0;
+}
+]])
+AC_MSG_CHECKING([for old iconv()])
+AC_COMPILE_IFELSE(OLDICONVTEST_SRC,
+       [AC_MSG_RESULT([no])],
+       [AC_MSG_RESULT([yes])
+       OLD_ICONV=UnfortunatelyYes])
+AC_SUBST(OLD_ICONV)
 
 
 ## Checks for typedefs, structures, and compiler characteristics.
diff --git a/contrib/examples/git-commit.sh b/contrib/examples/git-commit.sh
new file mode 100755 (executable)
index 0000000..2c4a406
--- /dev/null
@@ -0,0 +1,639 @@
+#!/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
+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"
index 7a1c3e497f00fd886a0602551bfa933931a995be..9befb92c410794a3e81cc571b82f9f35e0def091 100755 (executable)
@@ -211,7 +211,7 @@ def getgitenv(user, date):
     os.system('git-ls-files -x .hg --deleted | git-update-index --remove --stdin')
 
     # commit
-    os.system(getgitenv(user, date) + 'git-commit -a -F %s' % filecomment)
+    os.system(getgitenv(user, date) + 'git commit --allow-empty -a -F %s' % filecomment)
     os.unlink(filecomment)
 
     # tag
old mode 100644 (file)
new mode 100755 (executable)
index 5838b3a..1cda19f
@@ -11,11 +11,11 @@ if [ -d "$GIT_DIR"/remotes ]; then
        {
                cd "$GIT_DIR"/remotes
                ls | while read f; do
-                       name=$(printf "$f" | tr -c "A-Za-z0-9" ".")
+                       name=$(printf "$f" | tr -c "A-Za-z0-9-" ".")
                        sed -n \
-                       -e "s/^URL: \(.*\)$/remote.$name.url \1 ./p" \
-                       -e "s/^Pull: \(.*\)$/remote.$name.fetch \1 ^$ /p" \
-                       -e "s/^Push: \(.*\)$/remote.$name.push \1 ^$ /p" \
+                       -e "s/^URL:[    ]*\(.*\)$/remote.$name.url \1 ./p" \
+                       -e "s/^Pull:[   ]*\(.*\)$/remote.$name.fetch \1 ^$ /p" \
+                       -e "s/^Push:[   ]*\(.*\)$/remote.$name.push \1 ^$ /p" \
                        < "$f"
                done
                echo done
diff --git a/dir.c b/dir.c
index d448902909a7da216fbd49cecc505e68c0ba5e5f..6b3273d1d18298dc6cb8c08b42948af05c775221 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -692,7 +692,7 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i
 int file_exists(const char *f)
 {
        struct stat sb;
-       return stat(f, &sb) == 0;
+       return lstat(f, &sb) == 0;
 }
 
 /*
index fb1e92a7664f77aa5ca4ca30b4711bafaf155466..335c2c6b56875b97ad6cf8f4406218833afc53ef 100755 (executable)
@@ -2,6 +2,9 @@
 
 use strict;
 
+# command line options
+my $patch_mode;
+
 sub run_cmd_pipe {
        if ($^O eq 'MSWin32') {
                my @invalid = grep {m/[":*]/} @_;
@@ -37,14 +40,13 @@ sub list_untracked {
                chomp $_;
                $_;
        }
-       run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @_);
+       run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV);
 }
 
 my $status_fmt = '%12s %12s %s';
 my $status_head = sprintf($status_fmt, 'staged', 'unstaged', 'path');
 
 # Returns list of hashes, contents of each of which are:
-# PRINT:       print message
 # VALUE:       pathname
 # BINARY:      is a binary path
 # INDEX:       is index different from HEAD?
@@ -56,9 +58,17 @@ sub list_modified {
        my ($only) = @_;
        my (%data, @return);
        my ($add, $del, $adddel, $file);
+       my @tracked = ();
+
+       if (@ARGV) {
+               @tracked = map {
+                       chomp $_; $_;
+               } run_cmd_pipe(qw(git ls-files --exclude-standard --), @ARGV);
+               return if (!@tracked);
+       }
 
        for (run_cmd_pipe(qw(git diff-index --cached
-                            --numstat --summary HEAD))) {
+                            --numstat --summary HEAD --), @tracked)) {
                if (($add, $del, $file) =
                    /^([-\d]+)  ([-\d]+)        (.*)/) {
                        my ($change, $bin);
@@ -81,7 +91,7 @@ sub list_modified {
                }
        }
 
-       for (run_cmd_pipe(qw(git diff-files --numstat --summary))) {
+       for (run_cmd_pipe(qw(git diff-files --numstat --summary --), @tracked)) {
                if (($add, $del, $file) =
                    /^([-\d]+)  ([-\d]+)        (.*)/) {
                        if (!exists $data{$file}) {
@@ -122,8 +132,6 @@ sub list_modified {
                }
                push @return, +{
                        VALUE => $_,
-                       PRINT => (sprintf $status_fmt,
-                                 $it->{INDEX}, $it->{FILE}, $_),
                        %$it,
                };
        }
@@ -159,10 +167,96 @@ sub find_unique {
        return $found;
 }
 
+# inserts string into trie and updates count for each character
+sub update_trie {
+       my ($trie, $string) = @_;
+       foreach (split //, $string) {
+               $trie = $trie->{$_} ||= {COUNT => 0};
+               $trie->{COUNT}++;
+       }
+}
+
+# returns an array of tuples (prefix, remainder)
+sub find_unique_prefixes {
+       my @stuff = @_;
+       my @return = ();
+
+       # any single prefix exceeding the soft limit is omitted
+       # if any prefix exceeds the hard limit all are omitted
+       # 0 indicates no limit
+       my $soft_limit = 0;
+       my $hard_limit = 3;
+
+       # build a trie modelling all possible options
+       my %trie;
+       foreach my $print (@stuff) {
+               if ((ref $print) eq 'ARRAY') {
+                       $print = $print->[0];
+               }
+               elsif ((ref $print) eq 'HASH') {
+                       $print = $print->{VALUE};
+               }
+               update_trie(\%trie, $print);
+               push @return, $print;
+       }
+
+       # use the trie to find the unique prefixes
+       for (my $i = 0; $i < @return; $i++) {
+               my $ret = $return[$i];
+               my @letters = split //, $ret;
+               my %search = %trie;
+               my ($prefix, $remainder);
+               my $j;
+               for ($j = 0; $j < @letters; $j++) {
+                       my $letter = $letters[$j];
+                       if ($search{$letter}{COUNT} == 1) {
+                               $prefix = substr $ret, 0, $j + 1;
+                               $remainder = substr $ret, $j + 1;
+                               last;
+                       }
+                       else {
+                               my $prefix = substr $ret, 0, $j;
+                               return ()
+                                   if ($hard_limit && $j + 1 > $hard_limit);
+                       }
+                       %search = %{$search{$letter}};
+               }
+               if ($soft_limit && $j + 1 > $soft_limit) {
+                       $prefix = undef;
+                       $remainder = $ret;
+               }
+               $return[$i] = [$prefix, $remainder];
+       }
+       return @return;
+}
+
+# filters out prefixes which have special meaning to list_and_choose()
+sub is_valid_prefix {
+       my $prefix = shift;
+       return (defined $prefix) &&
+           !($prefix =~ /[\s,]/) && # separators
+           !($prefix =~ /^-/) &&    # deselection
+           !($prefix =~ /^\d+/) &&  # selection
+           ($prefix ne '*') &&      # "all" wildcard
+           ($prefix ne '?');        # prompt help
+}
+
+# given a prefix/remainder tuple return a string with the prefix highlighted
+# for now use square brackets; later might use ANSI colors (underline, bold)
+sub highlight_prefix {
+       my $prefix = shift;
+       my $remainder = shift;
+       return $remainder unless defined $prefix;
+       return is_valid_prefix($prefix) ?
+           "[$prefix]$remainder" :
+           "$prefix$remainder";
+}
+
 sub list_and_choose {
        my ($opts, @stuff) = @_;
        my (@chosen, @return);
        my $i;
+       my @prefixes = find_unique_prefixes(@stuff) unless $opts->{LIST_ONLY};
 
       TOPLOOP:
        while (1) {
@@ -177,13 +271,21 @@ sub list_and_choose {
                for ($i = 0; $i < @stuff; $i++) {
                        my $chosen = $chosen[$i] ? '*' : ' ';
                        my $print = $stuff[$i];
-                       if (ref $print) {
-                               if ((ref $print) eq 'ARRAY') {
-                                       $print = $print->[0];
-                               }
-                               else {
-                                       $print = $print->{PRINT};
-                               }
+                       my $ref = ref $print;
+                       my $highlighted = highlight_prefix(@{$prefixes[$i]})
+                           if @prefixes;
+                       if ($ref eq 'ARRAY') {
+                               $print = $highlighted || $print->[0];
+                       }
+                       elsif ($ref eq 'HASH') {
+                               my $value = $highlighted || $print->{VALUE};
+                               $print = sprintf($status_fmt,
+                                   $print->{INDEX},
+                                   $print->{FILE},
+                                   $value);
+                       }
+                       else {
+                               $print = $highlighted || $print;
                        }
                        printf("%s%2d: %s", $chosen, $i+1, $print);
                        if (($opts->{LIST_FLAT}) &&
@@ -217,6 +319,12 @@ sub list_and_choose {
                }
                chomp $line;
                last if $line eq '';
+               if ($line eq '?') {
+                       $opts->{SINGLETON} ?
+                           singleton_prompt_help_cmd() :
+                           prompt_help_cmd();
+                       next TOPLOOP;
+               }
                for my $choice (split(/[\s,]+/, $line)) {
                        my $choose = 1;
                        my ($bottom, $top);
@@ -252,7 +360,7 @@ sub list_and_choose {
                                $chosen[$i] = $choose;
                        }
                }
-               last if ($opts->{IMMEDIATE});
+               last if ($opts->{IMMEDIATE} || $line eq '*');
        }
        for ($i = 0; $i < @stuff; $i++) {
                if ($chosen[$i]) {
@@ -262,6 +370,28 @@ sub list_and_choose {
        return @return;
 }
 
+sub singleton_prompt_help_cmd {
+       print <<\EOF ;
+Prompt help:
+1          - select a numbered item
+foo        - select item based on unique prefix
+           - (empty) select nothing
+EOF
+}
+
+sub prompt_help_cmd {
+       print <<\EOF ;
+Prompt help:
+1          - select a single item
+3-5        - select a range of items
+2-3,6-9    - select multiple ranges
+foo        - select item based on unique prefix
+-...       - unselect specified items
+*          - choose all items
+           - (empty) finish selecting
+EOF
+}
+
 sub status_cmd {
        list_and_choose({ LIST_ONLY => 1, HEADER => $status_head },
                        list_modified());
@@ -544,27 +674,36 @@ sub help_patch_cmd {
        print <<\EOF ;
 y - stage this hunk
 n - do not stage this hunk
-a - stage this and all the remaining hunks
-d - do not stage this hunk nor any of the remaining hunks
+a - stage this and all the remaining hunks in the file
+d - do not stage this hunk nor any of the remaining hunks in the file
 j - leave this hunk undecided, see next undecided hunk
 J - leave this hunk undecided, see next hunk
 k - leave this hunk undecided, see previous undecided hunk
 K - leave this hunk undecided, see previous hunk
 s - split the current hunk into smaller hunks
+? - print help
 EOF
 }
 
 sub patch_update_cmd {
-       my @mods = list_modified('file-only');
-       @mods = grep { !($_->{BINARY}) } @mods;
-       return if (!@mods);
+       my @mods = grep { !($_->{BINARY}) } list_modified('file-only');
+       my @them;
 
-       my ($it) = list_and_choose({ PROMPT => 'Patch update',
-                                    SINGLETON => 1,
-                                    IMMEDIATE => 1,
-                                    HEADER => $status_head, },
-                                  @mods);
-       patch_update_file($it->{VALUE}) if ($it);
+       if (!@mods) {
+               print STDERR "No changes.\n";
+               return 0;
+       }
+       if ($patch_mode) {
+               @them = @mods;
+       }
+       else {
+               @them = list_and_choose({ PROMPT => 'Patch update',
+                                         HEADER => $status_head, },
+                                       @mods);
+       }
+       for (@them) {
+               patch_update_file($_->{VALUE});
+       }
 }
 
 sub patch_update_file {
@@ -775,6 +914,20 @@ sub help_cmd {
 EOF
 }
 
+sub process_args {
+       return unless @ARGV;
+       my $arg = shift @ARGV;
+       if ($arg eq "--patch") {
+               $patch_mode = 1;
+               $arg = shift @ARGV or die "missing --";
+               die "invalid argument $arg, expecting --"
+                   unless $arg eq "--";
+       }
+       elsif ($arg ne "--") {
+               die "invalid argument $arg, expecting --";
+       }
+}
+
 sub main_loop {
        my @cmd = ([ 'status', \&status_cmd, ],
                   [ 'update', \&update_cmd, ],
@@ -803,6 +956,12 @@ sub main_loop {
        }
 }
 
+process_args();
 refresh();
-status_cmd();
-main_loop();
+if ($patch_mode) {
+       patch_update_cmd();
+}
+else {
+       status_cmd();
+       main_loop();
+}
index 2e407084260350e7fa56987a7603afdcd00acfb5..65c634f77af98b3598bfb50583de05f0cf5d6eb4 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -117,6 +117,10 @@ It does not apply to blobs recorded in its index."
     unset GITHEAD_$his_tree
 }
 
+reread_subject () {
+       git stripspace <"$1" | sed -e 1q
+}
+
 prec=4
 dotest=.dotest sign= utf8=t keep= skip= interactive= resolved= binary=
 resolvemsg= resume=
@@ -307,9 +311,9 @@ do
        GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$dotest/info")"
        GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$dotest/info")"
 
-       if test -z "$GIT_AUTHOR_EMAIL" || test -z "$GIT_AUTHOR_DATE"
+       if test -z "$GIT_AUTHOR_EMAIL"
        then
-               echo "Patch does not have valid authorship information."
+               echo "Patch does not have a valid e-mail address."
                stop_here $this
        fi
 
@@ -376,6 +380,7 @@ do
                [aA]*) action=yes interactive= ;;
                [nN]*) action=skip ;;
                [eE]*) git_editor "$dotest/final-commit"
+                      SUBJECT=$(reread_subject "$dotest/final-commit")
                       action=again ;;
                [vV]*) action=again
                       LESS=-S ${PAGER:-less} "$dotest/patch" ;;
diff --git a/git-commit.sh b/git-commit.sh
deleted file mode 100755 (executable)
index 2c4a406..0000000
+++ /dev/null
@@ -1,639 +0,0 @@
-#!/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
-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"
index 82ac28fa27dc41b821905f1a83155994d56c4f3b..ad9fe628fdf0f7d5a9a968abeb03d6213e3772d7 100755 (executable)
@@ -158,7 +158,7 @@ module_add()
        die "'$path' already exists in the index"
 
        module_clone "$path" "$realrepo" || exit
-       (unset GIT_DIR && cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) ||
+       (unset GIT_DIR; cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) ||
        die "Unable to checkout submodule '$path'"
        git add "$path" ||
        die "Failed to add submodule '$path'"
@@ -228,14 +228,14 @@ modules_update()
                        module_clone "$path" "$url" || exit
                        subsha1=
                else
-                       subsha1=$(unset GIT_DIR && cd "$path" &&
+                       subsha1=$(unset GIT_DIR; cd "$path" &&
                                git rev-parse --verify HEAD) ||
                        die "Unable to find current revision in submodule path '$path'"
                fi
 
                if test "$subsha1" != "$sha1"
                then
-                       (unset GIT_DIR && cd "$path" && git-fetch &&
+                       (unset GIT_DIR; cd "$path" && git-fetch &&
                                git-checkout -q "$sha1") ||
                        die "Unable to checkout '$sha1' in submodule path '$path'"
 
@@ -246,7 +246,7 @@ modules_update()
 
 set_name_rev () {
        revname=$( (
-               unset GIT_DIR &&
+               unset GIT_DIR
                cd "$1" && {
                        git describe "$2" 2>/dev/null ||
                        git describe --tags "$2" 2>/dev/null ||
@@ -285,7 +285,7 @@ modules_list()
                else
                        if test -z "$cached"
                        then
-                               sha1=$(unset GIT_DIR && cd "$path" && git rev-parse --verify HEAD)
+                               sha1=$(unset GIT_DIR; cd "$path" && git rev-parse --verify HEAD)
                                set_name_rev "$path" "$sha1"
                        fi
                        say "+$sha1 $path$revname"
diff --git a/git.c b/git.c
index c4877a9714c227a1af77cfa84caa2230eb30bf9d..15fec8974ad5361a2308b3c46424e896bc8e3246 100644 (file)
--- a/git.c
+++ b/git.c
@@ -294,6 +294,7 @@ static void handle_internal_command(int argc, const char **argv)
                { "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 },
@@ -346,11 +347,11 @@ static void handle_internal_command(int argc, const char **argv)
                { "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 },
index ff5daa7901d9976dc35e1b4a05e1cf0140297d6a..24b31582afbc78415a5ce34acf06e0d589be0f91 100755 (executable)
@@ -695,10 +695,9 @@ sub validate_refname {
 # in utf-8 thanks to "binmode STDOUT, ':utf8'" at beginning
 sub to_utf8 {
        my $str = shift;
-       my $res;
-       eval { $res = decode_utf8($str, Encode::FB_CROAK); };
-       if (defined $res) {
-               return $res;
+       if (utf8::valid($str)) {
+               utf8::decode($str);
+               return $str;
        } else {
                return decode($fallback_encoding, $str, Encode::FB_DEFAULT);
        }
diff --git a/ident.c b/ident.c
index 9b2a852cb00327a607946caa864fcf31184da28e..7631698f27a8e1685614c233037eb067eef65168 100644 (file)
--- a/ident.c
+++ b/ident.c
@@ -113,25 +113,15 @@ static int add_raw(char *buf, size_t size, int offset, const char *str)
 
 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 == '\'';
 }
 
 /*
@@ -185,19 +175,21 @@ static const char *env_hint =
 "\n"
 "Run\n"
 "\n"
-"  git config --global user.email \"you@email.com\"\n"
+"  git config --global user.email \"you@example.com\"\n"
 "  git config --global user.name \"Your Name\"\n"
 "\n"
 "to set your account\'s default identity.\n"
 "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)
@@ -224,20 +216,36 @@ const char *fmt_ident(const char *name, const char *email,
        }
 
        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 --git a/refs.c b/refs.c
index 54ec98d153889f40313dba9a5ee8f07ddd0e160a..3e6e98c5eb20fc7a365d25b3abc275644b4c26f9 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -643,6 +643,37 @@ int check_ref_format(const char *ref)
        }
 }
 
+const char *ref_rev_parse_rules[] = {
+       "%.*s",
+       "refs/%.*s",
+       "refs/tags/%.*s",
+       "refs/heads/%.*s",
+       "refs/remotes/%.*s",
+       "refs/remotes/%.*s/HEAD",
+       NULL
+};
+
+const char *ref_fetch_rules[] = {
+       "%.*s",
+       "refs/%.*s",
+       "refs/heads/%.*s",
+       NULL
+};
+
+int refname_match(const char *abbrev_name, const char *full_name, const char **rules)
+{
+       const char **p;
+       const int abbrev_name_len = strlen(abbrev_name);
+
+       for (p = rules; *p; p++) {
+               if (!strcmp(full_name, mkpath(*p, abbrev_name_len, abbrev_name))) {
+                       return 1;
+               }
+       }
+
+       return 0;
+}
+
 static struct ref_lock *verify_lock(struct ref_lock *lock,
        const unsigned char *old_sha1, int mustexist)
 {
index 46e5f04243eb6075a91ac70350555d1aa521f802..3fb0f99b29e7ffd927abf166cfa004c1937aaf60 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -419,25 +419,6 @@ int remote_has_url(struct remote *remote, const char *url)
        return 0;
 }
 
-/*
- * Returns true if, under the matching rules for fetching, name is the
- * same as the given full name.
- */
-static int ref_matches_abbrev(const char *name, const char *full)
-{
-       if (!prefixcmp(name, "refs/") || !strcmp(name, "HEAD"))
-               return !strcmp(name, full);
-       if (prefixcmp(full, "refs/"))
-               return 0;
-       if (!prefixcmp(name, "heads/") ||
-           !prefixcmp(name, "tags/") ||
-           !prefixcmp(name, "remotes/"))
-               return !strcmp(name, full + 5);
-       if (prefixcmp(full + 5, "heads/"))
-               return 0;
-       return !strcmp(full + 11, name);
-}
-
 int remote_find_tracking(struct remote *remote, struct refspec *refspec)
 {
        int find_src = refspec->src == NULL;
@@ -533,10 +514,7 @@ static int count_refspec_match(const char *pattern,
                char *name = refs->name;
                int namelen = strlen(name);
 
-               if (namelen < patlen ||
-                   memcmp(name + namelen - patlen, pattern, patlen))
-                       continue;
-               if (namelen != patlen && name[namelen - patlen - 1] != '/')
+               if (!refname_match(pattern, name, ref_rev_parse_rules))
                        continue;
 
                /* A match is "weak" if it is with refs outside
@@ -818,7 +796,7 @@ int branch_merge_matches(struct branch *branch,
 {
        if (!branch || i < 0 || i >= branch->merge_nr)
                return 0;
-       return ref_matches_abbrev(branch->merge[i]->src, refname);
+       return refname_match(branch->merge[i]->src, refname, ref_fetch_rules);
 }
 
 static struct ref *get_expanded_map(const struct ref *remote_refs,
@@ -857,7 +835,7 @@ static const struct ref *find_ref_by_name_abbrev(const struct ref *refs, const c
 {
        const struct ref *ref;
        for (ref = refs; ref; ref = ref->next) {
-               if (ref_matches_abbrev(name, ref->name))
+               if (refname_match(name, ref->name, ref_fetch_rules))
                        return ref;
        }
        return NULL;
diff --git a/setup.c b/setup.c
index 2c7b5cb200414a0fa3f2727c68ea60e51b39c261..e96a316452197ed4b224f0c6839f036a01082bc2 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -222,6 +222,22 @@ void setup_work_tree(void)
        initialized = 1;
 }
 
+static int check_repository_format_gently(int *nongit_ok)
+{
+       git_config(check_repository_format_version);
+       if (GIT_REPO_VERSION < repository_format_version) {
+               if (!nongit_ok)
+                       die ("Expected git repo version <= %d, found %d",
+                            GIT_REPO_VERSION, repository_format_version);
+               warning("Expected git repo version <= %d, found %d",
+                       GIT_REPO_VERSION, repository_format_version);
+               warning("Please upgrade Git");
+               *nongit_ok = -1;
+               return -1;
+       }
+       return 0;
+}
+
 /*
  * We cannot decide in this function whether we are in the work tree or
  * not, since the config can only be read _after_ this function was called.
@@ -246,8 +262,15 @@ const char *setup_git_directory_gently(int *nongit_ok)
                        static char buffer[1024 + 1];
                        const char *retval;
 
-                       if (!work_tree_env)
-                               return set_work_tree(gitdirenv);
+                       if (!work_tree_env) {
+                               retval = set_work_tree(gitdirenv);
+                               /* config may override worktree */
+                               if (check_repository_format_gently(nongit_ok))
+                                       return NULL;
+                               return retval;
+                       }
+                       if (check_repository_format_gently(nongit_ok))
+                               return NULL;
                        retval = get_relative_cwd(buffer, sizeof(buffer) - 1,
                                        get_git_work_tree());
                        if (!retval || !*retval)
@@ -286,6 +309,7 @@ const char *setup_git_directory_gently(int *nongit_ok)
                        if (!work_tree_env)
                                inside_work_tree = 0;
                        setenv(GIT_DIR_ENVIRONMENT, ".", 1);
+                       check_repository_format_gently(nongit_ok);
                        return NULL;
                }
                chdir("..");
@@ -306,6 +330,8 @@ const char *setup_git_directory_gently(int *nongit_ok)
        if (!work_tree_env)
                inside_work_tree = 1;
        git_work_tree_cfg = xstrndup(cwd, offset);
+       if (check_repository_format_gently(nongit_ok))
+               return NULL;
        if (offset == len)
                return NULL;
 
@@ -356,11 +382,7 @@ int check_repository_format_version(const char *var, const char *value)
 
 int check_repository_format(void)
 {
-       git_config(check_repository_format_version);
-       if (GIT_REPO_VERSION < repository_format_version)
-               die ("Expected git repo version <= %d, found %d",
-                    GIT_REPO_VERSION, repository_format_version);
-       return 0;
+       return check_repository_format_gently(NULL);
 }
 
 const char *setup_git_directory(void)
index bd8fc05c4b9727ad4d14b20127012b53a9ef90ee..13e11645e1fd6b61812cc3e88d2ad6cd42cad9ce 100644 (file)
@@ -239,23 +239,13 @@ static int ambiguous_path(const char *path, int len)
        return slash;
 }
 
-static const char *ref_fmt[] = {
-       "%.*s",
-       "refs/%.*s",
-       "refs/tags/%.*s",
-       "refs/heads/%.*s",
-       "refs/remotes/%.*s",
-       "refs/remotes/%.*s/HEAD",
-       NULL
-};
-
 int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
 {
        const char **p, *r;
        int refs_found = 0;
 
        *ref = NULL;
-       for (p = ref_fmt; *p; p++) {
+       for (p = ref_rev_parse_rules; *p; p++) {
                unsigned char sha1_from_ref[20];
                unsigned char *this_result;
 
@@ -277,7 +267,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
        int logs_found = 0;
 
        *log = NULL;
-       for (p = ref_fmt; *p; p++) {
+       for (p = ref_rev_parse_rules; *p; p++) {
                struct stat st;
                unsigned char hash[20];
                char path[PATH_MAX];
index 13919123dc5261e7b8e4a4fdfc696f2355482b6c..36d61db65728f61188ef3bedbdbe88d0f2d9a0b9 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -117,5 +117,6 @@ extern int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint);
 extern int strbuf_getline(struct strbuf *, FILE *, int);
 
 extern void stripspace(struct strbuf *buf, int skip_comments);
+extern void launch_editor(const char *path, struct strbuf *buffer, const char *const *env);
 
 #endif /* STRBUF_H */
index b14b3ec39418b37a2a7e4b3745032360c146dff7..c015405f124f738e66213987c3ba88c7d310caab 100755 (executable)
@@ -25,7 +25,7 @@ check_config () {
 
 test_expect_success 'plain' '
        (
-               unset GIT_DIR GIT_WORK_TREE &&
+               unset GIT_DIR GIT_WORK_TREE
                mkdir plain &&
                cd plain &&
                git init
@@ -35,7 +35,7 @@ test_expect_success 'plain' '
 
 test_expect_success 'plain with GIT_WORK_TREE' '
        if (
-               unset GIT_DIR &&
+               unset GIT_DIR
                mkdir plain-wt &&
                cd plain-wt &&
                GIT_WORK_TREE=$(pwd) git init
@@ -48,7 +48,7 @@ test_expect_success 'plain with GIT_WORK_TREE' '
 
 test_expect_success 'plain bare' '
        (
-               unset GIT_DIR GIT_WORK_TREE GIT_CONFIG &&
+               unset GIT_DIR GIT_WORK_TREE GIT_CONFIG
                mkdir plain-bare-1 &&
                cd plain-bare-1 &&
                git --bare init
@@ -58,7 +58,7 @@ test_expect_success 'plain bare' '
 
 test_expect_success 'plain bare with GIT_WORK_TREE' '
        if (
-               unset GIT_DIR GIT_CONFIG &&
+               unset GIT_DIR GIT_CONFIG
                mkdir plain-bare-2 &&
                cd plain-bare-2 &&
                GIT_WORK_TREE=$(pwd) git --bare init
@@ -72,7 +72,7 @@ test_expect_success 'plain bare with GIT_WORK_TREE' '
 test_expect_success 'GIT_DIR bare' '
 
        (
-               unset GIT_CONFIG &&
+               unset GIT_CONFIG
                mkdir git-dir-bare.git &&
                GIT_DIR=git-dir-bare.git git init
        ) &&
@@ -82,7 +82,7 @@ test_expect_success 'GIT_DIR bare' '
 test_expect_success 'GIT_DIR non-bare' '
 
        (
-               unset GIT_CONFIG &&
+               unset GIT_CONFIG
                mkdir non-bare &&
                cd non-bare &&
                GIT_DIR=.git git init
@@ -93,7 +93,7 @@ test_expect_success 'GIT_DIR non-bare' '
 test_expect_success 'GIT_DIR & GIT_WORK_TREE (1)' '
 
        (
-               unset GIT_CONFIG &&
+               unset GIT_CONFIG
                mkdir git-dir-wt-1.git &&
                GIT_WORK_TREE=$(pwd) GIT_DIR=git-dir-wt-1.git git init
        ) &&
@@ -103,7 +103,7 @@ test_expect_success 'GIT_DIR & GIT_WORK_TREE (1)' '
 test_expect_success 'GIT_DIR & GIT_WORK_TREE (2)' '
 
        if (
-               unset GIT_CONFIG &&
+               unset GIT_CONFIG
                mkdir git-dir-wt-2.git &&
                GIT_WORK_TREE=$(pwd) GIT_DIR=git-dir-wt-2.git git --bare init
        )
diff --git a/t/t1302-repo-version.sh b/t/t1302-repo-version.sh
new file mode 100755 (executable)
index 0000000..37fc1c8
--- /dev/null
@@ -0,0 +1,46 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Nguyễn Thái Ngọc Duy
+#
+
+test_description='Test repository version check'
+
+. ./test-lib.sh
+
+cat >test.patch <<EOF
+diff --git a/test.txt b/test.txt
+new file mode 100644
+--- /dev/null
++++ b/test.txt
+@@ -0,0 +1 @@
++123
+EOF
+
+test_create_repo "test"
+test_create_repo "test2"
+
+GIT_CONFIG=test2/.git/config git config core.repositoryformatversion 99 || exit 1
+
+test_expect_success 'gitdir selection on normal repos' '
+       (test "$(git config core.repositoryformatversion)" = 0 &&
+       cd test &&
+       test "$(git config core.repositoryformatversion)" = 0)'
+
+# Make sure it would stop at test2, not trash
+test_expect_success 'gitdir selection on unsupported repo' '
+       (cd test2 &&
+       test "$(git config core.repositoryformatversion)" = 99)'
+
+test_expect_success 'gitdir not required mode' '
+       (git apply --stat test.patch &&
+       cd test && git apply --stat ../test.patch &&
+       cd ../test2 && git apply --stat ../test.patch)'
+
+test_expect_success 'gitdir required mode on normal repos' '
+       (git apply --check --index test.patch &&
+       cd test && git apply --check --index ../test.patch)'
+
+test_expect_failure 'gitdir required mode on unsupported repo' '
+       (cd test2 && git apply --check --index ../test.patch)'
+
+test_done
index ae0639d8f3a3fa428dca31df55193487a74b5b57..e25b25568337ae36a5ef1377a14df031d5dfaeb1 100755 (executable)
@@ -86,7 +86,7 @@ EOF
 
 git config core.excludesFile excludes-file
 
-git runstatus | grep "^#       " > output
+git status | grep "^#  " > output
 
 cat > expect << EOF
 #      .gitignore
index 2fe50bc7ce050f32affa287c5c6e3ab7b1a66f45..a32692417db73444dbdc143e6908b7371be79d42 100755 (executable)
@@ -71,10 +71,10 @@ test_expect_success 'favour same basenames over different ones' '
        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
index 35889c0a125fffdfb556ec521d61c18ac4e96c17..02882c1e4bdcef725b3575ccee6fb298f01c21b4 100755 (executable)
@@ -95,6 +95,31 @@ test_expect_success 'fetch following tags' '
 
 '
 
+test_expect_failure 'fetch must not resolve short tag name' '
+
+       cd "$D" &&
+
+       mkdir five &&
+       cd five &&
+       git init &&
+
+       git fetch .. anno:five
+
+'
+
+test_expect_failure 'fetch must not resolve short remote name' '
+
+       cd "$D" &&
+       git-update-ref refs/remotes/six/HEAD HEAD
+
+       mkdir six &&
+       cd six &&
+       git init &&
+
+       git fetch .. six:six
+
+'
+
 test_expect_success 'create bundle 1' '
        cd "$D" &&
        echo >file updated again by origin &&
@@ -228,4 +253,46 @@ test_expect_success 'bundle should record HEAD correctly' '
 
 '
 
+test_expect_success 'explicit fetch should not update tracking' '
+
+       cd "$D" &&
+       git branch -f side &&
+       (
+               cd three &&
+               o=$(git rev-parse --verify refs/remotes/origin/master) &&
+               git fetch origin master &&
+               n=$(git rev-parse --verify refs/remotes/origin/master) &&
+               test "$o" = "$n" &&
+               ! git rev-parse --verify refs/remotes/origin/side
+       )
+'
+
+test_expect_success 'explicit pull should not update tracking' '
+
+       cd "$D" &&
+       git branch -f side &&
+       (
+               cd three &&
+               o=$(git rev-parse --verify refs/remotes/origin/master) &&
+               git pull origin master &&
+               n=$(git rev-parse --verify refs/remotes/origin/master) &&
+               test "$o" = "$n" &&
+               ! git rev-parse --verify refs/remotes/origin/side
+       )
+'
+
+test_expect_success 'configured fetch updates tracking' '
+
+       cd "$D" &&
+       git branch -f side &&
+       (
+               cd three &&
+               o=$(git rev-parse --verify refs/remotes/origin/master) &&
+               git fetch origin &&
+               n=$(git rev-parse --verify refs/remotes/origin/master) &&
+               test "$o" != "$n" &&
+               git rev-parse --verify refs/remotes/origin/side
+       )
+'
+
 test_done
index 987c9d21caf2a3d7653157f5e6d74f0841863c8b..9d2dc33cbd0d1df19b0a9003e545104a982da694 100755 (executable)
@@ -145,11 +145,21 @@ test_expect_success 'push with no ambiguity (1)' '
 test_expect_success 'push with no ambiguity (2)' '
 
        mk_test remotes/origin/master &&
-       git push testrepo master:master &&
+       git push testrepo master:origin/master &&
        check_push_result $the_commit remotes/origin/master
 
 '
 
+test_expect_success 'push with colon-less refspec, no ambiguity' '
+
+       mk_test heads/master heads/t/master &&
+       git branch -f t/master master &&
+       git push testrepo master &&
+       check_push_result $the_commit heads/master &&
+       check_push_result $the_first_commit heads/t/master
+
+'
+
 test_expect_success 'push with weak ambiguity (1)' '
 
        mk_test heads/master remotes/origin/master &&
@@ -244,6 +254,23 @@ test_expect_success 'push with colon-less refspec (4)' '
 
 '
 
+test_expect_success 'push with HEAD' '
+
+       mk_test heads/master &&
+       git checkout master &&
+       git push testrepo HEAD &&
+       check_push_result $the_commit heads/master
+
+'
+
+test_expect_success 'push with HEAD nonexisting at remote' '
+
+       mk_test heads/master &&
+       git checkout -b local master &&
+       git push testrepo HEAD &&
+       check_push_result $the_commit heads/local
+'
+
 test_expect_success 'push with dry-run' '
 
        mk_test heads/master &&
index c722635050fed8011c04177dc5a8220cae12a843..8a23aaf21b1977fab66aa2989cac56635251ecb6 100755 (executable)
@@ -169,5 +169,44 @@ test_expect_success 'Verify descending sort' '
        git diff expected actual
 '
 
+cat >expected <<\EOF
+'refs/heads/master'
+'refs/tags/testtag'
+EOF
+
+test_expect_success 'Quoting style: shell' '
+       git for-each-ref --shell --format="%(refname)" >actual &&
+       git diff expected actual
+'
+
+test_expect_success 'Quoting style: perl' '
+       git for-each-ref --perl --format="%(refname)" >actual &&
+       git diff expected actual
+'
+
+test_expect_success 'Quoting style: python' '
+       git for-each-ref --python --format="%(refname)" >actual &&
+       git diff expected actual
+'
+
+cat >expected <<\EOF
+"refs/heads/master"
+"refs/tags/testtag"
+EOF
+
+test_expect_success 'Quoting style: tcl' '
+       git for-each-ref --tcl --format="%(refname)" >actual &&
+       git diff expected actual
+'
+
+for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do
+       test_expect_success "more than one quoting style: $i" "
+               git for-each-ref $i 2>&1 | (read line &&
+               case \$line in
+               \"error: more than one quoting style\"*) : happy;;
+               *) false
+               esac)
+       "
+done
 
 test_done
index f013c176ed910d278cbb886004429869f288ebc7..dfd118878fd37c096ccd426aa01fa2ac36581367 100755 (executable)
@@ -126,6 +126,20 @@ test_expect_success 'git-clean symbolic link' '
 
 '
 
+test_expect_success 'git-clean with wildcard' '
+
+       touch a.clean b.clean other.c &&
+       git-clean "*.clean" &&
+       test -f Makefile &&
+       test -f README &&
+       test -f src/part1.c &&
+       test -f src/part2.c &&
+       test ! -f a.clean &&
+       test ! -f b.clean &&
+       test -f other.c
+
+'
+
 test_expect_success 'git-clean -n' '
 
        mkdir -p build docs &&
index cf389b81da041e6bcbc7d20cd367b4274001353f..baed6ce96beb260a32e027dd573313c82202ea7b 100755 (executable)
@@ -122,7 +122,20 @@ test_expect_success 'using alternate GIT_INDEX_FILE (2)' '
 
        ) &&
        cmp .git/index saved-index >/dev/null
+'
+
+cat > expect << EOF
+zort
+
+Signed-off-by: C O Mitter <committer@example.com>
+EOF
 
+test_expect_success '--signoff' '
+       echo "yet another content *narf*" >> foo &&
+       echo "zort" |
+               GIT_EDITOR=../t7500/add-content git commit -s -F - foo &&
+       git cat-file commit HEAD | sed "1,/^$/d" > output &&
+       diff expect output
 '
 
 test_done
index 0316ecf5a16792f226725eb9626ad77c369877bb..19c4b2c5566dcd6a88b8b557d4d597079df21b4e 100755 (executable)
@@ -244,8 +244,35 @@ 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
diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh
new file mode 100755 (executable)
index 0000000..21ac785
--- /dev/null
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+test_description='git commit porcelain-ish'
+
+. ./test-lib.sh
+
+test_expect_success 'the basics' '
+
+       echo doing partial >"commit is" &&
+       mkdir not &&
+       echo very much encouraged but we should >not/forbid &&
+       git add "commit is" not &&
+       echo update added "commit is" file >"commit is" &&
+       echo also update another >not/forbid &&
+       test_tick &&
+       git commit -a -m "initial with -a" &&
+
+       git cat-file blob HEAD:"commit is" >current.1 &&
+       git cat-file blob HEAD:not/forbid >current.2 &&
+
+       cmp current.1 "commit is" &&
+       cmp current.2 not/forbid
+
+'
+
+test_expect_success 'partial' '
+
+       echo another >"commit is" &&
+       echo another >not/forbid &&
+       test_tick &&
+       git commit -m "partial commit to handle a file" "commit is" &&
+
+       changed=$(git diff-tree --name-only HEAD^ HEAD) &&
+       test "$changed" = "commit is"
+
+'
+
+test_expect_success 'partial modification in a subdirecotry' '
+
+       test_tick &&
+       git commit -m "partial commit to subdirectory" not &&
+
+       changed=$(git diff-tree -r --name-only HEAD^ HEAD) &&
+       test "$changed" = "not/forbid"
+
+'
+
+test_expect_success 'partial removal' '
+
+       git rm not/forbid &&
+       git commit -m "partial commit to remove not/forbid" not &&
+
+       changed=$(git diff-tree -r --name-only HEAD^ HEAD) &&
+       test "$changed" = "not/forbid" &&
+       remain=$(git ls-tree -r --name-only HEAD) &&
+       test "$remain" = "commit is"
+
+'
+
+test_expect_success 'sign off' '
+
+       >positive &&
+       git add positive &&
+       git commit -s -m "thank you" &&
+       actual=$(git cat-file commit HEAD | sed -ne "s/Signed-off-by: //p") &&
+       expected=$(git var GIT_COMMITTER_IDENT | sed -e "s/>.*/>/") &&
+       test "z$actual" = "z$expected"
+
+'
+
+test_expect_success 'multiple -m' '
+
+       >negative &&
+       git add negative &&
+       git commit -m "one" -m "two" -m "three" &&
+       actual=$(git cat-file commit HEAD | sed -e "1,/^\$/d") &&
+       expected=$(echo one; echo; echo two; echo; echo three) &&
+       test "z$actual" = "z$expected"
+
+'
+
+test_expect_success 'verbose' '
+
+       echo minus >negative &&
+       git add negative &&
+       git status -v | sed -ne "/^diff --git /p" >actual &&
+       echo "diff --git a/negative b/negative" >expect &&
+       diff -u expect actual
+
+'
+
+test_done
diff --git a/t/t7502-status.sh b/t/t7502-status.sh
new file mode 100755 (executable)
index 0000000..d6ae69d
--- /dev/null
@@ -0,0 +1,91 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='git-status'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       : > tracked &&
+       : > modified &&
+       mkdir dir1 &&
+       : > dir1/tracked &&
+       : > dir1/modified &&
+       mkdir dir2 &&
+       : > dir1/tracked &&
+       : > dir1/modified &&
+       git add . &&
+       test_tick &&
+       git commit -m initial &&
+       : > untracked &&
+       : > dir1/untracked &&
+       : > dir2/untracked &&
+       echo 1 > dir1/modified &&
+       echo 2 > dir2/modified &&
+       echo 3 > dir2/added &&
+       git add dir2/added
+'
+
+cat > expect << \EOF
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      new file:   dir2/added
+#
+# Changed but not updated:
+#   (use "git add <file>..." to update what will be committed)
+#
+#      modified:   dir1/modified
+#
+# Untracked files:
+#   (use "git add <file>..." to include in what will be committed)
+#
+#      dir1/untracked
+#      dir2/modified
+#      dir2/untracked
+#      expect
+#      output
+#      untracked
+EOF
+
+test_expect_success 'status' '
+
+       git status > output &&
+       git diff expect output
+
+'
+
+cat > expect << \EOF
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      new file:   ../dir2/added
+#
+# Changed but not updated:
+#   (use "git add <file>..." to update what will be committed)
+#
+#      modified:   modified
+#
+# Untracked files:
+#   (use "git add <file>..." to include in what will be committed)
+#
+#      untracked
+#      ../dir2/modified
+#      ../dir2/untracked
+#      ../expect
+#      ../output
+#      ../untracked
+EOF
+
+test_expect_success 'status with relative paths' '
+
+       (cd dir1 && git status) > output &&
+       git diff expect output
+
+'
+
+test_done
index 08f0f2a3a34d4d5e3bf45216f5d824854894f2f1..7706430d81e62e70d7124234e89b54fc65caa7d5 100755 (executable)
@@ -3,13 +3,29 @@
 test_description='git-cvsimport basic tests'
 . ./test-lib.sh
 
-if ! ( type cvs && type cvsps ) >/dev/null 2>&1
+if ! type cvs >/dev/null 2>&1
 then
-       test_expect_success 'skipping cvsimport tests, cvs/cvsps not found' ''
+       say 'skipping cvsimport tests, cvs not found'
        test_done
        exit
 fi
 
+cvsps_version=`cvsps -h 2>&1 | sed -ne 's/cvsps version //p'`
+case "$cvsps_version" in
+2.1)
+       ;;
+'')
+       say 'skipping cvsimport tests, cvsps not found'
+       test_done
+       exit
+       ;;
+*)
+       say 'skipping cvsimport tests, cvsps too old'
+       test_done
+       exit
+       ;;
+esac
+
 CVSROOT=$(pwd)/cvsroot
 export CVSROOT
 # for clean cvsps cache
index 3eb93b4875ed0e4884088565a0faa45cc3d287e3..58e66f6c11798dac416941ce5ad3dbae91c31a06 100644 (file)
@@ -659,7 +659,7 @@ static int fetch_refs_via_pack(struct transport *transport,
        free(heads);
        free_refs(refs);
        free(dest);
-       return 0;
+       return (refs ? 0 : -1);
 }
 
 static int git_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags)
index bf2fe8d237b51e87d40dbe60eb70f485273083ff..d35386dae19287a28ef7086d886bdfb3941d18b1 100644 (file)
@@ -81,33 +81,47 @@ static void wt_status_print_trailer(struct wt_status *s)
        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);
+
+       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 (scan = in, out = buf; *scan; scan++) {
-               int ch = *scan;
-               int quoted;
+       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,
@@ -115,10 +129,12 @@ 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) {
@@ -150,6 +166,8 @@ static void wt_status_print_filepair(struct wt_status *s,
                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 @@ static void wt_read_cache(struct wt_status *s)
 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;
@@ -214,11 +233,12 @@ static void wt_status_print_initial(struct wt_status *s)
        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)
@@ -253,7 +273,9 @@ static void wt_status_print_untracked(struct wt_status *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) {
@@ -285,20 +307,38 @@ static void wt_status_print_untracked(struct wt_status *s)
                        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)
index 77449326dbec5c3f83559c300aec2922445829ef..225fb4d53561f4f1d6ad43fd990dda1c6da956f0 100644 (file)
@@ -23,9 +23,11 @@ struct wt_status {
        int workdir_untracked;
        const char *index_file;
        FILE *fp;
+       const char *prefix;
 };
 
 int git_status_config(const char *var, const char *value);
+int wt_status_use_color;
 void wt_status_prepare(struct wt_status *s);
 void wt_status_print(struct wt_status *s);