Sync with 1.5.6.5
authorJunio C Hamano <gitster@pobox.com>
Wed, 6 Aug 2008 20:50:42 +0000 (13:50 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 6 Aug 2008 20:50:48 +0000 (13:50 -0700)
1  2 
Documentation/git-diff-tree.txt
Documentation/git.txt
builtin-commit.c
builtin-tag.c
parse-options.c
parse-options.h
t/t7004-tag.sh
t/t7500-commit.sh
index 8c8f35b7a762d42d3c45ff9f4eee1cb32d3917b6,5d23985b570280baf30746f9749751aa079c7e76..1fdf20dcc9169be2c7a51b32f94893442e160436
@@@ -9,7 -9,7 +9,7 @@@ git-diff-tree - Compares the content an
  SYNOPSIS
  --------
  [verse]
 -'git-diff-tree' [--stdin] [-m] [-s] [-v] [--no-commit-id] [--pretty]
 +'git diff-tree' [--stdin] [-m] [-s] [-v] [--no-commit-id] [--pretty]
              [-t] [-r] [-c | --cc] [--root] [<common diff options>]
              <tree-ish> [<tree-ish>] [<path>...]
  
@@@ -20,7 -20,7 +20,7 @@@ Compares the content and mode of the bl
  If there is only one <tree-ish> given, the commit is compared with its parents
  (see --stdin below).
  
 -Note that "git-diff-tree" can use the tree encapsulated in a commit object.
 +Note that 'git-diff-tree' can use the tree encapsulated in a commit object.
  
  OPTIONS
  -------
@@@ -49,34 -49,34 +49,34 @@@ include::diff-options.txt[
  --stdin::
        When '--stdin' is specified, the command does not take
        <tree-ish> arguments from the command line.  Instead, it
-       reads either one <commit> or a pair of <tree-ish>
+       reads either one <commit> or a list of <commit>
        separated with a single space from its standard input.
  +
  When a single commit is given on one line of such input, it compares
  the commit with its parents.  The following flags further affects its
- behavior.  This does not apply to the case where two <tree-ish>
separated with a single space are given.
+ behavior.  The remaining commits, when given, are used as if they are
parents of the first commit.
  
  -m::
 -      By default, "git-diff-tree --stdin" does not show
 +      By default, 'git-diff-tree --stdin' does not show
        differences for merge commits.  With this flag, it shows
        differences to that commit from all of its parents. See
        also '-c'.
  
  -s::
 -      By default, "git-diff-tree --stdin" shows differences,
 +      By default, 'git-diff-tree --stdin' shows differences,
        either in machine-readable form (without '-p') or in patch
        form (with '-p').  This output can be suppressed.  It is
        only useful with '-v' flag.
  
  -v::
 -      This flag causes "git-diff-tree --stdin" to also show
 +      This flag causes 'git-diff-tree --stdin' to also show
        the commit message before the differences.
  
  include::pretty-options.txt[]
  
  --no-commit-id::
 -      git-diff-tree outputs a line with the commit ID when
 +      'git-diff-tree' outputs a line with the commit ID when
        applicable.  This flag suppressed the commit ID output.
  
  -c::
@@@ -112,13 -112,13 +112,13 @@@ Limiting Outpu
  If you're only interested in differences in a subset of files, for
  example some architecture-specific files, you might do:
  
 -      git-diff-tree -r <tree-ish> <tree-ish> arch/ia64 include/asm-ia64
 +      git diff-tree -r <tree-ish> <tree-ish> arch/ia64 include/asm-ia64
  
  and it will only show you what changed in those two directories.
  
  Or if you are searching for what changed in just `kernel/sched.c`, just do
  
 -      git-diff-tree -r <tree-ish> <tree-ish> kernel/sched.c
 +      git diff-tree -r <tree-ish> <tree-ish> kernel/sched.c
  
  and it will ignore all differences to other files.
  
@@@ -129,7 -129,7 +129,7 @@@ so it can be used to name subdirectorie
  
  An example of normal usage is:
  
 -  torvalds@ppc970:~/git> git-diff-tree 5319e4......
 +  torvalds@ppc970:~/git> git diff-tree 5319e4......
    *100664->100664 blob    ac348b.......->a01513.......      git-fsck-objects.c
  
  which tells you that the last commit changed just one file (it's from
diff --combined Documentation/git.txt
index 3da5bf050c70b6b73ec72e192fa3fb5c1eb7641c,0f55f8005b651a5df9ec56c8b42c55ed6dfa1f08..b1cb97236982dd5fe718765136aa4f1a4fdb994f
@@@ -20,11 -20,11 +20,11 @@@ Git is a fast, scalable, distributed re
  unusually rich command set that provides both high-level operations
  and full access to internals.
  
 -See this linkgit:gittutorial[7][tutorial] to get started, then see
 +See linkgit:gittutorial[7] to get started, then see
  link:everyday.html[Everyday Git] for a useful minimum set of commands, and
  "man git-commandname" for documentation of each command.  CVS users may
 -also want to read linkgit:gitcvs-migration[7][CVS migration].  See
 -link:user-manual.html[Git User's Manual] for a more in-depth
 +also want to read linkgit:gitcvs-migration[7].  See
 +the link:user-manual.html[Git User's Manual] for a more in-depth
  introduction.
  
  The COMMAND is either a name of a Git command (see below) or an alias
@@@ -43,9 -43,10 +43,10 @@@ unreleased) version of git, that is ava
  branch of the `git.git` repository.
  Documentation for older releases are available here:
  
- * link:v1.5.6.4/git.html[documentation for release 1.5.6.4]
+ * link:v1.5.6.5/git.html[documentation for release 1.5.6.5]
  
  * release notes for
+   link:RelNotes-1.5.6.5.txt[1.5.6.5],
    link:RelNotes-1.5.6.4.txt[1.5.6.4],
    link:RelNotes-1.5.6.3.txt[1.5.6.3],
    link:RelNotes-1.5.6.2.txt[1.5.6.2],
@@@ -138,13 -139,13 +139,13 @@@ OPTION
  +
  Other options are available to control how the manual page is
  displayed. See linkgit:git-help[1] for more information,
 -because 'git --help ...' is converted internally into 'git
 -help ...'.
 +because `git --help ...` is converted internally into `git
 +help ...`.
  
  --exec-path::
        Path to wherever your core git programs are installed.
        This can also be controlled by setting the GIT_EXEC_PATH
 -      environment variable. If no path is given 'git' will print
 +      environment variable. If no path is given, 'git' will print
        the current setting and then exit.
  
  -p::
@@@ -185,14 -186,13 +186,14 @@@ See the references above to get starte
  probably more detail than necessary for a first-time user.
  
  The link:user-manual.html#git-concepts[git concepts chapter of the
 -user-manual] and the linkgit:gitcore-tutorial[7][Core tutorial] both provide
 +user-manual] and linkgit:gitcore-tutorial[7] both provide
  introductions to the underlying git architecture.
  
  See also the link:howto-index.html[howto] documents for some useful
  examples.
  
 -The internals are documented link:technical/api-index.html[here].
 +The internals are documented in the
 +link:technical/api-index.html[GIT API documentation].
  
  GIT COMMANDS
  ------------
@@@ -376,9 -376,10 +377,9 @@@ For a more complete list of ways to spe
  File/Directory Structure
  ------------------------
  
 -Please see the linkgit:gitrepository-layout[5][repository layout]
 -document.
 +Please see the linkgit:gitrepository-layout[5] document.
  
 -Read linkgit:githooks[5][hooks] for more details about each hook.
 +Read linkgit:githooks[5] for more details about each hook.
  
  Higher level SCMs may provide and manage additional information in the
  `$GIT_DIR`.
  
  Terminology
  -----------
 -Please see the linkgit:gitglossary[7][glossary] document.
 +Please see linkgit:gitglossary[7].
  
  
  Environment Variables
@@@ -413,9 -414,9 +414,9 @@@ git so take care if using Cogito etc
  'GIT_ALTERNATE_OBJECT_DIRECTORIES'::
        Due to the immutable nature of git objects, old objects can be
        archived into shared, read-only directories. This variable
 -      specifies a ":" separated list of git object directories which
 -      can be used to search for git objects. New objects will not be
 -      written to these directories.
 +      specifies a ":" separated (on Windows ";" separated) list
 +      of git object directories which can be used to search for git
 +      objects. New objects will not be written to these directories.
  
  'GIT_DIR'::
        If the 'GIT_DIR' environment variable is set then it
        This can also be controlled by the '--work-tree' command line
        option and the core.worktree configuration variable.
  
 +'GIT_CEILING_DIRECTORIES'::
 +      This should be a colon-separated list of absolute paths.
 +      If set, it is a list of directories that git should not chdir
 +      up into while looking for a repository directory.
 +      It will not exclude the current working directory or
 +      a GIT_DIR set on the command line or in the environment.
 +      (Useful for excluding slow-loading network directories.)
 +
  git Commits
  ~~~~~~~~~~~
  'GIT_AUTHOR_NAME'::
@@@ -494,10 -487,10 +495,10 @@@ othe
        a pager.
  
  'GIT_SSH'::
 -      If this environment variable is set then linkgit:git-fetch[1]
 -      and linkgit:git-push[1] will use this command instead
 -      of `ssh` when they need to connect to a remote system.
 -      The 'GIT_SSH' command will be given exactly two arguments:
 +      If this environment variable is set then 'git-fetch'
 +      and 'git-push' will use this command instead
 +      of 'ssh' when they need to connect to a remote system.
 +      The '$GIT_SSH' command will be given exactly two arguments:
        the 'username@host' (or just 'host') from the URL and the
        shell command to execute on that remote system.
  +
@@@ -511,8 -504,8 +512,8 @@@ for further details
  
  'GIT_FLUSH'::
        If this environment variable is set to "1", then commands such
 -      as git-blame (in incremental mode), git-rev-list, git-log,
 -      git-whatchanged, etc., will force a flush of the output stream
 +      as 'git-blame' (in incremental mode), 'git-rev-list', 'git-log',
 +      and 'git-whatchanged' will force a flush of the output stream
        after each commit-oriented record have been flushed.   If this
        variable is set to "0", the output of these commands will be done
        using completely buffered I/O.   If this environment variable is
@@@ -538,7 -531,7 +539,7 @@@ Discussion[[Discussion]
  
  More detail on the following is available from the
  link:user-manual.html#git-concepts[git concepts chapter of the
 -user-manual] and the linkgit:gitcore-tutorial[7][Core tutorial].
 +user-manual] and linkgit:gitcore-tutorial[7].
  
  A git project normally consists of a working directory with a ".git"
  subdirectory at the top level.  The .git directory contains, among other
@@@ -602,9 -595,9 +603,9 @@@ contributors on the git-list <git@vger.
  SEE ALSO
  --------
  linkgit:gittutorial[7], linkgit:gittutorial-2[7],
 -linkgit:giteveryday[7], linkgit:gitcvs-migration[7],
 +link:everyday.html[Everyday Git], linkgit:gitcvs-migration[7],
  linkgit:gitglossary[7], linkgit:gitcore-tutorial[7],
 -link:user-manual.html[The Git User's Manual]
 +linkgit:gitcli[7], link:user-manual.html[The Git User's Manual]
  
  GIT
  ---
diff --combined builtin-commit.c
index b783e6eb46d2603d61d3260420c5dff48913d182,0c6d1f4f455fb3832809f03fd2748feea143006a..649c8beb3e716dd5797787ced61d2fee23b7140f
  #include "strbuf.h"
  #include "utf8.h"
  #include "parse-options.h"
 -#include "path-list.h"
 +#include "string-list.h"
 +#include "rerere.h"
  #include "unpack-trees.h"
  
  static const char * const builtin_commit_usage[] = {
 -      "git-commit [options] [--] <filepattern>...",
 +      "git commit [options] [--] <filepattern>...",
        NULL
  };
  
  static const char * const builtin_status_usage[] = {
 -      "git-status [options] [--] <filepattern>...",
 +      "git status [options] [--] <filepattern>...",
        NULL
  };
  
@@@ -46,13 -45,12 +46,13 @@@ static enum 
        COMMIT_PARTIAL,
  } commit_style;
  
- static char *logfile, *force_author;
+ static const char *logfile, *force_author;
  static const char *template_file;
  static char *edit_message, *use_message;
  static char *author_name, *author_email, *author_date;
  static int all, edit_flag, also, interactive, only, amend, signoff;
 -static int quiet, verbose, untracked_files, no_verify, allow_empty;
 +static int quiet, verbose, no_verify, allow_empty;
 +static char *untracked_files_arg;
  /*
   * The default commit message cleanup mode will remove the lines
   * beginning with # (shell comments) and leading and trailing
@@@ -68,8 -66,8 +68,8 @@@ static enum 
  static char *cleanup_arg;
  
  static int use_editor = 1, initial_commit, in_merge;
 -const char *only_include_assumed;
 -struct strbuf message;
 +static const char *only_include_assumed;
 +static struct strbuf message;
  
  static int opt_parse_m(const struct option *opt, const char *arg, int unset)
  {
@@@ -78,7 -76,8 +78,7 @@@
                strbuf_setlen(buf, 0);
        else {
                strbuf_addstr(buf, arg);
 -              strbuf_addch(buf, '\n');
 -              strbuf_addch(buf, '\n');
 +              strbuf_addstr(buf, "\n\n");
        }
        return 0;
  }
@@@ -104,7 -103,7 +104,7 @@@ static struct option builtin_commit_opt
        OPT_BOOLEAN('o', "only", &only, "commit only specified files"),
        OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"),
        OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
 -      OPT_BOOLEAN('u', "untracked-files", &untracked_files, "show all untracked files"),
 +      { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
        OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
        OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
  
@@@ -149,7 -148,7 +149,7 @@@ static int commit_index_files(void
   * 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,
 +static int list_paths(struct string_list *list, const char *with_tree,
                      const char *prefix, const char **pattern)
  {
        int i;
                        continue;
                if (!pathspec_match(pattern, m, ce->name, 0))
                        continue;
 -              path_list_insert(ce->name, list);
 +              string_list_insert(ce->name, list);
        }
  
        return report_path_error(m, pattern, prefix ? strlen(prefix) : 0);
  }
  
 -static void add_remove_files(struct path_list *list)
 +static void add_remove_files(struct string_list *list)
  {
        int i;
        for (i = 0; i < list->nr; i++) {
                struct stat st;
 -              struct path_list_item *p = &(list->items[i]);
 +              struct string_list_item *p = &(list->items[i]);
  
 -              if (!lstat(p->path, &st)) {
 -                      if (add_to_cache(p->path, &st, 0))
 +              if (!lstat(p->string, &st)) {
 +                      if (add_to_cache(p->string, &st, 0))
                                die("updating files failed");
                } else
 -                      remove_file_from_cache(p->path);
 +                      remove_file_from_cache(p->string);
        }
  }
  
@@@ -220,7 -219,7 +220,7 @@@ static void create_base_index(void
  static char *prepare_index(int argc, const char **argv, const char *prefix)
  {
        int fd;
 -      struct path_list partial;
 +      struct string_list partial;
        const char **pathspec = NULL;
  
        if (interactive) {
                die("cannot do a partial commit during a merge.");
  
        memset(&partial, 0, sizeof(partial));
 -      partial.strdup_paths = 1;
 +      partial.strdup_strings = 1;
        if (list_paths(&partial, initial_commit ? NULL : "HEAD", prefix, pathspec))
                exit(1);
  
@@@ -349,7 -348,7 +349,7 @@@ static int run_status(FILE *fp, const c
                s.reference = "HEAD^1";
        }
        s.verbose = verbose;
 -      s.untracked = untracked_files;
 +      s.untracked = (show_untracked_files == SHOW_ALL_UNTRACKED_FILES);
        s.index_file = index_file;
        s.fp = fp;
        s.nowarn = nowarn;
@@@ -504,8 -503,7 +504,8 @@@ static int prepare_to_commit(const cha
  
        fp = fopen(git_path(commit_editmsg), "w");
        if (fp == NULL)
 -              die("could not open %s", git_path(commit_editmsg));
 +              die("could not open %s: %s",
 +                  git_path(commit_editmsg), strerror(errno));
  
        if (cleanup_mode != CLEANUP_NONE)
                stripspace(&sb, 0);
  
                fprintf(fp,
                        "\n"
 -                      "# Please enter the commit message for your changes.\n"
 -                      "# (Comment lines starting with '#' will ");
 +                      "# Please enter the commit message for your changes.");
                if (cleanup_mode == CLEANUP_ALL)
 -                      fprintf(fp, "not be included)\n");
 +                      fprintf(fp,
 +                              " Lines starting\n"
 +                              "# with '#' will be ignored, and an empty"
 +                              " message aborts the commit.\n");
                else /* CLEANUP_SPACE, that is. */
 -                      fprintf(fp, "be kept.\n"
 -                              "# You can remove them yourself if you want to)\n");
 +                      fprintf(fp,
 +                              " Lines starting\n"
 +                              "# with '#' will be kept; you may remove them"
 +                              " yourself if you want to.\n"
 +                              "# An empty message aborts the commit.\n");
                if (only_include_assumed)
                        fprintf(fp, "# %s\n", only_include_assumed);
  
                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), NULL, env);
 +              if (launch_editor(git_path(commit_editmsg), NULL, env)) {
 +                      fprintf(stderr,
 +                      "Please supply the message using either -m or -F option.\n");
 +                      exit(1);
 +              }
        }
  
        if (!no_verify &&
@@@ -711,11 -700,14 +711,14 @@@ static int message_is_empty(struct strb
  }
  
  static int parse_and_validate_options(int argc, const char *argv[],
-                                     const char * const usage[])
+                                     const char * const usage[],
+                                     const char *prefix)
  {
        int f = 0;
  
        argc = parse_options(argc, argv, builtin_commit_options, usage, 0);
+       logfile = parse_options_fix_filename(prefix, logfile);
+       template_file = parse_options_fix_filename(prefix, template_file);
  
        if (logfile || message.len || use_message)
                use_editor = 0;
        else
                die("Invalid cleanup mode %s", cleanup_arg);
  
 +      if (!untracked_files_arg)
 +              ; /* default already initialized */
 +      else if (!strcmp(untracked_files_arg, "no"))
 +              show_untracked_files = SHOW_NO_UNTRACKED_FILES;
 +      else if (!strcmp(untracked_files_arg, "normal"))
 +              show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
 +      else if (!strcmp(untracked_files_arg, "all"))
 +              show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
 +      else
 +              die("Invalid untracked files mode '%s'", untracked_files_arg);
 +
        if (all && argc > 0)
                die("Paths with -a does not make sense.");
        else if (interactive && argc > 0)
@@@ -836,7 -817,7 +839,7 @@@ int cmd_status(int argc, const char **a
        if (wt_status_use_color == -1)
                wt_status_use_color = git_use_color_default;
  
-       argc = parse_and_validate_options(argc, argv, builtin_status_usage);
+       argc = parse_and_validate_options(argc, argv, builtin_status_usage, prefix);
  
        index_file = prepare_index(argc, argv, prefix);
  
@@@ -885,7 -866,7 +888,7 @@@ static void print_summary(const char *p
        }
  }
  
 -int git_commit_config(const char *k, const char *v, void *cb)
 +static int git_commit_config(const char *k, const char *v, void *cb)
  {
        if (!strcmp(k, "commit.template"))
                return git_config_string(&template_file, k, v);
@@@ -929,7 -910,7 +932,7 @@@ int cmd_commit(int argc, const char **a
  
        git_config(git_commit_config, NULL);
  
-       argc = parse_and_validate_options(argc, argv, builtin_commit_usage);
+       argc = parse_and_validate_options(argc, argv, builtin_commit_usage, prefix);
  
        index_file = prepare_index(argc, argv, prefix);
  
                stripspace(&sb, cleanup_mode == CLEANUP_ALL);
        if (sb.len < header_len || message_is_empty(&sb, header_len)) {
                rollback_index_files();
 -              die("no commit message?  aborting commit.");
 +              fprintf(stderr, "Aborting commit due to empty commit message.\n");
 +              exit(1);
        }
        strbuf_addch(&sb, '\0');
        if (is_encoding_utf8(git_commit_encoding) && !is_utf8(sb.buf))
diff --combined builtin-tag.c
index 325b1b2632e44121c23bc6df556bf3aa4e32ba04,3bd019cc566aad3137a2c0a50a2e176003d9a442..f2853d08c77368b37b40c7ea51f5a124208d385f
  #include "parse-options.h"
  
  static const char * const git_tag_usage[] = {
 -      "git-tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
 -      "git-tag -d <tagname>...",
 -      "git-tag -l [-n[<num>]] [<pattern>]",
 -      "git-tag -v <tagname>...",
 +      "git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
 +      "git tag -d <tagname>...",
 +      "git tag -l [-n[<num>]] [<pattern>]",
 +      "git tag -v <tagname>...",
        NULL
  };
  
  static char signingkey[1000];
  
 -void launch_editor(const char *path, struct strbuf *buffer, const char *const *env)
 -{
 -      const char *editor, *terminal;
 -
 -      editor = getenv("GIT_EDITOR");
 -      if (!editor && editor_program)
 -              editor = editor_program;
 -      if (!editor)
 -              editor = getenv("VISUAL");
 -      if (!editor)
 -              editor = getenv("EDITOR");
 -
 -      terminal = getenv("TERM");
 -      if (!editor && (!terminal || !strcmp(terminal, "dumb"))) {
 -              fprintf(stderr,
 -              "Terminal is dumb but no VISUAL nor EDITOR defined.\n"
 -              "Please supply the message using either -m or -F option.\n");
 -              exit(1);
 -      }
 -
 -      if (!editor)
 -              editor = "vi";
 -
 -      if (strcmp(editor, ":")) {
 -              size_t len = strlen(editor);
 -              int i = 0;
 -              const char *args[6];
 -              struct strbuf arg0;
 -
 -              strbuf_init(&arg0, 0);
 -              if (strcspn(editor, "$ \t'") != len) {
 -                      /* there are specials */
 -                      strbuf_addf(&arg0, "%s \"$@\"", editor);
 -                      args[i++] = "sh";
 -                      args[i++] = "-c";
 -                      args[i++] = arg0.buf;
 -              }
 -              args[i++] = editor;
 -              args[i++] = path;
 -              args[i] = NULL;
 -
 -              if (run_command_v_opt_cd_env(args, 0, NULL, env))
 -                      die("There was a problem with the editor %s.", editor);
 -              strbuf_release(&arg0);
 -      }
 -
 -      if (!buffer)
 -              return;
 -      if (strbuf_read_file(buffer, path, 0) < 0)
 -              die("could not read message file '%s': %s",
 -                  path, strerror(errno));
 -}
 -
  struct tag_filter {
        const char *pattern;
        int lines;
@@@ -149,7 -202,6 +149,7 @@@ static int do_sign(struct strbuf *buffe
        const char *args[4];
        char *bracket;
        int len;
 +      int i, j;
  
        if (!*signingkey) {
                if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME),
        if (finish_command(&gpg) || !len || len < 0)
                return error("gpg failed to sign the tag");
  
 +      /* Strip CR from the line endings, in case we are on Windows. */
 +      for (i = j = 0; i < buffer->len; i++)
 +              if (buffer->buf[i] != '\r') {
 +                      if (i != j)
 +                              buffer->buf[j] = buffer->buf[i];
 +                      j++;
 +              }
 +      strbuf_setlen(buffer, j);
 +
        return 0;
  }
  
@@@ -295,11 -338,7 +295,11 @@@ static void create_tag(const unsigned c
                        write_or_die(fd, tag_template, strlen(tag_template));
                close(fd);
  
 -              launch_editor(path, buf, NULL);
 +              if (launch_editor(path, buf, NULL)) {
 +                      fprintf(stderr,
 +                      "Please supply the message using either -m or -F option.\n");
 +                      exit(1);
 +              }
  
                unlink(path);
                free(path);
@@@ -346,7 -385,7 +346,7 @@@ int cmd_tag(int argc, const char **argv
  
        int annotate = 0, sign = 0, force = 0, lines = 0,
                list = 0, delete = 0, verify = 0;
-       char *msgfile = NULL, *keyid = NULL;
+       const char *msgfile = NULL, *keyid = NULL;
        struct msg_arg msg = { 0, STRBUF_INIT };
        struct option options[] = {
                OPT_BOOLEAN('l', NULL, &list, "list tag names"),
        git_config(git_tag_config, NULL);
  
        argc = parse_options(argc, argv, options, git_tag_usage, 0);
+       msgfile = parse_options_fix_filename(prefix, msgfile);
  
        if (keyid) {
                sign = 1;
diff --combined parse-options.c
index 71a7acf4e22bd12c0919f277410d6ec52dd5efc8,12c882296e31e90b984aec8afdf5c06d3c213ec5..fd08bb425c241a0861588ec0aedd15041095a95f
@@@ -1,10 -1,33 +1,10 @@@
  #include "git-compat-util.h"
  #include "parse-options.h"
 +#include "cache.h"
  
  #define OPT_SHORT 1
  #define OPT_UNSET 2
  
 -struct optparse_t {
 -      const char **argv;
 -      const char **out;
 -      int argc, cpidx;
 -      const char *opt;
 -};
 -
 -static inline const char *get_arg(struct optparse_t *p)
 -{
 -      if (p->opt) {
 -              const char *res = p->opt;
 -              p->opt = NULL;
 -              return res;
 -      }
 -      p->argc--;
 -      return *++p->argv;
 -}
 -
 -static inline const char *skip_prefix(const char *str, const char *prefix)
 -{
 -      size_t len = strlen(prefix);
 -      return strncmp(str, prefix, len) ? NULL : str + len;
 -}
 -
  static int opterror(const struct option *opt, const char *reason, int flags)
  {
        if (flags & OPT_SHORT)
        return error("option `%s' %s", opt->long_name, reason);
  }
  
 -static int get_value(struct optparse_t *p,
 -                     const struct option *opt, int flags)
 +static int get_arg(struct parse_opt_ctx_t *p, const struct option *opt,
 +                 int flags, const char **arg)
 +{
 +      if (p->opt) {
 +              *arg = p->opt;
 +              p->opt = NULL;
 +      } else if (p->argc == 1 && (opt->flags & PARSE_OPT_LASTARG_DEFAULT)) {
 +              *arg = (const char *)opt->defval;
 +      } else if (p->argc > 1) {
 +              p->argc--;
 +              *arg = *++p->argv;
 +      } else
 +              return opterror(opt, "requires a value", flags);
 +      return 0;
 +}
 +
 +static int get_value(struct parse_opt_ctx_t *p,
 +                   const struct option *opt, int flags)
  {
        const char *s, *arg;
        const int unset = flags & OPT_UNSET;
@@@ -57,6 -64,7 +57,6 @@@
                }
        }
  
 -      arg = p->opt ? p->opt : (p->argc > 1 ? p->argv[1] : NULL);
        switch (opt->type) {
        case OPTION_BIT:
                if (unset)
                return 0;
  
        case OPTION_STRING:
 -              if (unset) {
 +              if (unset)
                        *(const char **)opt->value = NULL;
 -                      return 0;
 -              }
 -              if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
 +              else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
                        *(const char **)opt->value = (const char *)opt->defval;
 -                      return 0;
 -              }
 -              if (!arg)
 -                      return opterror(opt, "requires a value", flags);
 -              *(const char **)opt->value = get_arg(p);
 +              else
 +                      return get_arg(p, opt, flags, (const char **)opt->value);
                return 0;
  
        case OPTION_CALLBACK:
                if (unset)
 -                      return (*opt->callback)(opt, NULL, 1);
 +                      return (*opt->callback)(opt, NULL, 1) ? (-1) : 0;
                if (opt->flags & PARSE_OPT_NOARG)
 -                      return (*opt->callback)(opt, NULL, 0);
 +                      return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
                if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
 -                      return (*opt->callback)(opt, NULL, 0);
 -              if (!arg)
 -                      return opterror(opt, "requires a value", flags);
 -              return (*opt->callback)(opt, get_arg(p), 0);
 +                      return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
 +              if (get_arg(p, opt, flags, &arg))
 +                      return -1;
 +              return (*opt->callback)(opt, arg, 0) ? (-1) : 0;
  
        case OPTION_INTEGER:
                if (unset) {
                        *(int *)opt->value = opt->defval;
                        return 0;
                }
 -              if (!arg)
 -                      return opterror(opt, "requires a value", flags);
 -              *(int *)opt->value = strtol(get_arg(p), (char **)&s, 10);
 +              if (get_arg(p, opt, flags, &arg))
 +                      return -1;
 +              *(int *)opt->value = strtol(arg, (char **)&s, 10);
                if (*s)
                        return opterror(opt, "expects a numerical value", flags);
                return 0;
        }
  }
  
 -static int parse_short_opt(struct optparse_t *p, const struct option *options)
 +static int parse_short_opt(struct parse_opt_ctx_t *p, const struct option *options)
  {
        for (; options->type != OPTION_END; options++) {
                if (options->short_name == *p->opt) {
                        return get_value(p, options, OPT_SHORT);
                }
        }
 -      return error("unknown switch `%c'", *p->opt);
 +      return -2;
  }
  
 -static int parse_long_opt(struct optparse_t *p, const char *arg,
 +static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
                            const struct option *options)
  {
        const char *arg_end = strchr(arg, '=');
@@@ -211,10 -224,10 +211,10 @@@ is_abbreviated
                        abbrev_option->long_name);
        if (abbrev_option)
                return get_value(p, abbrev_option, abbrev_flags);
 -      return error("unknown option `%s'", arg);
 +      return -2;
  }
  
 -void check_typos(const char *arg, const struct option *options)
 +static void check_typos(const char *arg, const struct option *options)
  {
        if (strlen(arg) < 3)
                return;
        }
  }
  
 -static NORETURN void usage_with_options_internal(const char * const *,
 -                                                 const struct option *, int);
 +void parse_options_start(struct parse_opt_ctx_t *ctx,
 +                       int argc, const char **argv, int flags)
 +{
 +      memset(ctx, 0, sizeof(*ctx));
 +      ctx->argc = argc - 1;
 +      ctx->argv = argv + 1;
 +      ctx->out  = argv;
 +      ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0);
 +      ctx->flags = flags;
 +}
  
 -int parse_options(int argc, const char **argv, const struct option *options,
 -                  const char * const usagestr[], int flags)
 +static int usage_with_options_internal(const char * const *,
 +                                     const struct option *, int);
 +
 +int parse_options_step(struct parse_opt_ctx_t *ctx,
 +                     const struct option *options,
 +                     const char * const usagestr[])
  {
 -      struct optparse_t args = { argv + 1, argv, argc - 1, 0, NULL };
 +      /* we must reset ->opt, unknown short option leave it dangling */
 +      ctx->opt = NULL;
  
 -      for (; args.argc; args.argc--, args.argv++) {
 -              const char *arg = args.argv[0];
 +      for (; ctx->argc; ctx->argc--, ctx->argv++) {
 +              const char *arg = ctx->argv[0];
  
                if (*arg != '-' || !arg[1]) {
 -                      if (flags & PARSE_OPT_STOP_AT_NON_OPTION)
 +                      if (ctx->flags & PARSE_OPT_STOP_AT_NON_OPTION)
                                break;
 -                      args.out[args.cpidx++] = args.argv[0];
 +                      ctx->out[ctx->cpidx++] = ctx->argv[0];
                        continue;
                }
  
                if (arg[1] != '-') {
 -                      args.opt = arg + 1;
 -                      if (*args.opt == 'h')
 -                              usage_with_options(usagestr, options);
 -                      if (parse_short_opt(&args, options) < 0)
 -                              usage_with_options(usagestr, options);
 -                      if (args.opt)
 +                      ctx->opt = arg + 1;
 +                      if (*ctx->opt == 'h')
 +                              return parse_options_usage(usagestr, options);
 +                      switch (parse_short_opt(ctx, options)) {
 +                      case -1:
 +                              return parse_options_usage(usagestr, options);
 +                      case -2:
 +                              return PARSE_OPT_UNKNOWN;
 +                      }
 +                      if (ctx->opt)
                                check_typos(arg + 1, options);
 -                      while (args.opt) {
 -                              if (*args.opt == 'h')
 -                                      usage_with_options(usagestr, options);
 -                              if (parse_short_opt(&args, options) < 0)
 -                                      usage_with_options(usagestr, options);
 +                      while (ctx->opt) {
 +                              if (*ctx->opt == 'h')
 +                                      return parse_options_usage(usagestr, options);
 +                              switch (parse_short_opt(ctx, options)) {
 +                              case -1:
 +                                      return parse_options_usage(usagestr, options);
 +                              case -2:
 +                                      /* fake a short option thing to hide the fact that we may have
 +                                       * started to parse aggregated stuff
 +                                       *
 +                                       * This is leaky, too bad.
 +                                       */
 +                                      ctx->argv[0] = xstrdup(ctx->opt - 1);
 +                                      *(char *)ctx->argv[0] = '-';
 +                                      return PARSE_OPT_UNKNOWN;
 +                              }
                        }
                        continue;
                }
  
                if (!arg[2]) { /* "--" */
 -                      if (!(flags & PARSE_OPT_KEEP_DASHDASH)) {
 -                              args.argc--;
 -                              args.argv++;
 +                      if (!(ctx->flags & PARSE_OPT_KEEP_DASHDASH)) {
 +                              ctx->argc--;
 +                              ctx->argv++;
                        }
                        break;
                }
  
                if (!strcmp(arg + 2, "help-all"))
 -                      usage_with_options_internal(usagestr, options, 1);
 +                      return usage_with_options_internal(usagestr, options, 1);
                if (!strcmp(arg + 2, "help"))
 -                      usage_with_options(usagestr, options);
 -              if (parse_long_opt(&args, arg + 2, options))
 -                      usage_with_options(usagestr, options);
 +                      return parse_options_usage(usagestr, options);
 +              switch (parse_long_opt(ctx, arg + 2, options)) {
 +              case -1:
 +                      return parse_options_usage(usagestr, options);
 +              case -2:
 +                      return PARSE_OPT_UNKNOWN;
 +              }
        }
 +      return PARSE_OPT_DONE;
 +}
  
 -      memmove(args.out + args.cpidx, args.argv, args.argc * sizeof(*args.out));
 -      args.out[args.cpidx + args.argc] = NULL;
 -      return args.cpidx + args.argc;
 +int parse_options_end(struct parse_opt_ctx_t *ctx)
 +{
 +      memmove(ctx->out + ctx->cpidx, ctx->argv, ctx->argc * sizeof(*ctx->out));
 +      ctx->out[ctx->cpidx + ctx->argc] = NULL;
 +      return ctx->cpidx + ctx->argc;
 +}
 +
 +int parse_options(int argc, const char **argv, const struct option *options,
 +                const char * const usagestr[], int flags)
 +{
 +      struct parse_opt_ctx_t ctx;
 +
 +      parse_options_start(&ctx, argc, argv, flags);
 +      switch (parse_options_step(&ctx, options, usagestr)) {
 +      case PARSE_OPT_HELP:
 +              exit(129);
 +      case PARSE_OPT_DONE:
 +              break;
 +      default: /* PARSE_OPT_UNKNOWN */
 +              if (ctx.argv[0][1] == '-') {
 +                      error("unknown option `%s'", ctx.argv[0] + 2);
 +              } else {
 +                      error("unknown switch `%c'", *ctx.opt);
 +              }
 +              usage_with_options(usagestr, options);
 +      }
 +
 +      return parse_options_end(&ctx);
  }
  
  #define USAGE_OPTS_WIDTH 24
  #define USAGE_GAP         2
  
 -void usage_with_options_internal(const char * const *usagestr,
 -                                 const struct option *opts, int full)
 +int usage_with_options_internal(const char * const *usagestr,
 +                              const struct option *opts, int full)
  {
        fprintf(stderr, "usage: %s\n", *usagestr++);
        while (*usagestr && **usagestr)
                fprintf(stderr, "   or: %s\n", *usagestr++);
 -      while (*usagestr)
 -              fprintf(stderr, "    %s\n", *usagestr++);
 +      while (*usagestr) {
 +              fprintf(stderr, "%s%s\n",
 +                              **usagestr ? "    " : "",
 +                              *usagestr);
 +              usagestr++;
 +      }
  
        if (opts->type != OPTION_GROUP)
                fputc('\n', stderr);
        }
        fputc('\n', stderr);
  
 -      exit(129);
 +      return PARSE_OPT_HELP;
  }
  
  void usage_with_options(const char * const *usagestr,
 -                        const struct option *opts)
 +                      const struct option *opts)
  {
        usage_with_options_internal(usagestr, opts, 0);
 +      exit(129);
 +}
 +
 +int parse_options_usage(const char * const *usagestr,
 +                      const struct option *opts)
 +{
 +      return usage_with_options_internal(usagestr, opts, 0);
  }
  
 +
  /*----- some often used options -----*/
  #include "cache.h"
  
@@@ -483,3 -425,15 +483,15 @@@ int parse_opt_approxidate_cb(const stru
        *(unsigned long *)(opt->value) = approxidate(arg);
        return 0;
  }
+ /*
+  * This should really be OPTION_FILENAME type as a part of
+  * parse_options that take prefix to do this while parsing.
+  */
+ extern const char *parse_options_fix_filename(const char *prefix, const char *file)
+ {
+       if (!file || !prefix || is_absolute_path(file) || !strcmp("-", file))
+               return file;
+       return prefix_filename(prefix, strlen(prefix), file);
+ }
diff --combined parse-options.h
index bc317e7512af7a1cc86641a651ae5415d28e71c4,13ad15869e356ddaf3eef5a2d80401c39aaaf7d8..5199950c006df4625355ce227970cc3e8a72ed41
@@@ -20,7 -20,6 +20,7 @@@ enum parse_opt_type 
  enum parse_opt_flags {
        PARSE_OPT_KEEP_DASHDASH = 1,
        PARSE_OPT_STOP_AT_NON_OPTION = 2,
 +      PARSE_OPT_KEEP_ARGV0 = 4,
  };
  
  enum parse_opt_option_flags {
@@@ -28,7 -27,6 +28,7 @@@
        PARSE_OPT_NOARG   = 2,
        PARSE_OPT_NONEG   = 4,
        PARSE_OPT_HIDDEN  = 8,
 +      PARSE_OPT_LASTARG_DEFAULT = 16,
  };
  
  struct option;
@@@ -113,40 -111,6 +113,40 @@@ extern int parse_options(int argc, cons
  extern NORETURN void usage_with_options(const char * const *usagestr,
                                          const struct option *options);
  
 +/*----- incremantal advanced APIs -----*/
 +
 +enum {
 +      PARSE_OPT_HELP = -1,
 +      PARSE_OPT_DONE,
 +      PARSE_OPT_UNKNOWN,
 +};
 +
 +/*
 + * It's okay for the caller to consume argv/argc in the usual way.
 + * Other fields of that structure are private to parse-options and should not
 + * be modified in any way.
 + */
 +struct parse_opt_ctx_t {
 +      const char **argv;
 +      const char **out;
 +      int argc, cpidx;
 +      const char *opt;
 +      int flags;
 +};
 +
 +extern int parse_options_usage(const char * const *usagestr,
 +                             const struct option *opts);
 +
 +extern void parse_options_start(struct parse_opt_ctx_t *ctx,
 +                              int argc, const char **argv, int flags);
 +
 +extern int parse_options_step(struct parse_opt_ctx_t *ctx,
 +                            const struct option *options,
 +                            const char * const usagestr[]);
 +
 +extern int parse_options_end(struct parse_opt_ctx_t *ctx);
 +
 +
  /*----- some often used options -----*/
  extern int parse_opt_abbrev_cb(const struct option *, const char *, int);
  extern int parse_opt_approxidate_cb(const struct option *, const char *, int);
          "use <n> digits to display SHA-1s", \
          PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }
  
+ extern const char *parse_options_fix_filename(const char *prefix, const char *file);
  #endif
diff --combined t/t7004-tag.sh
index bc7ce2cbbb712f890245688da03be96146a1d9ed,acddbf5037310576e3ee0268e751dd49082cd441..8d44c2ed1f5148cbfc7affe155eaa4b6bd04c66a
@@@ -30,17 -30,17 +30,17 @@@ test_expect_success 'looking for a tag 
        '! (tag_exists mytag)'
  
  test_expect_success 'creating a tag in an empty tree should fail' '
 -      ! git-tag mynotag &&
 +      test_must_fail git-tag mynotag &&
        ! tag_exists mynotag
  '
  
  test_expect_success 'creating a tag for HEAD in an empty tree should fail' '
 -      ! git-tag mytaghead HEAD &&
 +      test_must_fail git-tag mytaghead HEAD &&
        ! tag_exists mytaghead
  '
  
  test_expect_success 'creating a tag for an unknown revision should fail' '
 -      ! git-tag mytagnorev aaaaaaaaaaa &&
 +      test_must_fail git-tag mytagnorev aaaaaaaaaaa &&
        ! tag_exists mytagnorev
  '
  
@@@ -85,16 -85,16 +85,16 @@@ test_expect_success 
  
  test_expect_success \
        'trying to create a tag with the name of one existing should fail' \
 -      '! git tag mytag'
 +      'test_must_fail git tag mytag'
  
  test_expect_success \
        'trying to create a tag with a non-valid name should fail' '
        test `git-tag -l | wc -l` -eq 1 &&
 -      ! git tag "" &&
 -      ! git tag .othertag &&
 -      ! git tag "other tag" &&
 -      ! git tag "othertag^" &&
 -      ! git tag "other~tag" &&
 +      test_must_fail git tag "" &&
 +      test_must_fail git tag .othertag &&
 +      test_must_fail git tag "other tag" &&
 +      test_must_fail git tag "othertag^" &&
 +      test_must_fail git tag "other~tag" &&
        test `git-tag -l | wc -l` -eq 1
  '
  
@@@ -107,7 -107,7 +107,7 @@@ test_expect_success 'creating a tag usi
  
  test_expect_success 'trying to delete an unknown tag should fail' '
        ! tag_exists unknown-tag &&
 -      ! git-tag -d unknown-tag
 +      test_must_fail git-tag -d unknown-tag
  '
  
  cat >expect <<EOF
@@@ -141,13 -141,13 +141,13 @@@ test_expect_success 
        'trying to delete two tags, existing and not, should fail in the 2nd' '
        tag_exists mytag &&
        ! tag_exists myhead &&
 -      ! git-tag -d mytag anothertag &&
 +      test_must_fail git-tag -d mytag anothertag &&
        ! tag_exists mytag &&
        ! tag_exists myhead
  '
  
  test_expect_success 'trying to delete an already deleted tag should fail' \
 -      '! git-tag -d mytag'
 +      'test_must_fail git-tag -d mytag'
  
  # listing various tags with pattern matching:
  
@@@ -266,15 -266,15 +266,15 @@@ test_expect_success 
  '
  
  test_expect_success 'trying to verify an unknown tag should fail' \
 -      '! git-tag -v unknown-tag'
 +      'test_must_fail git-tag -v unknown-tag'
  
  test_expect_success \
        'trying to verify a non-annotated and non-signed tag should fail' \
 -      '! git-tag -v non-annotated-tag'
 +      'test_must_fail git-tag -v non-annotated-tag'
  
  test_expect_success \
        'trying to verify many non-annotated or unknown tags, should fail' \
 -      '! git-tag -v unknown-tag1 non-annotated-tag unknown-tag2'
 +      'test_must_fail git-tag -v unknown-tag1 non-annotated-tag unknown-tag2'
  
  # creating annotated tags:
  
@@@ -334,7 -334,7 +334,7 @@@ test_expect_success 
        'trying to create a tag with a non-existing -F file should fail' '
        ! test -f nonexistingfile &&
        ! tag_exists notag &&
 -      ! git-tag -F nonexistingfile notag &&
 +      test_must_fail git-tag -F nonexistingfile notag &&
        ! tag_exists notag
  '
  
@@@ -343,12 -343,11 +343,12 @@@ test_expect_success 
        echo "message file 1" >msgfile1 &&
        echo "message file 2" >msgfile2 &&
        ! tag_exists msgtag &&
 -      ! git-tag -m "message 1" -F msgfile1 msgtag &&
 +      test_must_fail git-tag -m "message 1" -F msgfile1 msgtag &&
        ! tag_exists msgtag &&
 -      ! git-tag -F msgfile1 -m "message 1" msgtag &&
 +      test_must_fail git-tag -F msgfile1 -m "message 1" msgtag &&
        ! tag_exists msgtag &&
 -      ! git-tag -m "message 1" -F msgfile1 -m "message 2" msgtag &&
 +      test_must_fail git-tag -m "message 1" -F msgfile1 \
 +              -m "message 2" msgtag &&
        ! tag_exists msgtag
  '
  
  test_expect_success \
        'trying to verify an annotated non-signed tag should fail' '
        tag_exists annotated-tag &&
 -      ! git-tag -v annotated-tag
 +      test_must_fail git-tag -v annotated-tag
  '
  
  test_expect_success \
        'trying to verify a file-annotated non-signed tag should fail' '
        tag_exists file-annotated-tag &&
 -      ! git-tag -v file-annotated-tag
 +      test_must_fail git-tag -v file-annotated-tag
  '
  
  test_expect_success \
        'trying to verify two annotated non-signed tags should fail' '
        tag_exists annotated-tag file-annotated-tag &&
 -      ! git-tag -v annotated-tag file-annotated-tag
 +      test_must_fail git-tag -v annotated-tag file-annotated-tag
  '
  
  # creating and verifying signed tags:
@@@ -652,14 -651,13 +652,14 @@@ test_expect_success 'sign with a given 
  
  test_expect_success 'sign with an unknown id (1)' '
  
 -      ! git tag -u author@example.com -m "Another message" o-signed-tag
 +      test_must_fail git tag -u author@example.com \
 +              -m "Another message" o-signed-tag
  
  '
  
  test_expect_success 'sign with an unknown id (2)' '
  
 -      ! git tag -u DEADBEEF -m "Another message" o-signed-tag
 +      test_must_fail git tag -u DEADBEEF -m "Another message" o-signed-tag
  
  '
  
@@@ -720,7 -718,7 +720,7 @@@ test_expect_success 
        'trying to create a signed tag with non-existing -F file should fail' '
        ! test -f nonexistingfile &&
        ! tag_exists nosigtag &&
 -      ! git-tag -s -F nonexistingfile nosigtag &&
 +      test_must_fail git-tag -s -F nonexistingfile nosigtag &&
        ! tag_exists nosigtag
  '
  
@@@ -732,11 -730,10 +732,11 @@@ test_expect_success 'verifying two sign
  
  test_expect_success \
        'verifying many signed and non-signed tags should fail' '
 -      ! git-tag -v signed-tag annotated-tag &&
 -      ! git-tag -v file-annotated-tag file-signed-tag &&
 -      ! git-tag -v annotated-tag file-signed-tag file-annotated-tag &&
 -      ! git-tag -v signed-tag annotated-tag file-signed-tag
 +      test_must_fail git-tag -v signed-tag annotated-tag &&
 +      test_must_fail git-tag -v file-annotated-tag file-signed-tag &&
 +      test_must_fail git-tag -v annotated-tag \
 +              file-signed-tag file-annotated-tag &&
 +      test_must_fail git-tag -v signed-tag annotated-tag file-signed-tag
  '
  
  test_expect_success 'verifying a forged tag should fail' '
                sed -e "s/signed-tag/forged-tag/" |
                git mktag) &&
        git tag forged-tag $forged &&
 -      ! git-tag -v forged-tag
 +      test_must_fail git-tag -v forged-tag
  '
  
  # blank and empty messages for signed tags:
@@@ -1034,7 -1031,7 +1034,7 @@@ test_expect_success 
  git config user.signingkey BobTheMouse
  test_expect_success \
        'git-tag -s fails if gpg is misconfigured' \
 -      '! git tag -s -m tail tag-gpg-failure'
 +      'test_must_fail git tag -s -m tail tag-gpg-failure'
  git config --unset user.signingkey
  
  # try to verify without gpg:
  rm -rf gpghome
  test_expect_success \
        'verify signed tag fails when public key is not present' \
 -      '! git-tag -v signed-tag'
 +      'test_must_fail git-tag -v signed-tag'
  
  test_expect_success \
        'git-tag -a fails if tag annotation is empty' '
@@@ -1070,4 -1067,24 +1070,24 @@@ test_expect_success 
        test_cmp expect actual
  '
  
+ test_expect_success 'filename for the message is relative to cwd' '
+       mkdir subdir &&
+       echo "Tag message in top directory" >msgfile-5 &&
+       echo "Tag message in sub directory" >subdir/msgfile-5 &&
+       (
+               cd subdir &&
+               git tag -a -F msgfile-5 tag-from-subdir
+       ) &&
+       git cat-file tag tag-from-subdir | grep "in sub directory"
+ '
+ test_expect_success 'filename for the message is relative to cwd' '
+       echo "Tag message in sub directory" >subdir/msgfile-6 &&
+       (
+               cd subdir &&
+               git tag -a -F msgfile-6 tag-from-subdir-2
+       ) &&
+       git cat-file tag tag-from-subdir-2 | grep "in sub directory"
+ '
  test_done
diff --combined t/t7500-commit.sh
index d89f91a6fb7fa12d41cc4a346829bff7cbd3e76b,823256a246af72285b4d9fecb9ccd77340864a54..809bdba6309c57f149b6c7f3dc3f4e147f9eb24b
@@@ -23,12 -23,12 +23,12 @@@ test_expect_success 'a basic commit in 
  test_expect_success 'nonexistent template file should return error' '
        echo changes >> foo &&
        git add foo &&
 -      ! git commit --template "$PWD"/notexist
 +      test_must_fail git commit --template "$PWD"/notexist
  '
  
  test_expect_success 'nonexistent template file in config should return error' '
        git config commit.template "$PWD"/notexist &&
 -      ! git commit &&
 +      test_must_fail git commit &&
        git config --unset commit.template
  '
  
@@@ -37,12 -37,12 +37,12 @@@ TEMPLATE="$PWD"/templat
  
  test_expect_success 'unedited template should not commit' '
        echo "template line" > "$TEMPLATE" &&
 -      ! git commit --template "$TEMPLATE"
 +      test_must_fail git commit --template "$TEMPLATE"
  '
  
  test_expect_success 'unedited template with comments should not commit' '
        echo "# comment in template" >> "$TEMPLATE" &&
 -      ! git commit --template "$TEMPLATE"
 +      test_must_fail git commit --template "$TEMPLATE"
  '
  
  test_expect_success 'a Signed-off-by line by itself should not commit' '
@@@ -138,4 -138,33 +138,33 @@@ test_expect_success '--signoff' 
        diff expect output
  '
  
+ test_expect_success 'commit message from file (1)' '
+       mkdir subdir &&
+       echo "Log in top directory" >log &&
+       echo "Log in sub directory" >subdir/log &&
+       (
+               cd subdir &&
+               git commit --allow-empty -F log
+       ) &&
+       commit_msg_is "Log in sub directory"
+ '
+ test_expect_success 'commit message from file (2)' '
+       rm -f log &&
+       echo "Log in sub directory" >subdir/log &&
+       (
+               cd subdir &&
+               git commit --allow-empty -F log
+       ) &&
+       commit_msg_is "Log in sub directory"
+ '
+ test_expect_success 'commit message from stdin' '
+       (
+               cd subdir &&
+               echo "Log with foo word" | git commit --allow-empty -F -
+       ) &&
+       commit_msg_is "Log with foo word"
+ '
  test_done