diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index e726510ab1..9400f6a5d0 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git apply' [--stat] [--numstat] [--summary] [--check] [--index] - [--apply] [--no-add] [--build-fake-ancestor ] [-R | --reverse] + [--apply] [--no-add] [--build-fake-ancestor=] [-R | --reverse] [--allow-binary-replacement | --binary] [--reject] [-z] [-pNUM] [-CNUM] [--inaccurate-eof] [--recount] [--cached] [--whitespace=] @@ -64,7 +64,7 @@ OPTIONS cached data, apply the patch, and store the result in the index, without using the working tree. This implies '--index'. ---build-fake-ancestor :: +--build-fake-ancestor=:: Newer 'git-diff' output has embedded 'index information' for each blob to help identify the original version that the patch applies to. When this flag is given, and if diff --git a/builtin-apply.c b/builtin-apply.c index a8f75ed3ed..6d5a60214c 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -14,6 +14,7 @@ #include "builtin.h" #include "string-list.h" #include "dir.h" +#include "parse-options.h" /* * --check turns on checking that the working tree matches the @@ -45,9 +46,11 @@ static int apply_verbosely; static int no_add; static const char *fake_ancestor; static int line_termination = '\n'; -static unsigned long p_context = ULONG_MAX; -static const char apply_usage[] = -"git apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=] ..."; +static unsigned int p_context = UINT_MAX; +static const char * const apply_usage[] = { + "git apply [options] [...]", + NULL +}; static enum ws_error_action { nowarn_ws_error, @@ -61,6 +64,8 @@ static int applied_after_fixing_ws; static const char *patch_input_file; static const char *root; static int root_len; +static int read_stdin = 1; +static int options; static void parse_whitespace_option(const char *option) { @@ -3137,151 +3142,160 @@ static int git_apply_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } +static int option_parse_exclude(const struct option *opt, + const char *arg, int unset) +{ + add_name_limit(arg, 1); + return 0; +} + +static int option_parse_include(const struct option *opt, + const char *arg, int unset) +{ + add_name_limit(arg, 0); + has_include = 1; + return 0; +} + +static int option_parse_p(const struct option *opt, + const char *arg, int unset) +{ + p_value = atoi(arg); + p_value_known = 1; + return 0; +} + +static int option_parse_z(const struct option *opt, + const char *arg, int unset) +{ + if (unset) + line_termination = '\n'; + else + line_termination = 0; + return 0; +} + +static int option_parse_whitespace(const struct option *opt, + const char *arg, int unset) +{ + const char **whitespace_option = opt->value; + + *whitespace_option = arg; + parse_whitespace_option(arg); + return 0; +} + +static int option_parse_directory(const struct option *opt, + const char *arg, int unset) +{ + root_len = strlen(arg); + if (root_len && arg[root_len - 1] != '/') { + char *new_root; + root = new_root = xmalloc(root_len + 2); + strcpy(new_root, arg); + strcpy(new_root + root_len++, "/"); + } else + root = arg; + return 0; +} int cmd_apply(int argc, const char **argv, const char *unused_prefix) { int i; - int read_stdin = 1; - int options = 0; int errs = 0; int is_not_gitdir; + int binary; + int force_apply = 0; const char *whitespace_option = NULL; + struct option builtin_apply_options[] = { + { OPTION_CALLBACK, 0, "exclude", NULL, "path", + "donĀ“t apply changes matching the given path", + 0, option_parse_exclude }, + { OPTION_CALLBACK, 0, "include", NULL, "path", + "apply changes matching the given path", + 0, option_parse_include }, + { OPTION_CALLBACK, 'p', NULL, NULL, "num", + "remove leading slashes from traditional diff paths", + 0, option_parse_p }, + OPT_BOOLEAN(0, "no-add", &no_add, + "ignore additions made by the patch"), + OPT_BOOLEAN(0, "stat", &diffstat, + "instead of applying the patch, output diffstat for the input"), + OPT_BOOLEAN(0, "allow-binary-replacement", &binary, + "now no-op"), + OPT_BOOLEAN(0, "binary", &binary, + "now no-op"), + OPT_BOOLEAN(0, "numstat", &numstat, + "shows number of added and deleted lines in decimal notation"), + OPT_BOOLEAN(0, "summary", &summary, + "instead of applying the patch, output a summary for the input"), + OPT_BOOLEAN(0, "check", &check, + "instead of applying the patch, see if the patch is applicable"), + OPT_BOOLEAN(0, "index", &check_index, + "make sure the patch is applicable to the current index"), + OPT_BOOLEAN(0, "cached", &cached, + "apply a patch without touching the working tree"), + OPT_BOOLEAN(0, "apply", &force_apply, + "also apply the patch (use with --stat/--summary/--check)"), + OPT_STRING(0, "build-fake-ancestor", &fake_ancestor, "file", + "build a temporary index based on embedded index information"), + { OPTION_CALLBACK, 'z', NULL, NULL, NULL, + "paths are separated with NUL character", + PARSE_OPT_NOARG, option_parse_z }, + OPT_INTEGER('C', NULL, &p_context, + "ensure at least lines of context match"), + { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, "action", + "detect new or modified lines that have whitespace errors", + 0, option_parse_whitespace }, + OPT_BOOLEAN('R', "reverse", &apply_in_reverse, + "apply the patch in reverse"), + OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero, + "don't expect at least one line of context"), + OPT_BOOLEAN(0, "reject", &apply_with_reject, + "leave the rejected hunks in corresponding *.rej files"), + OPT__VERBOSE(&apply_verbosely), + OPT_BIT(0, "inaccurate-eof", &options, + "tolerate incorrectly detected missing new-line at the end of file", + INACCURATE_EOF), + OPT_BIT(0, "recount", &options, + "do not trust the line counts in the hunk headers", + RECOUNT), + { OPTION_CALLBACK, 0, "directory", NULL, "root", + "prepend to all filenames", + 0, option_parse_directory }, + OPT_END() + }; + prefix = setup_git_directory_gently(&is_not_gitdir); prefix_length = prefix ? strlen(prefix) : 0; git_config(git_apply_config, NULL); if (apply_default_whitespace) parse_whitespace_option(apply_default_whitespace); - for (i = 1; i < argc; i++) { + argc = parse_options(argc, argv, builtin_apply_options, + apply_usage, 0); + if (apply_with_reject) + apply = apply_verbosely = 1; + if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor)) + apply = 0; + if (check_index && is_not_gitdir) + die("--index outside a repository"); + if (cached) { + if (is_not_gitdir) + die("--cached outside a repository"); + check_index = 1; + } + for (i = 0; i < argc; i++) { const char *arg = argv[i]; - char *end; int fd; if (!strcmp(arg, "-")) { errs |= apply_patch(0, "", options); read_stdin = 0; continue; - } - if (!prefixcmp(arg, "--exclude=")) { - add_name_limit(arg + 10, 1); - continue; - } - if (!prefixcmp(arg, "--include=")) { - add_name_limit(arg + 10, 0); - has_include = 1; - continue; - } - if (!prefixcmp(arg, "-p")) { - p_value = atoi(arg + 2); - p_value_known = 1; - continue; - } - if (!strcmp(arg, "--no-add")) { - no_add = 1; - continue; - } - if (!strcmp(arg, "--stat")) { - apply = 0; - diffstat = 1; - continue; - } - if (!strcmp(arg, "--allow-binary-replacement") || - !strcmp(arg, "--binary")) { - continue; /* now no-op */ - } - if (!strcmp(arg, "--numstat")) { - apply = 0; - numstat = 1; - continue; - } - if (!strcmp(arg, "--summary")) { - apply = 0; - summary = 1; - continue; - } - if (!strcmp(arg, "--check")) { - apply = 0; - check = 1; - continue; - } - if (!strcmp(arg, "--index")) { - if (is_not_gitdir) - die("--index outside a repository"); - check_index = 1; - continue; - } - if (!strcmp(arg, "--cached")) { - if (is_not_gitdir) - die("--cached outside a repository"); - check_index = 1; - cached = 1; - continue; - } - if (!strcmp(arg, "--apply")) { - apply = 1; - continue; - } - if (!strcmp(arg, "--build-fake-ancestor")) { - apply = 0; - if (++i >= argc) - die ("need a filename"); - fake_ancestor = argv[i]; - continue; - } - if (!strcmp(arg, "-z")) { - line_termination = 0; - continue; - } - if (!prefixcmp(arg, "-C")) { - p_context = strtoul(arg + 2, &end, 0); - if (*end != '\0') - die("unrecognized context count '%s'", arg + 2); - continue; - } - if (!prefixcmp(arg, "--whitespace=")) { - whitespace_option = arg + 13; - parse_whitespace_option(arg + 13); - continue; - } - if (!strcmp(arg, "-R") || !strcmp(arg, "--reverse")) { - apply_in_reverse = 1; - continue; - } - if (!strcmp(arg, "--unidiff-zero")) { - unidiff_zero = 1; - continue; - } - if (!strcmp(arg, "--reject")) { - apply = apply_with_reject = apply_verbosely = 1; - continue; - } - if (!strcmp(arg, "-v") || !strcmp(arg, "--verbose")) { - apply_verbosely = 1; - continue; - } - if (!strcmp(arg, "--inaccurate-eof")) { - options |= INACCURATE_EOF; - continue; - } - if (!strcmp(arg, "--recount")) { - options |= RECOUNT; - continue; - } - if (!prefixcmp(arg, "--directory=")) { - arg += strlen("--directory="); - root_len = strlen(arg); - if (root_len && arg[root_len - 1] != '/') { - char *new_root; - root = new_root = xmalloc(root_len + 2); - strcpy(new_root, arg); - strcpy(new_root + root_len++, "/"); - } else - root = arg; - continue; - } - if (0 < prefix_length) + } else if (0 < prefix_length) arg = prefix_filename(prefix, prefix_length, arg); fd = open(arg, O_RDONLY); diff --git a/t/t4106-apply-stdin.sh b/t/t4106-apply-stdin.sh new file mode 100755 index 0000000000..72467a1e8e --- /dev/null +++ b/t/t4106-apply-stdin.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +test_description='git apply --numstat - text && + git add text && + echo goodbye >text && + git diff >patch +' + +test_expect_success 'git apply --numstat - < patch' ' + echo "1 1 text" >expect && + git apply --numstat - actual && + test_cmp expect actual +' + +test_expect_success 'git apply --numstat - < patch patch' ' + for i in 1 2; do echo "1 1 text"; done >expect && + git apply --numstat - < patch patch >actual && + test_cmp expect actual +' + +test_done