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>...]
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
-------
--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::
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.
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
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
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],
+
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::
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
------------
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
'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'::
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.
+
'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
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
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
---
#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
};
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
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)
{
strbuf_setlen(buf, 0);
else {
strbuf_addstr(buf, arg);
- strbuf_addch(buf, '\n');
- strbuf_addch(buf, '\n');
+ strbuf_addstr(buf, "\n\n");
}
return 0;
}
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"),
* 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);
}
}
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);
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;
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 &&
}
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)
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);
}
}
-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);
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))
#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;
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;
}
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);
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;
#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;
}
}
- 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, '=');
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"
*(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);
+ }
+
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 {
PARSE_OPT_NOARG = 2,
PARSE_OPT_NONEG = 4,
PARSE_OPT_HIDDEN = 8,
+ PARSE_OPT_LASTARG_DEFAULT = 16,
};
struct option;
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
'! (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
'
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
'
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
'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:
'
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:
'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
'
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:
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
'
'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
'
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:
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' '
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
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
'
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' '
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