diff options
Diffstat (limited to 't')
698 files changed, 51699 insertions, 9169 deletions
diff --git a/t/.gitattributes b/t/.gitattributes index 2d44088f56..3bd959ae52 100644 --- a/t/.gitattributes +++ b/t/.gitattributes @@ -1,2 +1,22 @@ t[0-9][0-9][0-9][0-9]/* -whitespace -t0110/url-* binary +/diff-lib/* eol=lf +/t0110/url-* binary +/t3900/*.txt eol=lf +/t3901/*.txt eol=lf +/t4034/*/* eol=lf +/t4013/* eol=lf +/t4018/* eol=lf +/t4051/* eol=lf +/t4100/* eol=lf +/t4101/* eol=lf +/t4109/* eol=lf +/t4110/* eol=lf +/t4135/* eol=lf +/t4211/* eol=lf +/t4252/* eol=lf +/t5100/* eol=lf +/t5515/* eol=lf +/t556x_common eol=lf +/t7500/* eol=lf +/t8005/*.txt eol=lf +/t9*/*.dump eol=lf diff --git a/t/Makefile b/t/Makefile index 43b15e36ae..96317a35f4 100644 --- a/t/Makefile +++ b/t/Makefile @@ -8,6 +8,7 @@ #GIT_TEST_OPTS = --verbose --debug SHELL_PATH ?= $(SHELL) +TEST_SHELL_PATH ?= $(SHELL_PATH) PERL_PATH ?= /usr/bin/perl TAR ?= $(TAR) RM ?= rm -f @@ -23,11 +24,11 @@ endif # Shell quote; SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) +TEST_SHELL_PATH_SQ = $(subst ','\'',$(TEST_SHELL_PATH)) PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) TEST_RESULTS_DIRECTORY_SQ = $(subst ','\'',$(TEST_RESULTS_DIRECTORY)) T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)) -TSVN = $(sort $(wildcard t91[0-9][0-9]-*.sh)) TGITWEB = $(sort $(wildcard t95[0-9][0-9]-*.sh)) THELPERS = $(sort $(filter-out $(T),$(wildcard *.sh))) @@ -36,12 +37,18 @@ all: $(DEFAULT_TEST_TARGET) test: pre-clean $(TEST_LINT) $(MAKE) aggregate-results-and-cleanup +failed: + @failed=$$(cd '$(TEST_RESULTS_DIRECTORY_SQ)' && \ + grep -l '^failed [1-9]' *.counts | \ + sed -n 's/\.counts$$/.sh/p') && \ + test -z "$$failed" || $(MAKE) $$failed + prove: pre-clean $(TEST_LINT) - @echo "*** prove ***"; $(PROVE) --exec '$(SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS) + @echo "*** prove ***"; $(PROVE) --exec '$(TEST_SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS) $(MAKE) clean-except-prove-cache $(T): - @echo "*** $@ ***"; '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS) + @echo "*** $@ ***"; '$(TEST_SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS) pre-clean: $(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)' @@ -53,7 +60,8 @@ clean-except-prove-cache: clean: clean-except-prove-cache $(RM) .prove -test-lint: test-lint-duplicates test-lint-executable test-lint-shell-syntax +test-lint: test-lint-duplicates test-lint-executable test-lint-shell-syntax \ + test-lint-filenames test-lint-duplicates: @dups=`echo $(T) | tr ' ' '\n' | sed 's/-.*//' | sort | uniq -d` && \ @@ -68,6 +76,14 @@ test-lint-executable: test-lint-shell-syntax: @'$(PERL_PATH_SQ)' check-non-portable-shell.pl $(T) $(THELPERS) +test-lint-filenames: + @# We do *not* pass a glob to ls-files but use grep instead, to catch + @# non-ASCII characters (which are quoted within double-quotes) + @bad="$$(git -c core.quotepath=true ls-files 2>/dev/null | \ + grep '["*:<>?\\|]')"; \ + test -z "$$bad" || { \ + echo >&2 "non-portable file name(s): $$bad"; exit 1; } + aggregate-results-and-cleanup: $(T) $(MAKE) aggregate-results $(MAKE) clean @@ -77,11 +93,6 @@ aggregate-results: echo "$$f"; \ done | '$(SHELL_PATH_SQ)' ./aggregate-results.sh -# we can test NO_OPTIMIZE_COMMITS independently of LC_ALL -full-svn-test: - $(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C - $(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=0 LC_ALL=en_US.UTF-8 - gitweb-test: $(MAKE) $(TGITWEB) @@ -84,9 +84,10 @@ appropriately before running "make". -x:: Turn on shell tracing (i.e., `set -x`) during the tests - themselves. Implies `--verbose`. Note that this can cause - failures in some tests which redirect and test the - output of shell functions. Use with caution. + themselves. Implies `--verbose`. + Ignored in test scripts that set the variable 'test_untraceable' + to a non-empty value, unless it's run with a Bash version + supporting BASH_XTRACEFD, i.e. v4.1 or later. -d:: --debug:: @@ -153,6 +154,12 @@ appropriately before running "make". As the names depend on the tests' file names, it is safe to run the tests with this option in parallel. +--verbose-log:: + Write verbose output to the same logfile as `--tee`, but do + _not_ write it to stdout. Unlike `--tee --verbose`, this option + is safe to use when stdout is being consumed by a TAP parser + like `prove`. Implies `--tee` and `--verbose`. + --with-dashes:: By default tests are run without dashed forms of commands (like git-commit) in the PATH (it only uses @@ -259,13 +266,13 @@ or: $ sh ./t9200-git-cvsexport-commit.sh --run='-3 21' -As noted above, the test set is built going though items left to -right, so this: +As noted above, the test set is built by going through the items +from left to right, so this: $ sh ./t9200-git-cvsexport-commit.sh --run='1-4 !3' -will run tests 1, 2, and 4. Items that comes later have higher -precendence. It means that this: +will run tests 1, 2, and 4. Items that come later have higher +precedence. It means that this: $ sh ./t9200-git-cvsexport-commit.sh --run='!3 1-4' @@ -326,13 +333,10 @@ Writing Tests ------------- The test script is written as a shell script. It should start -with the standard "#!/bin/sh" with copyright notices, and an +with the standard "#!/bin/sh", and an assignment to variable 'test_description', like this: #!/bin/sh - # - # Copyright (c) 2005 Junio C Hamano - # test_description='xxx test (option --frotz) @@ -449,6 +453,22 @@ Don't: causing the next test to start in an unexpected directory. Do so inside a subshell if necessary. + - save and verify the standard error of compound commands, i.e. group + commands, subshells, and shell functions (except test helper + functions like 'test_must_fail') like this: + + ( cd dir && git cmd ) 2>error && + test_cmp expect error + + When running the test with '-x' tracing, then the trace of commands + executed in the compound command will be included in standard error + as well, quite possibly throwing off the subsequent checks examining + the output. Instead, save only the relevant git command's standard + error: + + ( cd dir && git cmd 2>../error ) && + test_cmp expect error + - Break the TAP output The raw output from your test may be interpreted by a TAP harness. TAP @@ -465,13 +485,13 @@ Don't: their output. You can glean some further possible issues from the TAP grammar - (see http://search.cpan.org/perldoc?TAP::Parser::Grammar#TAP_Grammar) + (see https://metacpan.org/pod/TAP::Parser::Grammar#TAP-GRAMMAR) but the best indication is to just run the tests with prove(1), it'll complain if anything is amiss. Keep in mind: - - Inside <script> part, the standard output and standard error + - Inside the <script> part, the standard output and standard error streams are discarded, and the test harness only reports "ok" or "not ok" to the end user running the tests. Under --verbose, they are shown to help debugging the tests. @@ -563,6 +583,11 @@ library for your script to use. argument. This is primarily meant for use during the development of a new test script. + - debug <git-command> + + Run a git command inside a debugger. This is primarily meant for + use when debugging a failing test script. + - test_done Your test script must have test_done at the end. Its purpose @@ -600,9 +625,11 @@ library for your script to use. - test_have_prereq <prereq> - Check if we have a prerequisite previously set with - test_set_prereq. The most common use of this directly is to skip - all the tests if we don't have some essential prerequisite: + Check if we have a prerequisite previously set with test_set_prereq. + The most common way to use this explicitly (as opposed to the + implicit use when an argument is passed to test_expect_*) is to skip + all the tests at the start of the test script if we don't have some + essential prerequisite: if ! test_have_prereq PERL then @@ -645,7 +672,7 @@ library for your script to use. test_expect_code 1 git merge "merge msg" B master ' - - test_must_fail <git-command> + - test_must_fail [<options>] <git-command> Run a git command and ensure it fails in a controlled way. Use this instead of "! <git-command>". When git-command dies due to a @@ -653,17 +680,32 @@ library for your script to use. treats it as just another expected failure, which would let such a bug go unnoticed. - - test_might_fail <git-command> + Accepts the following options: + + ok=<signal-name>[,<...>]: + Don't treat an exit caused by the given signal as error. + Multiple signals can be specified as a comma separated list. + Currently recognized signal names are: sigpipe, success. + (Don't use 'success', use 'test_might_fail' instead.) + + - test_might_fail [<options>] <git-command> Similar to test_must_fail, but tolerate success, too. Use this instead of "<git-command> || :" to catch failures due to segv. + Accepts the same options as test_must_fail. + - test_cmp <expected> <actual> Check whether the content of the <actual> file matches the <expected> file. This behaves like "cmp" but produces more helpful output when the test is run with "-v" option. + - test_cmp_rev <expected> <actual> + + Check whether the <expected> rev points to the same commit as the + <actual> rev. + - test_line_count (= | -lt | -ge | ...) <length> <file> Check whether a file has the length it is expected to. @@ -790,11 +832,23 @@ use these, and "test_set_prereq" for how to define your own. Test is not run by root user, and an attempt to write to an unwritable file is expected to fail correctly. - - LIBPCRE + - PCRE - Git was compiled with USE_LIBPCRE=YesPlease. Wrap any tests + Git was compiled with support for PCRE. Wrap any tests that use git-grep --perl-regexp or git-grep -P in these. + - LIBPCRE1 + + Git was compiled with PCRE v1 support via + USE_LIBPCRE1=YesPlease. Wrap any PCRE using tests that for some + reason need v1 of the PCRE library instead of v2 in these. + + - LIBPCRE2 + + Git was compiled with PCRE v2 support via + USE_LIBPCRE2=YesPlease. Wrap any PCRE using tests that for some + reason need v2 of the PCRE library instead of v1 in these. + - CASE_INSENSITIVE_FS Test is run on a case insensitive file system. @@ -804,6 +858,10 @@ use these, and "test_set_prereq" for how to define your own. Test is run on a filesystem which converts decomposed utf-8 (nfd) to precomposed utf-8 (nfc). + - PTHREADS + + Git wasn't compiled with NO_PTHREADS=YesPlease. + Tips for Writing Tests ---------------------- diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh index f5c01758ca..093832fef1 100644 --- a/t/annotate-tests.sh +++ b/t/annotate-tests.sh @@ -68,6 +68,13 @@ test_expect_success 'blame 1 author' ' check_count A 2 ' +test_expect_success 'blame by tag objects' ' + git tag -m "test tag" testTag && + git tag -m "test tag #2" testTag2 testTag && + check_count -h testTag A 2 && + check_count -h testTag2 A 2 +' + test_expect_success 'setup B lines' ' echo "2A quick brown fox jumps over the" >>file && echo "lazy dog" >>file && @@ -111,6 +118,10 @@ test_expect_success 'blame 2 authors + 2 merged-in authors' ' check_count A 2 B 1 B1 2 B2 1 ' +test_expect_success 'blame --first-parent blames merge for branch1' ' + check_count --first-parent A 2 B 1 "A U Thor" 2 B2 1 +' + test_expect_success 'blame ancestor' ' check_count -h master A 2 B 2 ' diff --git a/t/check-non-portable-shell.pl b/t/check-non-portable-shell.pl index b170cbc045..e07f028437 100755 --- a/t/check-non-portable-shell.pl +++ b/t/check-non-portable-shell.pl @@ -17,10 +17,11 @@ sub err { while (<>) { chomp; /\bsed\s+-i/ and err 'sed -i is not portable'; - /\becho\s+-n/ and err 'echo -n is not portable (please use printf)'; + /\becho\s+-[neE]/ and err 'echo with option is not portable (please use printf)'; /^\s*declare\s+/ and err 'arrays/declare not portable'; /^\s*[^#]\s*which\s/ and err 'which is not portable (please use type)'; /\btest\s+[^=]*==/ and err '"test a == b" is not portable (please use =)'; + /\bwc -l.*"\s*=/ and err '`"$(wc -l)"` is not portable (please use test_line_count)'; /\bexport\s+[A-Za-z0-9_]*=/ and err '"export FOO=bar" is not portable (please use FOO=bar && export FOO)'; # this resets our $. for each file close ARGV if eof; diff --git a/t/gitweb-lib.sh b/t/gitweb-lib.sh index d5dab5a94f..006d2a8152 100644 --- a/t/gitweb-lib.sh +++ b/t/gitweb-lib.sh @@ -110,7 +110,12 @@ perl -MEncode -e '$e="";decode_utf8($e, Encode::FB_CROAK)' >/dev/null 2>&1 || { } perl -MCGI -MCGI::Util -MCGI::Carp -e 0 >/dev/null 2>&1 || { - skip_all='skipping gitweb tests, CGI module unusable' + skip_all='skipping gitweb tests, CGI & CGI::Util & CGI::Carp modules not available' + test_done +} + +perl -mTime::HiRes -e 0 >/dev/null 2>&1 || { + skip_all='skipping gitweb tests, Time::HiRes module not available' test_done } diff --git a/t/helper/.gitignore b/t/helper/.gitignore new file mode 100644 index 0000000000..2bad28af92 --- /dev/null +++ b/t/helper/.gitignore @@ -0,0 +1,5 @@ +* +!*.sh +!*.[ch] +!*.gitignore + diff --git a/t/helper/test-chmtime.c b/t/helper/test-chmtime.c new file mode 100644 index 0000000000..1790ceab51 --- /dev/null +++ b/t/helper/test-chmtime.c @@ -0,0 +1,120 @@ +/* + * This program can either change modification time of the given + * file(s) or just print it. The program does not change atime or + * ctime (their values are explicitly preserved). + * + * The mtime can be changed to an absolute value: + * + * test-tool chmtime =<seconds> file... + * + * Relative to the current time as returned by time(3): + * + * test-tool chmtime =+<seconds> (or =-<seconds>) file... + * + * Or relative to the current mtime of the file: + * + * test-tool chmtime <seconds> file... + * test-tool chmtime +<seconds> (or -<seconds>) file... + * + * Examples: + * + * To just print the mtime use --verbose and set the file mtime offset to 0: + * + * test-tool chmtime -v +0 file + * + * To set the mtime to current time: + * + * test-tool chmtime =+0 file + * + */ +#include "test-tool.h" +#include "git-compat-util.h" +#include <utime.h> + +static const char usage_str[] = "-v|--verbose (+|=|=+|=-|-)<seconds> <file>..."; + +static int timespec_arg(const char *arg, long int *set_time, int *set_eq) +{ + char *test; + const char *timespec = arg; + *set_eq = (*timespec == '=') ? 1 : 0; + if (*set_eq) { + timespec++; + if (*timespec == '+') { + *set_eq = 2; /* relative "in the future" */ + timespec++; + } + } + *set_time = strtol(timespec, &test, 10); + if (*test) { + fprintf(stderr, "Not a base-10 integer: %s\n", arg + 1); + return 0; + } + if ((*set_eq && *set_time < 0) || *set_eq == 2) { + time_t now = time(NULL); + *set_time += now; + } + return 1; +} + +int cmd__chmtime(int argc, const char **argv) +{ + static int verbose; + + int i = 1; + /* no mtime change by default */ + int set_eq = 0; + long int set_time = 0; + + if (argc < 3) + goto usage; + + if (strcmp(argv[i], "--verbose") == 0 || strcmp(argv[i], "-v") == 0) { + verbose = 1; + ++i; + } + if (timespec_arg(argv[i], &set_time, &set_eq)) + ++i; + else + goto usage; + + for (; i < argc; i++) { + struct stat sb; + struct utimbuf utb; + + if (stat(argv[i], &sb) < 0) { + fprintf(stderr, "Failed to stat %s: %s\n", + argv[i], strerror(errno)); + return 1; + } + +#ifdef GIT_WINDOWS_NATIVE + if (!(sb.st_mode & S_IWUSR) && + chmod(argv[i], sb.st_mode | S_IWUSR)) { + fprintf(stderr, "Could not make user-writable %s: %s", + argv[i], strerror(errno)); + return 1; + } +#endif + + utb.actime = sb.st_atime; + utb.modtime = set_eq ? set_time : sb.st_mtime + set_time; + + if (verbose) { + uintmax_t mtime = utb.modtime < 0 ? 0: utb.modtime; + printf("%"PRIuMAX"\t%s\n", mtime, argv[i]); + } + + if (utb.modtime != sb.st_mtime && utime(argv[i], &utb) < 0) { + fprintf(stderr, "Failed to modify time on %s: %s\n", + argv[i], strerror(errno)); + return 1; + } + } + + return 0; + +usage: + fprintf(stderr, "usage: %s %s\n", argv[0], usage_str); + return 1; +} diff --git a/t/helper/test-config.c b/t/helper/test-config.c new file mode 100644 index 0000000000..214003d5b2 --- /dev/null +++ b/t/helper/test-config.c @@ -0,0 +1,208 @@ +#include "test-tool.h" +#include "cache.h" +#include "config.h" +#include "string-list.h" + +/* + * This program exposes the C API of the configuration mechanism + * as a set of simple commands in order to facilitate testing. + * + * Reads stdin and prints result of command to stdout: + * + * get_value -> prints the value with highest priority for the entered key + * + * get_value_multi -> prints all values for the entered key in increasing order + * of priority + * + * get_int -> print integer value for the entered key or die + * + * get_bool -> print bool value for the entered key or die + * + * get_string -> print string value for the entered key or die + * + * configset_get_value -> returns value with the highest priority for the entered key + * from a config_set constructed from files entered as arguments. + * + * configset_get_value_multi -> returns value_list for the entered key sorted in + * ascending order of priority from a config_set + * constructed from files entered as arguments. + * + * iterate -> iterate over all values using git_config(), and print some + * data for each + * + * Examples: + * + * To print the value with highest priority for key "foo.bAr Baz.rock": + * test-tool config get_value "foo.bAr Baz.rock" + * + */ + +static const char *scope_name(enum config_scope scope) +{ + switch (scope) { + case CONFIG_SCOPE_SYSTEM: + return "system"; + case CONFIG_SCOPE_GLOBAL: + return "global"; + case CONFIG_SCOPE_REPO: + return "repo"; + case CONFIG_SCOPE_CMDLINE: + return "cmdline"; + default: + return "unknown"; + } +} +static int iterate_cb(const char *var, const char *value, void *data) +{ + static int nr; + + if (nr++) + putchar('\n'); + + printf("key=%s\n", var); + printf("value=%s\n", value ? value : "(null)"); + printf("origin=%s\n", current_config_origin_type()); + printf("name=%s\n", current_config_name()); + printf("scope=%s\n", scope_name(current_config_scope())); + + return 0; +} + +static int early_config_cb(const char *var, const char *value, void *vdata) +{ + const char *key = vdata; + + if (!strcmp(key, var)) + printf("%s\n", value); + + return 0; +} + +int cmd__config(int argc, const char **argv) +{ + int i, val; + const char *v; + const struct string_list *strptr; + struct config_set cs; + + if (argc == 3 && !strcmp(argv[1], "read_early_config")) { + read_early_config(early_config_cb, (void *)argv[2]); + return 0; + } + + setup_git_directory(); + + git_configset_init(&cs); + + if (argc < 2) { + fprintf(stderr, "Please, provide a command name on the command-line\n"); + goto exit1; + } else if (argc == 3 && !strcmp(argv[1], "get_value")) { + if (!git_config_get_value(argv[2], &v)) { + if (!v) + printf("(NULL)\n"); + else + printf("%s\n", v); + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (argc == 3 && !strcmp(argv[1], "get_value_multi")) { + strptr = git_config_get_value_multi(argv[2]); + if (strptr) { + for (i = 0; i < strptr->nr; i++) { + v = strptr->items[i].string; + if (!v) + printf("(NULL)\n"); + else + printf("%s\n", v); + } + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (argc == 3 && !strcmp(argv[1], "get_int")) { + if (!git_config_get_int(argv[2], &val)) { + printf("%d\n", val); + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (argc == 3 && !strcmp(argv[1], "get_bool")) { + if (!git_config_get_bool(argv[2], &val)) { + printf("%d\n", val); + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (argc == 3 && !strcmp(argv[1], "get_string")) { + if (!git_config_get_string_const(argv[2], &v)) { + printf("%s\n", v); + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (!strcmp(argv[1], "configset_get_value")) { + for (i = 3; i < argc; i++) { + int err; + if ((err = git_configset_add_file(&cs, argv[i]))) { + fprintf(stderr, "Error (%d) reading configuration file %s.\n", err, argv[i]); + goto exit2; + } + } + if (!git_configset_get_value(&cs, argv[2], &v)) { + if (!v) + printf("(NULL)\n"); + else + printf("%s\n", v); + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (!strcmp(argv[1], "configset_get_value_multi")) { + for (i = 3; i < argc; i++) { + int err; + if ((err = git_configset_add_file(&cs, argv[i]))) { + fprintf(stderr, "Error (%d) reading configuration file %s.\n", err, argv[i]); + goto exit2; + } + } + strptr = git_configset_get_value_multi(&cs, argv[2]); + if (strptr) { + for (i = 0; i < strptr->nr; i++) { + v = strptr->items[i].string; + if (!v) + printf("(NULL)\n"); + else + printf("%s\n", v); + } + goto exit0; + } else { + printf("Value not found for \"%s\"\n", argv[2]); + goto exit1; + } + } else if (!strcmp(argv[1], "iterate")) { + git_config(iterate_cb, NULL); + goto exit0; + } + + die("%s: Please check the syntax and the function name", argv[0]); + +exit0: + git_configset_clear(&cs); + return 0; + +exit1: + git_configset_clear(&cs); + return 1; + +exit2: + git_configset_clear(&cs); + return 2; +} diff --git a/t/helper/test-ctype.c b/t/helper/test-ctype.c new file mode 100644 index 0000000000..92c4c2313e --- /dev/null +++ b/t/helper/test-ctype.c @@ -0,0 +1,43 @@ +#include "test-tool.h" +#include "cache.h" + +static int rc; + +static void report_error(const char *class, int ch) +{ + printf("%s classifies char %d (0x%02x) wrongly\n", class, ch, ch); + rc = 1; +} + +static int is_in(const char *s, int ch) +{ + /* We can't find NUL using strchr. It's classless anyway. */ + if (ch == '\0') + return 0; + return !!strchr(s, ch); +} + +#define TEST_CLASS(t,s) { \ + int i; \ + for (i = 0; i < 256; i++) { \ + if (is_in(s, i) != t(i)) \ + report_error(#t, i); \ + } \ +} + +#define DIGIT "0123456789" +#define LOWER "abcdefghijklmnopqrstuvwxyz" +#define UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + +int cmd__ctype(int argc, const char **argv) +{ + TEST_CLASS(isdigit, DIGIT); + TEST_CLASS(isspace, " \n\r\t"); + TEST_CLASS(isalpha, LOWER UPPER); + TEST_CLASS(isalnum, LOWER UPPER DIGIT); + TEST_CLASS(is_glob_special, "*?[\\"); + TEST_CLASS(is_regex_special, "$()*+.?[\\^{|"); + TEST_CLASS(is_pathspec_magic, "!\"#%&',-/:;<=>@_`~"); + + return rc; +} diff --git a/t/helper/test-date.c b/t/helper/test-date.c new file mode 100644 index 0000000000..a0837371ab --- /dev/null +++ b/t/helper/test-date.c @@ -0,0 +1,118 @@ +#include "test-tool.h" +#include "cache.h" + +static const char *usage_msg = "\n" +" test-tool date relative [time_t]...\n" +" test-tool date show:<format> [time_t]...\n" +" test-tool date parse [date]...\n" +" test-tool date approxidate [date]...\n" +" test-tool date timestamp [date]...\n" +" test-tool date is64bit\n" +" test-tool date time_t-is64bit\n"; + +static void show_relative_dates(const char **argv, struct timeval *now) +{ + struct strbuf buf = STRBUF_INIT; + + for (; *argv; argv++) { + time_t t = atoi(*argv); + show_date_relative(t, 0, now, &buf); + printf("%s -> %s\n", *argv, buf.buf); + } + strbuf_release(&buf); +} + +static void show_dates(const char **argv, const char *format) +{ + struct date_mode mode; + + parse_date_format(format, &mode); + for (; *argv; argv++) { + char *arg; + timestamp_t t; + int tz; + + /* + * Do not use our normal timestamp parsing here, as the point + * is to test the formatting code in isolation. + */ + t = parse_timestamp(*argv, &arg, 10); + while (*arg == ' ') + arg++; + tz = atoi(arg); + + printf("%s -> %s\n", *argv, show_date(t, tz, &mode)); + } +} + +static void parse_dates(const char **argv, struct timeval *now) +{ + struct strbuf result = STRBUF_INIT; + + for (; *argv; argv++) { + timestamp_t t; + int tz; + + strbuf_reset(&result); + parse_date(*argv, &result); + if (sscanf(result.buf, "%"PRItime" %d", &t, &tz) == 2) + printf("%s -> %s\n", + *argv, show_date(t, tz, DATE_MODE(ISO8601))); + else + printf("%s -> bad\n", *argv); + } + strbuf_release(&result); +} + +static void parse_approxidate(const char **argv, struct timeval *now) +{ + for (; *argv; argv++) { + timestamp_t t; + t = approxidate_relative(*argv, now); + printf("%s -> %s\n", *argv, show_date(t, 0, DATE_MODE(ISO8601))); + } +} + +static void parse_approx_timestamp(const char **argv, struct timeval *now) +{ + for (; *argv; argv++) { + timestamp_t t; + t = approxidate_relative(*argv, now); + printf("%s -> %"PRItime"\n", *argv, t); + } +} + +int cmd__date(int argc, const char **argv) +{ + struct timeval now; + const char *x; + + x = getenv("TEST_DATE_NOW"); + if (x) { + now.tv_sec = atoi(x); + now.tv_usec = 0; + } + else + gettimeofday(&now, NULL); + + argv++; + if (!*argv) + usage(usage_msg); + if (!strcmp(*argv, "relative")) + show_relative_dates(argv+1, &now); + else if (skip_prefix(*argv, "show:", &x)) + show_dates(argv+1, x); + else if (!strcmp(*argv, "parse")) + parse_dates(argv+1, &now); + else if (!strcmp(*argv, "approxidate")) + parse_approxidate(argv+1, &now); + else if (!strcmp(*argv, "timestamp")) + parse_approx_timestamp(argv+1, &now); + else if (!strcmp(*argv, "is64bit")) + return sizeof(timestamp_t) == 8 ? 0 : 1; + else if (!strcmp(*argv, "time_t-is64bit")) + return sizeof(time_t) == 8 ? 0 : 1; + else + usage(usage_msg); + return 0; +} diff --git a/t/helper/test-delta.c b/t/helper/test-delta.c new file mode 100644 index 0000000000..34c7259248 --- /dev/null +++ b/t/helper/test-delta.c @@ -0,0 +1,79 @@ +/* + * test-delta.c: test code to exercise diff-delta.c and patch-delta.c + * + * (C) 2005 Nicolas Pitre <nico@fluxnic.net> + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "test-tool.h" +#include "git-compat-util.h" +#include "delta.h" +#include "cache.h" + +static const char usage_str[] = + "test-tool delta (-d|-p) <from_file> <data_file> <out_file>"; + +int cmd__delta(int argc, const char **argv) +{ + int fd; + struct stat st; + void *from_buf, *data_buf, *out_buf; + unsigned long from_size, data_size, out_size; + + if (argc != 5 || (strcmp(argv[1], "-d") && strcmp(argv[1], "-p"))) { + fprintf(stderr, "usage: %s\n", usage_str); + return 1; + } + + fd = open(argv[2], O_RDONLY); + if (fd < 0 || fstat(fd, &st)) { + perror(argv[2]); + return 1; + } + from_size = st.st_size; + from_buf = mmap(NULL, from_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (from_buf == MAP_FAILED) { + perror(argv[2]); + close(fd); + return 1; + } + close(fd); + + fd = open(argv[3], O_RDONLY); + if (fd < 0 || fstat(fd, &st)) { + perror(argv[3]); + return 1; + } + data_size = st.st_size; + data_buf = mmap(NULL, data_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (data_buf == MAP_FAILED) { + perror(argv[3]); + close(fd); + return 1; + } + close(fd); + + if (argv[1][1] == 'd') + out_buf = diff_delta(from_buf, from_size, + data_buf, data_size, + &out_size, 0); + else + out_buf = patch_delta(from_buf, from_size, + data_buf, data_size, + &out_size); + if (!out_buf) { + fprintf(stderr, "delta operation failed (returned NULL)\n"); + return 1; + } + + fd = open (argv[4], O_WRONLY|O_CREAT|O_TRUNC, 0666); + if (fd < 0 || write_in_full(fd, out_buf, out_size) < 0) { + perror(argv[4]); + return 1; + } + + return 0; +} diff --git a/t/helper/test-drop-caches.c b/t/helper/test-drop-caches.c new file mode 100644 index 0000000000..838760898b --- /dev/null +++ b/t/helper/test-drop-caches.c @@ -0,0 +1,165 @@ +#include "test-tool.h" +#include "git-compat-util.h" + +#if defined(GIT_WINDOWS_NATIVE) + +static int cmd_sync(void) +{ + char Buffer[MAX_PATH]; + DWORD dwRet; + char szVolumeAccessPath[] = "\\\\.\\X:"; + HANDLE hVolWrite; + int success = 0; + + dwRet = GetCurrentDirectory(MAX_PATH, Buffer); + if ((0 == dwRet) || (dwRet > MAX_PATH)) + return error("Error getting current directory"); + + if ((Buffer[0] < 'A') || (Buffer[0] > 'Z')) + return error("Invalid drive letter '%c'", Buffer[0]); + + szVolumeAccessPath[4] = Buffer[0]; + hVolWrite = CreateFile(szVolumeAccessPath, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (INVALID_HANDLE_VALUE == hVolWrite) + return error("Unable to open volume for writing, need admin access"); + + success = FlushFileBuffers(hVolWrite); + if (!success) + error("Unable to flush volume"); + + CloseHandle(hVolWrite); + + return !success; +} + +#define STATUS_SUCCESS (0x00000000L) +#define STATUS_PRIVILEGE_NOT_HELD (0xC0000061L) + +typedef enum _SYSTEM_INFORMATION_CLASS { + SystemMemoryListInformation = 80, +} SYSTEM_INFORMATION_CLASS; + +typedef enum _SYSTEM_MEMORY_LIST_COMMAND { + MemoryCaptureAccessedBits, + MemoryCaptureAndResetAccessedBits, + MemoryEmptyWorkingSets, + MemoryFlushModifiedList, + MemoryPurgeStandbyList, + MemoryPurgeLowPriorityStandbyList, + MemoryCommandMax +} SYSTEM_MEMORY_LIST_COMMAND; + +static BOOL GetPrivilege(HANDLE TokenHandle, LPCSTR lpName, int flags) +{ + BOOL bResult; + DWORD dwBufferLength; + LUID luid; + TOKEN_PRIVILEGES tpPreviousState; + TOKEN_PRIVILEGES tpNewState; + + dwBufferLength = 16; + bResult = LookupPrivilegeValueA(0, lpName, &luid); + if (bResult) { + tpNewState.PrivilegeCount = 1; + tpNewState.Privileges[0].Luid = luid; + tpNewState.Privileges[0].Attributes = 0; + bResult = AdjustTokenPrivileges(TokenHandle, 0, &tpNewState, + (DWORD)((LPBYTE)&(tpNewState.Privileges[1]) - (LPBYTE)&tpNewState), + &tpPreviousState, &dwBufferLength); + if (bResult) { + tpPreviousState.PrivilegeCount = 1; + tpPreviousState.Privileges[0].Luid = luid; + tpPreviousState.Privileges[0].Attributes = flags != 0 ? 2 : 0; + bResult = AdjustTokenPrivileges(TokenHandle, 0, &tpPreviousState, + dwBufferLength, 0, 0); + } + } + return bResult; +} + +static int cmd_dropcaches(void) +{ + HANDLE hProcess = GetCurrentProcess(); + HANDLE hToken; + HMODULE ntdll; + DWORD(WINAPI *NtSetSystemInformation)(INT, PVOID, ULONG); + SYSTEM_MEMORY_LIST_COMMAND command; + int status; + + if (!OpenProcessToken(hProcess, TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken)) + return error("Can't open current process token"); + + if (!GetPrivilege(hToken, "SeProfileSingleProcessPrivilege", 1)) + return error("Can't get SeProfileSingleProcessPrivilege"); + + CloseHandle(hToken); + + ntdll = LoadLibrary("ntdll.dll"); + if (!ntdll) + return error("Can't load ntdll.dll, wrong Windows version?"); + + NtSetSystemInformation = + (DWORD(WINAPI *)(INT, PVOID, ULONG))GetProcAddress(ntdll, "NtSetSystemInformation"); + if (!NtSetSystemInformation) + return error("Can't get function addresses, wrong Windows version?"); + + command = MemoryPurgeStandbyList; + status = NtSetSystemInformation( + SystemMemoryListInformation, + &command, + sizeof(SYSTEM_MEMORY_LIST_COMMAND) + ); + if (status == STATUS_PRIVILEGE_NOT_HELD) + error("Insufficient privileges to purge the standby list, need admin access"); + else if (status != STATUS_SUCCESS) + error("Unable to execute the memory list command %d", status); + + FreeLibrary(ntdll); + + return status; +} + +#elif defined(__linux__) + +static int cmd_sync(void) +{ + return system("sync"); +} + +static int cmd_dropcaches(void) +{ + return system("echo 3 | sudo tee /proc/sys/vm/drop_caches"); +} + +#elif defined(__APPLE__) + +static int cmd_sync(void) +{ + return system("sync"); +} + +static int cmd_dropcaches(void) +{ + return system("sudo purge"); +} + +#else + +static int cmd_sync(void) +{ + return 0; +} + +static int cmd_dropcaches(void) +{ + return error("drop caches not implemented on this platform"); +} + +#endif + +int cmd__drop_caches(int argc, const char **argv) +{ + cmd_sync(); + return cmd_dropcaches(); +} diff --git a/t/helper/test-dump-cache-tree.c b/t/helper/test-dump-cache-tree.c new file mode 100644 index 0000000000..98a4891f1d --- /dev/null +++ b/t/helper/test-dump-cache-tree.c @@ -0,0 +1,69 @@ +#include "test-tool.h" +#include "cache.h" +#include "tree.h" +#include "cache-tree.h" + + +static void dump_one(struct cache_tree *it, const char *pfx, const char *x) +{ + if (it->entry_count < 0) + printf("%-40s %s%s (%d subtrees)\n", + "invalid", x, pfx, it->subtree_nr); + else + printf("%s %s%s (%d entries, %d subtrees)\n", + oid_to_hex(&it->oid), x, pfx, + it->entry_count, it->subtree_nr); +} + +static int dump_cache_tree(struct cache_tree *it, + struct cache_tree *ref, + const char *pfx) +{ + int i; + int errs = 0; + + if (!it || !ref) + /* missing in either */ + return 0; + + if (it->entry_count < 0) { + /* invalid */ + dump_one(it, pfx, ""); + dump_one(ref, pfx, "#(ref) "); + } + else { + dump_one(it, pfx, ""); + if (oidcmp(&it->oid, &ref->oid) || + ref->entry_count != it->entry_count || + ref->subtree_nr != it->subtree_nr) { + /* claims to be valid but is lying */ + dump_one(ref, pfx, "#(ref) "); + errs = 1; + } + } + + for (i = 0; i < it->subtree_nr; i++) { + char path[PATH_MAX]; + struct cache_tree_sub *down = it->down[i]; + struct cache_tree_sub *rdwn; + + rdwn = cache_tree_sub(ref, down->name); + xsnprintf(path, sizeof(path), "%s%.*s/", pfx, down->namelen, down->name); + if (dump_cache_tree(down->cache_tree, rdwn->cache_tree, path)) + errs = 1; + } + return errs; +} + +int cmd__dump_cache_tree(int ac, const char **av) +{ + struct index_state istate; + struct cache_tree *another = cache_tree(); + setup_git_directory(); + if (read_cache() < 0) + die("unable to read index file"); + istate = the_index; + istate.cache_tree = another; + cache_tree_update(&istate, WRITE_TREE_DRY_RUN); + return dump_cache_tree(active_cache_tree, another, ""); +} diff --git a/t/helper/test-dump-fsmonitor.c b/t/helper/test-dump-fsmonitor.c new file mode 100644 index 0000000000..ad452707e8 --- /dev/null +++ b/t/helper/test-dump-fsmonitor.c @@ -0,0 +1,21 @@ +#include "cache.h" + +int cmd_main(int ac, const char **av) +{ + struct index_state *istate = &the_index; + int i; + + setup_git_directory(); + if (do_read_index(istate, get_index_file(), 0) < 0) + die("unable to read index file"); + if (!istate->fsmonitor_last_update) { + printf("no fsmonitor\n"); + return 0; + } + printf("fsmonitor last update %"PRIuMAX"\n", (uintmax_t)istate->fsmonitor_last_update); + + for (i = 0; i < istate->cache_nr; i++) + printf((istate->cache[i]->ce_flags & CE_FSMONITOR_VALID) ? "+" : "-"); + + return 0; +} diff --git a/t/helper/test-dump-split-index.c b/t/helper/test-dump-split-index.c new file mode 100644 index 0000000000..4e2fdb5e30 --- /dev/null +++ b/t/helper/test-dump-split-index.c @@ -0,0 +1,37 @@ +#include "test-tool.h" +#include "cache.h" +#include "split-index.h" +#include "ewah/ewok.h" + +static void show_bit(size_t pos, void *data) +{ + printf(" %d", (int)pos); +} + +int cmd__dump_split_index(int ac, const char **av) +{ + struct split_index *si; + int i; + + do_read_index(&the_index, av[1], 1); + printf("own %s\n", sha1_to_hex(the_index.sha1)); + si = the_index.split_index; + if (!si) { + printf("not a split index\n"); + return 0; + } + printf("base %s\n", sha1_to_hex(si->base_sha1)); + for (i = 0; i < the_index.cache_nr; i++) { + struct cache_entry *ce = the_index.cache[i]; + printf("%06o %s %d\t%s\n", ce->ce_mode, + oid_to_hex(&ce->oid), ce_stage(ce), ce->name); + } + printf("replacements:"); + if (si->replace_bitmap) + ewah_each_bit(si->replace_bitmap, show_bit, NULL); + printf("\ndeletions:"); + if (si->delete_bitmap) + ewah_each_bit(si->delete_bitmap, show_bit, NULL); + printf("\n"); + return 0; +} diff --git a/t/helper/test-dump-untracked-cache.c b/t/helper/test-dump-untracked-cache.c new file mode 100644 index 0000000000..d7c55c2355 --- /dev/null +++ b/t/helper/test-dump-untracked-cache.c @@ -0,0 +1,64 @@ +#include "cache.h" +#include "dir.h" + +static int compare_untracked(const void *a_, const void *b_) +{ + const char *const *a = a_; + const char *const *b = b_; + return strcmp(*a, *b); +} + +static int compare_dir(const void *a_, const void *b_) +{ + const struct untracked_cache_dir *const *a = a_; + const struct untracked_cache_dir *const *b = b_; + return strcmp((*a)->name, (*b)->name); +} + +static void dump(struct untracked_cache_dir *ucd, struct strbuf *base) +{ + int i, len; + QSORT(ucd->untracked, ucd->untracked_nr, compare_untracked); + QSORT(ucd->dirs, ucd->dirs_nr, compare_dir); + len = base->len; + strbuf_addf(base, "%s/", ucd->name); + printf("%s %s", base->buf, + sha1_to_hex(ucd->exclude_sha1)); + if (ucd->recurse) + fputs(" recurse", stdout); + if (ucd->check_only) + fputs(" check_only", stdout); + if (ucd->valid) + fputs(" valid", stdout); + printf("\n"); + for (i = 0; i < ucd->untracked_nr; i++) + printf("%s\n", ucd->untracked[i]); + for (i = 0; i < ucd->dirs_nr; i++) + dump(ucd->dirs[i], base); + strbuf_setlen(base, len); +} + +int cmd_main(int ac, const char **av) +{ + struct untracked_cache *uc; + struct strbuf base = STRBUF_INIT; + + /* Hack to avoid modifying the untracked cache when we read it */ + ignore_untracked_cache_config = 1; + + setup_git_directory(); + if (read_cache() < 0) + die("unable to read index file"); + uc = the_index.untracked; + if (!uc) { + printf("no untracked cache\n"); + return 0; + } + printf("info/exclude %s\n", oid_to_hex(&uc->ss_info_exclude.oid)); + printf("core.excludesfile %s\n", oid_to_hex(&uc->ss_excludes_file.oid)); + printf("exclude_per_dir %s\n", uc->exclude_per_dir); + printf("flags %08x\n", uc->dir_flags); + if (uc->root) + dump(uc->root, &base); + return 0; +} diff --git a/t/helper/test-example-decorate.c b/t/helper/test-example-decorate.c new file mode 100644 index 0000000000..081115bf8e --- /dev/null +++ b/t/helper/test-example-decorate.c @@ -0,0 +1,75 @@ +#include "test-tool.h" +#include "cache.h" +#include "object.h" +#include "decorate.h" + +int cmd__example_decorate(int argc, const char **argv) +{ + struct decoration n; + struct object_id one_oid = { {1} }; + struct object_id two_oid = { {2} }; + struct object_id three_oid = { {3} }; + struct object *one, *two, *three; + + int decoration_a, decoration_b; + + void *ret; + + int i, objects_noticed = 0; + + /* + * The struct must be zero-initialized. + */ + memset(&n, 0, sizeof(n)); + + /* + * Add 2 objects, one with a non-NULL decoration and one with a NULL + * decoration. + */ + one = lookup_unknown_object(one_oid.hash); + two = lookup_unknown_object(two_oid.hash); + ret = add_decoration(&n, one, &decoration_a); + if (ret) + die("BUG: when adding a brand-new object, NULL should be returned"); + ret = add_decoration(&n, two, NULL); + if (ret) + die("BUG: when adding a brand-new object, NULL should be returned"); + + /* + * When re-adding an already existing object, the old decoration is + * returned. + */ + ret = add_decoration(&n, one, NULL); + if (ret != &decoration_a) + die("BUG: when readding an already existing object, existing decoration should be returned"); + ret = add_decoration(&n, two, &decoration_b); + if (ret) + die("BUG: when readding an already existing object, existing decoration should be returned"); + + /* + * Lookup returns the added declarations, or NULL if the object was + * never added. + */ + ret = lookup_decoration(&n, one); + if (ret) + die("BUG: lookup should return added declaration"); + ret = lookup_decoration(&n, two); + if (ret != &decoration_b) + die("BUG: lookup should return added declaration"); + three = lookup_unknown_object(three_oid.hash); + ret = lookup_decoration(&n, three); + if (ret) + die("BUG: lookup for unknown object should return NULL"); + + /* + * The user can also loop through all entries. + */ + for (i = 0; i < n.size; i++) { + if (n.entries[i].base) + objects_noticed++; + } + if (objects_noticed != 2) + die("BUG: should have 2 objects"); + + return 0; +} diff --git a/t/helper/test-fake-ssh.c b/t/helper/test-fake-ssh.c new file mode 100644 index 0000000000..12beee99ad --- /dev/null +++ b/t/helper/test-fake-ssh.c @@ -0,0 +1,30 @@ +#include "git-compat-util.h" +#include "run-command.h" +#include "strbuf.h" + +int cmd_main(int argc, const char **argv) +{ + const char *trash_directory = getenv("TRASH_DIRECTORY"); + struct strbuf buf = STRBUF_INIT; + FILE *f; + int i; + const char *child_argv[] = { NULL, NULL }; + + /* First, print all parameters into $TRASH_DIRECTORY/ssh-output */ + if (!trash_directory) + die("Need a TRASH_DIRECTORY!"); + strbuf_addf(&buf, "%s/ssh-output", trash_directory); + f = fopen(buf.buf, "w"); + if (!f) + die("Could not write to %s", buf.buf); + for (i = 0; i < argc; i++) + fprintf(f, "%s%s", i > 0 ? " " : "", i > 0 ? argv[i] : "ssh:"); + fprintf(f, "\n"); + fclose(f); + + /* Now, evaluate the *last* parameter */ + if (argc < 2) + return 0; + child_argv[0] = argv[argc - 1]; + return run_command_v_opt(child_argv, RUN_USING_SHELL); +} diff --git a/t/helper/test-genrandom.c b/t/helper/test-genrandom.c new file mode 100644 index 0000000000..99b8dc1e2d --- /dev/null +++ b/t/helper/test-genrandom.c @@ -0,0 +1,34 @@ +/* + * Simple random data generator used to create reproducible test files. + * This is inspired from POSIX.1-2001 implementation example for rand(). + * Copyright (C) 2007 by Nicolas Pitre, licensed under the GPL version 2. + */ + +#include "test-tool.h" +#include "git-compat-util.h" + +int cmd__genrandom(int argc, const char **argv) +{ + unsigned long count, next = 0; + unsigned char *c; + + if (argc < 2 || argc > 3) { + fprintf(stderr, "usage: %s <seed_string> [<size>]\n", argv[0]); + return 1; + } + + c = (unsigned char *) argv[1]; + do { + next = next * 11 + *c; + } while (*c++); + + count = (argc == 3) ? strtoul(argv[2], NULL, 0) : -1L; + + while (count--) { + next = next * 1103515245 + 12345; + if (putchar((next >> 16) & 0xff) == EOF) + return -1; + } + + return 0; +} diff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c new file mode 100644 index 0000000000..23d2b172fe --- /dev/null +++ b/t/helper/test-hashmap.c @@ -0,0 +1,270 @@ +#include "test-tool.h" +#include "git-compat-util.h" +#include "hashmap.h" +#include "strbuf.h" + +struct test_entry +{ + struct hashmap_entry ent; + /* key and value as two \0-terminated strings */ + char key[FLEX_ARRAY]; +}; + +static const char *get_value(const struct test_entry *e) +{ + return e->key + strlen(e->key) + 1; +} + +static int test_entry_cmp(const void *cmp_data, + const void *entry, + const void *entry_or_key, + const void *keydata) +{ + const int ignore_case = cmp_data ? *((int *)cmp_data) : 0; + const struct test_entry *e1 = entry; + const struct test_entry *e2 = entry_or_key; + const char *key = keydata; + + if (ignore_case) + return strcasecmp(e1->key, key ? key : e2->key); + else + return strcmp(e1->key, key ? key : e2->key); +} + +static struct test_entry *alloc_test_entry(unsigned int hash, + char *key, char *value) +{ + size_t klen = strlen(key); + size_t vlen = strlen(value); + struct test_entry *entry = xmalloc(st_add4(sizeof(*entry), klen, vlen, 2)); + hashmap_entry_init(entry, hash); + memcpy(entry->key, key, klen + 1); + memcpy(entry->key + klen + 1, value, vlen + 1); + return entry; +} + +#define HASH_METHOD_FNV 0 +#define HASH_METHOD_I 1 +#define HASH_METHOD_IDIV10 2 +#define HASH_METHOD_0 3 +#define HASH_METHOD_X2 4 +#define TEST_SPARSE 8 +#define TEST_ADD 16 +#define TEST_SIZE 100000 + +static unsigned int hash(unsigned int method, unsigned int i, const char *key) +{ + unsigned int hash = 0; + switch (method & 3) + { + case HASH_METHOD_FNV: + hash = strhash(key); + break; + case HASH_METHOD_I: + hash = i; + break; + case HASH_METHOD_IDIV10: + hash = i / 10; + break; + case HASH_METHOD_0: + hash = 0; + break; + } + + if (method & HASH_METHOD_X2) + hash = 2 * hash; + return hash; +} + +/* + * Test performance of hashmap.[ch] + * Usage: time echo "perfhashmap method rounds" | test-tool hashmap + */ +static void perf_hashmap(unsigned int method, unsigned int rounds) +{ + struct hashmap map; + char buf[16]; + struct test_entry **entries; + unsigned int *hashes; + unsigned int i, j; + + ALLOC_ARRAY(entries, TEST_SIZE); + ALLOC_ARRAY(hashes, TEST_SIZE); + for (i = 0; i < TEST_SIZE; i++) { + xsnprintf(buf, sizeof(buf), "%i", i); + entries[i] = alloc_test_entry(0, buf, ""); + hashes[i] = hash(method, i, entries[i]->key); + } + + if (method & TEST_ADD) { + /* test adding to the map */ + for (j = 0; j < rounds; j++) { + hashmap_init(&map, test_entry_cmp, NULL, 0); + + /* add entries */ + for (i = 0; i < TEST_SIZE; i++) { + hashmap_entry_init(entries[i], hashes[i]); + hashmap_add(&map, entries[i]); + } + + hashmap_free(&map, 0); + } + } else { + /* test map lookups */ + hashmap_init(&map, test_entry_cmp, NULL, 0); + + /* fill the map (sparsely if specified) */ + j = (method & TEST_SPARSE) ? TEST_SIZE / 10 : TEST_SIZE; + for (i = 0; i < j; i++) { + hashmap_entry_init(entries[i], hashes[i]); + hashmap_add(&map, entries[i]); + } + + for (j = 0; j < rounds; j++) { + for (i = 0; i < TEST_SIZE; i++) { + hashmap_get_from_hash(&map, hashes[i], + entries[i]->key); + } + } + + hashmap_free(&map, 0); + } +} + +#define DELIM " \t\r\n" + +/* + * Read stdin line by line and print result of commands to stdout: + * + * hash key -> strhash(key) memhash(key) strihash(key) memihash(key) + * put key value -> NULL / old value + * get key -> NULL / value + * remove key -> NULL / old value + * iterate -> key1 value1\nkey2 value2\n... + * size -> tablesize numentries + * + * perfhashmap method rounds -> test hashmap.[ch] performance + */ +int cmd__hashmap(int argc, const char **argv) +{ + struct strbuf line = STRBUF_INIT; + struct hashmap map; + int icase; + + /* init hash map */ + icase = argc > 1 && !strcmp("ignorecase", argv[1]); + hashmap_init(&map, test_entry_cmp, &icase, 0); + + /* process commands from stdin */ + while (strbuf_getline(&line, stdin) != EOF) { + char *cmd, *p1 = NULL, *p2 = NULL; + unsigned int hash = 0; + struct test_entry *entry; + + /* break line into command and up to two parameters */ + cmd = strtok(line.buf, DELIM); + /* ignore empty lines */ + if (!cmd || *cmd == '#') + continue; + + p1 = strtok(NULL, DELIM); + if (p1) { + hash = icase ? strihash(p1) : strhash(p1); + p2 = strtok(NULL, DELIM); + } + + if (!strcmp("hash", cmd) && p1) { + + /* print results of different hash functions */ + printf("%u %u %u %u\n", + strhash(p1), memhash(p1, strlen(p1)), + strihash(p1), memihash(p1, strlen(p1))); + + } else if (!strcmp("add", cmd) && p1 && p2) { + + /* create entry with key = p1, value = p2 */ + entry = alloc_test_entry(hash, p1, p2); + + /* add to hashmap */ + hashmap_add(&map, entry); + + } else if (!strcmp("put", cmd) && p1 && p2) { + + /* create entry with key = p1, value = p2 */ + entry = alloc_test_entry(hash, p1, p2); + + /* add / replace entry */ + entry = hashmap_put(&map, entry); + + /* print and free replaced entry, if any */ + puts(entry ? get_value(entry) : "NULL"); + free(entry); + + } else if (!strcmp("get", cmd) && p1) { + + /* lookup entry in hashmap */ + entry = hashmap_get_from_hash(&map, hash, p1); + + /* print result */ + if (!entry) + puts("NULL"); + while (entry) { + puts(get_value(entry)); + entry = hashmap_get_next(&map, entry); + } + + } else if (!strcmp("remove", cmd) && p1) { + + /* setup static key */ + struct hashmap_entry key; + hashmap_entry_init(&key, hash); + + /* remove entry from hashmap */ + entry = hashmap_remove(&map, &key, p1); + + /* print result and free entry*/ + puts(entry ? get_value(entry) : "NULL"); + free(entry); + + } else if (!strcmp("iterate", cmd)) { + + struct hashmap_iter iter; + hashmap_iter_init(&map, &iter); + while ((entry = hashmap_iter_next(&iter))) + printf("%s %s\n", entry->key, get_value(entry)); + + } else if (!strcmp("size", cmd)) { + + /* print table sizes */ + printf("%u %u\n", map.tablesize, + hashmap_get_size(&map)); + + } else if (!strcmp("intern", cmd) && p1) { + + /* test that strintern works */ + const char *i1 = strintern(p1); + const char *i2 = strintern(p1); + if (strcmp(i1, p1)) + printf("strintern(%s) returns %s\n", p1, i1); + else if (i1 == p1) + printf("strintern(%s) returns input pointer\n", p1); + else if (i1 != i2) + printf("strintern(%s) != strintern(%s)", i1, i2); + else + printf("%s\n", i1); + + } else if (!strcmp("perfhashmap", cmd) && p1 && p2) { + + perf_hashmap(atoi(p1), atoi(p2)); + + } else { + + printf("Unknown command %s\n", cmd); + + } + } + + strbuf_release(&line); + hashmap_free(&map, 1); + return 0; +} diff --git a/t/helper/test-index-version.c b/t/helper/test-index-version.c new file mode 100644 index 0000000000..fcd10968cc --- /dev/null +++ b/t/helper/test-index-version.c @@ -0,0 +1,15 @@ +#include "test-tool.h" +#include "cache.h" + +int cmd__index_version(int argc, const char **argv) +{ + struct cache_header hdr; + int version; + + memset(&hdr,0,sizeof(hdr)); + if (read(0, &hdr, sizeof(hdr)) != sizeof(hdr)) + return 0; + version = ntohl(hdr.hdr_version); + printf("%d\n", version); + return 0; +} diff --git a/t/helper/test-lazy-init-name-hash.c b/t/helper/test-lazy-init-name-hash.c new file mode 100644 index 0000000000..b99a37080d --- /dev/null +++ b/t/helper/test-lazy-init-name-hash.c @@ -0,0 +1,265 @@ +#include "test-tool.h" +#include "cache.h" +#include "parse-options.h" + +static int single; +static int multi; +static int count = 1; +static int dump; +static int perf; +static int analyze; +static int analyze_step; + +/* + * Dump the contents of the "dir" and "name" hash tables to stdout. + * If you sort the result, you can compare it with the other type + * mode and verify that both single and multi produce the same set. + */ +static void dump_run(void) +{ + struct hashmap_iter iter_dir; + struct hashmap_iter iter_cache; + + /* Stolen from name-hash.c */ + struct dir_entry { + struct hashmap_entry ent; + struct dir_entry *parent; + int nr; + unsigned int namelen; + char name[FLEX_ARRAY]; + }; + + struct dir_entry *dir; + struct cache_entry *ce; + + read_cache(); + if (single) { + test_lazy_init_name_hash(&the_index, 0); + } else { + int nr_threads_used = test_lazy_init_name_hash(&the_index, 1); + if (!nr_threads_used) + die("non-threaded code path used"); + } + + dir = hashmap_iter_first(&the_index.dir_hash, &iter_dir); + while (dir) { + printf("dir %08x %7d %s\n", dir->ent.hash, dir->nr, dir->name); + dir = hashmap_iter_next(&iter_dir); + } + + ce = hashmap_iter_first(&the_index.name_hash, &iter_cache); + while (ce) { + printf("name %08x %s\n", ce->ent.hash, ce->name); + ce = hashmap_iter_next(&iter_cache); + } + + discard_cache(); +} + +/* + * Run the single or multi threaded version "count" times and + * report on the time taken. + */ +static uint64_t time_runs(int try_threaded) +{ + uint64_t t0, t1, t2; + uint64_t sum = 0; + uint64_t avg; + int nr_threads_used; + int i; + + for (i = 0; i < count; i++) { + t0 = getnanotime(); + read_cache(); + t1 = getnanotime(); + nr_threads_used = test_lazy_init_name_hash(&the_index, try_threaded); + t2 = getnanotime(); + + sum += (t2 - t1); + + if (try_threaded && !nr_threads_used) + die("non-threaded code path used"); + + if (nr_threads_used) + printf("%f %f %d multi %d\n", + ((double)(t1 - t0))/1000000000, + ((double)(t2 - t1))/1000000000, + the_index.cache_nr, + nr_threads_used); + else + printf("%f %f %d single\n", + ((double)(t1 - t0))/1000000000, + ((double)(t2 - t1))/1000000000, + the_index.cache_nr); + fflush(stdout); + + discard_cache(); + } + + avg = sum / count; + if (count > 1) + printf("avg %f %s\n", + (double)avg/1000000000, + (try_threaded) ? "multi" : "single"); + + return avg; +} + +/* + * Try a series of runs varying the "istate->cache_nr" and + * try to find a good value for the multi-threaded criteria. + */ +static void analyze_run(void) +{ + uint64_t t1s, t1m, t2s, t2m; + int cache_nr_limit; + int nr_threads_used = 0; + int i; + int nr; + + read_cache(); + cache_nr_limit = the_index.cache_nr; + discard_cache(); + + nr = analyze; + while (1) { + uint64_t sum_single = 0; + uint64_t sum_multi = 0; + uint64_t avg_single; + uint64_t avg_multi; + + if (nr > cache_nr_limit) + nr = cache_nr_limit; + + for (i = 0; i < count; i++) { + read_cache(); + the_index.cache_nr = nr; /* cheap truncate of index */ + t1s = getnanotime(); + test_lazy_init_name_hash(&the_index, 0); + t2s = getnanotime(); + sum_single += (t2s - t1s); + the_index.cache_nr = cache_nr_limit; + discard_cache(); + + read_cache(); + the_index.cache_nr = nr; /* cheap truncate of index */ + t1m = getnanotime(); + nr_threads_used = test_lazy_init_name_hash(&the_index, 1); + t2m = getnanotime(); + sum_multi += (t2m - t1m); + the_index.cache_nr = cache_nr_limit; + discard_cache(); + + if (!nr_threads_used) + printf(" [size %8d] [single %f] non-threaded code path used\n", + nr, ((double)(t2s - t1s))/1000000000); + else + printf(" [size %8d] [single %f] %c [multi %f %d]\n", + nr, + ((double)(t2s - t1s))/1000000000, + (((t2s - t1s) < (t2m - t1m)) ? '<' : '>'), + ((double)(t2m - t1m))/1000000000, + nr_threads_used); + fflush(stdout); + } + if (count > 1) { + avg_single = sum_single / count; + avg_multi = sum_multi / count; + if (!nr_threads_used) + printf("avg [size %8d] [single %f]\n", + nr, + (double)avg_single/1000000000); + else + printf("avg [size %8d] [single %f] %c [multi %f %d]\n", + nr, + (double)avg_single/1000000000, + (avg_single < avg_multi ? '<' : '>'), + (double)avg_multi/1000000000, + nr_threads_used); + fflush(stdout); + } + + if (nr >= cache_nr_limit) + return; + nr += analyze_step; + } +} + +int cmd__lazy_init_name_hash(int argc, const char **argv) +{ + const char *usage[] = { + "test-tool lazy-init-name-hash -d (-s | -m)", + "test-tool lazy-init-name-hash -p [-c c]", + "test-tool lazy-init-name-hash -a a [--step s] [-c c]", + "test-tool lazy-init-name-hash (-s | -m) [-c c]", + "test-tool lazy-init-name-hash -s -m [-c c]", + NULL + }; + struct option options[] = { + OPT_BOOL('s', "single", &single, "run single-threaded code"), + OPT_BOOL('m', "multi", &multi, "run multi-threaded code"), + OPT_INTEGER('c', "count", &count, "number of passes"), + OPT_BOOL('d', "dump", &dump, "dump hash tables"), + OPT_BOOL('p', "perf", &perf, "compare single vs multi"), + OPT_INTEGER('a', "analyze", &analyze, "analyze different multi sizes"), + OPT_INTEGER(0, "step", &analyze_step, "analyze step factor"), + OPT_END(), + }; + const char *prefix; + uint64_t avg_single, avg_multi; + + prefix = setup_git_directory(); + + argc = parse_options(argc, argv, prefix, options, usage, 0); + + /* + * istate->dir_hash is only created when ignore_case is set. + */ + ignore_case = 1; + + if (dump) { + if (perf || analyze > 0) + die("cannot combine dump, perf, or analyze"); + if (count > 1) + die("count not valid with dump"); + if (single && multi) + die("cannot use both single and multi with dump"); + if (!single && !multi) + die("dump requires either single or multi"); + dump_run(); + return 0; + } + + if (perf) { + if (analyze > 0) + die("cannot combine dump, perf, or analyze"); + if (single || multi) + die("cannot use single or multi with perf"); + avg_single = time_runs(0); + avg_multi = time_runs(1); + if (avg_multi > avg_single) + die("multi is slower"); + return 0; + } + + if (analyze) { + if (analyze < 500) + die("analyze must be at least 500"); + if (!analyze_step) + analyze_step = analyze; + if (single || multi) + die("cannot use single or multi with analyze"); + analyze_run(); + return 0; + } + + if (!single && !multi) + die("require either -s or -m or both"); + + if (single) + time_runs(0); + if (multi) + time_runs(1); + + return 0; +} diff --git a/t/helper/test-line-buffer.c b/t/helper/test-line-buffer.c new file mode 100644 index 0000000000..078dd7e29d --- /dev/null +++ b/t/helper/test-line-buffer.c @@ -0,0 +1,81 @@ +/* + * test-line-buffer.c: code to exercise the svn importer's input helper + */ + +#include "git-compat-util.h" +#include "strbuf.h" +#include "vcs-svn/line_buffer.h" + +static uint32_t strtouint32(const char *s) +{ + char *end; + uintmax_t n = strtoumax(s, &end, 10); + if (*s == '\0' || *end != '\0') + die("invalid count: %s", s); + return (uint32_t) n; +} + +static void handle_command(const char *command, const char *arg, struct line_buffer *buf) +{ + if (starts_with(command, "binary ")) { + struct strbuf sb = STRBUF_INIT; + strbuf_addch(&sb, '>'); + buffer_read_binary(buf, &sb, strtouint32(arg)); + fwrite(sb.buf, 1, sb.len, stdout); + strbuf_release(&sb); + } else if (starts_with(command, "copy ")) { + buffer_copy_bytes(buf, strtouint32(arg)); + } else if (starts_with(command, "skip ")) { + buffer_skip_bytes(buf, strtouint32(arg)); + } else { + die("unrecognized command: %s", command); + } +} + +static void handle_line(const char *line, struct line_buffer *stdin_buf) +{ + const char *arg = strchr(line, ' '); + if (!arg) + die("no argument in line: %s", line); + handle_command(line, arg + 1, stdin_buf); +} + +int cmd_main(int argc, const char **argv) +{ + struct line_buffer stdin_buf = LINE_BUFFER_INIT; + struct line_buffer file_buf = LINE_BUFFER_INIT; + struct line_buffer *input = &stdin_buf; + const char *filename; + char *s; + + if (argc == 1) + filename = NULL; + else if (argc == 2) + filename = argv[1]; + else + usage("test-line-buffer [file | &fd] < script"); + + if (buffer_init(&stdin_buf, NULL)) + die_errno("open error"); + if (filename) { + if (*filename == '&') { + if (buffer_fdinit(&file_buf, strtouint32(filename + 1))) + die_errno("error opening fd %s", filename + 1); + } else { + if (buffer_init(&file_buf, filename)) + die_errno("error opening %s", filename); + } + input = &file_buf; + } + + while ((s = buffer_read_line(&stdin_buf))) + handle_line(s, input); + + if (filename && buffer_deinit(&file_buf)) + die("error reading from %s", filename); + if (buffer_deinit(&stdin_buf)) + die("input error"); + if (ferror(stdout)) + die("output error"); + return 0; +} diff --git a/t/helper/test-match-trees.c b/t/helper/test-match-trees.c new file mode 100644 index 0000000000..96857f26ac --- /dev/null +++ b/t/helper/test-match-trees.c @@ -0,0 +1,27 @@ +#include "test-tool.h" +#include "cache.h" +#include "tree.h" + +int cmd__match_trees(int ac, const char **av) +{ + struct object_id hash1, hash2, shifted; + struct tree *one, *two; + + setup_git_directory(); + + if (get_oid(av[1], &hash1)) + die("cannot parse %s as an object name", av[1]); + if (get_oid(av[2], &hash2)) + die("cannot parse %s as an object name", av[2]); + one = parse_tree_indirect(&hash1); + if (!one) + die("not a tree-ish %s", av[1]); + two = parse_tree_indirect(&hash2); + if (!two) + die("not a tree-ish %s", av[2]); + + shift_tree(&one->object.oid, &two->object.oid, &shifted, -1); + printf("shifted: %s\n", oid_to_hex(&shifted)); + + exit(0); +} diff --git a/t/helper/test-mergesort.c b/t/helper/test-mergesort.c new file mode 100644 index 0000000000..c5cffaa4b7 --- /dev/null +++ b/t/helper/test-mergesort.c @@ -0,0 +1,53 @@ +#include "test-tool.h" +#include "cache.h" +#include "mergesort.h" + +struct line { + char *text; + struct line *next; +}; + +static void *get_next(const void *a) +{ + return ((const struct line *)a)->next; +} + +static void set_next(void *a, void *b) +{ + ((struct line *)a)->next = b; +} + +static int compare_strings(const void *a, const void *b) +{ + const struct line *x = a, *y = b; + return strcmp(x->text, y->text); +} + +int cmd__mergesort(int argc, const char **argv) +{ + struct line *line, *p = NULL, *lines = NULL; + struct strbuf sb = STRBUF_INIT; + + for (;;) { + if (strbuf_getwholeline(&sb, stdin, '\n')) + break; + line = xmalloc(sizeof(struct line)); + line->text = strbuf_detach(&sb, NULL); + if (p) { + line->next = p->next; + p->next = line; + } else { + line->next = NULL; + lines = line; + } + p = line; + } + + lines = llist_mergesort(lines, get_next, set_next, compare_strings); + + while (lines) { + printf("%s", lines->text); + lines = lines->next; + } + return 0; +} diff --git a/t/helper/test-mktemp.c b/t/helper/test-mktemp.c new file mode 100644 index 0000000000..2290688940 --- /dev/null +++ b/t/helper/test-mktemp.c @@ -0,0 +1,15 @@ +/* + * test-mktemp.c: code to exercise the creation of temporary files + */ +#include "test-tool.h" +#include "git-compat-util.h" + +int cmd__mktemp(int argc, const char **argv) +{ + if (argc != 2) + usage("Expected 1 parameter defining the temporary file template"); + + xmkstemp(xstrdup(argv[1])); + + return 0; +} diff --git a/t/helper/test-online-cpus.c b/t/helper/test-online-cpus.c new file mode 100644 index 0000000000..8cb0d53840 --- /dev/null +++ b/t/helper/test-online-cpus.c @@ -0,0 +1,9 @@ +#include "test-tool.h" +#include "git-compat-util.h" +#include "thread-utils.h" + +int cmd__online_cpus(int argc, const char **argv) +{ + printf("%d\n", online_cpus()); + return 0; +} diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c new file mode 100644 index 0000000000..630c76d127 --- /dev/null +++ b/t/helper/test-parse-options.c @@ -0,0 +1,181 @@ +#include "cache.h" +#include "parse-options.h" +#include "string-list.h" + +static int boolean = 0; +static int integer = 0; +static unsigned long magnitude = 0; +static timestamp_t timestamp; +static int abbrev = 7; +static int verbose = -1; /* unspecified */ +static int dry_run = 0, quiet = 0; +static char *string = NULL; +static char *file = NULL; +static int ambiguous; +static struct string_list list = STRING_LIST_INIT_NODUP; + +static struct { + int called; + const char *arg; + int unset; +} length_cb; + +static int length_callback(const struct option *opt, const char *arg, int unset) +{ + length_cb.called = 1; + length_cb.arg = arg; + length_cb.unset = unset; + + if (unset) + return 1; /* do not support unset */ + + *(int *)opt->value = strlen(arg); + return 0; +} + +static int number_callback(const struct option *opt, const char *arg, int unset) +{ + *(int *)opt->value = strtol(arg, NULL, 10); + return 0; +} + +static int collect_expect(const struct option *opt, const char *arg, int unset) +{ + struct string_list *expect; + struct string_list_item *item; + struct strbuf label = STRBUF_INIT; + const char *colon; + + if (!arg || unset) + die("malformed --expect option"); + + expect = (struct string_list *)opt->value; + colon = strchr(arg, ':'); + if (!colon) + die("malformed --expect option, lacking a colon"); + strbuf_add(&label, arg, colon - arg); + item = string_list_insert(expect, strbuf_detach(&label, NULL)); + if (item->util) + die("malformed --expect option, duplicate %s", label.buf); + item->util = (void *)arg; + return 0; +} + +__attribute__((format (printf,3,4))) +static void show(struct string_list *expect, int *status, const char *fmt, ...) +{ + struct string_list_item *item; + struct strbuf buf = STRBUF_INIT; + va_list args; + + va_start(args, fmt); + strbuf_vaddf(&buf, fmt, args); + va_end(args); + + if (!expect->nr) + printf("%s\n", buf.buf); + else { + char *colon = strchr(buf.buf, ':'); + if (!colon) + die("malformed output format, output lacking colon: %s", fmt); + *colon = '\0'; + item = string_list_lookup(expect, buf.buf); + *colon = ':'; + if (!item) + ; /* not among entries being checked */ + else { + if (strcmp((const char *)item->util, buf.buf)) { + printf("-%s\n", (char *)item->util); + printf("+%s\n", buf.buf); + *status = 1; + } + } + } + strbuf_release(&buf); +} + +int cmd_main(int argc, const char **argv) +{ + const char *prefix = "prefix/"; + const char *usage[] = { + "test-parse-options <options>", + "", + "A helper function for the parse-options API.", + NULL + }; + struct string_list expect = STRING_LIST_INIT_NODUP; + struct option options[] = { + OPT_BOOL(0, "yes", &boolean, "get a boolean"), + OPT_BOOL('D', "no-doubt", &boolean, "begins with 'no-'"), + { OPTION_SET_INT, 'B', "no-fear", &boolean, NULL, + "be brave", PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1 }, + OPT_COUNTUP('b', "boolean", &boolean, "increment by one"), + OPT_BIT('4', "or4", &boolean, + "bitwise-or boolean with ...0100", 4), + OPT_NEGBIT(0, "neg-or4", &boolean, "same as --no-or4", 4), + OPT_GROUP(""), + OPT_INTEGER('i', "integer", &integer, "get a integer"), + OPT_INTEGER('j', NULL, &integer, "get a integer, too"), + OPT_MAGNITUDE('m', "magnitude", &magnitude, "get a magnitude"), + OPT_SET_INT(0, "set23", &integer, "set integer to 23", 23), + OPT_DATE('t', NULL, ×tamp, "get timestamp of <time>"), + OPT_CALLBACK('L', "length", &integer, "str", + "get length of <str>", length_callback), + OPT_FILENAME('F', "file", &file, "set file to <file>"), + OPT_GROUP("String options"), + OPT_STRING('s', "string", &string, "string", "get a string"), + OPT_STRING(0, "string2", &string, "str", "get another string"), + OPT_STRING(0, "st", &string, "st", "get another string (pervert ordering)"), + OPT_STRING('o', NULL, &string, "str", "get another string"), + OPT_NOOP_NOARG(0, "obsolete"), + OPT_STRING_LIST(0, "list", &list, "str", "add str to list"), + OPT_GROUP("Magic arguments"), + OPT_ARGUMENT("quux", "means --quux"), + OPT_NUMBER_CALLBACK(&integer, "set integer to NUM", + number_callback), + { OPTION_COUNTUP, '+', NULL, &boolean, NULL, "same as -b", + PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH }, + { OPTION_COUNTUP, 0, "ambiguous", &ambiguous, NULL, + "positive ambiguity", PARSE_OPT_NOARG | PARSE_OPT_NONEG }, + { OPTION_COUNTUP, 0, "no-ambiguous", &ambiguous, NULL, + "negative ambiguity", PARSE_OPT_NOARG | PARSE_OPT_NONEG }, + OPT_GROUP("Standard options"), + OPT__ABBREV(&abbrev), + OPT__VERBOSE(&verbose, "be verbose"), + OPT__DRY_RUN(&dry_run, "dry run"), + OPT__QUIET(&quiet, "be quiet"), + OPT_CALLBACK(0, "expect", &expect, "string", + "expected output in the variable dump", + collect_expect), + OPT_END(), + }; + int i; + int ret = 0; + + argc = parse_options(argc, (const char **)argv, prefix, options, usage, 0); + + if (length_cb.called) { + const char *arg = length_cb.arg; + int unset = length_cb.unset; + show(&expect, &ret, "Callback: \"%s\", %d", + (arg ? arg : "not set"), unset); + } + show(&expect, &ret, "boolean: %d", boolean); + show(&expect, &ret, "integer: %d", integer); + show(&expect, &ret, "magnitude: %lu", magnitude); + show(&expect, &ret, "timestamp: %"PRItime, timestamp); + show(&expect, &ret, "string: %s", string ? string : "(not set)"); + show(&expect, &ret, "abbrev: %d", abbrev); + show(&expect, &ret, "verbose: %d", verbose); + show(&expect, &ret, "quiet: %d", quiet); + show(&expect, &ret, "dry run: %s", dry_run ? "yes" : "no"); + show(&expect, &ret, "file: %s", file ? file : "(not set)"); + + for (i = 0; i < list.nr; i++) + show(&expect, &ret, "list: %s", list.items[i].string); + + for (i = 0; i < argc; i++) + show(&expect, &ret, "arg %02d: %s", i, argv[i]); + + return ret; +} diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c new file mode 100644 index 0000000000..e115d44ac2 --- /dev/null +++ b/t/helper/test-path-utils.c @@ -0,0 +1,277 @@ +#include "test-tool.h" +#include "cache.h" +#include "string-list.h" + +/* + * A "string_list_each_func_t" function that normalizes an entry from + * GIT_CEILING_DIRECTORIES. If the path is unusable for some reason, + * die with an explanation. + */ +static int normalize_ceiling_entry(struct string_list_item *item, void *unused) +{ + char *ceil = item->string; + + if (!*ceil) + die("Empty path is not supported"); + if (!is_absolute_path(ceil)) + die("Path \"%s\" is not absolute", ceil); + if (normalize_path_copy(ceil, ceil) < 0) + die("Path \"%s\" could not be normalized", ceil); + return 1; +} + +static void normalize_argv_string(const char **var, const char *input) +{ + if (!strcmp(input, "<null>")) + *var = NULL; + else if (!strcmp(input, "<empty>")) + *var = ""; + else + *var = input; + + if (*var && (**var == '<' || **var == '(')) + die("Bad value: %s\n", input); +} + +struct test_data { + const char *from; /* input: transform from this ... */ + const char *to; /* output: ... to this. */ + const char *alternative; /* output: ... or this. */ +}; + +/* + * Compatibility wrappers for OpenBSD, whose basename(3) and dirname(3) + * have const parameters. + */ +static char *posix_basename(char *path) +{ + return basename(path); +} + +static char *posix_dirname(char *path) +{ + return dirname(path); +} + +static int test_function(struct test_data *data, char *(*func)(char *input), + const char *funcname) +{ + int failed = 0, i; + char buffer[1024]; + char *to; + + for (i = 0; data[i].to; i++) { + if (!data[i].from) + to = func(NULL); + else { + xsnprintf(buffer, sizeof(buffer), "%s", data[i].from); + to = func(buffer); + } + if (!strcmp(to, data[i].to)) + continue; + if (!data[i].alternative) + error("FAIL: %s(%s) => '%s' != '%s'\n", + funcname, data[i].from, to, data[i].to); + else if (!strcmp(to, data[i].alternative)) + continue; + else + error("FAIL: %s(%s) => '%s' != '%s', '%s'\n", + funcname, data[i].from, to, data[i].to, + data[i].alternative); + failed = 1; + } + return failed; +} + +static struct test_data basename_data[] = { + /* --- POSIX type paths --- */ + { NULL, "." }, + { "", "." }, + { ".", "." }, + { "..", ".." }, + { "/", "/" }, + { "//", "/", "//" }, + { "///", "/", "//" }, + { "////", "/", "//" }, + { "usr", "usr" }, + { "/usr", "usr" }, + { "/usr/", "usr" }, + { "/usr//", "usr" }, + { "/usr/lib", "lib" }, + { "usr/lib", "lib" }, + { "usr/lib///", "lib" }, + +#if defined(__MINGW32__) || defined(_MSC_VER) + /* --- win32 type paths --- */ + { "\\usr", "usr" }, + { "\\usr\\", "usr" }, + { "\\usr\\\\", "usr" }, + { "\\usr\\lib", "lib" }, + { "usr\\lib", "lib" }, + { "usr\\lib\\\\\\", "lib" }, + { "C:/usr", "usr" }, + { "C:/usr", "usr" }, + { "C:/usr/", "usr" }, + { "C:/usr//", "usr" }, + { "C:/usr/lib", "lib" }, + { "C:usr/lib", "lib" }, + { "C:usr/lib///", "lib" }, + { "C:", "." }, + { "C:a", "a" }, + { "C:/", "/" }, + { "C:///", "/" }, + { "\\", "\\", "/" }, + { "\\\\", "\\", "/" }, + { "\\\\\\", "\\", "/" }, +#endif + { NULL, NULL } +}; + +static struct test_data dirname_data[] = { + /* --- POSIX type paths --- */ + { NULL, "." }, + { "", "." }, + { ".", "." }, + { "..", "." }, + { "/", "/" }, + { "//", "/", "//" }, + { "///", "/", "//" }, + { "////", "/", "//" }, + { "usr", "." }, + { "/usr", "/" }, + { "/usr/", "/" }, + { "/usr//", "/" }, + { "/usr/lib", "/usr" }, + { "usr/lib", "usr" }, + { "usr/lib///", "usr" }, + +#if defined(__MINGW32__) || defined(_MSC_VER) + /* --- win32 type paths --- */ + { "\\", "\\" }, + { "\\\\", "\\\\" }, + { "\\usr", "\\" }, + { "\\usr\\", "\\" }, + { "\\usr\\\\", "\\" }, + { "\\usr\\lib", "\\usr" }, + { "usr\\lib", "usr" }, + { "usr\\lib\\\\\\", "usr" }, + { "C:a", "C:." }, + { "C:/", "C:/" }, + { "C:///", "C:/" }, + { "C:/usr", "C:/" }, + { "C:/usr/", "C:/" }, + { "C:/usr//", "C:/" }, + { "C:/usr/lib", "C:/usr" }, + { "C:usr/lib", "C:usr" }, + { "C:usr/lib///", "C:usr" }, + { "\\\\\\", "\\" }, + { "\\\\\\\\", "\\" }, + { "C:", "C:.", "." }, +#endif + { NULL, NULL } +}; + +int cmd__path_utils(int argc, const char **argv) +{ + if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) { + char *buf = xmallocz(strlen(argv[2])); + int rv = normalize_path_copy(buf, argv[2]); + if (rv) + buf = "++failed++"; + puts(buf); + return 0; + } + + if (argc >= 2 && !strcmp(argv[1], "real_path")) { + while (argc > 2) { + puts(real_path(argv[2])); + argc--; + argv++; + } + return 0; + } + + if (argc >= 2 && !strcmp(argv[1], "absolute_path")) { + while (argc > 2) { + puts(absolute_path(argv[2])); + argc--; + argv++; + } + return 0; + } + + if (argc == 4 && !strcmp(argv[1], "longest_ancestor_length")) { + int len; + struct string_list ceiling_dirs = STRING_LIST_INIT_DUP; + char *path = xstrdup(argv[2]); + + /* + * We have to normalize the arguments because under + * Windows, bash mangles arguments that look like + * absolute POSIX paths or colon-separate lists of + * absolute POSIX paths into DOS paths (e.g., + * "/foo:/foo/bar" might be converted to + * "D:\Src\msysgit\foo;D:\Src\msysgit\foo\bar"), + * whereas longest_ancestor_length() requires paths + * that use forward slashes. + */ + if (normalize_path_copy(path, path)) + die("Path \"%s\" could not be normalized", argv[2]); + string_list_split(&ceiling_dirs, argv[3], PATH_SEP, -1); + filter_string_list(&ceiling_dirs, 0, + normalize_ceiling_entry, NULL); + len = longest_ancestor_length(path, &ceiling_dirs); + string_list_clear(&ceiling_dirs, 0); + free(path); + printf("%d\n", len); + return 0; + } + + if (argc >= 4 && !strcmp(argv[1], "prefix_path")) { + const char *prefix = argv[2]; + int prefix_len = strlen(prefix); + int nongit_ok; + setup_git_directory_gently(&nongit_ok); + while (argc > 3) { + puts(prefix_path(prefix, prefix_len, argv[3])); + argc--; + argv++; + } + return 0; + } + + if (argc == 4 && !strcmp(argv[1], "strip_path_suffix")) { + char *prefix = strip_path_suffix(argv[2], argv[3]); + printf("%s\n", prefix ? prefix : "(null)"); + return 0; + } + + if (argc == 3 && !strcmp(argv[1], "print_path")) { + puts(argv[2]); + return 0; + } + + if (argc == 4 && !strcmp(argv[1], "relative_path")) { + struct strbuf sb = STRBUF_INIT; + const char *in, *prefix, *rel; + normalize_argv_string(&in, argv[2]); + normalize_argv_string(&prefix, argv[3]); + rel = relative_path(in, prefix, &sb); + if (!rel) + puts("(null)"); + else + puts(strlen(rel) > 0 ? rel : "(empty)"); + strbuf_release(&sb); + return 0; + } + + if (argc == 2 && !strcmp(argv[1], "basename")) + return test_function(basename_data, posix_basename, argv[1]); + + if (argc == 2 && !strcmp(argv[1], "dirname")) + return test_function(dirname_data, posix_dirname, argv[1]); + + fprintf(stderr, "%s: unknown function name: %s\n", argv[0], + argv[1] ? argv[1] : "(there was none)"); + return 1; +} diff --git a/t/helper/test-prio-queue.c b/t/helper/test-prio-queue.c new file mode 100644 index 0000000000..9807b649b1 --- /dev/null +++ b/t/helper/test-prio-queue.c @@ -0,0 +1,40 @@ +#include "test-tool.h" +#include "cache.h" +#include "prio-queue.h" + +static int intcmp(const void *va, const void *vb, void *data) +{ + const int *a = va, *b = vb; + return *a - *b; +} + +static void show(int *v) +{ + if (!v) + printf("NULL\n"); + else + printf("%d\n", *v); + free(v); +} + +int cmd__prio_queue(int argc, const char **argv) +{ + struct prio_queue pq = { intcmp }; + + while (*++argv) { + if (!strcmp(*argv, "get")) + show(prio_queue_get(&pq)); + else if (!strcmp(*argv, "dump")) { + int *v; + while ((v = prio_queue_get(&pq))) + show(v); + } + else { + int *v = malloc(sizeof(*v)); + *v = atoi(*argv); + prio_queue_put(&pq, v); + } + } + + return 0; +} diff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c new file mode 100644 index 0000000000..d674c88ba0 --- /dev/null +++ b/t/helper/test-read-cache.c @@ -0,0 +1,15 @@ +#include "test-tool.h" +#include "cache.h" + +int cmd__read_cache(int argc, const char **argv) +{ + int i, cnt = 1; + if (argc == 2) + cnt = strtol(argv[1], NULL, 0); + setup_git_directory(); + for (i = 0; i < cnt; i++) { + read_cache(); + discard_cache(); + } + return 0; +} diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c new file mode 100644 index 0000000000..7c4f43746e --- /dev/null +++ b/t/helper/test-ref-store.c @@ -0,0 +1,298 @@ +#include "test-tool.h" +#include "cache.h" +#include "refs.h" +#include "worktree.h" +#include "object-store.h" + +static const char *notnull(const char *arg, const char *name) +{ + if (!arg) + die("%s required", name); + return arg; +} + +static unsigned int arg_flags(const char *arg, const char *name) +{ + return atoi(notnull(arg, name)); +} + +static const char **get_store(const char **argv, struct ref_store **refs) +{ + const char *gitdir; + + if (!argv[0]) { + die("ref store required"); + } else if (!strcmp(argv[0], "main")) { + *refs = get_main_ref_store(); + } else if (skip_prefix(argv[0], "submodule:", &gitdir)) { + struct strbuf sb = STRBUF_INIT; + int ret; + + ret = strbuf_git_path_submodule(&sb, gitdir, "objects/"); + if (ret) + die("strbuf_git_path_submodule failed: %d", ret); + add_to_alternates_memory(sb.buf); + strbuf_release(&sb); + + *refs = get_submodule_ref_store(gitdir); + } else if (skip_prefix(argv[0], "worktree:", &gitdir)) { + struct worktree **p, **worktrees = get_worktrees(0); + + for (p = worktrees; *p; p++) { + struct worktree *wt = *p; + + if (!wt->id) { + /* special case for main worktree */ + if (!strcmp(gitdir, "main")) + break; + } else if (!strcmp(gitdir, wt->id)) + break; + } + if (!*p) + die("no such worktree: %s", gitdir); + + *refs = get_worktree_ref_store(*p); + } else + die("unknown backend %s", argv[0]); + + if (!*refs) + die("no ref store"); + + /* consume store-specific optional arguments if needed */ + + return argv + 1; +} + + +static int cmd_pack_refs(struct ref_store *refs, const char **argv) +{ + unsigned int flags = arg_flags(*argv++, "flags"); + + return refs_pack_refs(refs, flags); +} + +static int cmd_peel_ref(struct ref_store *refs, const char **argv) +{ + const char *refname = notnull(*argv++, "refname"); + struct object_id oid; + int ret; + + ret = refs_peel_ref(refs, refname, &oid); + if (!ret) + puts(oid_to_hex(&oid)); + return ret; +} + +static int cmd_create_symref(struct ref_store *refs, const char **argv) +{ + const char *refname = notnull(*argv++, "refname"); + const char *target = notnull(*argv++, "target"); + const char *logmsg = *argv++; + + return refs_create_symref(refs, refname, target, logmsg); +} + +static int cmd_delete_refs(struct ref_store *refs, const char **argv) +{ + unsigned int flags = arg_flags(*argv++, "flags"); + const char *msg = *argv++; + struct string_list refnames = STRING_LIST_INIT_NODUP; + + while (*argv) + string_list_append(&refnames, *argv++); + + return refs_delete_refs(refs, msg, &refnames, flags); +} + +static int cmd_rename_ref(struct ref_store *refs, const char **argv) +{ + const char *oldref = notnull(*argv++, "oldref"); + const char *newref = notnull(*argv++, "newref"); + const char *logmsg = *argv++; + + return refs_rename_ref(refs, oldref, newref, logmsg); +} + +static int each_ref(const char *refname, const struct object_id *oid, + int flags, void *cb_data) +{ + printf("%s %s 0x%x\n", oid_to_hex(oid), refname, flags); + return 0; +} + +static int cmd_for_each_ref(struct ref_store *refs, const char **argv) +{ + const char *prefix = notnull(*argv++, "prefix"); + + return refs_for_each_ref_in(refs, prefix, each_ref, NULL); +} + +static int cmd_resolve_ref(struct ref_store *refs, const char **argv) +{ + struct object_id oid; + const char *refname = notnull(*argv++, "refname"); + int resolve_flags = arg_flags(*argv++, "resolve-flags"); + int flags; + const char *ref; + + ref = refs_resolve_ref_unsafe(refs, refname, resolve_flags, + &oid, &flags); + printf("%s %s 0x%x\n", oid_to_hex(&oid), ref ? ref : "(null)", flags); + return ref ? 0 : 1; +} + +static int cmd_verify_ref(struct ref_store *refs, const char **argv) +{ + const char *refname = notnull(*argv++, "refname"); + struct strbuf err = STRBUF_INIT; + int ret; + + ret = refs_verify_refname_available(refs, refname, NULL, NULL, &err); + if (err.len) + puts(err.buf); + return ret; +} + +static int cmd_for_each_reflog(struct ref_store *refs, const char **argv) +{ + return refs_for_each_reflog(refs, each_ref, NULL); +} + +static int each_reflog(struct object_id *old_oid, struct object_id *new_oid, + const char *committer, timestamp_t timestamp, + int tz, const char *msg, void *cb_data) +{ + printf("%s %s %s %"PRItime" %d %s\n", + oid_to_hex(old_oid), oid_to_hex(new_oid), + committer, timestamp, tz, msg); + return 0; +} + +static int cmd_for_each_reflog_ent(struct ref_store *refs, const char **argv) +{ + const char *refname = notnull(*argv++, "refname"); + + return refs_for_each_reflog_ent(refs, refname, each_reflog, refs); +} + +static int cmd_for_each_reflog_ent_reverse(struct ref_store *refs, const char **argv) +{ + const char *refname = notnull(*argv++, "refname"); + + return refs_for_each_reflog_ent_reverse(refs, refname, each_reflog, refs); +} + +static int cmd_reflog_exists(struct ref_store *refs, const char **argv) +{ + const char *refname = notnull(*argv++, "refname"); + + return !refs_reflog_exists(refs, refname); +} + +static int cmd_create_reflog(struct ref_store *refs, const char **argv) +{ + const char *refname = notnull(*argv++, "refname"); + int force_create = arg_flags(*argv++, "force-create"); + struct strbuf err = STRBUF_INIT; + int ret; + + ret = refs_create_reflog(refs, refname, force_create, &err); + if (err.len) + puts(err.buf); + return ret; +} + +static int cmd_delete_reflog(struct ref_store *refs, const char **argv) +{ + const char *refname = notnull(*argv++, "refname"); + + return refs_delete_reflog(refs, refname); +} + +static int cmd_reflog_expire(struct ref_store *refs, const char **argv) +{ + die("not supported yet"); +} + +static int cmd_delete_ref(struct ref_store *refs, const char **argv) +{ + const char *msg = notnull(*argv++, "msg"); + const char *refname = notnull(*argv++, "refname"); + const char *sha1_buf = notnull(*argv++, "old-sha1"); + unsigned int flags = arg_flags(*argv++, "flags"); + struct object_id old_oid; + + if (get_oid_hex(sha1_buf, &old_oid)) + die("not sha-1"); + + return refs_delete_ref(refs, msg, refname, &old_oid, flags); +} + +static int cmd_update_ref(struct ref_store *refs, const char **argv) +{ + const char *msg = notnull(*argv++, "msg"); + const char *refname = notnull(*argv++, "refname"); + const char *new_sha1_buf = notnull(*argv++, "old-sha1"); + const char *old_sha1_buf = notnull(*argv++, "old-sha1"); + unsigned int flags = arg_flags(*argv++, "flags"); + struct object_id old_oid; + struct object_id new_oid; + + if (get_oid_hex(old_sha1_buf, &old_oid) || + get_oid_hex(new_sha1_buf, &new_oid)) + die("not sha-1"); + + return refs_update_ref(refs, msg, refname, + &new_oid, &old_oid, + flags, UPDATE_REFS_DIE_ON_ERR); +} + +struct command { + const char *name; + int (*func)(struct ref_store *refs, const char **argv); +}; + +static struct command commands[] = { + { "pack-refs", cmd_pack_refs }, + { "peel-ref", cmd_peel_ref }, + { "create-symref", cmd_create_symref }, + { "delete-refs", cmd_delete_refs }, + { "rename-ref", cmd_rename_ref }, + { "for-each-ref", cmd_for_each_ref }, + { "resolve-ref", cmd_resolve_ref }, + { "verify-ref", cmd_verify_ref }, + { "for-each-reflog", cmd_for_each_reflog }, + { "for-each-reflog-ent", cmd_for_each_reflog_ent }, + { "for-each-reflog-ent-reverse", cmd_for_each_reflog_ent_reverse }, + { "reflog-exists", cmd_reflog_exists }, + { "create-reflog", cmd_create_reflog }, + { "delete-reflog", cmd_delete_reflog }, + { "reflog-expire", cmd_reflog_expire }, + /* + * backend transaction functions can't be tested separately + */ + { "delete-ref", cmd_delete_ref }, + { "update-ref", cmd_update_ref }, + { NULL, NULL } +}; + +int cmd__ref_store(int argc, const char **argv) +{ + struct ref_store *refs; + const char *func; + struct command *cmd; + + setup_git_directory(); + + argv = get_store(argv + 1, &refs); + + func = *argv++; + if (!func) + die("ref function required"); + for (cmd = commands; cmd->name; cmd++) { + if (!strcmp(func, cmd->name)) + return cmd->func(refs, argv); + } + die("unknown function %s", func); + return 0; +} diff --git a/t/helper/test-regex.c b/t/helper/test-regex.c new file mode 100644 index 0000000000..10284cc56f --- /dev/null +++ b/t/helper/test-regex.c @@ -0,0 +1,76 @@ +#include "test-tool.h" +#include "git-compat-util.h" +#include "gettext.h" + +struct reg_flag { + const char *name; + int flag; +}; + +static struct reg_flag reg_flags[] = { + { "EXTENDED", REG_EXTENDED }, + { "NEWLINE", REG_NEWLINE }, + { "ICASE", REG_ICASE }, + { "NOTBOL", REG_NOTBOL }, +#ifdef REG_STARTEND + { "STARTEND", REG_STARTEND }, +#endif + { NULL, 0 } +}; + +static int test_regex_bug(void) +{ + char *pat = "[^={} \t]+"; + char *str = "={}\nfred"; + regex_t r; + regmatch_t m[1]; + + if (regcomp(&r, pat, REG_EXTENDED | REG_NEWLINE)) + die("failed regcomp() for pattern '%s'", pat); + if (regexec(&r, str, 1, m, 0)) + die("no match of pattern '%s' to string '%s'", pat, str); + + /* http://sourceware.org/bugzilla/show_bug.cgi?id=3957 */ + if (m[0].rm_so == 3) /* matches '\n' when it should not */ + die("regex bug confirmed: re-build git with NO_REGEX=1"); + + return 0; +} + +int cmd__regex(int argc, const char **argv) +{ + const char *pat; + const char *str; + int flags = 0; + regex_t r; + regmatch_t m[1]; + + if (argc == 2 && !strcmp(argv[1], "--bug")) + return test_regex_bug(); + else if (argc < 3) + usage("test-tool regex --bug\n" + "test-tool regex <pattern> <string> [<options>]"); + + argv++; + pat = *argv++; + str = *argv++; + while (*argv) { + struct reg_flag *rf; + for (rf = reg_flags; rf->name; rf++) + if (!strcmp(*argv, rf->name)) { + flags |= rf->flag; + break; + } + if (!rf->name) + die("do not recognize %s", *argv); + argv++; + } + git_setup_gettext(); + + if (regcomp(&r, pat, flags)) + die("failed regcomp() for pattern '%s'", pat); + if (regexec(&r, str, 1, m, 0)) + return 1; + + return 0; +} diff --git a/t/helper/test-revision-walking.c b/t/helper/test-revision-walking.c new file mode 100644 index 0000000000..4f8bc75821 --- /dev/null +++ b/t/helper/test-revision-walking.c @@ -0,0 +1,69 @@ +/* + * test-revision-walking.c: test revision walking API. + * + * (C) 2012 Heiko Voigt <hvoigt@hvoigt.net> + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "test-tool.h" +#include "cache.h" +#include "commit.h" +#include "diff.h" +#include "revision.h" + +static void print_commit(struct commit *commit) +{ + struct strbuf sb = STRBUF_INIT; + struct pretty_print_context ctx = {0}; + ctx.date_mode.type = DATE_NORMAL; + format_commit_message(commit, " %m %s", &sb, &ctx); + printf("%s\n", sb.buf); + strbuf_release(&sb); +} + +static int run_revision_walk(void) +{ + struct rev_info rev; + struct commit *commit; + const char *argv[] = {NULL, "--all", NULL}; + int argc = ARRAY_SIZE(argv) - 1; + int got_revision = 0; + + init_revisions(&rev, NULL); + setup_revisions(argc, argv, &rev, NULL); + if (prepare_revision_walk(&rev)) + die("revision walk setup failed"); + + while ((commit = get_revision(&rev)) != NULL) { + print_commit(commit); + got_revision = 1; + } + + reset_revision_walk(); + return got_revision; +} + +int cmd__revision_walking(int argc, const char **argv) +{ + if (argc < 2) + return 1; + + setup_git_directory(); + + if (!strcmp(argv[1], "run-twice")) { + printf("1st\n"); + if (!run_revision_walk()) + return 1; + printf("2nd\n"); + if (!run_revision_walk()) + return 1; + + return 0; + } + + fprintf(stderr, "check usage\n"); + return 1; +} diff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c new file mode 100644 index 0000000000..2cc93bb69c --- /dev/null +++ b/t/helper/test-run-command.c @@ -0,0 +1,97 @@ +/* + * test-run-command.c: test run command API. + * + * (C) 2009 Ilari Liusvaara <ilari.liusvaara@elisanet.fi> + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include "test-tool.h" +#include "git-compat-util.h" +#include "run-command.h" +#include "argv-array.h" +#include "strbuf.h" +#include <string.h> +#include <errno.h> + +static int number_callbacks; +static int parallel_next(struct child_process *cp, + struct strbuf *err, + void *cb, + void **task_cb) +{ + struct child_process *d = cb; + if (number_callbacks >= 4) + return 0; + + argv_array_pushv(&cp->args, d->argv); + strbuf_addstr(err, "preloaded output of a child\n"); + number_callbacks++; + return 1; +} + +static int no_job(struct child_process *cp, + struct strbuf *err, + void *cb, + void **task_cb) +{ + strbuf_addstr(err, "no further jobs available\n"); + return 0; +} + +static int task_finished(int result, + struct strbuf *err, + void *pp_cb, + void *pp_task_cb) +{ + strbuf_addstr(err, "asking for a quick stop\n"); + return 1; +} + +int cmd__run_command(int argc, const char **argv) +{ + struct child_process proc = CHILD_PROCESS_INIT; + int jobs; + + if (argc < 3) + return 1; + while (!strcmp(argv[1], "env")) { + if (!argv[2]) + die("env specifier without a value"); + argv_array_push(&proc.env_array, argv[2]); + argv += 2; + argc -= 2; + } + if (argc < 3) + return 1; + proc.argv = (const char **)argv + 2; + + if (!strcmp(argv[1], "start-command-ENOENT")) { + if (start_command(&proc) < 0 && errno == ENOENT) + return 0; + fprintf(stderr, "FAIL %s\n", argv[1]); + return 1; + } + if (!strcmp(argv[1], "run-command")) + exit(run_command(&proc)); + + jobs = atoi(argv[2]); + proc.argv = (const char **)argv + 3; + + if (!strcmp(argv[1], "run-command-parallel")) + exit(run_processes_parallel(jobs, parallel_next, + NULL, NULL, &proc)); + + if (!strcmp(argv[1], "run-command-abort")) + exit(run_processes_parallel(jobs, parallel_next, + NULL, task_finished, &proc)); + + if (!strcmp(argv[1], "run-command-no-jobs")) + exit(run_processes_parallel(jobs, no_job, + NULL, task_finished, &proc)); + + fprintf(stderr, "check usage\n"); + return 1; +} diff --git a/t/helper/test-scrap-cache-tree.c b/t/helper/test-scrap-cache-tree.c new file mode 100644 index 0000000000..d26d3e7c8b --- /dev/null +++ b/t/helper/test-scrap-cache-tree.c @@ -0,0 +1,19 @@ +#include "test-tool.h" +#include "cache.h" +#include "lockfile.h" +#include "tree.h" +#include "cache-tree.h" + +static struct lock_file index_lock; + +int cmd__scrap_cache_tree(int ac, const char **av) +{ + setup_git_directory(); + hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); + if (read_cache() < 0) + die("unable to read index file"); + active_cache_tree = NULL; + if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) + die("unable to write index file"); + return 0; +} diff --git a/t/helper/test-sha1-array.c b/t/helper/test-sha1-array.c new file mode 100644 index 0000000000..ad5e69f9d3 --- /dev/null +++ b/t/helper/test-sha1-array.c @@ -0,0 +1,36 @@ +#include "test-tool.h" +#include "cache.h" +#include "sha1-array.h" + +static int print_oid(const struct object_id *oid, void *data) +{ + puts(oid_to_hex(oid)); + return 0; +} + +int cmd__sha1_array(int argc, const char **argv) +{ + struct oid_array array = OID_ARRAY_INIT; + struct strbuf line = STRBUF_INIT; + + while (strbuf_getline(&line, stdin) != EOF) { + const char *arg; + struct object_id oid; + + if (skip_prefix(line.buf, "append ", &arg)) { + if (get_oid_hex(arg, &oid)) + die("not a hexadecimal SHA1: %s", arg); + oid_array_append(&array, &oid); + } else if (skip_prefix(line.buf, "lookup ", &arg)) { + if (get_oid_hex(arg, &oid)) + die("not a hexadecimal SHA1: %s", arg); + printf("%d\n", oid_array_lookup(&array, &oid)); + } else if (!strcmp(line.buf, "clear")) + oid_array_clear(&array); + else if (!strcmp(line.buf, "for_each_unique")) + oid_array_for_each_unique(&array, print_oid, NULL); + else + die("unknown command: %s", line.buf); + } + return 0; +} diff --git a/t/helper/test-sha1.c b/t/helper/test-sha1.c new file mode 100644 index 0000000000..1ba0675c75 --- /dev/null +++ b/t/helper/test-sha1.c @@ -0,0 +1,57 @@ +#include "test-tool.h" +#include "cache.h" + +int cmd__sha1(int ac, const char **av) +{ + git_SHA_CTX ctx; + unsigned char sha1[20]; + unsigned bufsz = 8192; + int binary = 0; + char *buffer; + + if (ac == 2) { + if (!strcmp(av[1], "-b")) + binary = 1; + else + bufsz = strtoul(av[1], NULL, 10) * 1024 * 1024; + } + + if (!bufsz) + bufsz = 8192; + + while ((buffer = malloc(bufsz)) == NULL) { + fprintf(stderr, "bufsz %u is too big, halving...\n", bufsz); + bufsz /= 2; + if (bufsz < 1024) + die("OOPS"); + } + + git_SHA1_Init(&ctx); + + while (1) { + ssize_t sz, this_sz; + char *cp = buffer; + unsigned room = bufsz; + this_sz = 0; + while (room) { + sz = xread(0, cp, room); + if (sz == 0) + break; + if (sz < 0) + die_errno("test-sha1"); + this_sz += sz; + cp += sz; + room -= sz; + } + if (this_sz == 0) + break; + git_SHA1_Update(&ctx, buffer, this_sz); + } + git_SHA1_Final(sha1, &ctx); + + if (binary) + fwrite(sha1, 1, 20, stdout); + else + puts(sha1_to_hex(sha1)); + exit(0); +} diff --git a/t/helper/test-sha1.sh b/t/helper/test-sha1.sh new file mode 100755 index 0000000000..84594885c7 --- /dev/null +++ b/t/helper/test-sha1.sh @@ -0,0 +1,83 @@ +#!/bin/sh + +dd if=/dev/zero bs=1048576 count=100 2>/dev/null | +/usr/bin/time t/helper/test-tool sha1 >/dev/null + +while read expect cnt pfx +do + case "$expect" in '#'*) continue ;; esac + actual=$( + { + test -z "$pfx" || echo "$pfx" + dd if=/dev/zero bs=1048576 count=$cnt 2>/dev/null | + perl -pe 'y/\000/g/' + } | ./t/helper/test-tool sha1 $cnt + ) + if test "$expect" = "$actual" + then + echo "OK: $expect $cnt $pfx" + else + echo >&2 "OOPS: $cnt" + echo >&2 "expect: $expect" + echo >&2 "actual: $actual" + exit 1 + fi +done <<EOF +da39a3ee5e6b4b0d3255bfef95601890afd80709 0 +3f786850e387550fdab836ed7e6dc881de23001b 0 a +5277cbb45a15902137d332d97e89cf8136545485 0 ab +03cfd743661f07975fa2f1220c5194cbaff48451 0 abc +3330b4373640f9e4604991e73c7e86bfd8da2dc3 0 abcd +ec11312386ad561674f724b8cca7cf1796e26d1d 0 abcde +bdc37c074ec4ee6050d68bc133c6b912f36474df 0 abcdef +69bca99b923859f2dc486b55b87f49689b7358c7 0 abcdefg +e414af7161c9554089f4106d6f1797ef14a73666 0 abcdefgh +0707f2970043f9f7c22029482db27733deaec029 0 abcdefghi +a4dd8aa74a5636728fe52451636e2e17726033aa 1 +9986b45e2f4d7086372533bb6953a8652fa3644a 1 frotz +23d8d4f788e8526b4877548a32577543cbaaf51f 10 +8cd23f822ab44c7f481b8c92d591f6d1fcad431c 10 frotz +f3b5604a4e604899c1233edb3bf1cc0ede4d8c32 512 +b095bd837a371593048136e429e9ac4b476e1bb3 512 frotz +08fa81d6190948de5ccca3966340cc48c10cceac 1200 xyzzy +e33a291f42c30a159733dd98b8b3e4ff34158ca0 4090 4G +#a3bf783bc20caa958f6cb24dd140a7b21984838d 9999 nitfol +EOF + +exit + +# generating test vectors +# inputs are number of megabytes followed by some random string to prefix. + +while read cnt pfx +do + actual=$( + { + test -z "$pfx" || echo "$pfx" + dd if=/dev/zero bs=1048576 count=$cnt 2>/dev/null | + perl -pe 'y/\000/g/' + } | sha1sum | + sed -e 's/ .*//' + ) + echo "$actual $cnt $pfx" +done <<EOF +0 +0 a +0 ab +0 abc +0 abcd +0 abcde +0 abcdef +0 abcdefg +0 abcdefgh +0 abcdefghi +1 +1 frotz +10 +10 frotz +512 +512 frotz +1200 xyzzy +4090 4G +9999 nitfol +EOF diff --git a/t/helper/test-sigchain.c b/t/helper/test-sigchain.c new file mode 100644 index 0000000000..77ac5bc33f --- /dev/null +++ b/t/helper/test-sigchain.c @@ -0,0 +1,23 @@ +#include "test-tool.h" +#include "cache.h" +#include "sigchain.h" + +#define X(f) \ +static void f(int sig) { \ + puts(#f); \ + fflush(stdout); \ + sigchain_pop(sig); \ + raise(sig); \ +} +X(one) +X(two) +X(three) +#undef X + +int cmd__sigchain(int argc, const char **argv) { + sigchain_push(SIGTERM, one); + sigchain_push(SIGTERM, two); + sigchain_push(SIGTERM, three); + raise(SIGTERM); + return 0; +} diff --git a/t/helper/test-strcmp-offset.c b/t/helper/test-strcmp-offset.c new file mode 100644 index 0000000000..44e4a6d143 --- /dev/null +++ b/t/helper/test-strcmp-offset.c @@ -0,0 +1,23 @@ +#include "test-tool.h" +#include "cache.h" + +int cmd__strcmp_offset(int argc, const char **argv) +{ + int result; + size_t offset; + + if (!argv[1] || !argv[2]) + die("usage: %s <string1> <string2>", argv[0]); + + result = strcmp_offset(argv[1], argv[2], &offset); + + /* + * Because different CRTs behave differently, only rely on signs + * of the result values. + */ + result = (result < 0 ? -1 : + result > 0 ? 1 : + 0); + printf("%d %"PRIuMAX"\n", result, (uintmax_t)offset); + return 0; +} diff --git a/t/helper/test-string-list.c b/t/helper/test-string-list.c new file mode 100644 index 0000000000..2123dda85b --- /dev/null +++ b/t/helper/test-string-list.c @@ -0,0 +1,129 @@ +#include "test-tool.h" +#include "cache.h" +#include "string-list.h" + +/* + * Parse an argument into a string list. arg should either be a + * ':'-separated list of strings, or "-" to indicate an empty string + * list (as opposed to "", which indicates a string list containing a + * single empty string). list->strdup_strings must be set. + */ +static void parse_string_list(struct string_list *list, const char *arg) +{ + if (!strcmp(arg, "-")) + return; + + (void)string_list_split(list, arg, ':', -1); +} + +static void write_list(const struct string_list *list) +{ + int i; + for (i = 0; i < list->nr; i++) + printf("[%d]: \"%s\"\n", i, list->items[i].string); +} + +static void write_list_compact(const struct string_list *list) +{ + int i; + if (!list->nr) + printf("-\n"); + else { + printf("%s", list->items[0].string); + for (i = 1; i < list->nr; i++) + printf(":%s", list->items[i].string); + printf("\n"); + } +} + +static int prefix_cb(struct string_list_item *item, void *cb_data) +{ + const char *prefix = (const char *)cb_data; + return starts_with(item->string, prefix); +} + +int cmd__string_list(int argc, const char **argv) +{ + if (argc == 5 && !strcmp(argv[1], "split")) { + struct string_list list = STRING_LIST_INIT_DUP; + int i; + const char *s = argv[2]; + int delim = *argv[3]; + int maxsplit = atoi(argv[4]); + + i = string_list_split(&list, s, delim, maxsplit); + printf("%d\n", i); + write_list(&list); + string_list_clear(&list, 0); + return 0; + } + + if (argc == 5 && !strcmp(argv[1], "split_in_place")) { + struct string_list list = STRING_LIST_INIT_NODUP; + int i; + char *s = xstrdup(argv[2]); + int delim = *argv[3]; + int maxsplit = atoi(argv[4]); + + i = string_list_split_in_place(&list, s, delim, maxsplit); + printf("%d\n", i); + write_list(&list); + string_list_clear(&list, 0); + free(s); + return 0; + } + + if (argc == 4 && !strcmp(argv[1], "filter")) { + /* + * Retain only the items that have the specified prefix. + * Arguments: list|- prefix + */ + struct string_list list = STRING_LIST_INIT_DUP; + const char *prefix = argv[3]; + + parse_string_list(&list, argv[2]); + filter_string_list(&list, 0, prefix_cb, (void *)prefix); + write_list_compact(&list); + string_list_clear(&list, 0); + return 0; + } + + if (argc == 3 && !strcmp(argv[1], "remove_duplicates")) { + struct string_list list = STRING_LIST_INIT_DUP; + + parse_string_list(&list, argv[2]); + string_list_remove_duplicates(&list, 0); + write_list_compact(&list); + string_list_clear(&list, 0); + return 0; + } + + if (argc == 2 && !strcmp(argv[1], "sort")) { + struct string_list list = STRING_LIST_INIT_NODUP; + struct strbuf sb = STRBUF_INIT; + struct string_list_item *item; + + strbuf_read(&sb, 0, 0); + + /* + * Split by newline, but don't create a string_list item + * for the empty string after the last separator. + */ + if (sb.len && sb.buf[sb.len - 1] == '\n') + strbuf_setlen(&sb, sb.len - 1); + string_list_split_in_place(&list, sb.buf, '\n', -1); + + string_list_sort(&list); + + for_each_string_list_item(item, &list) + puts(item->string); + + string_list_clear(&list, 0); + strbuf_release(&sb); + return 0; + } + + fprintf(stderr, "%s: unknown function name: %s\n", argv[0], + argv[1] ? argv[1] : "(there was none)"); + return 1; +} diff --git a/t/helper/test-submodule-config.c b/t/helper/test-submodule-config.c new file mode 100644 index 0000000000..5c6e4b010d --- /dev/null +++ b/t/helper/test-submodule-config.c @@ -0,0 +1,71 @@ +#include "test-tool.h" +#include "cache.h" +#include "config.h" +#include "submodule-config.h" +#include "submodule.h" + +static void die_usage(int argc, const char **argv, const char *msg) +{ + fprintf(stderr, "%s\n", msg); + fprintf(stderr, "Usage: %s [<commit> <submodulepath>] ...\n", argv[0]); + exit(1); +} + +int cmd__submodule_config(int argc, const char **argv) +{ + const char **arg = argv; + int my_argc = argc; + int output_url = 0; + int lookup_name = 0; + + arg++; + my_argc--; + while (arg[0] && starts_with(arg[0], "--")) { + if (!strcmp(arg[0], "--url")) + output_url = 1; + if (!strcmp(arg[0], "--name")) + lookup_name = 1; + arg++; + my_argc--; + } + + if (my_argc % 2 != 0) + die_usage(argc, argv, "Wrong number of arguments."); + + setup_git_directory(); + + while (*arg) { + struct object_id commit_oid; + const struct submodule *submodule; + const char *commit; + const char *path_or_name; + + commit = arg[0]; + path_or_name = arg[1]; + + if (commit[0] == '\0') + oidclr(&commit_oid); + else if (get_oid(commit, &commit_oid) < 0) + die_usage(argc, argv, "Commit not found."); + + if (lookup_name) { + submodule = submodule_from_name(&commit_oid, path_or_name); + } else + submodule = submodule_from_path(&commit_oid, path_or_name); + if (!submodule) + die_usage(argc, argv, "Submodule not found."); + + if (output_url) + printf("Submodule url: '%s' for path '%s'\n", + submodule->url, submodule->path); + else + printf("Submodule name: '%s' for path '%s'\n", + submodule->name, submodule->path); + + arg += 2; + } + + submodule_free(); + + return 0; +} diff --git a/t/helper/test-subprocess.c b/t/helper/test-subprocess.c new file mode 100644 index 0000000000..92b69de635 --- /dev/null +++ b/t/helper/test-subprocess.c @@ -0,0 +1,20 @@ +#include "test-tool.h" +#include "cache.h" +#include "run-command.h" + +int cmd__subprocess(int argc, const char **argv) +{ + struct child_process cp = CHILD_PROCESS_INIT; + int nogit = 0; + + setup_git_directory_gently(&nogit); + if (nogit) + die("No git repo found"); + if (argc > 1 && !strcmp(argv[1], "--setup-work-tree")) { + setup_work_tree(); + argv++; + } + cp.git_cmd = 1; + cp.argv = (const char **)argv + 1; + return run_command(&cp); +} diff --git a/t/helper/test-svn-fe.c b/t/helper/test-svn-fe.c new file mode 100644 index 0000000000..7667c0803f --- /dev/null +++ b/t/helper/test-svn-fe.c @@ -0,0 +1,52 @@ +/* + * test-svn-fe: Code to exercise the svn import lib + */ + +#include "git-compat-util.h" +#include "vcs-svn/svndump.h" +#include "vcs-svn/svndiff.h" +#include "vcs-svn/sliding_window.h" +#include "vcs-svn/line_buffer.h" + +static const char test_svnfe_usage[] = + "test-svn-fe (<dumpfile> | [-d] <preimage> <delta> <len>)"; + +static int apply_delta(int argc, const char **argv) +{ + struct line_buffer preimage = LINE_BUFFER_INIT; + struct line_buffer delta = LINE_BUFFER_INIT; + struct sliding_view preimage_view = SLIDING_VIEW_INIT(&preimage, -1); + + if (argc != 5) + usage(test_svnfe_usage); + + if (buffer_init(&preimage, argv[2])) + die_errno("cannot open preimage"); + if (buffer_init(&delta, argv[3])) + die_errno("cannot open delta"); + if (svndiff0_apply(&delta, (off_t) strtoumax(argv[4], NULL, 0), + &preimage_view, stdout)) + return 1; + if (buffer_deinit(&preimage)) + die_errno("cannot close preimage"); + if (buffer_deinit(&delta)) + die_errno("cannot close delta"); + strbuf_release(&preimage_view.buf); + return 0; +} + +int cmd_main(int argc, const char **argv) +{ + if (argc == 2) { + if (svndump_init(argv[1])) + return 1; + svndump_read(NULL, "refs/heads/master", "refs/notes/svn/revs"); + svndump_deinit(); + svndump_reset(); + return 0; + } + + if (argc >= 2 && !strcmp(argv[1], "-d")) + return apply_delta(argc, argv); + usage(test_svnfe_usage); +} diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c new file mode 100644 index 0000000000..87066ced62 --- /dev/null +++ b/t/helper/test-tool.c @@ -0,0 +1,62 @@ +#include "git-compat-util.h" +#include "test-tool.h" + +struct test_cmd { + const char *name; + int (*fn)(int argc, const char **argv); +}; + +static struct test_cmd cmds[] = { + { "chmtime", cmd__chmtime }, + { "config", cmd__config }, + { "ctype", cmd__ctype }, + { "date", cmd__date }, + { "delta", cmd__delta }, + { "drop-caches", cmd__drop_caches }, + { "dump-cache-tree", cmd__dump_cache_tree }, + { "dump-split-index", cmd__dump_split_index }, + { "example-decorate", cmd__example_decorate }, + { "genrandom", cmd__genrandom }, + { "hashmap", cmd__hashmap }, + { "index-version", cmd__index_version }, + { "lazy-init-name-hash", cmd__lazy_init_name_hash }, + { "match-trees", cmd__match_trees }, + { "mergesort", cmd__mergesort }, + { "mktemp", cmd__mktemp }, + { "online-cpus", cmd__online_cpus }, + { "path-utils", cmd__path_utils }, + { "prio-queue", cmd__prio_queue }, + { "read-cache", cmd__read_cache }, + { "ref-store", cmd__ref_store }, + { "regex", cmd__regex }, + { "revision-walking", cmd__revision_walking }, + { "run-command", cmd__run_command }, + { "scrap-cache-tree", cmd__scrap_cache_tree }, + { "sha1-array", cmd__sha1_array }, + { "sha1", cmd__sha1 }, + { "sigchain", cmd__sigchain }, + { "strcmp-offset", cmd__strcmp_offset }, + { "string-list", cmd__string_list }, + { "submodule-config", cmd__submodule_config }, + { "subprocess", cmd__subprocess }, + { "urlmatch-normalization", cmd__urlmatch_normalization }, + { "wildmatch", cmd__wildmatch }, + { "write-cache", cmd__write_cache }, +}; + +int cmd_main(int argc, const char **argv) +{ + int i; + + if (argc < 2) + die("I need a test name!"); + + for (i = 0; i < ARRAY_SIZE(cmds); i++) { + if (!strcmp(cmds[i].name, argv[1])) { + argv++; + argc--; + return cmds[i].fn(argc, argv); + } + } + die("There is no test named '%s'", argv[1]); +} diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h new file mode 100644 index 0000000000..7116ddfb94 --- /dev/null +++ b/t/helper/test-tool.h @@ -0,0 +1,40 @@ +#ifndef __TEST_TOOL_H__ +#define __TEST_TOOL_H__ + +int cmd__chmtime(int argc, const char **argv); +int cmd__config(int argc, const char **argv); +int cmd__ctype(int argc, const char **argv); +int cmd__date(int argc, const char **argv); +int cmd__delta(int argc, const char **argv); +int cmd__drop_caches(int argc, const char **argv); +int cmd__dump_cache_tree(int argc, const char **argv); +int cmd__dump_split_index(int argc, const char **argv); +int cmd__example_decorate(int argc, const char **argv); +int cmd__genrandom(int argc, const char **argv); +int cmd__hashmap(int argc, const char **argv); +int cmd__index_version(int argc, const char **argv); +int cmd__lazy_init_name_hash(int argc, const char **argv); +int cmd__match_trees(int argc, const char **argv); +int cmd__mergesort(int argc, const char **argv); +int cmd__mktemp(int argc, const char **argv); +int cmd__online_cpus(int argc, const char **argv); +int cmd__path_utils(int argc, const char **argv); +int cmd__prio_queue(int argc, const char **argv); +int cmd__read_cache(int argc, const char **argv); +int cmd__ref_store(int argc, const char **argv); +int cmd__regex(int argc, const char **argv); +int cmd__revision_walking(int argc, const char **argv); +int cmd__run_command(int argc, const char **argv); +int cmd__scrap_cache_tree(int argc, const char **argv); +int cmd__sha1_array(int argc, const char **argv); +int cmd__sha1(int argc, const char **argv); +int cmd__sigchain(int argc, const char **argv); +int cmd__strcmp_offset(int argc, const char **argv); +int cmd__string_list(int argc, const char **argv); +int cmd__submodule_config(int argc, const char **argv); +int cmd__subprocess(int argc, const char **argv); +int cmd__urlmatch_normalization(int argc, const char **argv); +int cmd__wildmatch(int argc, const char **argv); +int cmd__write_cache(int argc, const char **argv); + +#endif diff --git a/t/helper/test-urlmatch-normalization.c b/t/helper/test-urlmatch-normalization.c new file mode 100644 index 0000000000..8f4d67e646 --- /dev/null +++ b/t/helper/test-urlmatch-normalization.c @@ -0,0 +1,51 @@ +#include "test-tool.h" +#include "git-compat-util.h" +#include "urlmatch.h" + +int cmd__urlmatch_normalization(int argc, const char **argv) +{ + const char usage[] = "test-tool urlmatch-normalization [-p | -l] <url1> | <url1> <url2>"; + char *url1, *url2; + int opt_p = 0, opt_l = 0; + + /* + * For one url, succeed if url_normalize succeeds on it, fail otherwise. + * For two urls, succeed only if url_normalize succeeds on both and + * the results compare equal with strcmp. If -p is given (one url only) + * and url_normalize succeeds, print the result followed by "\n". If + * -l is given (one url only) and url_normalize succeeds, print the + * returned length in decimal followed by "\n". + */ + + if (argc > 1 && !strcmp(argv[1], "-p")) { + opt_p = 1; + argc--; + argv++; + } else if (argc > 1 && !strcmp(argv[1], "-l")) { + opt_l = 1; + argc--; + argv++; + } + + if (argc < 2 || argc > 3) + die("%s", usage); + + if (argc == 2) { + struct url_info info; + url1 = url_normalize(argv[1], &info); + if (!url1) + return 1; + if (opt_p) + printf("%s\n", url1); + if (opt_l) + printf("%u\n", (unsigned)info.url_len); + return 0; + } + + if (opt_p || opt_l) + die("%s", usage); + + url1 = url_normalize(argv[1], NULL); + url2 = url_normalize(argv[2], NULL); + return (url1 && url2 && !strcmp(url1, url2)) ? 0 : 1; +} diff --git a/t/helper/test-wildmatch.c b/t/helper/test-wildmatch.c new file mode 100644 index 0000000000..2c103d1824 --- /dev/null +++ b/t/helper/test-wildmatch.c @@ -0,0 +1,24 @@ +#include "test-tool.h" +#include "cache.h" + +int cmd__wildmatch(int argc, const char **argv) +{ + int i; + for (i = 2; i < argc; i++) { + if (argv[i][0] == '/') + die("Forward slash is not allowed at the beginning of the\n" + "pattern because Windows does not like it. Use `XXX/' instead."); + else if (!strncmp(argv[i], "XXX/", 4)) + argv[i] += 3; + } + if (!strcmp(argv[1], "wildmatch")) + return !!wildmatch(argv[3], argv[2], WM_PATHNAME); + else if (!strcmp(argv[1], "iwildmatch")) + return !!wildmatch(argv[3], argv[2], WM_PATHNAME | WM_CASEFOLD); + else if (!strcmp(argv[1], "pathmatch")) + return !!wildmatch(argv[3], argv[2], 0); + else if (!strcmp(argv[1], "ipathmatch")) + return !!wildmatch(argv[3], argv[2], WM_CASEFOLD); + else + return 1; +} diff --git a/t/helper/test-write-cache.c b/t/helper/test-write-cache.c new file mode 100644 index 0000000000..017dc30380 --- /dev/null +++ b/t/helper/test-write-cache.c @@ -0,0 +1,24 @@ +#include "test-tool.h" +#include "cache.h" +#include "lockfile.h" + +static struct lock_file index_lock; + +int cmd__write_cache(int argc, const char **argv) +{ + int i, cnt = 1, lockfd; + if (argc == 2) + cnt = strtol(argv[1], NULL, 0); + setup_git_directory(); + read_cache(); + for (i = 0; i < cnt; i++) { + lockfd = hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); + if (0 <= lockfd) { + write_locked_index(&the_index, &index_lock, COMMIT_LOCK); + } else { + rollback_lock_file(&index_lock); + } + } + + return 0; +} diff --git a/t/interop/.gitignore b/t/interop/.gitignore new file mode 100644 index 0000000000..49c78d3dba --- /dev/null +++ b/t/interop/.gitignore @@ -0,0 +1,4 @@ +/trash directory*/ +/test-results/ +/.prove/ +/build/ diff --git a/t/interop/Makefile b/t/interop/Makefile new file mode 100644 index 0000000000..31a4bbc716 --- /dev/null +++ b/t/interop/Makefile @@ -0,0 +1,16 @@ +-include ../../config.mak +export GIT_TEST_OPTIONS + +SHELL_PATH ?= $(SHELL) +SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) +T = $(sort $(wildcard i[0-9][0-9][0-9][0-9]-*.sh)) + +all: $(T) + +$(T): + @echo "*** $@ ***"; '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS) + +clean: + rm -rf build "trash directory".* test-results + +.PHONY: all clean $(T) diff --git a/t/interop/README b/t/interop/README new file mode 100644 index 0000000000..72d42bd856 --- /dev/null +++ b/t/interop/README @@ -0,0 +1,85 @@ +Git version interoperability tests +================================== + +This directory has interoperability tests for git. Each script is +similar to the normal test scripts found in t/, but with the added twist +that two special versions of git, "git.a" and "git.b", are available in +the PATH. Individual tests can then check the interaction between the +two versions. + +When you add a feature that handles backwards compatibility between git +versions, it's encouraged to add a test here to make sure it behaves as +you expect. + + +Running Tests +------------- + +The easiest way to run tests is to say "make". This runs all +the tests against their default versions. + +You can run a single test like: + + $ ./i0000-basic.sh + ok 1 - bare git is forbidden + ok 2 - git.a version (v1.6.6.3) + ok 3 - git.b version (v2.11.1) + # passed all 3 test(s) + 1..3 + +Each test contains default versions to run against. You may override +these by setting `GIT_TEST_VERSION_A` and `GIT_TEST_VERSION_B` in the +environment. Note that not all combinations will give sensible outcomes +for all tests (e.g., a test checking for a specific old/new interaction +may want something "old" enough" and something "new" enough; see +individual tests for details). + +Version names should be resolvable as revisions in the current +repository. They will be exported and built as needed using the +config.mak files found at the root of your working tree. + +The exception is the special version "." which uses the currently-built +contents of your working tree. + +You can set the following variables (in the environment or in your config.mak): + + GIT_INTEROP_MAKE_OPTS + Options to pass to `make` when building a git version (e.g., + `-j8`). + +You can also pass any command-line options taken by ordinary git tests (e.g., +"-v"). + + +Naming Tests +------------ + +The interop test files are named like: + + iNNNN-short-description.sh + +where N is a decimal digit. The same conventions for choosing NNNN as +for normal tests apply. + + +Writing Tests +------------- + +An interop test script starts like a normal script, declaring a few +variables and then including interop-lib.sh (which includes test-lib.sh). +Besides test_description, you should also set the $VERSION_A and $VERSION_B +variables to give the default versions to test against. See t0000-basic.sh for +an example. + +You can then use test_expect_success as usual, with a few differences: + + 1. The special commands "git.a" and "git.b" correspond to the + two versions. + + 2. You cannot call a bare "git". This is to prevent accidents where + you meant "git.a" or "git.b". + + 3. The trash directory is _not_ a git repository by default. You + should create one with the appropriate version of git. + +At the end of the script, call test_done as usual. diff --git a/t/interop/i0000-basic.sh b/t/interop/i0000-basic.sh new file mode 100755 index 0000000000..903e9193f8 --- /dev/null +++ b/t/interop/i0000-basic.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +# Note that this test only works on real version numbers, +# as it depends on matching the output to "git version". +VERSION_A=v1.6.6.3 +VERSION_B=v2.11.1 + +test_description='sanity test interop library' +. ./interop-lib.sh + +test_expect_success 'bare git is forbidden' ' + test_must_fail git version +' + +test_expect_success "git.a version ($VERSION_A)" ' + echo git version ${VERSION_A#v} >expect && + git.a version >actual && + test_cmp expect actual +' + +test_expect_success "git.b version ($VERSION_B)" ' + echo git version ${VERSION_B#v} >expect && + git.b version >actual && + test_cmp expect actual +' + +test_done diff --git a/t/interop/i5500-git-daemon.sh b/t/interop/i5500-git-daemon.sh new file mode 100755 index 0000000000..1daf69420b --- /dev/null +++ b/t/interop/i5500-git-daemon.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +VERSION_A=. +VERSION_B=v1.0.0 + +: ${LIB_GIT_DAEMON_PORT:=5500} +LIB_GIT_DAEMON_COMMAND='git.a daemon' + +test_description='clone and fetch by older client' +. ./interop-lib.sh +. "$TEST_DIRECTORY"/lib-git-daemon.sh + +start_git_daemon --export-all + +repo=$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo + +test_expect_success "create repo served by $VERSION_A" ' + git.a init "$repo" && + git.a -C "$repo" commit --allow-empty -m one +' + +test_expect_success "clone with $VERSION_B" ' + git.b clone "$GIT_DAEMON_URL/repo" child && + echo one >expect && + git.a -C child log -1 --format=%s >actual && + test_cmp expect actual +' + +test_expect_success "fetch with $VERSION_B" ' + git.a -C "$repo" commit --allow-empty -m two && + ( + cd child && + git.b fetch + ) && + echo two >expect && + git.a -C child log -1 --format=%s FETCH_HEAD >actual && + test_cmp expect actual +' + +stop_git_daemon +test_done diff --git a/t/interop/i5700-protocol-transition.sh b/t/interop/i5700-protocol-transition.sh new file mode 100755 index 0000000000..97e8e580ef --- /dev/null +++ b/t/interop/i5700-protocol-transition.sh @@ -0,0 +1,68 @@ +#!/bin/sh + +VERSION_A=. +VERSION_B=v2.0.0 + +: ${LIB_GIT_DAEMON_PORT:=5700} +LIB_GIT_DAEMON_COMMAND='git.b daemon' + +test_description='clone and fetch by client who is trying to use a new protocol' +. ./interop-lib.sh +. "$TEST_DIRECTORY"/lib-git-daemon.sh + +start_git_daemon --export-all + +repo=$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo + +test_expect_success "create repo served by $VERSION_B" ' + git.b init "$repo" && + git.b -C "$repo" commit --allow-empty -m one +' + +test_expect_success "git:// clone with $VERSION_A and protocol v1" ' + GIT_TRACE_PACKET=1 git.a -c protocol.version=1 clone "$GIT_DAEMON_URL/repo" child 2>log && + git.a -C child log -1 --format=%s >actual && + git.b -C "$repo" log -1 --format=%s >expect && + test_cmp expect actual && + grep "version=1" log +' + +test_expect_success "git:// fetch with $VERSION_A and protocol v1" ' + git.b -C "$repo" commit --allow-empty -m two && + git.b -C "$repo" log -1 --format=%s >expect && + + GIT_TRACE_PACKET=1 git.a -C child -c protocol.version=1 fetch 2>log && + git.a -C child log -1 --format=%s FETCH_HEAD >actual && + + test_cmp expect actual && + grep "version=1" log && + ! grep "version 1" log +' + +stop_git_daemon + +test_expect_success "create repo served by $VERSION_B" ' + git.b init parent && + git.b -C parent commit --allow-empty -m one +' + +test_expect_success "file:// clone with $VERSION_A and protocol v1" ' + GIT_TRACE_PACKET=1 git.a -c protocol.version=1 clone --upload-pack="git.b upload-pack" parent child2 2>log && + git.a -C child2 log -1 --format=%s >actual && + git.b -C parent log -1 --format=%s >expect && + test_cmp expect actual && + ! grep "version 1" log +' + +test_expect_success "file:// fetch with $VERSION_A and protocol v1" ' + git.b -C parent commit --allow-empty -m two && + git.b -C parent log -1 --format=%s >expect && + + GIT_TRACE_PACKET=1 git.a -C child2 -c protocol.version=1 fetch --upload-pack="git.b upload-pack" 2>log && + git.a -C child2 log -1 --format=%s FETCH_HEAD >actual && + + test_cmp expect actual && + ! grep "version 1" log +' + +test_done diff --git a/t/interop/interop-lib.sh b/t/interop/interop-lib.sh new file mode 100644 index 0000000000..3e0a2911d4 --- /dev/null +++ b/t/interop/interop-lib.sh @@ -0,0 +1,92 @@ +# Interoperability testing framework. Each script should source +# this after setting default $VERSION_A and $VERSION_B variables. + +. ../../GIT-BUILD-OPTIONS +INTEROP_ROOT=$(pwd) +BUILD_ROOT=$INTEROP_ROOT/build + +build_version () { + if test -z "$1" + then + echo >&2 "error: test script did not set default versions" + return 1 + fi + + if test "$1" = "." + then + git rev-parse --show-toplevel + return 0 + fi + + sha1=$(git rev-parse "$1^{tree}") || return 1 + dir=$BUILD_ROOT/$sha1 + + if test -e "$dir/.built" + then + echo "$dir" + return 0 + fi + + echo >&2 "==> Building $1..." + + mkdir -p "$dir" || return 1 + + (cd "$(git rev-parse --show-cdup)" && git archive --format=tar "$sha1") | + (cd "$dir" && tar x) || + return 1 + + for config in config.mak config.mak.autogen config.status + do + if test -e "$INTEROP_ROOT/../../$config" + then + cp "$INTEROP_ROOT/../../$config" "$dir/" || return 1 + fi + done + + ( + cd "$dir" && + make $GIT_INTEROP_MAKE_OPTS >&2 && + touch .built + ) || return 1 + + echo "$dir" +} + +# Old versions of git don't have bin-wrappers, so let's give a rough emulation. +wrap_git () { + write_script "$1" <<-EOF + GIT_EXEC_PATH="$2" + export GIT_EXEC_PATH + PATH="$2:\$PATH" + export GIT_EXEC_PATH + exec git "\$@" + EOF +} + +generate_wrappers () { + mkdir -p .bin && + wrap_git .bin/git.a "$DIR_A" && + wrap_git .bin/git.b "$DIR_B" && + write_script .bin/git <<-\EOF && + echo >&2 fatal: test tried to run generic git + exit 1 + EOF + PATH=$(pwd)/.bin:$PATH +} + +VERSION_A=${GIT_TEST_VERSION_A:-$VERSION_A} +VERSION_B=${GIT_TEST_VERSION_B:-$VERSION_B} + +if ! DIR_A=$(build_version "$VERSION_A") || + ! DIR_B=$(build_version "$VERSION_B") +then + echo >&2 "fatal: unable to build git versions" + exit 1 +fi + +TEST_DIRECTORY=$INTEROP_ROOT/.. +TEST_OUTPUT_DIRECTORY=$INTEROP_ROOT +TEST_NO_CREATE_REPO=t +. "$TEST_DIRECTORY"/test-lib.sh + +generate_wrappers || die "unable to set up interop test environment" diff --git a/t/lib-credential.sh b/t/lib-credential.sh index d8e41f7ddd..937b831ea6 100755 --- a/t/lib-credential.sh +++ b/t/lib-credential.sh @@ -44,6 +44,7 @@ helper_test_clean() { reject $1 https example.com user2 reject $1 http path.tld user reject $1 https timeout.tld user + reject $1 https sso.tld } reject() { @@ -250,6 +251,24 @@ helper_test() { password=pass2 EOF ' + + test_expect_success "helper ($HELPER) can store empty username" ' + check approve $HELPER <<-\EOF && + protocol=https + host=sso.tld + username= + password= + EOF + check fill $HELPER <<-\EOF + protocol=https + host=sso.tld + -- + protocol=https + host=sso.tld + username= + password= + EOF + ' } helper_test_timeout() { diff --git a/t/lib-git-daemon.sh b/t/lib-git-daemon.sh index bc4b3412fb..edbea2d986 100644 --- a/t/lib-git-daemon.sh +++ b/t/lib-git-daemon.sh @@ -23,11 +23,17 @@ then test_done fi +if test_have_prereq !PIPE +then + test_skip_or_die $GIT_TEST_GIT_DAEMON "file system does not support FIFOs" +fi + LIB_GIT_DAEMON_PORT=${LIB_GIT_DAEMON_PORT-${this_test#t}} GIT_DAEMON_PID= GIT_DAEMON_DOCUMENT_ROOT_PATH="$PWD"/repo -GIT_DAEMON_URL=git://127.0.0.1:$LIB_GIT_DAEMON_PORT +GIT_DAEMON_HOST_PORT=127.0.0.1:$LIB_GIT_DAEMON_PORT +GIT_DAEMON_URL=git://$GIT_DAEMON_HOST_PORT start_git_daemon() { if test -n "$GIT_DAEMON_PID" @@ -41,17 +47,26 @@ start_git_daemon() { say >&3 "Starting git daemon ..." mkfifo git_daemon_output - git daemon --listen=127.0.0.1 --port="$LIB_GIT_DAEMON_PORT" \ + ${LIB_GIT_DAEMON_COMMAND:-git daemon} \ + --listen=127.0.0.1 --port="$LIB_GIT_DAEMON_PORT" \ --reuseaddr --verbose \ --base-path="$GIT_DAEMON_DOCUMENT_ROOT_PATH" \ "$@" "$GIT_DAEMON_DOCUMENT_ROOT_PATH" \ >&3 2>git_daemon_output & GIT_DAEMON_PID=$! + >daemon.log { - read line <&7 - echo >&4 "$line" - cat <&7 >&4 & - } 7<git_daemon_output && + read -r line <&7 + printf "%s\n" "$line" + printf >&4 "%s\n" "$line" + ( + while read -r line <&7 + do + printf "%s\n" "$line" + printf >&4 "%s\n" "$line" + done + ) & + } 7<git_daemon_output >>"$TRASH_DIRECTORY/daemon.log" && # Check expected output if test x"$(expr "$line" : "\[[0-9]*\] \(.*\)")" != x"Ready to rumble" @@ -77,11 +92,32 @@ stop_git_daemon() { kill "$GIT_DAEMON_PID" wait "$GIT_DAEMON_PID" >&3 2>&4 ret=$? - # expect exit with status 143 = 128+15 for signal TERM=15 - if test $ret -ne 143 + if test_match_signal 15 $? then error "git daemon exited with status: $ret" fi GIT_DAEMON_PID= rm -f git_daemon_output } + +# A stripped-down version of a netcat client, that connects to a "host:port" +# given in $1, sends its stdin followed by EOF, then dumps the response (until +# EOF) to stdout. +fake_nc() { + if ! test_declared_prereq FAKENC + then + echo >&4 "fake_nc: need to declare FAKENC prerequisite" + return 127 + fi + perl -Mstrict -MIO::Socket::INET -e ' + my $s = IO::Socket::INET->new(shift) + or die "unable to open socket: $!"; + print $s <STDIN>; + $s->shutdown(1); + print <$s>; + ' "$@" +} + +test_lazy_prereq FAKENC ' + perl -MIO::Socket::INET -e "exit 0" +' diff --git a/t/lib-git-p4.sh b/t/lib-git-p4.sh index 75482254a3..c27599474c 100644 --- a/t/lib-git-p4.sh +++ b/t/lib-git-p4.sh @@ -6,6 +6,14 @@ # a subdirectory called "$git" TEST_NO_CREATE_REPO=NoThanks +# Some operations require multiple attempts to be successful. Define +# here the maximal retry timeout in seconds. +RETRY_TIMEOUT=60 + +# Sometimes p4d seems to hang. Terminate the p4d process automatically after +# the defined timeout in seconds. +P4D_TIMEOUT=300 + . ./test-lib.sh if ! test_have_prereq PYTHON @@ -25,17 +33,26 @@ fi # Older versions of perforce were available compiled natively for # cygwin. Those do not accept native windows paths, so make sure # not to convert for them. -native_path() { +native_path () { path="$1" && if test_have_prereq CYGWIN && ! p4 -V | grep -q CYGWIN then path=$(cygpath --windows "$path") else - path=$(test-path-utils real_path "$path") + path=$(test-tool path-utils real_path "$path") fi && echo "$path" } +# On Solaris the 'date +%s' function is not supported and therefore we +# need this replacement. +# Attention: This function is not safe again against time offset updates +# at runtime (e.g. via NTP). The 'clock_gettime(CLOCK_MONOTONIC)' +# function could fix that but it is not in Python until 3.3. +time_in_seconds () { + (cd / && "$PYTHON_PATH" -c 'import time; print(int(time.time()))') +} + # Try to pick a unique port: guess a large number, then hope # no more than one of each test is running. # @@ -57,13 +74,22 @@ cli="$TRASH_DIRECTORY/cli" git="$TRASH_DIRECTORY/git" pidfile="$TRASH_DIRECTORY/p4d.pid" +# Sometimes "prove" seems to hang on exit because p4d is still running +cleanup () { + if test -f "$pidfile" + then + kill -9 $(cat "$pidfile") 2>/dev/null && exit 255 + fi +} +trap cleanup EXIT + # git p4 submit generates a temp file, which will # not get cleaned up if the submission fails. Don't # clutter up /tmp on the test machine. TMPDIR="$TRASH_DIRECTORY" export TMPDIR -start_p4d() { +start_p4d () { mkdir -p "$db" "$cli" "$git" && rm -f "$pidfile" && ( @@ -81,6 +107,19 @@ start_p4d() { # will be caught with the "kill -0" check below. i=${P4D_START_PATIENCE:-300} pid=$(cat "$pidfile") + + timeout=$(($(time_in_seconds) + $P4D_TIMEOUT)) + while true + do + if test $(time_in_seconds) -gt $timeout + then + kill -9 $pid + exit 1 + fi + sleep 1 + done & + watchdog_pid=$! + ready= while test $i -gt 0 do @@ -112,7 +151,7 @@ start_p4d() { return 0 } -p4_add_user() { +p4_add_user () { name=$1 && p4 user -f -i <<-EOF User: $name @@ -121,33 +160,57 @@ p4_add_user() { EOF } -kill_p4d() { - pid=$(cat "$pidfile") - # it had better exist for the first kill - kill $pid && - for i in 1 2 3 4 5 ; do - kill $pid >/dev/null 2>&1 || break +p4_add_job () { + p4 job -f -i <<-EOF + Job: $1 + Status: open + User: dummy + Description: + EOF +} + +retry_until_success () { + timeout=$(($(time_in_seconds) + $RETRY_TIMEOUT)) + until "$@" 2>/dev/null || test $(time_in_seconds) -gt $timeout + do + sleep 1 + done +} + +retry_until_fail () { + timeout=$(($(time_in_seconds) + $RETRY_TIMEOUT)) + until ! "$@" 2>/dev/null || test $(time_in_seconds) -gt $timeout + do sleep 1 - done && + done +} + +kill_p4d () { + pid=$(cat "$pidfile") + retry_until_fail kill $pid + retry_until_fail kill -9 $pid # complain if it would not die test_must_fail kill $pid >/dev/null 2>&1 && - rm -rf "$db" "$cli" "$pidfile" + rm -rf "$db" "$cli" "$pidfile" && + retry_until_fail kill -9 $watchdog_pid } -cleanup_git() { - rm -rf "$git" && - mkdir "$git" +cleanup_git () { + retry_until_success rm -r "$git" + test_must_fail test -d "$git" && + retry_until_success mkdir "$git" } -marshal_dump() { +marshal_dump () { what=$1 && line=${2:-1} && cat >"$TRASH_DIRECTORY/marshal-dump.py" <<-EOF && import marshal import sys + instream = getattr(sys.stdin, 'buffer', sys.stdin) for i in range($line): - d = marshal.load(sys.stdin) - print d['$what'] + d = marshal.load(instream) + print(d[b'$what'].decode('utf-8')) EOF "$PYTHON_PATH" "$TRASH_DIRECTORY/marshal-dump.py" } @@ -155,7 +218,7 @@ marshal_dump() { # # Construct a client with this list of View lines # -client_view() { +client_view () { ( cat <<-EOF && Client: $P4CLIENT @@ -169,7 +232,7 @@ client_view() { ) | p4 client -i } -is_cli_file_writeable() { +is_cli_file_writeable () { # cygwin version of p4 does not set read-only attr, # will be marked 444 but -w is true file="$1" && diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index b0ec12ff6c..a8130f9119 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -1,8 +1,5 @@ . ./test-lib.sh -remotes_git_svn=remotes/git""-svn -git_svn_id=git""-svn-id - if test -n "$NO_SVN_TESTS" then skip_all='skipping git svn tests, NO_SVN_TESTS defined' @@ -20,8 +17,8 @@ SVN_TREE=$GIT_SVN_DIR/svn-tree svn >/dev/null 2>&1 if test $? -ne 1 then - skip_all='skipping git svn tests, svn not found' - test_done + skip_all='skipping git svn tests, svn not found' + test_done fi svnrepo=$PWD/svnrepo @@ -52,7 +49,7 @@ rawsvnrepo="$svnrepo" svnrepo="file://$svnrepo" poke() { - test-chmtime +1 "$1" + test-tool chmtime +1 "$1" } # We need this, because we should pass empty configuration directory to @@ -68,81 +65,22 @@ svn_cmd () { svn "$orig_svncmd" --config-dir "$svnconf" "$@" } -prepare_httpd () { - for d in \ - "$SVN_HTTPD_PATH" \ - /usr/sbin/apache2 \ - /usr/sbin/httpd \ - ; do - if test -f "$d" - then - SVN_HTTPD_PATH="$d" - break - fi - done - if test -z "$SVN_HTTPD_PATH" - then - echo >&2 '*** error: Apache not found' - return 1 - fi - for d in \ - "$SVN_HTTPD_MODULE_PATH" \ - /usr/lib/apache2/modules \ - /usr/libexec/apache2 \ - ; do - if test -d "$d" - then - SVN_HTTPD_MODULE_PATH="$d" - break - fi - done - if test -z "$SVN_HTTPD_MODULE_PATH" - then - echo >&2 '*** error: Apache module dir not found' - return 1 - fi - if test ! -f "$SVN_HTTPD_MODULE_PATH/mod_dav_svn.so" - then - echo >&2 '*** error: Apache module "mod_dav_svn" not found' - return 1 - fi - - repo_base_path="${1-svn}" - mkdir "$GIT_DIR"/logs - - cat > "$GIT_DIR/httpd.conf" <<EOF -ServerName "git svn test" -ServerRoot "$GIT_DIR" -DocumentRoot "$GIT_DIR" -PidFile "$GIT_DIR/httpd.pid" -LockFile logs/accept.lock -Listen 127.0.0.1:$SVN_HTTPD_PORT -LoadModule dav_module $SVN_HTTPD_MODULE_PATH/mod_dav.so -LoadModule dav_svn_module $SVN_HTTPD_MODULE_PATH/mod_dav_svn.so -<Location /$repo_base_path> - DAV svn - SVNPath "$rawsvnrepo" -</Location> -EOF -} - -start_httpd () { - if test -z "$SVN_HTTPD_PORT" - then - echo >&2 'SVN_HTTPD_PORT is not defined!' - return - fi - - prepare_httpd "$1" || return 1 - - "$SVN_HTTPD_PATH" -f "$GIT_DIR"/httpd.conf -k start - svnrepo="http://127.0.0.1:$SVN_HTTPD_PORT/$repo_base_path" -} - -stop_httpd () { - test -z "$SVN_HTTPD_PORT" && return - test ! -f "$GIT_DIR/httpd.conf" && return - "$SVN_HTTPD_PATH" -f "$GIT_DIR"/httpd.conf -k stop +maybe_start_httpd () { + loc=${1-svn} + + test_tristate GIT_SVN_TEST_HTTPD + case $GIT_SVN_TEST_HTTPD in + true) + . "$TEST_DIRECTORY"/lib-httpd.sh + LIB_HTTPD_SVN="$loc" + start_httpd + ;; + *) + stop_httpd () { + : noop + } + ;; + esac } convert_to_rev_db () { @@ -172,17 +110,31 @@ EOF } require_svnserve () { - if test -z "$SVNSERVE_PORT" - then - skip_all='skipping svnserve test. (set $SVNSERVE_PORT to enable)' - test_done - fi + test_tristate GIT_TEST_SVNSERVE + if ! test "$GIT_TEST_SVNSERVE" = true + then + skip_all='skipping svnserve test. (set $GIT_TEST_SVNSERVE to enable)' + test_done + fi } start_svnserve () { - svnserve --listen-port $SVNSERVE_PORT \ - --root "$rawsvnrepo" \ - --listen-once \ - --listen-host 127.0.0.1 & + SVNSERVE_PORT=${SVNSERVE_PORT-${this_test#t}} + svnserve --listen-port $SVNSERVE_PORT \ + --root "$rawsvnrepo" \ + --listen-once \ + --listen-host 127.0.0.1 & } +prepare_a_utf8_locale () { + a_utf8_locale=$(locale -a | sed -n '/\.[uU][tT][fF]-*8$/{ + p + q +}') + if test -n "$a_utf8_locale" + then + test_set_prereq UTF8 + else + say "# UTF-8 locale not available, some tests are skipped" + fi +} diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh index db2ef22e8f..a5d3b2cbaa 100755 --- a/t/lib-gpg.sh +++ b/t/lib-gpg.sh @@ -1,9 +1,8 @@ #!/bin/sh gpg_version=$(gpg --version 2>&1) -if test $? = 127; then - say "You do not seem to have gpg installed" -else +if test $? != 127 +then # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19 # the gpg version 1.0.6 didn't parse trust packets correctly, so for # that version, creation of signed tags using the generated key fails. @@ -32,6 +31,7 @@ else chmod 0700 ./gpghome && GNUPGHOME="$(pwd)/gpghome" && export GNUPGHOME && + (gpgconf --kill gpg-agent >/dev/null 2>&1 || : ) && gpg --homedir "${GNUPGHOME}" 2>/dev/null --import \ "$TEST_DIRECTORY"/lib-gpg/keyring.gpg && gpg --homedir "${GNUPGHOME}" 2>/dev/null --import-ownertrust \ diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index e9714467d0..435a37465a 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -24,7 +24,7 @@ # LIB_HTTPD_MODULE_PATH web server modules path # LIB_HTTPD_PORT listening port # LIB_HTTPD_DAV enable DAV -# LIB_HTTPD_SVN enable SVN +# LIB_HTTPD_SVN enable SVN at given location (e.g. "svn") # LIB_HTTPD_SSL enable SSL # # Copyright (c) 2008 Clemens Buchacher <drizzd@aon.at> @@ -98,8 +98,8 @@ then test_skip_or_die $GIT_TEST_HTTPD "no web server found at '$LIB_HTTPD_PATH'" fi -HTTPD_VERSION=`$LIB_HTTPD_PATH -v | \ - sed -n 's/^Server version: Apache\/\([0-9]*\)\..*$/\1/p; q'` +HTTPD_VERSION=$($LIB_HTTPD_PATH -v | \ + sed -n 's/^Server version: Apache\/\([0-9]*\)\..*$/\1/p; q') if test -n "$HTTPD_VERSION" then @@ -162,8 +162,10 @@ prepare_httpd() { if test -n "$LIB_HTTPD_SVN" then HTTPD_PARA="$HTTPD_PARA -DSVN" - rawsvnrepo="$HTTPD_ROOT_PATH/svnrepo" - svnrepo="http://127.0.0.1:$LIB_HTTPD_PORT/svn" + LIB_HTTPD_SVNPATH="$rawsvnrepo" + svnrepo="http://127.0.0.1:$LIB_HTTPD_PORT/" + svnrepo="$svnrepo$LIB_HTTPD_SVN" + export LIB_HTTPD_SVN LIB_HTTPD_SVNPATH fi fi } @@ -180,6 +182,7 @@ start_httpd() { if test $? -ne 0 then trap 'die' EXIT + cat "$HTTPD_ROOT_PATH"/error.log >&4 2>/dev/null test_skip_or_die $GIT_TEST_HTTPD "web server setup failed" fi } diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 0b81a0047b..724d9ae462 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -25,6 +25,9 @@ ErrorLog error.log <IfModule !mod_headers.c> LoadModule headers_module modules/mod_headers.so </IfModule> +<IfModule !mod_setenvif.c> + LoadModule setenvif_module modules/mod_setenvif.so +</IfModule> <IfVersion < 2.4> LockFile accept.lock @@ -64,6 +67,9 @@ LockFile accept.lock <IfModule !mod_mpm_prefork.c> LoadModule mpm_prefork_module modules/mod_mpm_prefork.so </IfModule> +<IfModule !mod_unixd.c> + LoadModule unixd_module modules/mod_unixd.so +</IfModule> </IfVersion> PassEnv GIT_VALGRIND @@ -71,6 +77,9 @@ PassEnv GIT_VALGRIND_OPTIONS PassEnv GNUPGHOME PassEnv ASAN_OPTIONS PassEnv GIT_TRACE +PassEnv GIT_CONFIG_NOSYSTEM + +SetEnvIf Git-Protocol ".*" GIT_PROTOCOL=$0 Alias /dumb/ www/ Alias /auth/dumb/ www/auth/dumb/ @@ -98,6 +107,10 @@ Alias /auth/dumb/ www/auth/dumb/ SetEnv GIT_HTTP_EXPORT_ALL Header set Set-Cookie name=value </LocationMatch> +<LocationMatch /smart_headers/> + SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH} + SetEnv GIT_HTTP_EXPORT_ALL +</LocationMatch> ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1 ScriptAlias /broken_smart/ broken-smart-http.sh/ ScriptAlias /error/ error.sh/ @@ -115,10 +128,49 @@ ScriptAlias /error/ error.sh/ </Files> RewriteEngine on +RewriteRule ^/dumb-redir/(.*)$ /dumb/$1 [R=301] RewriteRule ^/smart-redir-perm/(.*)$ /smart/$1 [R=301] RewriteRule ^/smart-redir-temp/(.*)$ /smart/$1 [R=302] RewriteRule ^/smart-redir-auth/(.*)$ /auth/smart/$1 [R=301] RewriteRule ^/smart-redir-limited/(.*)/info/refs$ /smart/$1/info/refs [R=301] +RewriteRule ^/ftp-redir/(.*)$ ftp://localhost:1000/$1 [R=302] + +RewriteRule ^/loop-redir/x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-(.*) /$1 [R=302] +RewriteRule ^/loop-redir/(.*)$ /loop-redir/x-$1 [R=302] + +# redir-to/502/x?y -> really-redir-to?path=502/x&qs=y which returns 502 +# redir-to/x?y -> really-redir-to?path=x&qs=y -> x?y +RewriteCond %{QUERY_STRING} ^(.*)$ +RewriteRule ^/redir-to/(.*)$ /really-redir-to?path=$1&qs=%1 [R=302] +RewriteCond %{QUERY_STRING} ^path=502/(.*)&qs=(.*)$ +RewriteRule ^/really-redir-to$ - [R=502,L] +RewriteCond %{QUERY_STRING} ^path=(.*)&qs=(.*)$ +RewriteRule ^/really-redir-to$ /%1?%2 [R=302] + +# The first rule issues a client-side redirect to something +# that _doesn't_ look like a git repo. The second rule is a +# server-side rewrite, so that it turns out the odd-looking +# thing _is_ a git repo. The "[PT]" tells Apache to match +# the usual ScriptAlias rules for /smart. +RewriteRule ^/insane-redir/(.*)$ /intern-redir/$1/foo [R=301] +RewriteRule ^/intern-redir/(.*)/foo$ /smart/$1 [PT] + +# Serve info/refs internally without redirecting, but +# issue a redirect for any object requests. +RewriteRule ^/redir-objects/(.*/info/refs)$ /dumb/$1 [PT] +RewriteRule ^/redir-objects/(.*/objects/.*)$ /dumb/$1 [R=301] + +# Apache 2.2 does not understand <RequireAll>, so we use RewriteCond. +# And as RewriteCond does not allow testing for non-matches, we match +# the desired case first (one has abra, two has cadabra), and let it +# pass by marking the RewriteRule as [L], "last rule, do not process +# any other matching RewriteRules after this"), and then have another +# RewriteRule that matches all other cases and lets them fail via '[F]', +# "fail the request". +RewriteCond %{HTTP:x-magic-one} =abra +RewriteCond %{HTTP:x-magic-two} =cadabra +RewriteRule ^/smart_headers/.* - [L] +RewriteRule ^/smart_headers/.* - [F] <IfDefine SSL> LoadModule ssl_module modules/mod_ssl.so @@ -184,8 +236,8 @@ RewriteRule ^/half-auth-complete/ - [E=AUTHREQUIRED:yes] <IfDefine SVN> LoadModule dav_svn_module modules/mod_dav_svn.so - <Location /svn> + <Location /${LIB_HTTPD_SVN}> DAV svn - SVNPath svnrepo + SVNPath "${LIB_HTTPD_SVNPATH}" </Location> </IfDefine> diff --git a/t/lib-pack.sh b/t/lib-pack.sh index 7509846571..501078249d 100644 --- a/t/lib-pack.sh +++ b/t/lib-pack.sh @@ -85,7 +85,7 @@ pack_obj () { # Compute and append pack trailer to "$1" pack_trailer () { - test-sha1 -b <"$1" >trailer.tmp && + test-tool sha1 -b <"$1" >trailer.tmp && cat trailer.tmp >>"$1" && rm -f trailer.tmp } diff --git a/t/lib-proto-disable.sh b/t/lib-proto-disable.sh new file mode 100644 index 0000000000..83babe57d9 --- /dev/null +++ b/t/lib-proto-disable.sh @@ -0,0 +1,220 @@ +# Test routines for checking protocol disabling. + +# Test clone/fetch/push with GIT_ALLOW_PROTOCOL whitelist +test_whitelist () { + desc=$1 + proto=$2 + url=$3 + + test_expect_success "clone $desc (enabled)" ' + rm -rf tmp.git && + ( + GIT_ALLOW_PROTOCOL=$proto && + export GIT_ALLOW_PROTOCOL && + git clone --bare "$url" tmp.git + ) + ' + + test_expect_success "fetch $desc (enabled)" ' + ( + cd tmp.git && + GIT_ALLOW_PROTOCOL=$proto && + export GIT_ALLOW_PROTOCOL && + git fetch + ) + ' + + test_expect_success "push $desc (enabled)" ' + ( + cd tmp.git && + GIT_ALLOW_PROTOCOL=$proto && + export GIT_ALLOW_PROTOCOL && + git push origin HEAD:pushed + ) + ' + + test_expect_success "push $desc (disabled)" ' + ( + cd tmp.git && + GIT_ALLOW_PROTOCOL=none && + export GIT_ALLOW_PROTOCOL && + test_must_fail git push origin HEAD:pushed + ) + ' + + test_expect_success "fetch $desc (disabled)" ' + ( + cd tmp.git && + GIT_ALLOW_PROTOCOL=none && + export GIT_ALLOW_PROTOCOL && + test_must_fail git fetch + ) + ' + + test_expect_success "clone $desc (disabled)" ' + rm -rf tmp.git && + ( + GIT_ALLOW_PROTOCOL=none && + export GIT_ALLOW_PROTOCOL && + test_must_fail git clone --bare "$url" tmp.git + ) + ' + + test_expect_success "clone $desc (env var has precedence)" ' + rm -rf tmp.git && + ( + GIT_ALLOW_PROTOCOL=none && + export GIT_ALLOW_PROTOCOL && + test_must_fail git -c protocol.allow=always clone --bare "$url" tmp.git && + test_must_fail git -c protocol.$proto.allow=always clone --bare "$url" tmp.git + ) + ' +} + +test_config () { + desc=$1 + proto=$2 + url=$3 + + # Test clone/fetch/push with protocol.<type>.allow config + test_expect_success "clone $desc (enabled with config)" ' + rm -rf tmp.git && + git -c protocol.$proto.allow=always clone --bare "$url" tmp.git + ' + + test_expect_success "fetch $desc (enabled)" ' + git -C tmp.git -c protocol.$proto.allow=always fetch + ' + + test_expect_success "push $desc (enabled)" ' + git -C tmp.git -c protocol.$proto.allow=always push origin HEAD:pushed + ' + + test_expect_success "push $desc (disabled)" ' + test_must_fail git -C tmp.git -c protocol.$proto.allow=never push origin HEAD:pushed + ' + + test_expect_success "fetch $desc (disabled)" ' + test_must_fail git -C tmp.git -c protocol.$proto.allow=never fetch + ' + + test_expect_success "clone $desc (disabled)" ' + rm -rf tmp.git && + test_must_fail git -c protocol.$proto.allow=never clone --bare "$url" tmp.git + ' + + # Test clone/fetch/push with protocol.user.allow and its env var + test_expect_success "clone $desc (enabled)" ' + rm -rf tmp.git && + git -c protocol.$proto.allow=user clone --bare "$url" tmp.git + ' + + test_expect_success "fetch $desc (enabled)" ' + git -C tmp.git -c protocol.$proto.allow=user fetch + ' + + test_expect_success "push $desc (enabled)" ' + git -C tmp.git -c protocol.$proto.allow=user push origin HEAD:pushed + ' + + test_expect_success "push $desc (disabled)" ' + ( + cd tmp.git && + GIT_PROTOCOL_FROM_USER=0 && + export GIT_PROTOCOL_FROM_USER && + test_must_fail git -c protocol.$proto.allow=user push origin HEAD:pushed + ) + ' + + test_expect_success "fetch $desc (disabled)" ' + ( + cd tmp.git && + GIT_PROTOCOL_FROM_USER=0 && + export GIT_PROTOCOL_FROM_USER && + test_must_fail git -c protocol.$proto.allow=user fetch + ) + ' + + test_expect_success "clone $desc (disabled)" ' + rm -rf tmp.git && + ( + GIT_PROTOCOL_FROM_USER=0 && + export GIT_PROTOCOL_FROM_USER && + test_must_fail git -c protocol.$proto.allow=user clone --bare "$url" tmp.git + ) + ' + + # Test clone/fetch/push with protocol.allow user defined default + test_expect_success "clone $desc (enabled)" ' + rm -rf tmp.git && + test_config_global protocol.allow always && + git clone --bare "$url" tmp.git + ' + + test_expect_success "fetch $desc (enabled)" ' + test_config_global protocol.allow always && + git -C tmp.git fetch + ' + + test_expect_success "push $desc (enabled)" ' + test_config_global protocol.allow always && + git -C tmp.git push origin HEAD:pushed + ' + + test_expect_success "push $desc (disabled)" ' + test_config_global protocol.allow never && + test_must_fail git -C tmp.git push origin HEAD:pushed + ' + + test_expect_success "fetch $desc (disabled)" ' + test_config_global protocol.allow never && + test_must_fail git -C tmp.git fetch + ' + + test_expect_success "clone $desc (disabled)" ' + rm -rf tmp.git && + test_config_global protocol.allow never && + test_must_fail git clone --bare "$url" tmp.git + ' +} + +# test cloning a particular protocol +# $1 - description of the protocol +# $2 - machine-readable name of the protocol +# $3 - the URL to try cloning +test_proto () { + test_whitelist "$@" + + test_config "$@" +} + +# set up an ssh wrapper that will access $host/$repo in the +# trash directory, and enable it for subsequent tests. +setup_ssh_wrapper () { + test_expect_success 'setup ssh wrapper' ' + write_script ssh-wrapper <<-\EOF && + echo >&2 "ssh: $*" + host=$1; shift + cd "$TRASH_DIRECTORY/$host" && + eval "$*" + EOF + GIT_SSH="$PWD/ssh-wrapper" && + export GIT_SSH && + export TRASH_DIRECTORY + ' +} + +# set up a wrapper that can be used with remote-ext to +# access repositories in the "remote" directory of trash-dir, +# like "ext::fake-remote %S repo.git" +setup_ext_wrapper () { + test_expect_success 'setup ext wrapper' ' + write_script fake-remote <<-\EOF && + echo >&2 "fake-remote: $*" + cd "$TRASH_DIRECTORY/remote" && + eval "$*" + EOF + PATH=$TRASH_DIRECTORY:$PATH && + export TRASH_DIRECTORY + ' +} diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh index 6bd252212a..25a77ee5cb 100644 --- a/t/lib-rebase.sh +++ b/t/lib-rebase.sh @@ -14,7 +14,7 @@ # specified line. # # "<cmd> <lineno>" -- add a line with the specified command -# ("squash", "fixup", "edit", or "reword") and the SHA1 taken +# ("squash", "fixup", "edit", "reword" or "drop") and the SHA1 taken # from the specified line. # # "exec_cmd_with_args" -- add an "exec cmd with args" line. @@ -29,6 +29,7 @@ set_fake_editor () { */COMMIT_EDITMSG) test -z "$EXPECT_HEADER_COUNT" || test "$EXPECT_HEADER_COUNT" = "$(sed -n '1s/^# This is a combination of \(.*\) commits\./\1/p' < "$1")" || + test "# # GETTEXT POISON #" = "$(sed -n '1p' < "$1")" || exit test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1" test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1" @@ -46,7 +47,7 @@ set_fake_editor () { action=pick for line in $FAKE_LINES; do case $line in - squash|fixup|edit|reword) + squash|fixup|edit|reword|drop) action="$line";; exec*) echo "$line" | sed 's/_/ /g' >> "$1";; @@ -54,6 +55,11 @@ set_fake_editor () { echo '# comment' >> "$1";; ">") echo >> "$1";; + bad) + action="badcmd";; + fakesha) + echo "$action XXXXXXX False commit" >> "$1" + action=pick;; *) sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1" action=pick;; diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh index 79cdd34a54..1f38a85371 100755 --- a/t/lib-submodule-update.sh +++ b/t/lib-submodule-update.sh @@ -4,6 +4,7 @@ # - New submodule (no_submodule => add_sub1) # - Removed submodule (add_sub1 => remove_sub1) # - Updated submodule (add_sub1 => modify_sub1) +# - Updated submodule recursively (add_nested_sub => modify_sub1_recursively) # - Submodule updated to invalid commit (add_sub1 => invalid_sub1) # - Submodule updated from invalid commit (invalid_sub1 => valid_sub1) # - Submodule replaced by tracked files in directory (add_sub1 => @@ -15,23 +16,50 @@ # - Tracked file replaced by submodule (replace_sub1_with_file => # replace_file_with_sub1) # -# --O-----O -# / ^ replace_directory_with_sub1 -# / replace_sub1_with_directory -# /----O -# / ^ -# / modify_sub1 -# O------O-------O -# ^ ^\ ^ -# | | \ remove_sub1 -# | | -----O-----O -# | | \ ^ replace_file_with_sub1 -# | | \ replace_sub1_with_file -# | add_sub1 --O-----O -# no_submodule ^ valid_sub1 -# invalid_sub1 +# ----O +# / ^ +# / remove_sub1 +# / +# add_sub1 /-------O---------O--------O modify_sub1_recursively +# | / ^ add_nested_sub +# | / modify_sub1 +# v/ +# O------O-----------O---------O +# ^ \ ^ replace_directory_with_sub1 +# | \ replace_sub1_with_directory +# no_submodule \ +# --------O---------O +# \ ^ replace_file_with_sub1 +# \ replace_sub1_with_file +# \ +# ----O---------O +# ^ valid_sub1 +# invalid_sub1 # + create_lib_submodule_repo () { + git init submodule_update_sub1 && + ( + cd submodule_update_sub1 && + echo "expect" >>.gitignore && + echo "actual" >>.gitignore && + echo "x" >file1 && + echo "y" >file2 && + git add .gitignore file1 file2 && + git commit -m "Base inside first submodule" && + git branch "no_submodule" + ) && + git init submodule_update_sub2 && + ( + cd submodule_update_sub2 + echo "expect" >>.gitignore && + echo "actual" >>.gitignore && + echo "x" >file1 && + echo "y" >file2 && + git add .gitignore file1 file2 && + git commit -m "nested submodule base" && + git branch "no_submodule" + ) && git init submodule_update_repo && ( cd submodule_update_repo && @@ -44,15 +72,17 @@ create_lib_submodule_repo () { git branch "no_submodule" && git checkout -b "add_sub1" && - git submodule add ./. sub1 && + git submodule add ../submodule_update_sub1 sub1 && + git submodule add ../submodule_update_sub1 uninitialized_sub && git config -f .gitmodules submodule.sub1.ignore all && git config submodule.sub1.ignore all && git add .gitmodules && git commit -m "Add sub1" && - git checkout -b remove_sub1 && + + git checkout -b remove_sub1 add_sub1 && git revert HEAD && - git checkout -b "modify_sub1" "add_sub1" && + git checkout -b modify_sub1 add_sub1 && git submodule update && ( cd sub1 && @@ -67,33 +97,53 @@ create_lib_submodule_repo () { git add sub1 && git commit -m "Modify sub1" && - git checkout -b "replace_sub1_with_directory" "add_sub1" && + git checkout -b add_nested_sub modify_sub1 && + git -C sub1 checkout -b "add_nested_sub" && + git -C sub1 submodule add --branch no_submodule ../submodule_update_sub2 sub2 && + git -C sub1 commit -a -m "add a nested submodule" && + git add sub1 && + git commit -a -m "update submodule, that updates a nested submodule" && + git checkout -b modify_sub1_recursively && + git -C sub1 checkout -b modify_sub1_recursively && + git -C sub1/sub2 checkout -b modify_sub1_recursively && + echo change >sub1/sub2/file3 && + git -C sub1/sub2 add file3 && + git -C sub1/sub2 commit -m "make a change in nested sub" && + git -C sub1 add sub2 && + git -C sub1 commit -m "update nested sub" && + git add sub1 && + git commit -m "update sub1, that updates nested sub" && + git -C sub1 push origin modify_sub1_recursively && + git -C sub1/sub2 push origin modify_sub1_recursively && + git -C sub1 submodule deinit -f --all && + + git checkout -b replace_sub1_with_directory add_sub1 && git submodule update && - ( - cd sub1 && - git checkout modifications - ) && + git -C sub1 checkout modifications && git rm --cached sub1 && rm sub1/.git* && git config -f .gitmodules --remove-section "submodule.sub1" && git add .gitmodules sub1/* && git commit -m "Replace sub1 with directory" && + git checkout -b replace_directory_with_sub1 && git revert HEAD && - git checkout -b "replace_sub1_with_file" "add_sub1" && + git checkout -b replace_sub1_with_file add_sub1 && git rm sub1 && echo "content" >sub1 && git add sub1 && git commit -m "Replace sub1 with file" && + git checkout -b replace_file_with_sub1 && git revert HEAD && - git checkout -b "invalid_sub1" "add_sub1" && + git checkout -b invalid_sub1 add_sub1 && git update-index --cacheinfo 160000 0123456789012345678901234567890123456789 sub1 && git commit -m "Invalid sub1 commit" && git checkout -b valid_sub1 && git revert HEAD && + git checkout master ) } @@ -133,6 +183,15 @@ test_git_directory_is_unchanged () { ) } +test_git_directory_exists() { + test -e ".git/modules/$1" && + if test -f sub1/.git + then + # does core.worktree point at the right place? + test "$(git -C .git/modules/$1 config core.worktree)" = "../../../$1" + fi +} + # Helper function to be executed at the start of every test below, it sets up # the submodule repo if it doesn't exist and configures the most problematic # settings for diff.ignoreSubmodules. @@ -154,15 +213,36 @@ reset_work_tree_to () { git checkout -f "$1" && git status -u -s >actual && test_must_be_empty actual && - sha1=$(git rev-parse --revs-only HEAD:sub1) && - if test -n "$sha1" && - test $(cd "sub1" && git rev-parse --verify "$sha1^{commit}") + hash=$(git rev-parse --revs-only HEAD:sub1) && + if test -n "$hash" && + test $(cd "../submodule_update_sub1" && git rev-parse --verify "$hash^{commit}") then git submodule update --init --recursive "sub1" fi ) } +reset_work_tree_to_interested () { + reset_work_tree_to $1 && + # make the submodule git dirs available + if ! test -d submodule_update/.git/modules/sub1 + then + mkdir -p submodule_update/.git/modules && + cp -r submodule_update_repo/.git/modules/sub1 submodule_update/.git/modules/sub1 + GIT_WORK_TREE=. git -C submodule_update/.git/modules/sub1 config --unset core.worktree + fi && + if ! test -d submodule_update/.git/modules/sub1/modules/sub2 + then + mkdir -p submodule_update/.git/modules/sub1/modules && + cp -r submodule_update_repo/.git/modules/sub1/modules/sub2 submodule_update/.git/modules/sub1/modules/sub2 + GIT_WORK_TREE=. git -C submodule_update/.git/modules/sub1/modules/sub2 config --unset core.worktree + fi && + # indicate we are interested in the submodule: + git -C submodule_update config submodule.sub1.url "bogus" && + # sub1 might not be checked out, so use the git dir + git -C submodule_update/.git/modules/sub1 config submodule.sub2.url "bogus" +} + # Test that the superproject contains the content according to commit "$1" # (the work tree must match the index for everything but submodules but the # index must exactly match the given commit including any submodule SHA-1s). @@ -176,6 +256,11 @@ test_superproject_content () { # Test that the given submodule at path "$1" contains the content according # to the submodule commit recorded in the superproject's commit "$2" test_submodule_content () { + if test x"$1" = "x-C" + then + cd "$2" + shift; shift; + fi if test $# != 2 then echo "test_submodule_content needs two arguments" @@ -221,9 +306,9 @@ test_submodule_content () { # to protect the history! # -# Test that submodule contents are currently not updated when switching -# between commits that change a submodule. -test_submodule_switch () { +# Internal function; use test_submodule_switch() or +# test_submodule_forced_switch() instead. +test_submodule_switch_common() { command="$1" ######################### Appearing submodule ######################### # Switching to a commit letting a submodule appear creates empty dir ... @@ -247,7 +332,7 @@ test_submodule_switch () { test_submodule_content sub1 origin/add_sub1 ) ' - # ... and doesn't care if it already exists ... + # ... and doesn't care if it already exists. test_expect_$RESULT "$command: added submodule leaves existing empty directory alone" ' prolog && reset_work_tree_to no_submodule && @@ -262,19 +347,6 @@ test_submodule_switch () { test_submodule_content sub1 origin/add_sub1 ) ' - # ... unless there is an untracked file in its place. - test_expect_success "$command: added submodule doesn't remove untracked unignored file with same name" ' - prolog && - reset_work_tree_to no_submodule && - ( - cd submodule_update && - git branch -t add_sub1 origin/add_sub1 && - >sub1 && - test_must_fail $command add_sub1 && - test_superproject_content origin/no_submodule && - test_must_be_empty sub1 - ) - ' # Replacing a tracked file with a submodule produces an empty # directory ... test_expect_$RESULT "$command: replace tracked file with submodule creates empty directory" ' @@ -356,6 +428,11 @@ test_submodule_switch () { # submodule files with the newly checked out ones in the # directory of the same name while it shouldn't. RESULT="failure" + elif test "$KNOWN_FAILURE_FORCED_SWITCH_TESTS" = 1 + then + # All existing tests that use test_submodule_forced_switch() + # require this. + RESULT="failure" else RESULT="success" fi @@ -437,7 +514,6 @@ test_submodule_switch () { test_submodule_content sub1 origin/modify_sub1 ) ' - # Updating a submodule to an invalid sha1 doesn't update the # submodule's work tree, subsequent update will fail test_expect_$RESULT "$command: modified submodule does not update submodule work tree to invalid commit" ' @@ -470,211 +546,487 @@ test_submodule_switch () { ' } -# Test that submodule contents are currently not updated when switching -# between commits that change a submodule, but throwing away local changes in +# Declares and invokes several tests that, in various situations, checks that +# the provided transition function: +# - succeeds in updating the worktree and index of a superproject to a target +# commit, or fails atomically (depending on the test situation) +# - if succeeds, the contents of submodule directories are unchanged +# - if succeeds, once "git submodule update" is invoked, the contents of +# submodule directories are updated +# +# If the command under test is known to not work with submodules in certain +# conditions, set the appropriate KNOWN_FAILURE_* variable used in the tests +# below to 1. +# +# Use as follows: +# +# my_func () { +# target=$1 +# # Do something here that updates the worktree and index to match target, +# # but not any submodule directories. +# } +# test_submodule_switch "my_func" +test_submodule_switch () { + command="$1" + test_submodule_switch_common "$command" + + # An empty directory does not prevent the creation of a submodule of + # the same name, but a file does. + test_expect_success "$command: added submodule doesn't remove untracked unignored file with same name" ' + prolog && + reset_work_tree_to no_submodule && + ( + cd submodule_update && + git branch -t add_sub1 origin/add_sub1 && + >sub1 && + test_must_fail $command add_sub1 && + test_superproject_content origin/no_submodule && + test_must_be_empty sub1 + ) + ' +} + +# Same as test_submodule_switch(), except that throwing away local changes in # the superproject is allowed. test_submodule_forced_switch () { command="$1" - ######################### Appearing submodule ######################### - # Switching to a commit letting a submodule appear creates empty dir ... - test_expect_success "$command: added submodule creates empty directory" ' + KNOWN_FAILURE_FORCED_SWITCH_TESTS=1 + test_submodule_switch_common "$command" + + # When forced, a file in the superproject does not prevent creating a + # submodule of the same name. + test_expect_success "$command: added submodule does remove untracked unignored file with same name when forced" ' prolog && reset_work_tree_to no_submodule && ( cd submodule_update && git branch -t add_sub1 origin/add_sub1 && + >sub1 && $command add_sub1 && test_superproject_content origin/add_sub1 && - test_dir_is_empty sub1 && - git submodule update --init --recursive && - test_submodule_content sub1 origin/add_sub1 + test_dir_is_empty sub1 ) ' - # ... and doesn't care if it already exists ... - test_expect_success "$command: added submodule leaves existing empty directory alone" ' +} + +# Test that submodule contents are correctly updated when switching +# between commits that change a submodule. +# Test that the following transitions are correctly handled: +# (These tests are also above in the case where we expect no change +# in the submodule) +# - Updated submodule +# - New submodule +# - Removed submodule +# - Directory containing tracked files replaced by submodule +# - Submodule replaced by tracked files in directory +# - Submodule replaced by tracked file with the same name +# - tracked file replaced by submodule +# +# New test cases +# - Removing a submodule with a git directory absorbs the submodules +# git directory first into the superproject. + +# Internal function; use test_submodule_switch_recursing_with_args() or +# test_submodule_forced_switch_recursing_with_args() instead. +test_submodule_recursing_with_args_common() { + command="$1" + + ######################### Appearing submodule ######################### + # Switching to a commit letting a submodule appear checks it out ... + test_expect_success "$command: added submodule is checked out" ' prolog && - reset_work_tree_to no_submodule && + reset_work_tree_to_interested no_submodule && ( cd submodule_update && git branch -t add_sub1 origin/add_sub1 && - mkdir sub1 && $command add_sub1 && test_superproject_content origin/add_sub1 && - test_dir_is_empty sub1 && - git submodule update --init --recursive && test_submodule_content sub1 origin/add_sub1 ) ' - # ... unless there is an untracked file in its place. - test_expect_success "$command: added submodule does remove untracked unignored file with same name when forced" ' + # ... ignoring an empty existing directory. + test_expect_success "$command: added submodule is checked out in empty dir" ' prolog && - reset_work_tree_to no_submodule && + reset_work_tree_to_interested no_submodule && ( cd submodule_update && + mkdir sub1 && git branch -t add_sub1 origin/add_sub1 && - >sub1 && $command add_sub1 && test_superproject_content origin/add_sub1 && - test_dir_is_empty sub1 + test_submodule_content sub1 origin/add_sub1 ) ' - # Replacing a tracked file with a submodule produces an empty - # directory ... - test_expect_success "$command: replace tracked file with submodule creates empty directory" ' + test_expect_success "$command: submodule branch is not changed, detach HEAD instead" ' prolog && - reset_work_tree_to replace_sub1_with_file && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git -C sub1 checkout -b keep_branch && + git -C sub1 rev-parse HEAD >expect && + git branch -t modify_sub1 origin/modify_sub1 && + $command modify_sub1 && + test_superproject_content origin/modify_sub1 && + test_submodule_content sub1 origin/modify_sub1 && + git -C sub1 rev-parse keep_branch >actual && + test_cmp expect actual && + test_must_fail git -C sub1 symbolic-ref HEAD + ) + ' + + # Replacing a tracked file with a submodule produces a checked out submodule + test_expect_success "$command: replace tracked file with submodule checks out submodule" ' + prolog && + reset_work_tree_to_interested replace_sub1_with_file && ( cd submodule_update && git branch -t replace_file_with_sub1 origin/replace_file_with_sub1 && $command replace_file_with_sub1 && test_superproject_content origin/replace_file_with_sub1 && - test_dir_is_empty sub1 && - git submodule update --init --recursive && test_submodule_content sub1 origin/replace_file_with_sub1 ) ' - # ... as does removing a directory with tracked files with a - # submodule. + # ... as does removing a directory with tracked files with a submodule. test_expect_success "$command: replace directory with submodule" ' prolog && - reset_work_tree_to replace_sub1_with_directory && + reset_work_tree_to_interested replace_sub1_with_directory && ( cd submodule_update && git branch -t replace_directory_with_sub1 origin/replace_directory_with_sub1 && $command replace_directory_with_sub1 && test_superproject_content origin/replace_directory_with_sub1 && - test_dir_is_empty sub1 && - git submodule update --init --recursive && test_submodule_content sub1 origin/replace_directory_with_sub1 ) ' ######################## Disappearing submodule ####################### - # Removing a submodule doesn't remove its work tree ... - test_expect_success "$command: removed submodule leaves submodule directory and its contents in place" ' + # Removing a submodule removes its work tree ... + test_expect_success "$command: removed submodule removes submodules working tree" ' prolog && - reset_work_tree_to add_sub1 && + reset_work_tree_to_interested add_sub1 && ( cd submodule_update && git branch -t remove_sub1 origin/remove_sub1 && $command remove_sub1 && test_superproject_content origin/remove_sub1 && - test_submodule_content sub1 origin/add_sub1 + ! test -e sub1 ) ' - # ... especially when it contains a .git directory. - test_expect_success "$command: removed submodule leaves submodule containing a .git directory alone" ' + # ... absorbing a .git directory along the way. + test_expect_success "$command: removed submodule absorbs submodules .git directory" ' prolog && - reset_work_tree_to add_sub1 && + reset_work_tree_to_interested add_sub1 && ( cd submodule_update && git branch -t remove_sub1 origin/remove_sub1 && replace_gitfile_with_git_dir sub1 && + rm -rf .git/modules && $command remove_sub1 && test_superproject_content origin/remove_sub1 && - test_git_directory_is_unchanged sub1 && - test_submodule_content sub1 origin/add_sub1 + ! test -e sub1 && + test_git_directory_exists sub1 ) ' - # Replacing a submodule with files in a directory must fail as the - # submodule work tree isn't removed ... - test_expect_failure "$command: replace submodule with a directory must fail" ' + + # Replacing it with a file ... + test_expect_success "$command: replace submodule with a file" ' prolog && - reset_work_tree_to add_sub1 && + reset_work_tree_to_interested add_sub1 && ( cd submodule_update && - git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory && - test_must_fail $command replace_sub1_with_directory && + git branch -t replace_sub1_with_file origin/replace_sub1_with_file && + $command replace_sub1_with_file && + test_superproject_content origin/replace_sub1_with_file && + test -f sub1 + ) + ' + RESULTDS=success + if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1 + then + RESULTDS=failure + fi + # ... must check its local work tree for untracked files + test_expect_$RESULTDS "$command: replace submodule with a file must fail with untracked files" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_file origin/replace_sub1_with_file && + : >sub1/untrackedfile && + test_must_fail $command replace_sub1_with_file && test_superproject_content origin/add_sub1 && test_submodule_content sub1 origin/add_sub1 + test -f sub1/untracked_file ) ' - # ... especially when it contains a .git directory. - test_expect_failure "$command: replace submodule containing a .git directory with a directory must fail" ' + + ########################## Modified submodule ######################### + # Updating a submodule sha1 updates the submodule's work tree + test_expect_success "$command: modified submodule updates submodule work tree" ' prolog && - reset_work_tree_to add_sub1 && + reset_work_tree_to_interested add_sub1 && ( cd submodule_update && - git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory && - replace_gitfile_with_git_dir sub1 && - test_must_fail $command replace_sub1_with_directory && + git branch -t modify_sub1 origin/modify_sub1 && + $command modify_sub1 && + test_superproject_content origin/modify_sub1 && + test_submodule_content sub1 origin/modify_sub1 + ) + ' + # Updating a submodule to an invalid sha1 doesn't update the + # superproject nor the submodule's work tree. + test_expect_success "$command: updating to a missing submodule commit fails" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t invalid_sub1 origin/invalid_sub1 && + test_must_fail $command invalid_sub1 && test_superproject_content origin/add_sub1 && - test_git_directory_is_unchanged sub1 && test_submodule_content sub1 origin/add_sub1 ) ' - # Replacing it with a file must fail as it could throw away any local - # work tree changes ... - test_expect_failure "$command: replace submodule with a file must fail" ' +} + +# Declares and invokes several tests that, in various situations, checks that +# the provided Git command, when invoked with --recurse-submodules: +# - succeeds in updating the worktree and index of a superproject to a target +# commit, or fails atomically (depending on the test situation) +# - if succeeds, the contents of submodule directories are updated +# +# Specify the Git command so that "git $GIT_COMMAND --recurse-submodules" +# works. +# +# If the command under test is known to not work with submodules in certain +# conditions, set the appropriate KNOWN_FAILURE_* variable used in the tests +# below to 1. +# +# Use as follows: +# +# test_submodule_switch_recursing_with_args "$GIT_COMMAND" +test_submodule_switch_recursing_with_args () { + cmd_args="$1" + command="git $cmd_args --recurse-submodules" + test_submodule_recursing_with_args_common "$command" + + RESULTDS=success + if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1 + then + RESULTDS=failure + fi + RESULTOI=success + if test "$KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED" = 1 + then + RESULTOI=failure + fi + # Switching to a commit letting a submodule appear cannot override an + # untracked file. + test_expect_success "$command: added submodule doesn't remove untracked file with same name" ' prolog && - reset_work_tree_to add_sub1 && + reset_work_tree_to_interested no_submodule && ( cd submodule_update && - git branch -t replace_sub1_with_file origin/replace_sub1_with_file && - test_must_fail $command replace_sub1_with_file && + git branch -t add_sub1 origin/add_sub1 && + : >sub1 && + test_must_fail $command add_sub1 && + test_superproject_content origin/no_submodule && + test_must_be_empty sub1 + ) + ' + # ... but an ignored file is fine. + test_expect_$RESULTOI "$command: added submodule removes an untracked ignored file" ' + test_when_finished "rm submodule_update/.git/info/exclude" && + prolog && + reset_work_tree_to_interested no_submodule && + ( + cd submodule_update && + git branch -t add_sub1 origin/add_sub1 && + : >sub1 && + echo sub1 >.git/info/exclude + $command add_sub1 && test_superproject_content origin/add_sub1 && test_submodule_content sub1 origin/add_sub1 ) ' - # ... or even destroy unpushed parts of submodule history if that - # still uses a .git directory. - test_expect_failure "$command: replace submodule containing a .git directory with a file must fail" ' + + # Replacing a submodule with files in a directory must succeeds + # when the submodule is clean + test_expect_$RESULTDS "$command: replace submodule with a directory" ' prolog && - reset_work_tree_to add_sub1 && + reset_work_tree_to_interested add_sub1 && ( cd submodule_update && - git branch -t replace_sub1_with_file origin/replace_sub1_with_file && + git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory && + $command replace_sub1_with_directory && + test_superproject_content origin/replace_sub1_with_directory && + test_submodule_content sub1 origin/replace_sub1_with_directory + ) + ' + # ... absorbing a .git directory. + test_expect_$RESULTDS "$command: replace submodule containing a .git directory with a directory must absorb the git dir" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory && replace_gitfile_with_git_dir sub1 && - test_must_fail $command replace_sub1_with_file && - test_superproject_content origin/add_sub1 && - test_git_directory_is_unchanged sub1 && - test_submodule_content sub1 origin/add_sub1 + rm -rf .git/modules && + $command replace_sub1_with_directory && + test_superproject_content origin/replace_sub1_with_directory && + test_git_directory_exists sub1 ) ' - ########################## Modified submodule ######################### - # Updating a submodule sha1 doesn't update the submodule's work tree - test_expect_success "$command: modified submodule does not update submodule work tree" ' + # ... and ignored files are ignored + test_expect_success "$command: replace submodule with a file works ignores ignored files in submodule" ' + test_when_finished "rm submodule_update/.git/modules/sub1/info/exclude" && prolog && - reset_work_tree_to add_sub1 && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_file origin/replace_sub1_with_file && + echo ignored >.git/modules/sub1/info/exclude && + : >sub1/ignored && + $command replace_sub1_with_file && + test_superproject_content origin/replace_sub1_with_file && + test -f sub1 + ) + ' + + test_expect_success "git -c submodule.recurse=true $cmd_args: modified submodule updates submodule work tree" ' + prolog && + reset_work_tree_to_interested add_sub1 && ( cd submodule_update && git branch -t modify_sub1 origin/modify_sub1 && - $command modify_sub1 && + git -c submodule.recurse=true $cmd_args modify_sub1 && test_superproject_content origin/modify_sub1 && - test_submodule_content sub1 origin/add_sub1 && - git submodule update && test_submodule_content sub1 origin/modify_sub1 ) ' - # Updating a submodule to an invalid sha1 doesn't update the - # submodule's work tree, subsequent update will fail - test_expect_success "$command: modified submodule does not update submodule work tree to invalid commit" ' + + # recursing deeper than one level doesn't work yet. + test_expect_success "$command: modified submodule updates submodule recursively" ' prolog && - reset_work_tree_to add_sub1 && + reset_work_tree_to_interested add_nested_sub && ( cd submodule_update && - git branch -t invalid_sub1 origin/invalid_sub1 && - $command invalid_sub1 && - test_superproject_content origin/invalid_sub1 && - test_submodule_content sub1 origin/add_sub1 && - test_must_fail git submodule update && + git branch -t modify_sub1_recursively origin/modify_sub1_recursively && + $command modify_sub1_recursively && + test_superproject_content origin/modify_sub1_recursively && + test_submodule_content sub1 origin/modify_sub1_recursively && + test_submodule_content -C sub1 sub2 origin/modify_sub1_recursively + ) + ' +} + +# Same as test_submodule_switch_recursing_with_args(), except that throwing +# away local changes in the superproject is allowed. +test_submodule_forced_switch_recursing_with_args () { + cmd_args="$1" + command="git $cmd_args --recurse-submodules" + test_submodule_recursing_with_args_common "$command" + + RESULT=success + if test "$KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS" = 1 + then + RESULT=failure + fi + # Switching to a commit letting a submodule appear does not care about + # an untracked file. + test_expect_success "$command: added submodule does remove untracked unignored file with same name when forced" ' + prolog && + reset_work_tree_to_interested no_submodule && + ( + cd submodule_update && + git branch -t add_sub1 origin/add_sub1 && + >sub1 && + $command add_sub1 && + test_superproject_content origin/add_sub1 && test_submodule_content sub1 origin/add_sub1 ) ' - # Updating a submodule from an invalid sha1 doesn't update the - # submodule's work tree, subsequent update will succeed - test_expect_success "$command: modified submodule does not update submodule work tree from invalid commit" ' + + # Replacing a submodule with files in a directory ... + test_expect_success "$command: replace submodule with a directory" ' prolog && - reset_work_tree_to invalid_sub1 && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory && + $command replace_sub1_with_directory && + test_superproject_content origin/replace_sub1_with_directory + ) + ' + # ... absorbing a .git directory. + test_expect_success "$command: replace submodule containing a .git directory with a directory must fail" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_directory origin/replace_sub1_with_directory && + replace_gitfile_with_git_dir sub1 && + rm -rf .git/modules/sub1 && + $command replace_sub1_with_directory && + test_superproject_content origin/replace_sub1_with_directory && + test_submodule_content sub1 origin/modify_sub1 + test_git_directory_exists sub1 + ) + ' + + # ... even if the submodule contains ignored files + test_expect_success "$command: replace submodule with a file ignoring ignored files" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t replace_sub1_with_file origin/replace_sub1_with_file && + : >sub1/expect && + $command replace_sub1_with_file && + test_superproject_content origin/replace_sub1_with_file + ) + ' + + # Updating a submodule from an invalid sha1 updates + test_expect_success "$command: modified submodule does update submodule work tree from invalid commit" ' + prolog && + reset_work_tree_to_interested invalid_sub1 && ( cd submodule_update && git branch -t valid_sub1 origin/valid_sub1 && $command valid_sub1 && test_superproject_content origin/valid_sub1 && - test_dir_is_empty sub1 && - git submodule update --init --recursive && test_submodule_content sub1 origin/valid_sub1 ) ' + + # Old versions of Git were buggy writing the .git link file + # (e.g. before f8eaa0ba98b and then moving the superproject repo + # whose submodules contained absolute paths) + test_expect_success "$command: updating submodules fixes .git links" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + git branch -t modify_sub1 origin/modify_sub1 && + echo "gitdir: bogus/path" >sub1/.git && + $command modify_sub1 && + test_superproject_content origin/modify_sub1 && + test_submodule_content sub1 origin/modify_sub1 + ) + ' + + test_expect_success "$command: changed submodule worktree is reset" ' + prolog && + reset_work_tree_to_interested add_sub1 && + ( + cd submodule_update && + rm sub1/file1 && + : >sub1/new_file && + git -C sub1 add new_file && + $command HEAD && + test_path_is_file sub1/file1 && + test_path_is_missing sub1/new_file + ) + ' } diff --git a/t/lib-terminal.sh b/t/lib-terminal.sh index cd220e378e..e3809dcead 100644 --- a/t/lib-terminal.sh +++ b/t/lib-terminal.sh @@ -9,8 +9,8 @@ test_terminal () { echo >&4 "test_terminal: need to declare TTY prerequisite" return 127 fi - perl "$TEST_DIRECTORY"/test-terminal.perl "$@" -} + perl "$TEST_DIRECTORY"/test-terminal.perl "$@" 2>&7 +} 7>&2 2>&4 test_lazy_prereq TTY ' test_have_prereq PERL && diff --git a/t/perf/README b/t/perf/README index 8848c14619..21321a0f36 100644 --- a/t/perf/README +++ b/t/perf/README @@ -60,7 +60,22 @@ You can set the following variables (also in your config.mak): GIT_PERF_MAKE_OPTS Options to use when automatically building a git tree for - performance testing. E.g., -j6 would be useful. + performance testing. E.g., -j6 would be useful. Passed + directly to make as "make $GIT_PERF_MAKE_OPTS". + + GIT_PERF_MAKE_COMMAND + An arbitrary command that'll be run in place of the make + command, if set the GIT_PERF_MAKE_OPTS variable is + ignored. Useful in cases where source tree changes might + require issuing a different make command to different + revisions. + + This can be (ab)used to monkeypatch or otherwise change the + tree about to be built. Note that the build directory can be + re-used for subsequent runs so the make command might get + executed multiple times on the same tree, but don't count on + any of that, that's an implementation detail that might change + in the future. GIT_PERF_REPO GIT_PERF_LARGE_REPO @@ -106,6 +121,7 @@ sources perf-lib.sh: After that you will want to use some of the following: + test_perf_fresh_repo # sets up an empty repository test_perf_default_repo # sets up a "normal" repository test_perf_large_repo # sets up a "large" repository @@ -115,8 +131,16 @@ After that you will want to use some of the following: At least one of the first two is required! -You can use test_expect_success as usual. For actual performance -tests, use +You can use test_expect_success as usual. In both test_expect_success +and in test_perf, running "git" points to the version that is being +perf-tested. The $MODERN_GIT variable points to the git wrapper for the +currently checked-out version (i.e., the one that matches the t/perf +scripts you are running). This is useful if your setup uses commands +that only work with newer versions of git than what you might want to +test (but obviously your new commands must still create a state that can +be used by the older version of git you are testing). + +For actual performance tests, use test_perf 'descriptive string' ' command1 && diff --git a/t/perf/aggregate.perl b/t/perf/aggregate.perl index 15f7fc1b80..48637ef64b 100755 --- a/t/perf/aggregate.perl +++ b/t/perf/aggregate.perl @@ -1,7 +1,9 @@ #!/usr/bin/perl +use lib '../../perl/build/lib'; use strict; use warnings; +use JSON; use Git; sub get_times { @@ -34,10 +36,46 @@ sub format_times { return $out; } -my (@dirs, %dirnames, %dirabbrevs, %prefixes, @tests); +my (@dirs, %dirnames, %dirabbrevs, %prefixes, @tests, + $codespeed, $sortby, $subsection, $reponame); while (scalar @ARGV) { my $arg = $ARGV[0]; my $dir; + if ($arg eq "--codespeed") { + $codespeed = 1; + shift @ARGV; + next; + } + if ($arg =~ /--sort-by(?:=(.*))?/) { + shift @ARGV; + if (defined $1) { + $sortby = $1; + } else { + $sortby = shift @ARGV; + if (! defined $sortby) { + die "'--sort-by' requires an argument"; + } + } + next; + } + if ($arg eq "--subsection") { + shift @ARGV; + $subsection = $ARGV[0]; + shift @ARGV; + if (! $subsection) { + die "empty subsection"; + } + next; + } + if ($arg eq "--reponame") { + shift @ARGV; + $reponame = $ARGV[0]; + shift @ARGV; + if (! $reponame) { + die "empty reponame"; + } + next; + } last if -f $arg or $arg eq "--"; if (! -d $arg) { my $rev = Git::command_oneline(qw(rev-parse --verify), $arg); @@ -68,12 +106,24 @@ if (not @tests) { @tests = glob "p????-*.sh"; } +my $resultsdir = "test-results"; + +if (! $subsection and + exists $ENV{GIT_PERF_SUBSECTION} and + $ENV{GIT_PERF_SUBSECTION} ne "") { + $subsection = $ENV{GIT_PERF_SUBSECTION}; +} + +if ($subsection) { + $resultsdir .= "/" . $subsection; +} + my @subtests; my %shorttests; for my $t (@tests) { $t =~ s{(?:.*/)?(p(\d+)-[^/]+)\.sh$}{$1} or die "bad test name: $t"; my $n = $2; - my $fname = "test-results/$t.subtests"; + my $fname = "$resultsdir/$t.subtests"; open my $fp, "<", $fname or die "cannot open $fname: $!"; for (<$fp>) { chomp; @@ -87,19 +137,13 @@ for my $t (@tests) { sub read_descr { my $name = shift; open my $fh, "<", $name or return "<error reading description>"; + binmode $fh, ":utf8" or die "PANIC on binmode: $!"; my $line = <$fh>; close $fh or die "cannot close $name"; chomp $line; return $line; } -my %descrs; -my $descrlen = 4; # "Test" -for my $t (@subtests) { - $descrs{$t} = $shorttests{$t}.": ".read_descr("test-results/$t.descr"); - $descrlen = length $descrs{$t} if length $descrs{$t}>$descrlen; -} - sub have_duplicate { my %seen; for (@_) { @@ -115,52 +159,167 @@ sub have_slash { return 0; } -my %newdirabbrevs = %dirabbrevs; -while (!have_duplicate(values %newdirabbrevs)) { - %dirabbrevs = %newdirabbrevs; - last if !have_slash(values %dirabbrevs); - %newdirabbrevs = %dirabbrevs; - for (values %newdirabbrevs) { - s{^[^/]*/}{}; - } +sub display_dir { + my ($d) = @_; + return exists $dirabbrevs{$d} ? $dirabbrevs{$d} : $dirnames{$d}; } -my %times; -my @colwidth = ((0)x@dirs); -for my $i (0..$#dirs) { - my $d = $dirs[$i]; - my $w = length (exists $dirabbrevs{$d} ? $dirabbrevs{$d} : $dirnames{$d}); - $colwidth[$i] = $w if $w > $colwidth[$i]; -} -for my $t (@subtests) { - my $firstr; +sub print_default_results { + my %descrs; + my $descrlen = 4; # "Test" + for my $t (@subtests) { + $descrs{$t} = $shorttests{$t}.": ".read_descr("$resultsdir/$t.descr"); + $descrlen = length $descrs{$t} if length $descrs{$t}>$descrlen; + } + + my %newdirabbrevs = %dirabbrevs; + while (!have_duplicate(values %newdirabbrevs)) { + %dirabbrevs = %newdirabbrevs; + last if !have_slash(values %dirabbrevs); + %newdirabbrevs = %dirabbrevs; + for (values %newdirabbrevs) { + s{^[^/]*/}{}; + } + } + + my %times; + my @colwidth = ((0)x@dirs); for my $i (0..$#dirs) { - my $d = $dirs[$i]; - $times{$prefixes{$d}.$t} = [get_times("test-results/$prefixes{$d}$t.times")]; - my ($r,$u,$s) = @{$times{$prefixes{$d}.$t}}; - my $w = length format_times($r,$u,$s,$firstr); + my $w = length display_dir($dirs[$i]); $colwidth[$i] = $w if $w > $colwidth[$i]; - $firstr = $r unless defined $firstr; } -} -my $totalwidth = 3*@dirs+$descrlen; -$totalwidth += $_ for (@colwidth); + for my $t (@subtests) { + my $firstr; + for my $i (0..$#dirs) { + my $d = $dirs[$i]; + $times{$prefixes{$d}.$t} = [get_times("$resultsdir/$prefixes{$d}$t.times")]; + my ($r,$u,$s) = @{$times{$prefixes{$d}.$t}}; + my $w = length format_times($r,$u,$s,$firstr); + $colwidth[$i] = $w if $w > $colwidth[$i]; + $firstr = $r unless defined $firstr; + } + } + my $totalwidth = 3*@dirs+$descrlen; + $totalwidth += $_ for (@colwidth); -printf "%-${descrlen}s", "Test"; -for my $i (0..$#dirs) { - my $d = $dirs[$i]; - printf " %-$colwidth[$i]s", (exists $dirabbrevs{$d} ? $dirabbrevs{$d} : $dirnames{$d}); -} -print "\n"; -print "-"x$totalwidth, "\n"; -for my $t (@subtests) { - printf "%-${descrlen}s", $descrs{$t}; - my $firstr; + printf "%-${descrlen}s", "Test"; for my $i (0..$#dirs) { - my $d = $dirs[$i]; - my ($r,$u,$s) = @{$times{$prefixes{$d}.$t}}; - printf " %-$colwidth[$i]s", format_times($r,$u,$s,$firstr); - $firstr = $r unless defined $firstr; + printf " %-$colwidth[$i]s", display_dir($dirs[$i]); } print "\n"; + print "-"x$totalwidth, "\n"; + for my $t (@subtests) { + printf "%-${descrlen}s", $descrs{$t}; + my $firstr; + for my $i (0..$#dirs) { + my $d = $dirs[$i]; + my ($r,$u,$s) = @{$times{$prefixes{$d}.$t}}; + printf " %-$colwidth[$i]s", format_times($r,$u,$s,$firstr); + $firstr = $r unless defined $firstr; + } + print "\n"; + } +} + +sub print_sorted_results { + my ($sortby) = @_; + + if ($sortby ne "regression") { + die "only 'regression' is supported as '--sort-by' argument"; + } + + my @evolutions; + for my $t (@subtests) { + my ($prevr, $prevu, $prevs, $prevrev); + for my $i (0..$#dirs) { + my $d = $dirs[$i]; + my ($r, $u, $s) = get_times("$resultsdir/$prefixes{$d}$t.times"); + if ($i > 0 and defined $r and defined $prevr and $prevr > 0) { + my $percent = 100.0 * ($r - $prevr) / $prevr; + push @evolutions, { "percent" => $percent, + "test" => $t, + "prevrev" => $prevrev, + "rev" => $d, + "prevr" => $prevr, + "r" => $r, + "prevu" => $prevu, + "u" => $u, + "prevs" => $prevs, + "s" => $s}; + } + ($prevr, $prevu, $prevs, $prevrev) = ($r, $u, $s, $d); + } + } + + my @sorted_evolutions = sort { $b->{percent} <=> $a->{percent} } @evolutions; + + for my $e (@sorted_evolutions) { + printf "%+.1f%%", $e->{percent}; + print " " . $e->{test}; + print " " . format_times($e->{prevr}, $e->{prevu}, $e->{prevs}); + print " " . format_times($e->{r}, $e->{u}, $e->{s}); + print " " . display_dir($e->{prevrev}); + print " " . display_dir($e->{rev}); + print "\n"; + } +} + +sub print_codespeed_results { + my ($subsection) = @_; + + my $project = "Git"; + + my $executable = `uname -s -m`; + chomp $executable; + + if ($subsection) { + $executable .= ", " . $subsection; + } + + my $environment; + if ($reponame) { + $environment = $reponame; + } elsif (exists $ENV{GIT_PERF_REPO_NAME} and $ENV{GIT_PERF_REPO_NAME} ne "") { + $environment = $ENV{GIT_PERF_REPO_NAME}; + } elsif (exists $ENV{GIT_TEST_INSTALLED} and $ENV{GIT_TEST_INSTALLED} ne "") { + $environment = $ENV{GIT_TEST_INSTALLED}; + $environment =~ s|/bin-wrappers$||; + } else { + $environment = `uname -r`; + chomp $environment; + } + + my @data; + + for my $t (@subtests) { + for my $d (@dirs) { + my $commitid = $prefixes{$d}; + $commitid =~ s/^build_//; + $commitid =~ s/\.$//; + my ($result_value, $u, $s) = get_times("$resultsdir/$prefixes{$d}$t.times"); + + my %vals = ( + "commitid" => $commitid, + "project" => $project, + "branch" => $dirnames{$d}, + "executable" => $executable, + "benchmark" => $shorttests{$t} . " " . read_descr("$resultsdir/$t.descr"), + "environment" => $environment, + "result_value" => $result_value, + ); + push @data, \%vals; + } + } + + print to_json(\@data, {utf8 => 1, pretty => 1, canonical => 1}), "\n"; +} + +binmode STDOUT, ":utf8" or die "PANIC on binmode: $!"; + +if ($codespeed) { + print_codespeed_results($subsection); +} elsif (defined $sortby) { + print_sorted_results($sortby); +} else { + print_default_results(); } diff --git a/t/perf/lib-pack.sh b/t/perf/lib-pack.sh new file mode 100644 index 0000000000..d3865db286 --- /dev/null +++ b/t/perf/lib-pack.sh @@ -0,0 +1,25 @@ +# Helpers for dealing with large numbers of packs. + +# create $1 nonsense packs, each with a single blob +create_packs () { + perl -le ' + my ($n) = @ARGV; + for (1..$n) { + print "blob"; + print "data <<EOF"; + print "$_"; + print "EOF"; + print "checkpoint" + } + ' "$@" | + git fast-import +} + +# create a large number of packs, disabling any gc which might +# cause us to repack them +setup_many_packs () { + git config gc.auto 0 && + git config gc.autopacklimit 0 && + git config fastimport.unpacklimit 0 && + create_packs 500 +} diff --git a/t/perf/p0000-perf-lib-sanity.sh b/t/perf/p0000-perf-lib-sanity.sh index cf8e1efce7..002c21e52a 100755 --- a/t/perf/p0000-perf-lib-sanity.sh +++ b/t/perf/p0000-perf-lib-sanity.sh @@ -33,6 +33,8 @@ test_perf 'export a weird var' ' test_export bar ' +test_perf 'éḿÃẗ ńöń-ÃŚĆÃà ćḧáŕáćẗéŕś' 'true' + test_expect_success 'test_export works with weird vars' ' echo "$bar" && test "$bar" = "weird # variable" diff --git a/t/perf/p0001-rev-list.sh b/t/perf/p0001-rev-list.sh index 16359d51ae..ebf172401b 100755 --- a/t/perf/p0001-rev-list.sh +++ b/t/perf/p0001-rev-list.sh @@ -15,7 +15,8 @@ test_perf 'rev-list --all --objects' ' ' test_expect_success 'create new unreferenced commit' ' - commit=$(git commit-tree HEAD^{tree} -p HEAD) + commit=$(git commit-tree HEAD^{tree} -p HEAD) && + test_export commit ' test_perf 'rev-list $commit --not --all' ' diff --git a/t/perf/p0002-read-cache.sh b/t/perf/p0002-read-cache.sh index 9180ae9343..cdd105a594 100755 --- a/t/perf/p0002-read-cache.sh +++ b/t/perf/p0002-read-cache.sh @@ -8,7 +8,7 @@ test_perf_default_repo count=1000 test_perf "read_cache/discard_cache $count times" " - test-read-cache $count + test-tool read-cache $count " test_done diff --git a/t/perf/p0003-delta-base-cache.sh b/t/perf/p0003-delta-base-cache.sh new file mode 100755 index 0000000000..62369eaaf0 --- /dev/null +++ b/t/perf/p0003-delta-base-cache.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +test_description='Test operations that emphasize the delta base cache. + +We look at both "log --raw", which should put only trees into the delta cache, +and "log -Sfoo --raw", which should look at both trees and blobs. + +Any effects will be emphasized if the test repository is fully packed (loose +objects obviously do not use the delta base cache at all). It is also +emphasized if the pack has long delta chains (e.g., as produced by "gc +--aggressive"), though cache is still quite noticeable even with the default +depth of 50. + +The setting of core.deltaBaseCacheLimit in the source repository is also +relevant (depending on the size of your test repo), so be sure it is consistent +between runs. +' +. ./perf-lib.sh + +test_perf_large_repo + +# puts mostly trees into the delta base cache +test_perf 'log --raw' ' + git log --raw >/dev/null +' + +test_perf 'log -S' ' + git log --raw -Sfoo >/dev/null +' + +test_done diff --git a/t/perf/p0004-lazy-init-name-hash.sh b/t/perf/p0004-lazy-init-name-hash.sh new file mode 100755 index 0000000000..1afc08fe7f --- /dev/null +++ b/t/perf/p0004-lazy-init-name-hash.sh @@ -0,0 +1,56 @@ +#!/bin/sh + +test_description='Tests multi-threaded lazy_init_name_hash' +. ./perf-lib.sh + +test_perf_large_repo +test_checkout_worktree + +test_expect_success 'verify both methods build the same hashmaps' ' + test-tool lazy-init-name-hash --dump --single >out.single && + if test-tool lazy-init-name-hash --dump --multi >out.multi + then + test_set_prereq REPO_BIG_ENOUGH_FOR_MULTI && + sort <out.single >sorted.single && + sort <out.multi >sorted.multi && + test_cmp sorted.single sorted.multi + fi +' + +test_expect_success 'calibrate' ' + entries=$(wc -l <out.single) && + + case $entries in + ?) count=1000000 ;; + ??) count=100000 ;; + ???) count=10000 ;; + ????) count=1000 ;; + ?????) count=100 ;; + ??????) count=10 ;; + *) count=1 ;; + esac && + export count && + + case $entries in + 1) entries_desc="1 entry" ;; + *) entries_desc="$entries entries" ;; + esac && + + case $count in + 1) count_desc="1 round" ;; + *) count_desc="$count rounds" ;; + esac && + + desc="$entries_desc, $count_desc" && + export desc +' + +test_perf "single-threaded, $desc" " + test-tool lazy-init-name-hash --single --count=$count +" + +test_perf REPO_BIG_ENOUGH_FOR_MULTI "multi-threaded, $desc" " + test-tool lazy-init-name-hash --multi --count=$count +" + +test_done diff --git a/t/perf/p0005-status.sh b/t/perf/p0005-status.sh new file mode 100755 index 0000000000..0b0aa9858f --- /dev/null +++ b/t/perf/p0005-status.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# +# This test measures the performance of various read-tree +# and status operations. It is primarily interested in +# the algorithmic costs of index operations and recursive +# tree traversal -- and NOT disk I/O on thousands of files. + +test_description="Tests performance of read-tree" + +. ./perf-lib.sh + +test_perf_default_repo + +# If the test repo was generated by ./repos/many-files.sh +# then we know something about the data shape and branches, +# so we can isolate testing to the ballast-related commits +# and setup sparse-checkout so we don't have to populate +# the ballast files and directories. +# +# Otherwise, we make some general assumptions about the +# repo and consider the entire history of the current +# branch to be the ballast. + +test_expect_success "setup repo" ' + if git rev-parse --verify refs/heads/p0006-ballast^{commit} + then + echo Assuming synthetic repo from many-files.sh + git branch br_base master + git branch br_ballast p0006-ballast + git config --local core.sparsecheckout 1 + cat >.git/info/sparse-checkout <<-EOF + /* + !ballast/* + EOF + else + echo Assuming non-synthetic repo... + git branch br_base $(git rev-list HEAD | tail -n 1) + git branch br_ballast HEAD + fi && + git checkout -q br_ballast && + nr_files=$(git ls-files | wc -l) +' + +test_perf "read-tree status br_ballast ($nr_files)" ' + git read-tree HEAD && + git status +' + +test_done diff --git a/t/perf/p0006-read-tree-checkout.sh b/t/perf/p0006-read-tree-checkout.sh new file mode 100755 index 0000000000..78cc23fe2f --- /dev/null +++ b/t/perf/p0006-read-tree-checkout.sh @@ -0,0 +1,67 @@ +#!/bin/sh +# +# This test measures the performance of various read-tree +# and checkout operations. It is primarily interested in +# the algorithmic costs of index operations and recursive +# tree traversal -- and NOT disk I/O on thousands of files. + +test_description="Tests performance of read-tree" + +. ./perf-lib.sh + +test_perf_default_repo + +# If the test repo was generated by ./repos/many-files.sh +# then we know something about the data shape and branches, +# so we can isolate testing to the ballast-related commits +# and setup sparse-checkout so we don't have to populate +# the ballast files and directories. +# +# Otherwise, we make some general assumptions about the +# repo and consider the entire history of the current +# branch to be the ballast. + +test_expect_success "setup repo" ' + if git rev-parse --verify refs/heads/p0006-ballast^{commit} + then + echo Assuming synthetic repo from many-files.sh + git branch br_base master + git branch br_ballast p0006-ballast^ + git branch br_ballast_alias p0006-ballast^ + git branch br_ballast_plus_1 p0006-ballast + git config --local core.sparsecheckout 1 + cat >.git/info/sparse-checkout <<-EOF + /* + !ballast/* + EOF + else + echo Assuming non-synthetic repo... + git branch br_base $(git rev-list HEAD | tail -n 1) + git branch br_ballast HEAD^ || error "no ancestor commit from current head" + git branch br_ballast_alias HEAD^ + git branch br_ballast_plus_1 HEAD + fi && + git checkout -q br_ballast && + nr_files=$(git ls-files | wc -l) +' + +test_perf "read-tree br_base br_ballast ($nr_files)" ' + git read-tree -m br_base br_ballast -n +' + +test_perf "switch between br_base br_ballast ($nr_files)" ' + git checkout -q br_base && + git checkout -q br_ballast +' + +test_perf "switch between br_ballast br_ballast_plus_1 ($nr_files)" ' + git checkout -q br_ballast_plus_1 && + git checkout -q br_ballast +' + +test_perf "switch between aliases ($nr_files)" ' + git checkout -q br_ballast_alias && + git checkout -q br_ballast +' + +test_done diff --git a/t/perf/p0007-write-cache.sh b/t/perf/p0007-write-cache.sh new file mode 100755 index 0000000000..09595264f0 --- /dev/null +++ b/t/perf/p0007-write-cache.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +test_description="Tests performance of writing the index" + +. ./perf-lib.sh + +test_perf_default_repo + +test_expect_success "setup repo" ' + if git rev-parse --verify refs/heads/p0006-ballast^{commit} + then + echo Assuming synthetic repo from many-files.sh + git config --local core.sparsecheckout 1 + cat >.git/info/sparse-checkout <<-EOF + /* + !ballast/* + EOF + else + echo Assuming non-synthetic repo... + fi && + nr_files=$(git ls-files | wc -l) +' + +count=3 +test_perf "write_locked_index $count times ($nr_files files)" " + test-tool write-cache $count +" + +test_done diff --git a/t/perf/p0071-sort.sh b/t/perf/p0071-sort.sh new file mode 100755 index 0000000000..6e924f5fa3 --- /dev/null +++ b/t/perf/p0071-sort.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +test_description='Basic sort performance tests' +. ./perf-lib.sh + +test_perf_default_repo + +test_expect_success 'setup' ' + git ls-files --stage "*.[ch]" "*.sh" | + cut -f2 -d" " | + git cat-file --batch >unsorted +' + +test_perf 'sort(1)' ' + sort <unsorted >expect +' + +test_perf 'string_list_sort()' ' + test-tool string-list sort <unsorted >actual +' + +test_expect_success 'string_list_sort() sorts like sort(1)' ' + test_cmp_bin expect actual +' + +test_done diff --git a/t/perf/p0100-globbing.sh b/t/perf/p0100-globbing.sh new file mode 100755 index 0000000000..dd18a9ce2b --- /dev/null +++ b/t/perf/p0100-globbing.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +test_description="Tests pathological globbing performance + +Shows how Git's globbing performance performs when given the sort of +pathological patterns described in at https://research.swtch.com/glob +" + +. ./perf-lib.sh + +test_globs_big='10 25 50 75 100' +test_globs_small='1 2 3 4 5 6' + +test_perf_fresh_repo + +test_expect_success 'setup' ' + for i in $(test_seq 1 100) + do + printf "a" >>refname && + for j in $(test_seq 1 $i) + do + printf "a*" >>refglob.$i + done && + echo b >>refglob.$i + done && + test_commit test $(cat refname).t "" $(cat refname).t +' + +for i in $test_globs_small +do + test_perf "refglob((a*)^nb) against tag (a^100).t; n = $i" ' + git for-each-ref "refs/tags/$(cat refglob.'$i')b" + ' +done + +for i in $test_globs_small +do + test_perf "fileglob((a*)^nb) against file (a^100).t; n = $i" ' + git ls-files "$(cat refglob.'$i')b" + ' +done + +test_done diff --git a/t/perf/p3400-rebase.sh b/t/perf/p3400-rebase.sh new file mode 100755 index 0000000000..ce271ca4c1 --- /dev/null +++ b/t/perf/p3400-rebase.sh @@ -0,0 +1,56 @@ +#!/bin/sh + +test_description='Tests rebase performance' +. ./perf-lib.sh + +test_perf_default_repo + +test_expect_success 'setup rebasing on top of a lot of changes' ' + git checkout -f -b base && + git checkout -b to-rebase && + git checkout -b upstream && + for i in $(seq 100) + do + # simulate huge diffs + echo change$i >unrelated-file$i && + seq 1000 >>unrelated-file$i && + git add unrelated-file$i && + test_tick && + git commit -m commit$i unrelated-file$i && + echo change$i >unrelated-file$i && + seq 1000 | tac >>unrelated-file$i && + git add unrelated-file$i && + test_tick && + git commit -m commit$i-reverse unrelated-file$i || + break + done && + git checkout to-rebase && + test_commit our-patch interesting-file +' + +test_perf 'rebase on top of a lot of unrelated changes' ' + git rebase --onto upstream HEAD^ && + git rebase --onto base HEAD^ +' + +test_expect_success 'setup rebasing many changes without split-index' ' + git config core.splitIndex false && + git checkout -b upstream2 to-rebase && + git checkout -b to-rebase2 upstream +' + +test_perf 'rebase a lot of unrelated changes without split-index' ' + git rebase --onto upstream2 base && + git rebase --onto base upstream2 +' + +test_expect_success 'setup rebasing many changes with split-index' ' + git config core.splitIndex true +' + +test_perf 'rebase a lot of unrelated changes with split-index' ' + git rebase --onto upstream2 base && + git rebase --onto base upstream2 +' + +test_done diff --git a/t/perf/p3404-rebase-interactive.sh b/t/perf/p3404-rebase-interactive.sh new file mode 100755 index 0000000000..88f47de282 --- /dev/null +++ b/t/perf/p3404-rebase-interactive.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +test_description='Tests rebase -i performance' +. ./perf-lib.sh + +test_perf_default_repo + +# This commit merges a sufficiently long topic branch for reasonable +# performance testing +branch_merge=ba5312da19c6fdb6c6747d479f58932aae6e900c^{commit} +export branch_merge + +git rev-parse --verify $branch_merge >/dev/null 2>&1 || { + skip_all='skipping because $branch_merge was not found' + test_done +} + +write_script swap-first-two.sh <<\EOF +case "$1" in +*/COMMIT_EDITMSG) + mv "$1" "$1".bak && + sed -e '1{h;d}' -e 2G <"$1".bak >"$1" + ;; +esac +EOF + +test_expect_success 'setup' ' + git config core.editor "\"$PWD"/swap-first-two.sh\" && + git checkout -f $branch_merge^2 +' + +test_perf 'rebase -i' ' + git rebase -i $branch_merge^ +' + +test_done diff --git a/t/perf/p4205-log-pretty-formats.sh b/t/perf/p4205-log-pretty-formats.sh new file mode 100755 index 0000000000..7c26f4f337 --- /dev/null +++ b/t/perf/p4205-log-pretty-formats.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +test_description='Tests the performance of various pretty format placeholders' + +. ./perf-lib.sh + +test_perf_default_repo + +for format in %H %h %T %t %P %p %h-%h-%h +do + test_perf "log with $format" " + git log --format=\"$format\" >/dev/null + " +done + +test_done diff --git a/t/perf/p4211-line-log.sh b/t/perf/p4211-line-log.sh index 3d074b0e41..392bcc0e51 100755 --- a/t/perf/p4211-line-log.sh +++ b/t/perf/p4211-line-log.sh @@ -23,12 +23,20 @@ test_perf 'git log --follow (baseline for -M)' ' git log --oneline --follow -- "$file" >/dev/null ' -test_perf 'git log -L' ' - git log -L 1:"$file" >/dev/null +test_perf 'git log -L (renames off)' ' + git log --no-renames -L 1:"$file" >/dev/null ' -test_perf 'git log -M -L' ' +test_perf 'git log -L (renames on)' ' git log -M -L 1:"$file" >/dev/null ' +test_perf 'git log --oneline --raw --parents' ' + git log --oneline --raw --parents >/dev/null +' + +test_perf 'git log --oneline --raw --parents -1000' ' + git log --oneline --raw --parents -1000 >/dev/null +' + test_done diff --git a/t/perf/p4220-log-grep-engines.sh b/t/perf/p4220-log-grep-engines.sh new file mode 100755 index 0000000000..2bc47ded4d --- /dev/null +++ b/t/perf/p4220-log-grep-engines.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +test_description="Comparison of git-log's --grep regex engines + +Set GIT_PERF_4220_LOG_OPTS in the environment to pass options to +git-grep. Make sure to include a leading space, +e.g. GIT_PERF_4220_LOG_OPTS=' -i'. Some options to try: + + -i + --invert-grep + -i --invert-grep +" + +. ./perf-lib.sh + +test_perf_large_repo +test_checkout_worktree + +for pattern in \ + 'how.to' \ + '^how to' \ + '[how] to' \ + '\(e.t[^ ]*\|v.ry\) rare' \ + 'm\(ú\|u\)lt.b\(æ\|y\)te' +do + for engine in basic extended perl + do + if test $engine != "basic" + then + # Poor man's basic -> extended converter. + pattern=$(echo $pattern | sed 's/\\//g') + fi + if test $engine = "perl" && ! test_have_prereq PCRE + then + prereq="PCRE" + else + prereq="" + fi + test_perf $prereq "$engine log$GIT_PERF_4220_LOG_OPTS --grep='$pattern'" " + git -c grep.patternType=$engine log --pretty=format:%h$GIT_PERF_4220_LOG_OPTS --grep='$pattern' >'out.$engine' || : + " + done + + test_expect_success "assert that all engines found the same for$GIT_PERF_4220_LOG_OPTS '$pattern'" ' + test_cmp out.basic out.extended && + if test_have_prereq PCRE + then + test_cmp out.basic out.perl + fi + ' +done + +test_done diff --git a/t/perf/p4221-log-grep-engines-fixed.sh b/t/perf/p4221-log-grep-engines-fixed.sh new file mode 100755 index 0000000000..060971265a --- /dev/null +++ b/t/perf/p4221-log-grep-engines-fixed.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +test_description="Comparison of git-log's --grep regex engines with -F + +Set GIT_PERF_4221_LOG_OPTS in the environment to pass options to +git-grep. Make sure to include a leading space, +e.g. GIT_PERF_4221_LOG_OPTS=' -i'. Some options to try: + + -i + --invert-grep + -i --invert-grep +" + +. ./perf-lib.sh + +test_perf_large_repo +test_checkout_worktree + +for pattern in 'int' 'uncommon' 'æ' +do + for engine in fixed basic extended perl + do + if test $engine = "perl" && ! test_have_prereq PCRE + then + prereq="PCRE" + else + prereq="" + fi + test_perf $prereq "$engine log$GIT_PERF_4221_LOG_OPTS --grep='$pattern'" " + git -c grep.patternType=$engine log --pretty=format:%h$GIT_PERF_4221_LOG_OPTS --grep='$pattern' >'out.$engine' || : + " + done + + test_expect_success "assert that all engines found the same for$GIT_PERF_4221_LOG_OPTS '$pattern'" ' + test_cmp out.fixed out.basic && + test_cmp out.fixed out.extended && + if test_have_prereq PCRE + then + test_cmp out.fixed out.perl + fi + ' +done + +test_done diff --git a/t/perf/p5302-pack-index.sh b/t/perf/p5302-pack-index.sh index 5ee9211f98..99bdb16c85 100755 --- a/t/perf/p5302-pack-index.sh +++ b/t/perf/p5302-pack-index.sh @@ -13,6 +13,13 @@ test_expect_success 'repack' ' export PACK ' +test_expect_success 'create target repositories' ' + for repo in t1 t2 t3 t4 t5 t6 + do + git init --bare $repo + done +' + test_perf 'index-pack 0 threads' ' GIT_DIR=t1 git index-pack --threads=1 --stdin < $PACK ' diff --git a/t/perf/p5303-many-packs.sh b/t/perf/p5303-many-packs.sh new file mode 100755 index 0000000000..3779851941 --- /dev/null +++ b/t/perf/p5303-many-packs.sh @@ -0,0 +1,87 @@ +#!/bin/sh + +test_description='performance with large numbers of packs' +. ./perf-lib.sh + +test_perf_large_repo + +# A real many-pack situation would probably come from having a lot of pushes +# over time. We don't know how big each push would be, but we can fake it by +# just walking the first-parent chain and having every 5 commits be their own +# "push". This isn't _entirely_ accurate, as real pushes would have some +# duplicate objects due to thin-pack fixing, but it's a reasonable +# approximation. +# +# And then all of the rest of the objects can go in a single packfile that +# represents the state before any of those pushes (actually, we'll generate +# that first because in such a setup it would be the oldest pack, and we sort +# the packs by reverse mtime inside git). +repack_into_n () { + rm -rf staging && + mkdir staging && + + git rev-list --first-parent HEAD | + sed -n '1~5p' | + head -n "$1" | + perl -e 'print reverse <>' \ + >pushes + + # create base packfile + head -n 1 pushes | + git pack-objects --delta-base-offset --revs staging/pack + + # and then incrementals between each pair of commits + last= && + while read rev + do + if test -n "$last"; then + { + echo "$rev" && + echo "^$last" + } | + git pack-objects --delta-base-offset --revs \ + staging/pack || return 1 + fi + last=$rev + done <pushes && + + # and install the whole thing + rm -f .git/objects/pack/* && + mv staging/* .git/objects/pack/ +} + +# Pretend we just have a single branch and no reflogs, and that everything is +# in objects/pack; that makes our fake pack-building via repack_into_n() +# much simpler. +test_expect_success 'simplify reachability' ' + tip=$(git rev-parse --verify HEAD) && + git for-each-ref --format="option no-deref%0adelete %(refname)" | + git update-ref --stdin && + rm -rf .git/logs && + git update-ref refs/heads/master $tip && + git symbolic-ref HEAD refs/heads/master && + git repack -ad +' + +for nr_packs in 1 50 1000 +do + test_expect_success "create $nr_packs-pack scenario" ' + repack_into_n $nr_packs + ' + + test_perf "rev-list ($nr_packs)" ' + git rev-list --objects --all >/dev/null + ' + + # This simulates the interesting part of the repack, which is the + # actual pack generation, without smudging the on-disk setup + # between trials. + test_perf "repack ($nr_packs)" ' + git pack-objects --keep-true-parents \ + --honor-pack-keep --non-empty --all \ + --reflog --indexed-objects --delta-base-offset \ + --stdout </dev/null >/dev/null + ' +done + +test_done diff --git a/t/perf/p5310-pack-bitmaps.sh b/t/perf/p5310-pack-bitmaps.sh index f8ed8573b7..bb91dbb173 100755 --- a/t/perf/p5310-pack-bitmaps.sh +++ b/t/perf/p5310-pack-bitmaps.sh @@ -32,6 +32,14 @@ test_perf 'simulated fetch' ' } | git pack-objects --revs --stdout >/dev/null ' +test_perf 'pack to file' ' + git pack-objects --all pack1 </dev/null >/dev/null +' + +test_perf 'pack to file (bitmap)' ' + git pack-objects --use-bitmap-index --all pack1b </dev/null >/dev/null +' + test_expect_success 'create partial bitmap state' ' # pick a commit to represent the repo tip in the past cutoff=$(git rev-list HEAD~100 -1) && @@ -39,22 +47,26 @@ test_expect_success 'create partial bitmap state' ' # now kill off all of the refs and pretend we had # just the one tip - rm -rf .git/logs .git/refs/* .git/packed-refs - git update-ref HEAD $cutoff + rm -rf .git/logs .git/refs/* .git/packed-refs && + git update-ref HEAD $cutoff && # and then repack, which will leave us with a nice # big bitmap pack of the "old" history, and all of # the new history will be loose, as if it had been pushed # up incrementally and exploded via unpack-objects - git repack -Ad + git repack -Ad && # and now restore our original tip, as if the pushes # had happened git update-ref HEAD $orig_tip ' -test_perf 'partial bitmap' ' +test_perf 'clone (partial bitmap)' ' git pack-objects --stdout --all </dev/null >/dev/null ' +test_perf 'pack to file (partial bitmap)' ' + git pack-objects --use-bitmap-index --all pack2b </dev/null >/dev/null +' + test_done diff --git a/t/perf/p5550-fetch-tags.sh b/t/perf/p5550-fetch-tags.sh new file mode 100755 index 0000000000..d0e0e019ea --- /dev/null +++ b/t/perf/p5550-fetch-tags.sh @@ -0,0 +1,78 @@ +#!/bin/sh + +test_description='performance of tag-following with many tags + +This tests a fairly pathological case, so rather than rely on a real-world +case, we will construct our own repository. The situation is roughly as +follows. + +The parent repository has a large number of tags which are disconnected from +the rest of history. That makes them candidates for tag-following, but we never +actually grab them (and thus they will impact each subsequent fetch). + +The child repository is a clone of parent, without the tags, and is at least +one commit behind the parent (meaning that we will fetch one object and then +examine the tags to see if they need followed). Furthermore, it has a large +number of packs. + +The exact values of "large" here are somewhat arbitrary; I picked values that +start to show a noticeable performance problem on my machine, but without +taking too long to set up and run the tests. +' +. ./perf-lib.sh +. "$TEST_DIRECTORY/perf/lib-pack.sh" + +# make a long nonsense history on branch $1, consisting of $2 commits, each +# with a unique file pointing to the blob at $2. +create_history () { + perl -le ' + my ($branch, $n, $blob) = @ARGV; + for (1..$n) { + print "commit refs/heads/$branch"; + print "committer nobody <nobody@example.com> now"; + print "data 4"; + print "foo"; + print "M 100644 $blob $_"; + } + ' "$@" | + git fast-import --date-format=now +} + +# make a series of tags, one per commit in the revision range given by $@ +create_tags () { + git rev-list "$@" | + perl -lne 'print "create refs/tags/$. $_"' | + git update-ref --stdin +} + +test_expect_success 'create parent and child' ' + git init parent && + git -C parent commit --allow-empty -m base && + git clone parent child && + git -C parent commit --allow-empty -m trigger-fetch +' + +test_expect_success 'populate parent tags' ' + ( + cd parent && + blob=$(echo content | git hash-object -w --stdin) && + create_history cruft 3000 $blob && + create_tags cruft && + git branch -D cruft + ) +' + +test_expect_success 'create child packs' ' + ( + cd child && + setup_many_packs + ) +' + +test_perf 'fetch' ' + # make sure there is something to fetch on each iteration + git -C child update-ref -d refs/remotes/origin/master && + git -C child fetch +' + +test_done diff --git a/t/perf/p5551-fetch-rescan.sh b/t/perf/p5551-fetch-rescan.sh new file mode 100755 index 0000000000..b99dc23e32 --- /dev/null +++ b/t/perf/p5551-fetch-rescan.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +test_description='fetch performance with many packs + +It is common for fetch to consider objects that we might not have, and it is an +easy mistake for the code to use a function like `parse_object` that might +give the correct _answer_ on such an object, but do so slowly (due to +re-scanning the pack directory for lookup failures). + +The resulting performance drop can be hard to notice in a real repository, but +becomes quite large in a repository with a large number of packs. So this +test creates a more pathological case, since any mistakes would produce a more +noticeable slowdown. +' +. ./perf-lib.sh +. "$TEST_DIRECTORY"/perf/lib-pack.sh + +test_expect_success 'create parent and child' ' + git init parent && + git clone parent child +' + + +test_expect_success 'create refs in the parent' ' + ( + cd parent && + git commit --allow-empty -m foo && + head=$(git rev-parse HEAD) && + test_seq 1000 | + sed "s,.*,update refs/heads/& $head," | + $MODERN_GIT update-ref --stdin + ) +' + +test_expect_success 'create many packs in the child' ' + ( + cd child && + setup_many_packs + ) +' + +test_perf 'fetch' ' + # start at the same state for each iteration + obj=$($MODERN_GIT -C parent rev-parse HEAD) && + ( + cd child && + $MODERN_GIT for-each-ref --format="delete %(refname)" refs/remotes | + $MODERN_GIT update-ref --stdin && + rm -vf .git/objects/$(echo $obj | sed "s|^..|&/|") && + + git fetch + ) +' + +test_done diff --git a/t/perf/p7000-filter-branch.sh b/t/perf/p7000-filter-branch.sh new file mode 100755 index 0000000000..b029586ccb --- /dev/null +++ b/t/perf/p7000-filter-branch.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +test_description='performance of filter-branch' +. ./perf-lib.sh + +test_perf_default_repo +test_checkout_worktree + +test_expect_success 'mark bases for tests' ' + git tag -f tip && + git tag -f base HEAD~100 +' + +test_perf 'noop filter' ' + git checkout --detach tip && + git filter-branch -f base..HEAD +' + +test_perf 'noop prune-empty' ' + git checkout --detach tip && + git filter-branch -f --prune-empty base..HEAD +' + +test_done diff --git a/t/perf/p7300-clean.sh b/t/perf/p7300-clean.sh new file mode 100755 index 0000000000..7c1888a27e --- /dev/null +++ b/t/perf/p7300-clean.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +test_description="Test git-clean performance" + +. ./perf-lib.sh + +test_perf_default_repo +test_checkout_worktree + +test_expect_success 'setup untracked directory with many sub dirs' ' + rm -rf 500_sub_dirs 100000_sub_dirs clean_test_dir && + mkdir 500_sub_dirs 100000_sub_dirs clean_test_dir && + for i in $(test_seq 1 500) + do + mkdir 500_sub_dirs/dir$i || return $? + done && + for i in $(test_seq 1 200) + do + cp -r 500_sub_dirs 100000_sub_dirs/dir$i || return $? + done +' + +test_perf 'clean many untracked sub dirs, check for nested git' ' + git clean -n -q -f -d 100000_sub_dirs/ +' + +test_perf 'clean many untracked sub dirs, ignore nested git' ' + git clean -n -q -f -f -d 100000_sub_dirs/ +' + +test_perf 'ls-files -o' ' + git ls-files -o +' + +test_done diff --git a/t/perf/p7519-fsmonitor.sh b/t/perf/p7519-fsmonitor.sh new file mode 100755 index 0000000000..def7ecdbc7 --- /dev/null +++ b/t/perf/p7519-fsmonitor.sh @@ -0,0 +1,183 @@ +#!/bin/sh + +test_description="Test core.fsmonitor" + +. ./perf-lib.sh + +# +# Performance test for the fsmonitor feature which enables git to talk to a +# file system change monitor and avoid having to scan the working directory +# for new or modified files. +# +# By default, the performance test will utilize the Watchman file system +# monitor if it is installed. If Watchman is not installed, it will use a +# dummy integration script that does not report any new or modified files. +# The dummy script has very little overhead which provides optimistic results. +# +# The performance test will also use the untracked cache feature if it is +# available as fsmonitor uses it to speed up scanning for untracked files. +# +# There are 3 environment variables that can be used to alter the default +# behavior of the performance test: +# +# GIT_PERF_7519_UNTRACKED_CACHE: used to configure core.untrackedCache +# GIT_PERF_7519_SPLIT_INDEX: used to configure core.splitIndex +# GIT_PERF_7519_FSMONITOR: used to configure core.fsMonitor +# +# The big win for using fsmonitor is the elimination of the need to scan the +# working directory looking for changed and untracked files. If the file +# information is all cached in RAM, the benefits are reduced. +# +# GIT_PERF_7519_DROP_CACHE: if set, the OS caches are dropped between tests +# + +test_perf_large_repo +test_checkout_worktree + +test_lazy_prereq UNTRACKED_CACHE ' + { git update-index --test-untracked-cache; ret=$?; } && + test $ret -ne 1 +' + +test_lazy_prereq WATCHMAN ' + command -v watchman +' + +if test_have_prereq WATCHMAN +then + # Convert unix style paths to escaped Windows style paths for Watchman + case "$(uname -s)" in + MSYS_NT*) + GIT_WORK_TREE="$(cygpath -aw "$PWD" | sed 's,\\,/,g')" + ;; + *) + GIT_WORK_TREE="$PWD" + ;; + esac +fi + +if test -n "$GIT_PERF_7519_DROP_CACHE" +then + # When using GIT_PERF_7519_DROP_CACHE, GIT_PERF_REPEAT_COUNT must be 1 to + # generate valid results. Otherwise the caching that happens for the nth + # run will negate the validity of the comparisons. + if test "$GIT_PERF_REPEAT_COUNT" -ne 1 + then + echo "warning: Setting GIT_PERF_REPEAT_COUNT=1" >&2 + GIT_PERF_REPEAT_COUNT=1 + fi +fi + +test_expect_success "setup for fsmonitor" ' + # set untrackedCache depending on the environment + if test -n "$GIT_PERF_7519_UNTRACKED_CACHE" + then + git config core.untrackedCache "$GIT_PERF_7519_UNTRACKED_CACHE" + else + if test_have_prereq UNTRACKED_CACHE + then + git config core.untrackedCache true + else + git config core.untrackedCache false + fi + fi && + + # set core.splitindex depending on the environment + if test -n "$GIT_PERF_7519_SPLIT_INDEX" + then + git config core.splitIndex "$GIT_PERF_7519_SPLIT_INDEX" + fi && + + # set INTEGRATION_SCRIPT depending on the environment + if test -n "$GIT_PERF_7519_FSMONITOR" + then + INTEGRATION_SCRIPT="$GIT_PERF_7519_FSMONITOR" + else + # + # Choose integration script based on existence of Watchman. + # If Watchman exists, watch the work tree and attempt a query. + # If everything succeeds, use Watchman integration script, + # else fall back to an empty integration script. + # + mkdir .git/hooks && + if test_have_prereq WATCHMAN + then + INTEGRATION_SCRIPT=".git/hooks/fsmonitor-watchman" && + cp "$TEST_DIRECTORY/../templates/hooks--fsmonitor-watchman.sample" "$INTEGRATION_SCRIPT" && + watchman watch "$GIT_WORK_TREE" && + watchman watch-list | grep -q -F "$GIT_WORK_TREE" + else + INTEGRATION_SCRIPT=".git/hooks/fsmonitor-empty" && + write_script "$INTEGRATION_SCRIPT"<<-\EOF + EOF + fi + fi && + + git config core.fsmonitor "$INTEGRATION_SCRIPT" && + git update-index --fsmonitor +' + +if test -n "$GIT_PERF_7519_DROP_CACHE"; then + test-tool drop-caches +fi + +test_perf "status (fsmonitor=$INTEGRATION_SCRIPT)" ' + git status +' + +if test -n "$GIT_PERF_7519_DROP_CACHE"; then + test-tool drop-caches +fi + +test_perf "status -uno (fsmonitor=$INTEGRATION_SCRIPT)" ' + git status -uno +' + +if test -n "$GIT_PERF_7519_DROP_CACHE"; then + test-tool drop-caches +fi + +test_perf "status -uall (fsmonitor=$INTEGRATION_SCRIPT)" ' + git status -uall +' + +test_expect_success "setup without fsmonitor" ' + unset INTEGRATION_SCRIPT && + git config --unset core.fsmonitor && + git update-index --no-fsmonitor +' + +if test -n "$GIT_PERF_7519_DROP_CACHE"; then + test-tool drop-caches +fi + +test_perf "status (fsmonitor=$INTEGRATION_SCRIPT)" ' + git status +' + +if test -n "$GIT_PERF_7519_DROP_CACHE"; then + test-tool drop-caches +fi + +test_perf "status -uno (fsmonitor=$INTEGRATION_SCRIPT)" ' + git status -uno +' + +if test -n "$GIT_PERF_7519_DROP_CACHE"; then + test-tool drop-caches +fi + +test_perf "status -uall (fsmonitor=$INTEGRATION_SCRIPT)" ' + git status -uall +' + +if test_have_prereq WATCHMAN +then + watchman watch-del "$GIT_WORK_TREE" >/dev/null 2>&1 && + + # Work around Watchman bug on Windows where it holds on to handles + # preventing the removal of the trash directory + watchman shutdown-server >/dev/null 2>&1 +fi + +test_done diff --git a/t/perf/p7820-grep-engines.sh b/t/perf/p7820-grep-engines.sh new file mode 100755 index 0000000000..8b09c5bf32 --- /dev/null +++ b/t/perf/p7820-grep-engines.sh @@ -0,0 +1,88 @@ +#!/bin/sh + +test_description="Comparison of git-grep's regex engines + +Set GIT_PERF_7820_GREP_OPTS in the environment to pass options to +git-grep. Make sure to include a leading space, +e.g. GIT_PERF_7820_GREP_OPTS=' -i'. Some options to try: + + -i + -w + -v + -vi + -vw + -viw + +If GIT_PERF_GREP_THREADS is set to a list of threads (e.g. '1 4 8' +etc.) we will test the patterns under those numbers of threads. +" + +. ./perf-lib.sh + +test_perf_large_repo +test_checkout_worktree + +if test -n "$GIT_PERF_GREP_THREADS" +then + test_set_prereq PERF_GREP_ENGINES_THREADS +fi + +for pattern in \ + 'how.to' \ + '^how to' \ + '[how] to' \ + '\(e.t[^ ]*\|v.ry\) rare' \ + 'm\(ú\|u\)lt.b\(æ\|y\)te' +do + for engine in basic extended perl + do + if test $engine != "basic" + then + # Poor man's basic -> extended converter. + pattern=$(echo "$pattern" | sed 's/\\//g') + fi + if test $engine = "perl" && ! test_have_prereq PCRE + then + prereq="PCRE" + else + prereq="" + fi + if ! test_have_prereq PERF_GREP_ENGINES_THREADS + then + test_perf $prereq "$engine grep$GIT_PERF_7820_GREP_OPTS '$pattern'" " + git -c grep.patternType=$engine grep$GIT_PERF_7820_GREP_OPTS -- '$pattern' >'out.$engine' || : + " + else + for threads in $GIT_PERF_GREP_THREADS + do + test_perf PTHREADS,$prereq "$engine grep$GIT_PERF_7820_GREP_OPTS '$pattern' with $threads threads" " + git -c grep.patternType=$engine -c grep.threads=$threads grep$GIT_PERF_7820_GREP_OPTS -- '$pattern' >'out.$engine.$threads' || : + " + done + fi + done + + if ! test_have_prereq PERF_GREP_ENGINES_THREADS + then + test_expect_success "assert that all engines found the same for$GIT_PERF_7820_GREP_OPTS '$pattern'" ' + test_cmp out.basic out.extended && + if test_have_prereq PCRE + then + test_cmp out.basic out.perl + fi + ' + else + for threads in $GIT_PERF_GREP_THREADS + do + test_expect_success PTHREADS "assert that all engines found the same for$GIT_PERF_7820_GREP_OPTS '$pattern' under threading" " + test_cmp out.basic.$threads out.extended.$threads && + if test_have_prereq PCRE + then + test_cmp out.basic.$threads out.perl.$threads + fi + " + done + fi +done + +test_done diff --git a/t/perf/p7821-grep-engines-fixed.sh b/t/perf/p7821-grep-engines-fixed.sh new file mode 100755 index 0000000000..61e41b82cf --- /dev/null +++ b/t/perf/p7821-grep-engines-fixed.sh @@ -0,0 +1,74 @@ +#!/bin/sh + +test_description="Comparison of git-grep's regex engines with -F + +Set GIT_PERF_7821_GREP_OPTS in the environment to pass options to +git-grep. Make sure to include a leading space, +e.g. GIT_PERF_7821_GREP_OPTS=' -w'. See p7820-grep-engines.sh for more +options to try. + +If GIT_PERF_7821_THREADS is set to a list of threads (e.g. '1 4 8' +etc.) we will test the patterns under those numbers of threads. +" + +. ./perf-lib.sh + +test_perf_large_repo +test_checkout_worktree + +if test -n "$GIT_PERF_GREP_THREADS" +then + test_set_prereq PERF_GREP_ENGINES_THREADS +fi + +for pattern in 'int' 'uncommon' 'æ' +do + for engine in fixed basic extended perl + do + if test $engine = "perl" && ! test_have_prereq PCRE + then + prereq="PCRE" + else + prereq="" + fi + if ! test_have_prereq PERF_GREP_ENGINES_THREADS + then + test_perf $prereq "$engine grep$GIT_PERF_7821_GREP_OPTS $pattern" " + git -c grep.patternType=$engine grep$GIT_PERF_7821_GREP_OPTS $pattern >'out.$engine' || : + " + else + for threads in $GIT_PERF_GREP_THREADS + do + test_perf PTHREADS,$prereq "$engine grep$GIT_PERF_7821_GREP_OPTS $pattern with $threads threads" " + git -c grep.patternType=$engine -c grep.threads=$threads grep$GIT_PERF_7821_GREP_OPTS $pattern >'out.$engine.$threads' || : + " + done + fi + done + + if ! test_have_prereq PERF_GREP_ENGINES_THREADS + then + test_expect_success "assert that all engines found the same for$GIT_PERF_7821_GREP_OPTS $pattern" ' + test_cmp out.fixed out.basic && + test_cmp out.fixed out.extended && + if test_have_prereq PCRE + then + test_cmp out.fixed out.perl + fi + ' + else + for threads in $GIT_PERF_GREP_THREADS + do + test_expect_success PTHREADS "assert that all engines found the same for$GIT_PERF_7821_GREP_OPTS $pattern under threading" " + test_cmp out.fixed.$threads out.basic.$threads && + test_cmp out.fixed.$threads out.extended.$threads && + if test_have_prereq PCRE + then + test_cmp out.fixed.$threads out.perl.$threads + fi + " + done + fi +done + +test_done diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh index 5cf74eddec..e4c343a6b7 100644 --- a/t/perf/perf-lib.sh +++ b/t/perf/perf-lib.sh @@ -52,13 +52,14 @@ TEST_NO_MALLOC_CHECK=t # need to export them for test_perf subshells export TEST_DIRECTORY TRASH_DIRECTORY GIT_BUILD_DIR GIT_TEST_CMP +MODERN_GIT=$GIT_BUILD_DIR/bin-wrappers/git +export MODERN_GIT + perf_results_dir=$TEST_OUTPUT_DIRECTORY/test-results +test -n "$GIT_PERF_SUBSECTION" && perf_results_dir="$perf_results_dir/$GIT_PERF_SUBSECTION" mkdir -p "$perf_results_dir" rm -f "$perf_results_dir"/$(basename "$0" .sh).subtests -if test -z "$GIT_PERF_REPEAT_COUNT"; then - GIT_PERF_REPEAT_COUNT=3 -fi die_if_build_dir_not_repo () { if ! ( cd "$TEST_DIRECTORY/.." && git rev-parse --build-dir >/dev/null 2>&1 ); then @@ -75,33 +76,57 @@ if test -z "$GIT_PERF_LARGE_REPO"; then GIT_PERF_LARGE_REPO=$TEST_DIRECTORY/.. fi +test_perf_do_repo_symlink_config_ () { + test_have_prereq SYMLINKS || git config core.symlinks false +} + test_perf_create_repo_from () { test "$#" = 2 || error "bug in the test script: not 2 parameters to test-create-repo" repo="$1" source="$2" - source_git=$source/$(cd "$source" && git rev-parse --git-dir) + source_git="$("$MODERN_GIT" -C "$source" rev-parse --git-dir)" + objects_dir="$("$MODERN_GIT" -C "$source" rev-parse --git-path objects)" mkdir -p "$repo/.git" ( - cd "$repo/.git" && - { cp -Rl "$source_git/objects" . 2>/dev/null || - cp -R "$source_git/objects" .; } && + cd "$source" && + { cp -Rl "$objects_dir" "$repo/.git/" 2>/dev/null || + cp -R "$objects_dir" "$repo/.git/"; } && for stuff in "$source_git"/*; do case "$stuff" in - */objects|*/hooks|*/config) + */objects|*/hooks|*/config|*/commondir) ;; *) - cp -R "$stuff" . || exit 1 + cp -R "$stuff" "$repo/.git/" || exit 1 ;; esac - done && - cd .. && - git init -q && - mv .git/hooks .git/hooks-disabled 2>/dev/null + done + ) && + ( + cd "$repo" && + "$MODERN_GIT" init -q && + test_perf_do_repo_symlink_config_ && + mv .git/hooks .git/hooks-disabled 2>/dev/null && + if test -f .git/index.lock + then + # We may be copying a repo that can't run "git + # status" due to a locked index. Since we have + # a copy it's fine to remove the lock. + rm .git/index.lock + fi ) || error "failed to copy repository '$source' to '$repo'" } # call at least one of these to establish an appropriately-sized repository +test_perf_fresh_repo () { + repo="${1:-$TRASH_DIRECTORY}" + "$MODERN_GIT" init -q "$repo" && + ( + cd "$repo" && + test_perf_do_repo_symlink_config_ + ) +} + test_perf_default_repo () { test_perf_create_repo_from "${1:-$TRASH_DIRECTORY}" "$GIT_PERF_REPO" } @@ -121,11 +146,15 @@ test_checkout_worktree () { # Performance tests should never fail. If they do, stop immediately immediate=t +# Perf tests require GNU time +case "$(uname -s)" in Darwin) GTIME="${GTIME:-gtime}";; esac +GTIME="${GTIME:-/usr/bin/time}" + test_run_perf_ () { test_cleanup=: test_export_="test_cleanup" export test_cleanup test_export_ - /usr/bin/time -f "%E %U %S" -o test_time.$i "$SHELL" -c ' + "$GTIME" -f "%E %U %S" -o test_time.$i "$SHELL" -c ' . '"$TEST_DIRECTORY"/test-lib-functions.sh' test_export () { [ $# != 0 ] || return 0 diff --git a/t/perf/repos/.gitignore b/t/perf/repos/.gitignore new file mode 100644 index 0000000000..72e3dc3e19 --- /dev/null +++ b/t/perf/repos/.gitignore @@ -0,0 +1 @@ +gen-*/ diff --git a/t/perf/repos/inflate-repo.sh b/t/perf/repos/inflate-repo.sh new file mode 100755 index 0000000000..fcfc992b5b --- /dev/null +++ b/t/perf/repos/inflate-repo.sh @@ -0,0 +1,85 @@ +#!/bin/sh +# Inflate the size of an EXISTING repo. +# +# This script should be run inside the worktree of a TEST repo. +# It will use the contents of the current HEAD to generate a +# commit containing copies of the current worktree such that the +# total size of the commit has at least <target_size> files. +# +# Usage: [-t target_size] [-b branch_name] + +set -e + +target_size=10000 +branch_name=p0006-ballast +ballast=ballast + +while test "$#" -ne 0 +do + case "$1" in + -b) + shift; + test "$#" -ne 0 || { echo 'error: -b requires an argument' >&2; exit 1; } + branch_name=$1; + shift ;; + -t) + shift; + test "$#" -ne 0 || { echo 'error: -t requires an argument' >&2; exit 1; } + target_size=$1; + shift ;; + *) + echo "error: unknown option '$1'" >&2; exit 1 ;; + esac +done + +git ls-tree -r HEAD >GEN_src_list +nr_src_files=$(cat GEN_src_list | wc -l) + +src_branch=$(git symbolic-ref --short HEAD) + +echo "Branch $src_branch initially has $nr_src_files files." + +if test $target_size -le $nr_src_files +then + echo "Repository already exceeds target size $target_size." + rm GEN_src_list + exit 1 +fi + +# Create well-known branch and add 1 file change to start +# if off before the ballast. +git checkout -b $branch_name HEAD +echo "$target_size" > inflate-repo.params +git add inflate-repo.params +git commit -q -m params + +# Create ballast for in our branch. +copy=1 +nr_files=$nr_src_files +while test $nr_files -lt $target_size +do + sed -e "s| | $ballast/$copy/|" <GEN_src_list | + git update-index --index-info + + nr_files=$(expr $nr_files + $nr_src_files) + copy=$(expr $copy + 1) +done +rm GEN_src_list +git commit -q -m "ballast" + +# Modify 1 file and commit. +echo "$target_size" >> inflate-repo.params +git add inflate-repo.params +git commit -q -m "ballast plus 1" + +nr_files=$(git ls-files | wc -l) + +# Checkout master to put repo in canonical state (because +# the perf test may need to clone and enable sparse-checkout +# before attempting to checkout a commit with the ballast +# (because it may contain 100K directories and 1M files)). +git checkout $src_branch + +echo "Repository inflated. Branch $branch_name has $nr_files files." + +exit 0 diff --git a/t/perf/repos/many-files.sh b/t/perf/repos/many-files.sh new file mode 100755 index 0000000000..28720e4e10 --- /dev/null +++ b/t/perf/repos/many-files.sh @@ -0,0 +1,110 @@ +#!/bin/sh +# Generate test data repository using the given parameters. +# When omitted, we create "gen-many-files-d-w-f.git". +# +# Usage: [-r repo] [-d depth] [-w width] [-f files] +# +# -r repo: path to the new repo to be generated +# -d depth: the depth of sub-directories +# -w width: the number of sub-directories at each level +# -f files: the number of files created in each directory +# +# Note that all files will have the same SHA-1 and each +# directory at a level will have the same SHA-1, so we +# will potentially have a large index, but not a large +# ODB. +# +# Ballast will be created under "ballast/". + +EMPTY_BLOB=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 + +set -e + +# (5, 10, 9) will create 999,999 ballast files. +# (4, 10, 9) will create 99,999 ballast files. +depth=5 +width=10 +files=9 + +while test "$#" -ne 0 +do + case "$1" in + -r) + shift; + test "$#" -ne 0 || { echo 'error: -r requires an argument' >&2; exit 1; } + repo=$1; + shift ;; + -d) + shift; + test "$#" -ne 0 || { echo 'error: -d requires an argument' >&2; exit 1; } + depth=$1; + shift ;; + -w) + shift; + test "$#" -ne 0 || { echo 'error: -w requires an argument' >&2; exit 1; } + width=$1; + shift ;; + -f) + shift; + test "$#" -ne 0 || { echo 'error: -f requires an argument' >&2; exit 1; } + files=$1; + shift ;; + *) + echo "error: unknown option '$1'" >&2; exit 1 ;; + esac +done + +# Inflate the index with thousands of empty files. +# usage: dir depth width files +fill_index() { + awk -v arg_dir=$1 -v arg_depth=$2 -v arg_width=$3 -v arg_files=$4 ' + function make_paths(dir, depth, width, files, f, w) { + for (f = 1; f <= files; f++) { + print dir "/file" f + } + if (depth > 0) { + for (w = 1; w <= width; w++) { + make_paths(dir "/dir" w, depth - 1, width, files) + } + } + } + END { make_paths(arg_dir, arg_depth, arg_width, arg_files) } + ' </dev/null | + sed "s/^/100644 $EMPTY_BLOB /" | + git update-index --index-info + return 0 +} + +[ -z "$repo" ] && repo=gen-many-files-$depth.$width.$files.git + +mkdir $repo +cd $repo +git init . + +# Create an initial commit just to define master. +touch many-files.empty +echo "$depth $width $files" >many-files.params +git add many-files.* +git commit -q -m params + +# Create ballast for p0006 based upon the given params and +# inflate the index with thousands of empty files and commit. +git checkout -b p0006-ballast +fill_index "ballast" $depth $width $files +git commit -q -m "ballast" + +nr_files=$(git ls-files | wc -l) + +# Modify 1 file and commit. +echo "$depth $width $files" >>many-files.params +git add many-files.params +git commit -q -m "ballast plus 1" + +# Checkout master to put repo in canonical state (because +# the perf test may need to clone and enable sparse-checkout +# before attempting to checkout a commit with the ballast +# (because it may contain 100K directories and 1M files)). +git checkout master + +echo "Repository "$repo" ($depth, $width, $files) created. Ballast $nr_files." +exit 0 diff --git a/t/perf/run b/t/perf/run index cfd70129bb..213da5d6b9 100755 --- a/t/perf/run +++ b/t/perf/run @@ -2,9 +2,14 @@ case "$1" in --help) - echo "usage: $0 [other_git_tree...] [--] [test_scripts]" + echo "usage: $0 [--config file] [other_git_tree...] [--] [test_scripts]" exit 0 ;; + --config) + shift + GIT_PERF_CONFIG_FILE=$(cd "$(dirname "$1")"; pwd)/$(basename "$1") + export GIT_PERF_CONFIG_FILE + shift ;; esac die () { @@ -24,15 +29,32 @@ run_one_dir () { unpack_git_rev () { rev=$1 + echo "=== Unpacking $rev in build/$rev ===" mkdir -p build/$rev (cd "$(git rev-parse --show-cdup)" && git archive --format=tar $rev) | (cd build/$rev && tar x) } + build_git_rev () { rev=$1 - cp ../../config.mak build/$rev/config.mak - (cd build/$rev && make $GIT_PERF_MAKE_OPTS) || - die "failed to build revision '$mydir'" + name="$2" + for config in config.mak config.mak.autogen config.status + do + if test -e "../../$config" + then + cp "../../$config" "build/$rev/" + fi + done + echo "=== Building $rev ($name) ===" + ( + cd build/$rev && + if test -n "$GIT_PERF_MAKE_COMMAND" + then + sh -c "$GIT_PERF_MAKE_COMMAND" + else + make $GIT_PERF_MAKE_OPTS + fi + ) || die "failed to build revision '$mydir'" } run_dirs_helper () { @@ -50,13 +72,16 @@ run_dirs_helper () { if [ ! -d build/$rev ]; then unpack_git_rev $rev fi - build_git_rev $rev + build_git_rev $rev "$mydir" mydir=build/$rev fi if test "$mydir" = .; then unset GIT_TEST_INSTALLED else GIT_TEST_INSTALLED="$mydir/bin-wrappers" + # Older versions of git lacked bin-wrappers; fallback to the + # files in the root. + test -d "$GIT_TEST_INSTALLED" || GIT_TEST_INSTALLED=$mydir export GIT_TEST_INSTALLED fi run_one_dir "$@" @@ -69,14 +94,95 @@ run_dirs () { done } -GIT_PERF_AGGREGATING_LATER=t -export GIT_PERF_AGGREGATING_LATER +get_subsections () { + section="$1" + test -z "$GIT_PERF_CONFIG_FILE" && return + git config -f "$GIT_PERF_CONFIG_FILE" --name-only --get-regex "$section\..*\.[^.]+" | + sed -e "s/$section\.\(.*\)\..*/\1/" | sort | uniq +} + +get_var_from_env_or_config () { + env_var="$1" + conf_sec="$2" + conf_var="$3" + conf_opts="$4" # optional + + # Do nothing if the env variable is already set + eval "test -z \"\${$env_var+x}\"" || return + + test -z "$GIT_PERF_CONFIG_FILE" && return + + # Check if the variable is in the config file + if test -n "$GIT_PERF_SUBSECTION" + then + var="$conf_sec.$GIT_PERF_SUBSECTION.$conf_var" + conf_value=$(git config $conf_opts -f "$GIT_PERF_CONFIG_FILE" "$var") && + eval "$env_var=\"$conf_value\"" && return + fi + var="$conf_sec.$conf_var" + conf_value=$(git config $conf_opts -f "$GIT_PERF_CONFIG_FILE" "$var") && + eval "$env_var=\"$conf_value\"" +} + +run_subsection () { + get_var_from_env_or_config "GIT_PERF_REPEAT_COUNT" "perf" "repeatCount" "--int" + : ${GIT_PERF_REPEAT_COUNT:=3} + export GIT_PERF_REPEAT_COUNT + + get_var_from_env_or_config "GIT_PERF_DIRS_OR_REVS" "perf" "dirsOrRevs" + set -- $GIT_PERF_DIRS_OR_REVS "$@" + + get_var_from_env_or_config "GIT_PERF_MAKE_COMMAND" "perf" "makeCommand" + get_var_from_env_or_config "GIT_PERF_MAKE_OPTS" "perf" "makeOpts" + + get_var_from_env_or_config "GIT_PERF_REPO_NAME" "perf" "repoName" + export GIT_PERF_REPO_NAME + + GIT_PERF_AGGREGATING_LATER=t + export GIT_PERF_AGGREGATING_LATER + + if test $# = 0 -o "$1" = -- -o -f "$1"; then + set -- . "$@" + fi + + codespeed_opt= + test "$GIT_PERF_CODESPEED_OUTPUT" = "true" && codespeed_opt="--codespeed" + + run_dirs "$@" + + if test -z "$GIT_PERF_SEND_TO_CODESPEED" + then + ./aggregate.perl $codespeed_opt "$@" + else + json_res_file="test-results/$GIT_PERF_SUBSECTION/aggregate.json" + ./aggregate.perl --codespeed "$@" | tee "$json_res_file" + send_data_url="$GIT_PERF_SEND_TO_CODESPEED/result/add/json/" + curl -v --request POST --data-urlencode "json=$(cat "$json_res_file")" "$send_data_url" + fi +} + +get_var_from_env_or_config "GIT_PERF_CODESPEED_OUTPUT" "perf" "codespeedOutput" "--bool" +get_var_from_env_or_config "GIT_PERF_SEND_TO_CODESPEED" "perf" "sendToCodespeed" cd "$(dirname $0)" . ../../GIT-BUILD-OPTIONS -if test $# = 0 -o "$1" = -- -o -f "$1"; then - set -- . "$@" +mkdir -p test-results +get_subsections "perf" >test-results/run_subsections.names + +if test $(wc -l <test-results/run_subsections.names) -eq 0 +then + ( + run_subsection "$@" + ) +else + while read -r subsec + do + ( + GIT_PERF_SUBSECTION="$subsec" + export GIT_PERF_SUBSECTION + echo "======== Run for subsection '$GIT_PERF_SUBSECTION' ========" + run_subsection "$@" + ) + done <test-results/run_subsections.names fi -run_dirs "$@" -./aggregate.perl "$@" diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 79b9074172..7fd87dd544 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -20,6 +20,31 @@ modification *should* take notice and update the test vectors here. . ./test-lib.sh +try_local_x () { + local x="local" && + echo "$x" +} + +# This test is an experiment to check whether any Git users are using +# Shells that don't support the "local" keyword. "local" is not +# POSIX-standard, but it is very widely supported by POSIX-compliant +# shells, and if it doesn't cause problems for people, we would like +# to be able to use it in Git code. +# +# For now, this is the only test that requires "local". If your shell +# fails this test, you can ignore the failure, but please report the +# problem to the Git mailing list <git@vger.kernel.org>, as it might +# convince us to continue avoiding the use of "local". +test_expect_success 'verify that the running shell supports "local"' ' + x="notlocal" && + echo "local" >expected1 && + try_local_x >actual1 && + test_cmp expected1 actual1 && + echo "notlocal" >expected2 && + echo "$x" >actual2 && + test_cmp expected2 actual2 +' + ################################################################ # git init has been done in an empty repository. # make sure it is empty. @@ -98,7 +123,7 @@ check_sub_test_lib_test () { } check_sub_test_lib_test_err () { - name="$1" # stdin is the expected output output from the test + name="$1" # stdin is the expected output from the test # expected error output is in descriptior 3 ( cd "$name" && @@ -834,7 +859,7 @@ test_expect_success 'git write-tree should be able to write an empty tree' ' ' test_expect_success 'validate object ID of a known tree' ' - test "$tree" = 4b825dc642cb6eb9a060e54bf8d69288fbee4904 + test "$tree" = $EMPTY_TREE ' # Various types of objects diff --git a/t/t0001-init.sh b/t/t0001-init.sh index 7de8d85ee8..c413bff9cf 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -87,6 +87,21 @@ test_expect_success 'plain nested in bare through aliased command' ' check_config bare-ancestor-aliased.git/plain-nested/.git false unset ' +test_expect_success 'No extra GIT_* on alias scripts' ' + write_script script <<-\EOF && + env | + sed -n \ + -e "/^GIT_PREFIX=/d" \ + -e "/^GIT_TEXTDOMAINDIR=/d" \ + -e "/^GIT_/s/=.*//p" | + sort + EOF + ./script >expected && + git config alias.script \!./script && + ( mkdir sub && cd sub && git script >../actual ) && + test_cmp expected actual +' + test_expect_success 'plain with GIT_WORK_TREE' ' mkdir plain-wt && test_must_fail env GIT_WORK_TREE="$(pwd)/plain-wt" git init plain-wt @@ -202,8 +217,8 @@ test_expect_success 'init honors global core.sharedRepository' ' x$(git config -f shared-honor-global/.git/config core.sharedRepository) ' -test_expect_success 'init rejects insanely long --template' ' - test_must_fail git init --template=$(printf "x%09999dx" 1) test +test_expect_success 'init allows insanely long --template' ' + git init --template=$(printf "x%09999dx" 1) test ' test_expect_success 'init creates a new directory' ' @@ -243,6 +258,9 @@ test_expect_success POSIXPERM 'init creates a new deep directory (umask vs. shar ( # Leading directories should honor umask while # the repository itself should follow "shared" + mkdir newdir && + # Remove a default ACL if possible. + (setfacl -k newdir 2>/dev/null || true) && umask 002 && git init --bare --shared=0660 newdir/a/b/c && test_path_is_dir newdir/a/b/c/refs && @@ -297,6 +315,46 @@ test_expect_success 'init with separate gitdir' ' test_path_is_dir realgitdir/refs ' +test_lazy_prereq GETCWD_IGNORES_PERMS ' + base=GETCWD_TEST_BASE_DIR && + mkdir -p $base/dir && + chmod 100 $base || + error "bug in test script: cannot prepare $base" + + (cd $base/dir && /bin/pwd -P) + status=$? + + chmod 700 $base && + rm -rf $base || + error "bug in test script: cannot clean $base" + return $status +' + +check_long_base_path () { + # exceed initial buffer size of strbuf_getcwd() + component=123456789abcdef && + test_when_finished "chmod 0700 $component; rm -rf $component" && + p31=$component/$component && + p127=$p31/$p31/$p31/$p31 && + mkdir -p $p127 && + if test $# = 1 + then + chmod $1 $component + fi && + ( + cd $p127 && + git init newdir + ) +} + +test_expect_success 'init in long base path' ' + check_long_base_path +' + +test_expect_success GETCWD_IGNORES_PERMS 'init in long restricted base path' ' + check_long_base_path 0111 +' + test_expect_success 're-init on .git file' ' ( cd newdir && git init ) ' @@ -339,4 +397,72 @@ test_expect_success SYMLINKS 're-init to move gitdir symlink' ' test_path_is_dir realgitdir/refs ' +# Tests for the hidden file attribute on windows +is_hidden () { + # Use the output of `attrib`, ignore the absolute path + case "$(attrib "$1")" in *H*?:*) return 0;; esac + return 1 +} + +test_expect_success MINGW '.git hidden' ' + rm -rf newdir && + ( + unset GIT_DIR GIT_WORK_TREE + mkdir newdir && + cd newdir && + git init && + is_hidden .git + ) && + check_config newdir/.git false unset +' + +test_expect_success MINGW 'bare git dir not hidden' ' + rm -rf newdir && + ( + unset GIT_DIR GIT_WORK_TREE GIT_CONFIG + mkdir newdir && + cd newdir && + git --bare init + ) && + ! is_hidden newdir +' + +test_expect_success 'remote init from does not use config from cwd' ' + rm -rf newdir && + test_config core.logallrefupdates true && + git init newdir && + echo true >expect && + git -C newdir config --bool core.logallrefupdates >actual && + test_cmp expect actual +' + +test_expect_success 're-init from a linked worktree' ' + git init main-worktree && + ( + cd main-worktree && + test_commit first && + git worktree add ../linked-worktree && + mv .git/info/exclude expected-exclude && + cp .git/config expected-config && + find .git/worktrees -print | sort >expected && + git -C ../linked-worktree init && + test_cmp expected-exclude .git/info/exclude && + test_cmp expected-config .git/config && + find .git/worktrees -print | sort >actual && + test_cmp expected actual + ) +' + +test_expect_success MINGW 'redirect std handles' ' + GIT_REDIRECT_STDOUT=output.txt git rev-parse --git-dir && + test .git = "$(cat output.txt)" && + test -z "$(GIT_REDIRECT_STDOUT=off git rev-parse --git-dir)" && + test_must_fail env \ + GIT_REDIRECT_STDOUT=output.txt \ + GIT_REDIRECT_STDERR="2>&1" \ + git rev-parse --git-dir --verify refs/invalid && + printf ".git\nfatal: Needed a single revision\n" >expect && + test_cmp expect output.txt +' + test_done diff --git a/t/t0002-gitfile.sh b/t/t0002-gitfile.sh index 37e9396e5d..3691023d51 100755 --- a/t/t0002-gitfile.sh +++ b/t/t0002-gitfile.sh @@ -10,15 +10,6 @@ objpath() { echo "$1" | sed -e 's|\(..\)|\1/|' } -objck() { - p=$(objpath "$1") - if test ! -f "$REAL/objects/$p" - then - echo "Object not found: $REAL/objects/$p" - false - fi -} - test_expect_success 'initial setup' ' REAL="$(pwd)/.real" && mv .git "$REAL" @@ -26,30 +17,14 @@ test_expect_success 'initial setup' ' test_expect_success 'bad setup: invalid .git file format' ' echo "gitdir $REAL" >.git && - if git rev-parse 2>.err - then - echo "git rev-parse accepted an invalid .git file" - false - fi && - if ! grep "Invalid gitfile format" .err - then - echo "git rev-parse returned wrong error" - false - fi + test_must_fail git rev-parse 2>.err && + test_i18ngrep "invalid gitfile format" .err ' test_expect_success 'bad setup: invalid .git file path' ' echo "gitdir: $REAL.not" >.git && - if git rev-parse 2>.err - then - echo "git rev-parse accepted an invalid .git file path" - false - fi && - if ! grep "Not a git repository" .err - then - echo "git rev-parse returned wrong error" - false - fi + test_must_fail git rev-parse 2>.err && + test_i18ngrep "not a git repository" .err ' test_expect_success 'final setup + check rev-parse --git-dir' ' @@ -60,7 +35,7 @@ test_expect_success 'final setup + check rev-parse --git-dir' ' test_expect_success 'check hash-object' ' echo "foo" >bar && SHA=$(cat bar | git hash-object -w --stdin) && - objck $SHA + test_path_is_file "$REAL/objects/$(objpath $SHA)" ' test_expect_success 'check cat-file' ' @@ -69,29 +44,21 @@ test_expect_success 'check cat-file' ' ' test_expect_success 'check update-index' ' - if test -f "$REAL/index" - then - echo "Hmm, $REAL/index exists?" - false - fi && + test_path_is_missing "$REAL/index" && rm -f "$REAL/objects/$(objpath $SHA)" && git update-index --add bar && - if ! test -f "$REAL/index" - then - echo "$REAL/index not found" - false - fi && - objck $SHA + test_path_is_file "$REAL/index" && + test_path_is_file "$REAL/objects/$(objpath $SHA)" ' test_expect_success 'check write-tree' ' SHA=$(git write-tree) && - objck $SHA + test_path_is_file "$REAL/objects/$(objpath $SHA)" ' test_expect_success 'check commit-tree' ' SHA=$(echo "commit bar" | git commit-tree $SHA) && - objck $SHA + test_path_is_file "$REAL/objects/$(objpath $SHA)" ' test_expect_success 'check rev-list' ' @@ -99,4 +66,63 @@ test_expect_success 'check rev-list' ' test "$SHA" = "$(git rev-list HEAD)" ' +test_expect_success 'setup_git_dir twice in subdir' ' + git init sgd && + ( + cd sgd && + git config alias.lsfi ls-files && + mv .git .realgit && + echo "gitdir: .realgit" >.git && + mkdir subdir && + cd subdir && + >foo && + git add foo && + git lsfi >actual && + echo foo >expected && + test_cmp expected actual + ) +' + +test_expect_success 'enter_repo non-strict mode' ' + test_create_repo enter_repo && + ( + cd enter_repo && + test_tick && + test_commit foo && + mv .git .realgit && + echo "gitdir: .realgit" >.git + ) && + git ls-remote enter_repo >actual && + cat >expected <<-\EOF && + 946e985ab20de757ca5b872b16d64e92ff3803a9 HEAD + 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/heads/master + 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/tags/foo + EOF + test_cmp expected actual +' + +test_expect_success 'enter_repo linked checkout' ' + ( + cd enter_repo && + git worktree add ../foo refs/tags/foo + ) && + git ls-remote foo >actual && + cat >expected <<-\EOF && + 946e985ab20de757ca5b872b16d64e92ff3803a9 HEAD + 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/heads/master + 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/tags/foo + EOF + test_cmp expected actual +' + +test_expect_success 'enter_repo strict mode' ' + git ls-remote --upload-pack="git upload-pack --strict" foo/.git >actual && + cat >expected <<-\EOF && + 946e985ab20de757ca5b872b16d64e92ff3803a9 HEAD + 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/heads/master + 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/tags/foo + EOF + test_cmp expected actual +' + test_done diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh index f0fbb42554..f19ae4f8cc 100755 --- a/t/t0003-attributes.sh +++ b/t/t0003-attributes.sh @@ -13,10 +13,31 @@ attr_check () { test_line_count = 0 err } +attr_check_quote () { + + path="$1" + quoted_path="$2" + expect="$3" + + git check-attr test -- "$path" >actual && + echo "\"$quoted_path\": test: $expect" >expect && + test_cmp expect actual + +} + +test_expect_success 'open-quoted pathname' ' + echo "\"a test=a" >.gitattributes && + test_must_fail attr_check a a +' + + test_expect_success 'setup' ' mkdir -p a/b/d a/c b && ( echo "[attr]notest !test" + echo "\" d \" test=d" + echo " e test=e" + echo " e\" test=e" echo "f test=f" echo "a/i test=a/i" echo "onoff test -test" @@ -69,6 +90,11 @@ test_expect_success 'command line checks' ' ' test_expect_success 'attribute test' ' + + attr_check " d " d && + attr_check e e && + attr_check_quote e\" e\\\" e && + attr_check f f && attr_check a/f f && attr_check a/c/f f && diff --git a/t/t0005-signals.sh b/t/t0005-signals.sh index e7f27ebbc1..4c214bd11c 100755 --- a/t/t0005-signals.sh +++ b/t/t0005-signals.sh @@ -10,20 +10,21 @@ one EOF test_expect_success 'sigchain works' ' - { test-sigchain >actual; ret=$?; } && - case "$ret" in - 143) true ;; # POSIX w/ SIGTERM=15 - 271) true ;; # ksh w/ SIGTERM=15 - 3) true ;; # Windows - *) false ;; - esac && + { test-tool sigchain >actual; ret=$?; } && + { + # Signal death by raise() on Windows acts like exit(3), + # regardless of the signal number. So we must allow that + # as well as the normal signal check. + test_match_signal 15 "$ret" || + test "$ret" = 3 + } && test_cmp expect actual ' test_expect_success !MINGW 'signals are propagated using shell convention' ' # we use exec here to avoid any sub-shell interpretation # of the exit code - git config alias.sigterm "!exec test-sigchain" && + git config alias.sigterm "!exec test-tool sigchain" && test_expect_code 143 git sigterm ' @@ -35,18 +36,18 @@ large_git () { } test_expect_success 'create blob' ' - test-genrandom foo 16384 >file && + test-tool genrandom foo 16384 >file && git add file ' test_expect_success !MINGW 'a constipated git dies with SIGPIPE' ' OUT=$( ((large_git; echo $? 1>&3) | :) 3>&1 ) && - test "$OUT" -eq 141 + test_match_signal 13 "$OUT" ' test_expect_success !MINGW 'a constipated git dies with SIGPIPE even if parent ignores it' ' OUT=$( ((trap "" PIPE; large_git; echo $? 1>&3) | :) 3>&1 ) && - test "$OUT" -eq 141 + test_match_signal 13 "$OUT" ' test_done diff --git a/t/t0006-date.sh b/t/t0006-date.sh index fac0986134..64ff86df8e 100755 --- a/t/t0006-date.sh +++ b/t/t0006-date.sh @@ -6,31 +6,72 @@ test_description='test date parsing and printing' # arbitrary reference time: 2009-08-30 19:20:00 TEST_DATE_NOW=1251660000; export TEST_DATE_NOW -check_show() { +check_relative() { t=$(($TEST_DATE_NOW - $1)) echo "$t -> $2" >expect test_expect_${3:-success} "relative date ($2)" " - test-date show $t >actual && + test-tool date relative $t >actual && test_i18ncmp expect actual " } -check_show 5 '5 seconds ago' -check_show 300 '5 minutes ago' -check_show 18000 '5 hours ago' -check_show 432000 '5 days ago' -check_show 1728000 '3 weeks ago' -check_show 13000000 '5 months ago' -check_show 37500000 '1 year, 2 months ago' -check_show 55188000 '1 year, 9 months ago' -check_show 630000000 '20 years ago' -check_show 31449600 '12 months ago' -check_show 62985600 '2 years ago' +check_relative 5 '5 seconds ago' +check_relative 300 '5 minutes ago' +check_relative 18000 '5 hours ago' +check_relative 432000 '5 days ago' +check_relative 1728000 '3 weeks ago' +check_relative 13000000 '5 months ago' +check_relative 37500000 '1 year, 2 months ago' +check_relative 55188000 '1 year, 9 months ago' +check_relative 630000000 '20 years ago' +check_relative 31449600 '12 months ago' +check_relative 62985600 '2 years ago' + +check_show () { + format=$1 + time=$2 + expect=$3 + prereqs=$4 + zone=$5 + test_expect_success $prereqs "show date ($format:$time)" ' + echo "$time -> $expect" >expect && + TZ=${zone:-$TZ} test-tool date show:"$format" "$time" >actual && + test_cmp expect actual + ' +} + +# arbitrary but sensible time for examples +TIME='1466000000 +0200' +check_show iso8601 "$TIME" '2016-06-15 16:13:20 +0200' +check_show iso8601-strict "$TIME" '2016-06-15T16:13:20+02:00' +check_show rfc2822 "$TIME" 'Wed, 15 Jun 2016 16:13:20 +0200' +check_show short "$TIME" '2016-06-15' +check_show default "$TIME" 'Wed Jun 15 16:13:20 2016 +0200' +check_show raw "$TIME" '1466000000 +0200' +check_show unix "$TIME" '1466000000' +check_show iso-local "$TIME" '2016-06-15 14:13:20 +0000' +check_show raw-local "$TIME" '1466000000 +0000' +check_show unix-local "$TIME" '1466000000' + +check_show 'format:%z' "$TIME" '+0200' +check_show 'format-local:%z' "$TIME" '+0000' +check_show 'format:%Z' "$TIME" '' +check_show 'format-local:%Z' "$TIME" 'UTC' +check_show 'format:%%z' "$TIME" '%z' +check_show 'format-local:%%z' "$TIME" '%z' + +check_show 'format:%Y-%m-%d %H:%M:%S' "$TIME" '2016-06-15 16:13:20' +check_show 'format-local:%Y-%m-%d %H:%M:%S' "$TIME" '2016-06-15 09:13:20' '' EST5 + +# arbitrary time absurdly far in the future +FUTURE="5758122296 -0400" +check_show iso "$FUTURE" "2152-06-19 18:24:56 -0400" TIME_IS_64BIT,TIME_T_IS_64BIT +check_show iso-local "$FUTURE" "2152-06-19 22:24:56 +0000" TIME_IS_64BIT,TIME_T_IS_64BIT check_parse() { echo "$1 -> $2" >expect test_expect_${4:-success} "parse date ($1${3:+ TZ=$3})" " - TZ=${3:-$TZ} test-date parse '$1' >actual && + TZ=${3:-$TZ} test-tool date parse '$1' >actual && test_cmp expect actual " } @@ -51,7 +92,7 @@ check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 -0500' EST5 check_approxidate() { echo "$1 -> $2 +0000" >expect test_expect_${3:-success} "parse approxidate ($1)" " - test-date approxidate '$1' >actual && + test-tool date approxidate '$1' >actual && test_cmp expect actual " } diff --git a/t/t0008-ignores.sh b/t/t0008-ignores.sh index 4ef5ed484c..c03f155a35 100755 --- a/t/t0008-ignores.sh +++ b/t/t0008-ignores.sh @@ -5,7 +5,7 @@ test_description=check-ignore . ./test-lib.sh init_vars () { - global_excludes="$(pwd)/global-excludes" + global_excludes="global-excludes" } enable_global_excludes () { @@ -34,7 +34,7 @@ expect_from_stdin () { test_stderr () { expected="$1" expect_in stderr "$1" && - test_cmp "$HOME/expected-stderr" "$HOME/stderr" + test_i18ncmp "$HOME/expected-stderr" "$HOME/stderr" } broken_c_unquote () { @@ -47,7 +47,7 @@ broken_c_unquote_verbose () { stderr_contains () { regexp="$1" - if grep "$regexp" "$HOME/stderr" + if test_i18ngrep "$regexp" "$HOME/stderr" then return 0 else @@ -307,7 +307,7 @@ test_expect_success_multi 'needs work tree' '' ' cd .git && test_check_ignore "foo" 128 ) && - stderr_contains "fatal: This operation must be run in a work tree" + stderr_contains "fatal: this operation must be run in a work tree" ' ############################################################################ @@ -605,7 +605,7 @@ cat <<-EOF >expected-verbose a/b/.gitignore:8:!on* a/b/one a/b/.gitignore:8:!on* a/b/one one a/b/.gitignore:8:!on* a/b/one two - a/b/.gitignore:8:!on* "a/b/one\"three" + a/b/.gitignore:8:!on* "a/b/one\\"three" a/b/.gitignore:9:!two a/b/two a/.gitignore:1:two* a/b/twooo $global_excludes:2:!globaltwo globaltwo @@ -686,7 +686,7 @@ cat <<-EOF >expected-all a/b/.gitignore:8:!on* b/one a/b/.gitignore:8:!on* b/one one a/b/.gitignore:8:!on* b/one two - a/b/.gitignore:8:!on* "b/one\"three" + a/b/.gitignore:8:!on* "b/one\\"three" a/b/.gitignore:9:!two b/two :: b/not-ignored a/.gitignore:1:two* b/twooo @@ -775,6 +775,26 @@ test_expect_success PIPE 'streaming support for --stdin' ' echo "$response" | grep "^:: two" ' +test_expect_success 'existing file and directory' ' + test_when_finished "rm one" && + test_when_finished "rmdir top-level-dir" && + >one && + mkdir top-level-dir && + git check-ignore one top-level-dir >actual && + grep one actual && + grep top-level-dir actual +' + +test_expect_success 'existing directory and file' ' + test_when_finished "rm one" && + test_when_finished "rmdir top-level-dir" && + >one && + mkdir top-level-dir && + git check-ignore top-level-dir one >actual && + grep one actual && + grep top-level-dir actual +' + ############################################################################ # # test whitespace handling diff --git a/t/t0009-prio-queue.sh b/t/t0009-prio-queue.sh index 94045c3fad..e56dfce668 100755 --- a/t/t0009-prio-queue.sh +++ b/t/t0009-prio-queue.sh @@ -17,7 +17,7 @@ cat >expect <<'EOF' 10 EOF test_expect_success 'basic ordering' ' - test-prio-queue 2 6 3 10 9 5 7 4 5 8 1 dump >actual && + test-tool prio-queue 2 6 3 10 9 5 7 4 5 8 1 dump >actual && test_cmp expect actual ' @@ -30,7 +30,7 @@ cat >expect <<'EOF' 6 EOF test_expect_success 'mixed put and get' ' - test-prio-queue 6 2 4 get 5 3 get get 1 dump >actual && + test-tool prio-queue 6 2 4 get 5 3 get get 1 dump >actual && test_cmp expect actual ' @@ -43,7 +43,7 @@ NULL NULL EOF test_expect_success 'notice empty queue' ' - test-prio-queue 1 2 get get get 1 2 get get get >actual && + test-tool prio-queue 1 2 get get get 1 2 get get get >actual && test_cmp expect actual ' diff --git a/t/t0011-hashmap.sh b/t/t0011-hashmap.sh index 9c217d948c..3f1f505e89 100755 --- a/t/t0011-hashmap.sh +++ b/t/t0011-hashmap.sh @@ -4,7 +4,7 @@ test_description='test hashmap and string hash functions' . ./test-lib.sh test_hashmap() { - echo "$1" | test-hashmap $3 > actual && + echo "$1" | test-tool hashmap $3 > actual && echo "$2" > expect && test_cmp expect actual } @@ -232,7 +232,7 @@ test_expect_success 'grow / shrink' ' echo value40 >> expect && echo size >> in && echo 64 39 >> expect && - cat in | test-hashmap > out && + cat in | test-tool hashmap > out && test_cmp expect out ' diff --git a/t/t0012-help.sh b/t/t0012-help.sh new file mode 100755 index 0000000000..487b92a5de --- /dev/null +++ b/t/t0012-help.sh @@ -0,0 +1,64 @@ +#!/bin/sh + +test_description='help' + +. ./test-lib.sh + +configure_help () { + test_config help.format html && + + # Unless the path has "://" in it, Git tries to make sure + # the documentation directory locally exists. Avoid it as + # we are only interested in seeing an attempt to correctly + # invoke a help browser in this test. + test_config help.htmlpath test://html && + + # Name a custom browser + test_config browser.test.cmd ./test-browser && + test_config help.browser test +} + +test_expect_success "setup" ' + # Just write out which page gets requested + write_script test-browser <<-\EOF + echo "$*" >test-browser.log + EOF +' + +test_expect_success "works for commands and guides by default" ' + configure_help && + git help status && + echo "test://html/git-status.html" >expect && + test_cmp expect test-browser.log && + git help revisions && + echo "test://html/gitrevisions.html" >expect && + test_cmp expect test-browser.log +' + +test_expect_success "--exclude-guides does not work for guides" ' + >test-browser.log && + test_must_fail git help --exclude-guides revisions && + test_must_be_empty test-browser.log +' + +test_expect_success "--help does not work for guides" " + cat <<-EOF >expect && + git: 'revisions' is not a git command. See 'git --help'. + EOF + test_must_fail git revisions --help 2>actual && + test_i18ncmp expect actual +" + +test_expect_success 'generate builtin list' ' + git --list-builtins >builtins +' + +while read builtin +do + test_expect_success "$builtin can handle -h" ' + test_expect_code 129 git $builtin -h >output 2>&1 && + test_i18ngrep usage output + ' +done <builtins + +test_done diff --git a/t/t0013-sha1dc.sh b/t/t0013-sha1dc.sh new file mode 100755 index 0000000000..419f31a8f7 --- /dev/null +++ b/t/t0013-sha1dc.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +test_description='test sha1 collision detection' +. ./test-lib.sh +TEST_DATA="$TEST_DIRECTORY/t0013" + +if test -z "$DC_SHA1" +then + skip_all='skipping sha1 collision tests, DC_SHA1 not set' + test_done +fi + +test_expect_success 'test-sha1 detects shattered pdf' ' + test_must_fail test-tool sha1 <"$TEST_DATA/shattered-1.pdf" 2>err && + test_i18ngrep collision err && + grep 38762cf7f55934b34d179ae6a4c80cadccbb7f0a err +' + +test_done diff --git a/t/t0013/shattered-1.pdf b/t/t0013/shattered-1.pdf Binary files differnew file mode 100644 index 0000000000..ba9aaa145c --- /dev/null +++ b/t/t0013/shattered-1.pdf diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh index f94120a894..71350e0657 100755 --- a/t/t0020-crlf.sh +++ b/t/t0020-crlf.sh @@ -83,7 +83,11 @@ test_expect_success 'safecrlf: print warning only once' ' git add doublewarn && git commit -m "nowarn" && for w in Oh here is CRLFQ in text; do echo $w; done | q_to_cr >doublewarn && - test $(git add doublewarn 2>&1 | grep "CRLF will be replaced by LF" | wc -l) = 1 + git add doublewarn 2>err && + if test_have_prereq C_LOCALE_OUTPUT + then + test $(grep "CRLF will be replaced by LF" err | wc -l) = 1 + fi ' diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh index 718efa04d3..9479a4aaab 100755 --- a/t/t0021-conversion.sh +++ b/t/t0021-conversion.sh @@ -4,13 +4,72 @@ test_description='blob conversion via gitattributes' . ./test-lib.sh -cat <<EOF >rot13.sh -#!$SHELL_PATH +TEST_ROOT="$PWD" +PATH=$TEST_ROOT:$PATH + +write_script <<\EOF "$TEST_ROOT/rot13.sh" tr \ 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' \ 'nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM' EOF -chmod +x rot13.sh + +write_script rot13-filter.pl "$PERL_PATH" \ + <"$TEST_DIRECTORY"/t0021/rot13-filter.pl + +generate_random_characters () { + LEN=$1 + NAME=$2 + test-tool genrandom some-seed $LEN | + perl -pe "s/./chr((ord($&) % 26) + ord('a'))/sge" >"$TEST_ROOT/$NAME" +} + +file_size () { + perl -e 'print -s $ARGV[0]' "$1" +} + +filter_git () { + rm -f *.log && + git "$@" +} + +# Compare two files and ensure that `clean` and `smudge` respectively are +# called at least once if specified in the `expect` file. The actual +# invocation count is not relevant because their number can vary. +# c.f. http://public-inbox.org/git/xmqqshv18i8i.fsf@gitster.mtv.corp.google.com/ +test_cmp_count () { + expect=$1 + actual=$2 + for FILE in "$expect" "$actual" + do + sort "$FILE" | uniq -c | + sed -e "s/^ *[0-9][0-9]*[ ]*IN: /x IN: /" >"$FILE.tmp" + done && + test_cmp "$expect.tmp" "$actual.tmp" && + rm "$expect.tmp" "$actual.tmp" +} + +# Compare two files but exclude all `clean` invocations because Git can +# call `clean` zero or more times. +# c.f. http://public-inbox.org/git/xmqqshv18i8i.fsf@gitster.mtv.corp.google.com/ +test_cmp_exclude_clean () { + expect=$1 + actual=$2 + for FILE in "$expect" "$actual" + do + grep -v "IN: clean" "$FILE" >"$FILE.tmp" + done && + test_cmp "$expect.tmp" "$actual.tmp" && + rm "$expect.tmp" "$actual.tmp" +} + +# Check that the contents of two files are equal and that their rot13 version +# is equal to the committed content. +test_cmp_committed_rot13 () { + test_cmp "$1" "$2" && + rot13.sh <"$1" >expected && + git cat-file blob :"$2" >actual && + test_cmp expected actual +} test_expect_success setup ' git config filter.rot13.smudge ./rot13.sh && @@ -31,15 +90,18 @@ test_expect_success setup ' cat test >test.i && git add test test.t test.i && rm -f test test.t test.i && - git checkout -- test test.t test.i + git checkout -- test test.t test.i && + + echo "content-test2" >test2.o && + echo "content-test3 - filename with special characters" >"test3 '\''sq'\'',\$x=.o" ' script='s/^\$Id: \([0-9a-f]*\) \$/\1/p' test_expect_success check ' - cmp test.o test && - cmp test.o test.t && + test_cmp test.o test && + test_cmp test.o test.t && # ident should be stripped in the repository git diff --raw --exit-code :test :test.i && @@ -47,10 +109,10 @@ test_expect_success check ' embedded=$(sed -ne "$script" test.i) && test "z$id" = "z$embedded" && - git cat-file blob :test.t > test.r && + git cat-file blob :test.t >test.r && - ./rot13.sh < test.o > test.t && - cmp test.r test.t + ./rot13.sh <test.o >test.t && + test_cmp test.r test.t ' # If an expanded ident ever gets into the repository, we want to make sure that @@ -130,7 +192,7 @@ test_expect_success 'filter shell-escaped filenames' ' # delete the files and check them out again, using a smudge filter # that will count the args and echo the command-line back to us - git config filter.argc.smudge "sh ./argc.sh %f" && + test_config filter.argc.smudge "sh ./argc.sh %f" && rm "$normal" "$special" && git checkout -- "$normal" "$special" && @@ -141,7 +203,7 @@ test_expect_success 'filter shell-escaped filenames' ' test_cmp expect "$special" && # do the same thing, but with more args in the filter expression - git config filter.argc.smudge "sh ./argc.sh %f --my-extra-arg" && + test_config filter.argc.smudge "sh ./argc.sh %f --my-extra-arg" && rm "$normal" "$special" && git checkout -- "$normal" "$special" && @@ -154,9 +216,9 @@ test_expect_success 'filter shell-escaped filenames' ' ' test_expect_success 'required filter should filter data' ' - git config filter.required.smudge ./rot13.sh && - git config filter.required.clean ./rot13.sh && - git config filter.required.required true && + test_config filter.required.smudge ./rot13.sh && + test_config filter.required.clean ./rot13.sh && + test_config filter.required.required true && echo "*.r filter=required" >.gitattributes && @@ -165,17 +227,17 @@ test_expect_success 'required filter should filter data' ' rm -f test.r && git checkout -- test.r && - cmp test.o test.r && + test_cmp test.o test.r && ./rot13.sh <test.o >expected && git cat-file blob :test.r >actual && - cmp expected actual + test_cmp expected actual ' test_expect_success 'required filter smudge failure' ' - git config filter.failsmudge.smudge false && - git config filter.failsmudge.clean cat && - git config filter.failsmudge.required true && + test_config filter.failsmudge.smudge false && + test_config filter.failsmudge.clean cat && + test_config filter.failsmudge.required true && echo "*.fs filter=failsmudge" >.gitattributes && @@ -186,9 +248,9 @@ test_expect_success 'required filter smudge failure' ' ' test_expect_success 'required filter clean failure' ' - git config filter.failclean.smudge cat && - git config filter.failclean.clean false && - git config filter.failclean.required true && + test_config filter.failclean.smudge cat && + test_config filter.failclean.clean false && + test_config filter.failclean.required true && echo "*.fc filter=failclean" >.gitattributes && @@ -197,17 +259,17 @@ test_expect_success 'required filter clean failure' ' ' test_expect_success 'filtering large input to small output should use little memory' ' - git config filter.devnull.clean "cat >/dev/null" && - git config filter.devnull.required true && + test_config filter.devnull.clean "cat >/dev/null" && + test_config filter.devnull.required true && for i in $(test_seq 1 30); do printf "%1048576d" 1; done >30MB && echo "30MB filter=devnull" >.gitattributes && GIT_MMAP_LIMIT=1m GIT_ALLOC_LIMIT=1m git add 30MB ' test_expect_success 'filter that does not read is fine' ' - test-genrandom foo $((128 * 1024 + 1)) >big && + test-tool genrandom foo $((128 * 1024 + 1)) >big && echo "big filter=epipe" >.gitattributes && - git config filter.epipe.clean "echo xyzzy" && + test_config filter.epipe.clean "echo xyzzy" && git add big && git cat-file blob :big >actual && echo xyzzy >expect && @@ -215,20 +277,20 @@ test_expect_success 'filter that does not read is fine' ' ' test_expect_success EXPENSIVE 'filter large file' ' - git config filter.largefile.smudge cat && - git config filter.largefile.clean cat && + test_config filter.largefile.smudge cat && + test_config filter.largefile.clean cat && for i in $(test_seq 1 2048); do printf "%1048576d" 1; done >2GB && echo "2GB filter=largefile" >.gitattributes && git add 2GB 2>err && - ! test -s err && + test_must_be_empty err && rm -f 2GB && git checkout -- 2GB 2>err && - ! test -s err + test_must_be_empty err ' test_expect_success "filter: clean empty file" ' - git config filter.in-repo-header.clean "echo cleaned && cat" && - git config filter.in-repo-header.smudge "sed 1d" && + test_config filter.in-repo-header.clean "echo cleaned && cat" && + test_config filter.in-repo-header.smudge "sed 1d" && echo "empty-in-worktree filter=in-repo-header" >>.gitattributes && >empty-in-worktree && @@ -240,8 +302,8 @@ test_expect_success "filter: clean empty file" ' ' test_expect_success "filter: smudge empty file" ' - git config filter.empty-in-repo.clean "cat >/dev/null" && - git config filter.empty-in-repo.smudge "echo smudged && cat" && + test_config filter.empty-in-repo.clean "cat >/dev/null" && + test_config filter.empty-in-repo.smudge "echo smudged && cat" && echo "empty-in-repo filter=empty-in-repo" >>.gitattributes && echo dead data walking >empty-in-repo && @@ -252,4 +314,507 @@ test_expect_success "filter: smudge empty file" ' test_cmp expected filtered-empty-in-repo ' +test_expect_success 'disable filter with empty override' ' + test_config_global filter.disable.smudge false && + test_config_global filter.disable.clean false && + test_config filter.disable.smudge false && + test_config filter.disable.clean false && + + echo "*.disable filter=disable" >.gitattributes && + + echo test >test.disable && + git -c filter.disable.clean= add test.disable 2>err && + test_must_be_empty err && + rm -f test.disable && + git -c filter.disable.smudge= checkout -- test.disable 2>err && + test_must_be_empty err +' + +test_expect_success 'diff does not reuse worktree files that need cleaning' ' + test_config filter.counter.clean "echo . >>count; sed s/^/clean:/" && + echo "file filter=counter" >.gitattributes && + test_commit one file && + test_commit two file && + + >count && + git diff-tree -p HEAD && + test_line_count = 0 count +' + +test_expect_success PERL 'required process filter should filter data' ' + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && + test_config_global filter.protocol.required true && + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + + echo "*.r filter=protocol" >.gitattributes && + git add . && + git commit -m "test commit 1" && + git branch empty-branch && + + cp "$TEST_ROOT/test.o" test.r && + cp "$TEST_ROOT/test2.o" test2.r && + mkdir testsubdir && + cp "$TEST_ROOT/test3 '\''sq'\'',\$x=.o" "testsubdir/test3 '\''sq'\'',\$x=.r" && + >test4-empty.r && + + S=$(file_size test.r) && + S2=$(file_size test2.r) && + S3=$(file_size "testsubdir/test3 '\''sq'\'',\$x=.r") && + + filter_git add . && + cat >expected.log <<-EOF && + START + init handshake complete + IN: clean test.r $S [OK] -- OUT: $S . [OK] + IN: clean test2.r $S2 [OK] -- OUT: $S2 . [OK] + IN: clean test4-empty.r 0 [OK] -- OUT: 0 [OK] + IN: clean testsubdir/test3 '\''sq'\'',\$x=.r $S3 [OK] -- OUT: $S3 . [OK] + STOP + EOF + test_cmp_count expected.log debug.log && + + git commit -m "test commit 2" && + rm -f test2.r "testsubdir/test3 '\''sq'\'',\$x=.r" && + + filter_git checkout --quiet --no-progress . && + cat >expected.log <<-EOF && + START + init handshake complete + IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK] + IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $S3 [OK] -- OUT: $S3 . [OK] + STOP + EOF + test_cmp_exclude_clean expected.log debug.log && + + filter_git checkout --quiet --no-progress empty-branch && + cat >expected.log <<-EOF && + START + init handshake complete + IN: clean test.r $S [OK] -- OUT: $S . [OK] + STOP + EOF + test_cmp_exclude_clean expected.log debug.log && + + filter_git checkout --quiet --no-progress master && + cat >expected.log <<-EOF && + START + init handshake complete + IN: smudge test.r $S [OK] -- OUT: $S . [OK] + IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK] + IN: smudge test4-empty.r 0 [OK] -- OUT: 0 [OK] + IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $S3 [OK] -- OUT: $S3 . [OK] + STOP + EOF + test_cmp_exclude_clean expected.log debug.log && + + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.r && + test_cmp_committed_rot13 "$TEST_ROOT/test2.o" test2.r && + test_cmp_committed_rot13 "$TEST_ROOT/test3 '\''sq'\'',\$x=.o" "testsubdir/test3 '\''sq'\'',\$x=.r" + ) +' + +test_expect_success PERL 'required process filter takes precedence' ' + test_config_global filter.protocol.clean false && + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean" && + test_config_global filter.protocol.required true && + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + + echo "*.r filter=protocol" >.gitattributes && + cp "$TEST_ROOT/test.o" test.r && + S=$(file_size test.r) && + + # Check that the process filter is invoked here + filter_git add . && + cat >expected.log <<-EOF && + START + init handshake complete + IN: clean test.r $S [OK] -- OUT: $S . [OK] + STOP + EOF + test_cmp_count expected.log debug.log + ) +' + +test_expect_success PERL 'required process filter should be used only for "clean" operation only' ' + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean" && + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + + echo "*.r filter=protocol" >.gitattributes && + cp "$TEST_ROOT/test.o" test.r && + S=$(file_size test.r) && + + filter_git add . && + cat >expected.log <<-EOF && + START + init handshake complete + IN: clean test.r $S [OK] -- OUT: $S . [OK] + STOP + EOF + test_cmp_count expected.log debug.log && + + rm test.r && + + filter_git checkout --quiet --no-progress . && + # If the filter would be used for "smudge", too, we would see + # "IN: smudge test.r 57 [OK] -- OUT: 57 . [OK]" here + cat >expected.log <<-EOF && + START + init handshake complete + STOP + EOF + test_cmp_exclude_clean expected.log debug.log + ) +' + +test_expect_success PERL 'required process filter should process multiple packets' ' + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && + test_config_global filter.protocol.required true && + + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + + # Generate data requiring 1, 2, 3 packets + S=65516 && # PKTLINE_DATA_MAXLEN -> Maximal size of a packet + generate_random_characters $(($S )) 1pkt_1__.file && + generate_random_characters $(($S +1)) 2pkt_1+1.file && + generate_random_characters $(($S*2-1)) 2pkt_2-1.file && + generate_random_characters $(($S*2 )) 2pkt_2__.file && + generate_random_characters $(($S*2+1)) 3pkt_2+1.file && + + for FILE in "$TEST_ROOT"/*.file + do + cp "$FILE" . && + rot13.sh <"$FILE" >"$FILE.rot13" + done && + + echo "*.file filter=protocol" >.gitattributes && + filter_git add *.file .gitattributes && + cat >expected.log <<-EOF && + START + init handshake complete + IN: clean 1pkt_1__.file $(($S )) [OK] -- OUT: $(($S )) . [OK] + IN: clean 2pkt_1+1.file $(($S +1)) [OK] -- OUT: $(($S +1)) .. [OK] + IN: clean 2pkt_2-1.file $(($S*2-1)) [OK] -- OUT: $(($S*2-1)) .. [OK] + IN: clean 2pkt_2__.file $(($S*2 )) [OK] -- OUT: $(($S*2 )) .. [OK] + IN: clean 3pkt_2+1.file $(($S*2+1)) [OK] -- OUT: $(($S*2+1)) ... [OK] + STOP + EOF + test_cmp_count expected.log debug.log && + + rm -f *.file && + + filter_git checkout --quiet --no-progress -- *.file && + cat >expected.log <<-EOF && + START + init handshake complete + IN: smudge 1pkt_1__.file $(($S )) [OK] -- OUT: $(($S )) . [OK] + IN: smudge 2pkt_1+1.file $(($S +1)) [OK] -- OUT: $(($S +1)) .. [OK] + IN: smudge 2pkt_2-1.file $(($S*2-1)) [OK] -- OUT: $(($S*2-1)) .. [OK] + IN: smudge 2pkt_2__.file $(($S*2 )) [OK] -- OUT: $(($S*2 )) .. [OK] + IN: smudge 3pkt_2+1.file $(($S*2+1)) [OK] -- OUT: $(($S*2+1)) ... [OK] + STOP + EOF + test_cmp_exclude_clean expected.log debug.log && + + for FILE in *.file + do + test_cmp_committed_rot13 "$TEST_ROOT/$FILE" $FILE + done + ) +' + +test_expect_success PERL 'required process filter with clean error should fail' ' + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && + test_config_global filter.protocol.required true && + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + + echo "*.r filter=protocol" >.gitattributes && + + cp "$TEST_ROOT/test.o" test.r && + echo "this is going to fail" >clean-write-fail.r && + echo "content-test3-subdir" >test3.r && + + test_must_fail git add . + ) +' + +test_expect_success PERL 'process filter should restart after unexpected write failure' ' + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + + echo "*.r filter=protocol" >.gitattributes && + + cp "$TEST_ROOT/test.o" test.r && + cp "$TEST_ROOT/test2.o" test2.r && + echo "this is going to fail" >smudge-write-fail.o && + cp smudge-write-fail.o smudge-write-fail.r && + + S=$(file_size test.r) && + S2=$(file_size test2.r) && + SF=$(file_size smudge-write-fail.r) && + + git add . && + rm -f *.r && + + rm -f debug.log && + git checkout --quiet --no-progress . 2>git-stderr.log && + + grep "smudge write error at" git-stderr.log && + grep "error: external filter" git-stderr.log && + + cat >expected.log <<-EOF && + START + init handshake complete + IN: smudge smudge-write-fail.r $SF [OK] -- [WRITE FAIL] + START + init handshake complete + IN: smudge test.r $S [OK] -- OUT: $S . [OK] + IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK] + STOP + EOF + test_cmp_exclude_clean expected.log debug.log && + + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.r && + test_cmp_committed_rot13 "$TEST_ROOT/test2.o" test2.r && + + # Smudge failed + ! test_cmp smudge-write-fail.o smudge-write-fail.r && + rot13.sh <smudge-write-fail.o >expected && + git cat-file blob :smudge-write-fail.r >actual && + test_cmp expected actual + ) +' + +test_expect_success PERL 'process filter should not be restarted if it signals an error' ' + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + + echo "*.r filter=protocol" >.gitattributes && + + cp "$TEST_ROOT/test.o" test.r && + cp "$TEST_ROOT/test2.o" test2.r && + echo "this will cause an error" >error.o && + cp error.o error.r && + + S=$(file_size test.r) && + S2=$(file_size test2.r) && + SE=$(file_size error.r) && + + git add . && + rm -f *.r && + + filter_git checkout --quiet --no-progress . && + cat >expected.log <<-EOF && + START + init handshake complete + IN: smudge error.r $SE [OK] -- [ERROR] + IN: smudge test.r $S [OK] -- OUT: $S . [OK] + IN: smudge test2.r $S2 [OK] -- OUT: $S2 . [OK] + STOP + EOF + test_cmp_exclude_clean expected.log debug.log && + + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.r && + test_cmp_committed_rot13 "$TEST_ROOT/test2.o" test2.r && + test_cmp error.o error.r + ) +' + +test_expect_success PERL 'process filter abort stops processing of all further files' ' + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + + echo "*.r filter=protocol" >.gitattributes && + + cp "$TEST_ROOT/test.o" test.r && + cp "$TEST_ROOT/test2.o" test2.r && + echo "error this blob and all future blobs" >abort.o && + cp abort.o abort.r && + + SA=$(file_size abort.r) && + + git add . && + rm -f *.r && + + # Note: This test assumes that Git filters files in alphabetical + # order ("abort.r" before "test.r"). + filter_git checkout --quiet --no-progress . && + cat >expected.log <<-EOF && + START + init handshake complete + IN: smudge abort.r $SA [OK] -- [ABORT] + STOP + EOF + test_cmp_exclude_clean expected.log debug.log && + + test_cmp "$TEST_ROOT/test.o" test.r && + test_cmp "$TEST_ROOT/test2.o" test2.r && + test_cmp abort.o abort.r + ) +' + +test_expect_success PERL 'invalid process filter must fail (and not hang!)' ' + test_config_global filter.protocol.process cat && + test_config_global filter.protocol.required true && + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + + echo "*.r filter=protocol" >.gitattributes && + + cp "$TEST_ROOT/test.o" test.r && + test_must_fail git add . 2>git-stderr.log && + grep "expected git-filter-server" git-stderr.log + ) +' + +test_expect_success PERL 'delayed checkout in process filter' ' + test_config_global filter.a.process "rot13-filter.pl a.log clean smudge delay" && + test_config_global filter.a.required true && + test_config_global filter.b.process "rot13-filter.pl b.log clean smudge delay" && + test_config_global filter.b.required true && + + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + echo "*.a filter=a" >.gitattributes && + echo "*.b filter=b" >>.gitattributes && + cp "$TEST_ROOT/test.o" test.a && + cp "$TEST_ROOT/test.o" test-delay10.a && + cp "$TEST_ROOT/test.o" test-delay11.a && + cp "$TEST_ROOT/test.o" test-delay20.a && + cp "$TEST_ROOT/test.o" test-delay10.b && + git add . && + git commit -m "test commit" + ) && + + S=$(file_size "$TEST_ROOT/test.o") && + cat >a.exp <<-EOF && + START + init handshake complete + IN: smudge test.a $S [OK] -- OUT: $S . [OK] + IN: smudge test-delay10.a $S [OK] -- [DELAYED] + IN: smudge test-delay11.a $S [OK] -- [DELAYED] + IN: smudge test-delay20.a $S [OK] -- [DELAYED] + IN: list_available_blobs test-delay10.a test-delay11.a [OK] + IN: smudge test-delay10.a 0 [OK] -- OUT: $S . [OK] + IN: smudge test-delay11.a 0 [OK] -- OUT: $S . [OK] + IN: list_available_blobs test-delay20.a [OK] + IN: smudge test-delay20.a 0 [OK] -- OUT: $S . [OK] + IN: list_available_blobs [OK] + STOP + EOF + cat >b.exp <<-EOF && + START + init handshake complete + IN: smudge test-delay10.b $S [OK] -- [DELAYED] + IN: list_available_blobs test-delay10.b [OK] + IN: smudge test-delay10.b 0 [OK] -- OUT: $S . [OK] + IN: list_available_blobs [OK] + STOP + EOF + + rm -rf repo-cloned && + filter_git clone repo repo-cloned && + test_cmp_count a.exp repo-cloned/a.log && + test_cmp_count b.exp repo-cloned/b.log && + + ( + cd repo-cloned && + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.a && + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay10.a && + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay11.a && + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay20.a && + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay10.b && + + rm *.a *.b && + filter_git checkout . && + test_cmp_count ../a.exp a.log && + test_cmp_count ../b.exp b.log && + + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test.a && + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay10.a && + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay11.a && + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay20.a && + test_cmp_committed_rot13 "$TEST_ROOT/test.o" test-delay10.b + ) +' + +test_expect_success PERL 'missing file in delayed checkout' ' + test_config_global filter.bug.process "rot13-filter.pl bug.log clean smudge delay" && + test_config_global filter.bug.required true && + + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + echo "*.a filter=bug" >.gitattributes && + cp "$TEST_ROOT/test.o" missing-delay.a + git add . && + git commit -m "test commit" + ) && + + rm -rf repo-cloned && + test_must_fail git clone repo repo-cloned 2>git-stderr.log && + cat git-stderr.log && + grep "error: .missing-delay\.a. was not filtered properly" git-stderr.log +' + +test_expect_success PERL 'invalid file in delayed checkout' ' + test_config_global filter.bug.process "rot13-filter.pl bug.log clean smudge delay" && + test_config_global filter.bug.required true && + + rm -rf repo && + mkdir repo && + ( + cd repo && + git init && + echo "*.a filter=bug" >.gitattributes && + cp "$TEST_ROOT/test.o" invalid-delay.a && + cp "$TEST_ROOT/test.o" unfiltered + git add . && + git commit -m "test commit" + ) && + + rm -rf repo-cloned && + test_must_fail git clone repo repo-cloned 2>git-stderr.log && + grep "error: external filter .* signaled that .unfiltered. is now available although it has not been delayed earlier" git-stderr.log +' + test_done diff --git a/t/t0021/rot13-filter.pl b/t/t0021/rot13-filter.pl new file mode 100644 index 0000000000..470107248e --- /dev/null +++ b/t/t0021/rot13-filter.pl @@ -0,0 +1,226 @@ +# +# Example implementation for the Git filter protocol version 2 +# See Documentation/gitattributes.txt, section "Filter Protocol" +# +# The first argument defines a debug log file that the script write to. +# All remaining arguments define a list of supported protocol +# capabilities ("clean", "smudge", etc). +# +# This implementation supports special test cases: +# (1) If data with the pathname "clean-write-fail.r" is processed with +# a "clean" operation then the write operation will die. +# (2) If data with the pathname "smudge-write-fail.r" is processed with +# a "smudge" operation then the write operation will die. +# (3) If data with the pathname "error.r" is processed with any +# operation then the filter signals that it cannot or does not want +# to process the file. +# (4) If data with the pathname "abort.r" is processed with any +# operation then the filter signals that it cannot or does not want +# to process the file and any file after that is processed with the +# same command. +# (5) If data with a pathname that is a key in the DELAY hash is +# requested (e.g. "test-delay10.a") then the filter responds with +# a "delay" status and sets the "requested" field in the DELAY hash. +# The filter will signal the availability of this object after +# "count" (field in DELAY hash) "list_available_blobs" commands. +# (6) If data with the pathname "missing-delay.a" is processed that the +# filter will drop the path from the "list_available_blobs" response. +# (7) If data with the pathname "invalid-delay.a" is processed that the +# filter will add the path "unfiltered" which was not delayed before +# to the "list_available_blobs" response. +# + +use 5.008; +sub gitperllib { + # Git assumes that all path lists are Unix-y colon-separated ones. But + # when the Git for Windows executes the test suite, its MSYS2 Bash + # calls git.exe, and colon-separated path lists are converted into + # Windows-y semicolon-separated lists of *Windows* paths (which + # naturally contain a colon after the drive letter, so splitting by + # colons simply does not cut it). + # + # Detect semicolon-separated path list and handle them appropriately. + + if ($ENV{GITPERLLIB} =~ /;/) { + return split(/;/, $ENV{GITPERLLIB}); + } + return split(/:/, $ENV{GITPERLLIB}); +} +use lib (gitperllib()); +use strict; +use warnings; +use IO::File; +use Git::Packet; + +my $MAX_PACKET_CONTENT_SIZE = 65516; +my $log_file = shift @ARGV; +my @capabilities = @ARGV; + +open my $debug, ">>", $log_file or die "cannot open log file: $!"; + +my %DELAY = ( + 'test-delay10.a' => { "requested" => 0, "count" => 1 }, + 'test-delay11.a' => { "requested" => 0, "count" => 1 }, + 'test-delay20.a' => { "requested" => 0, "count" => 2 }, + 'test-delay10.b' => { "requested" => 0, "count" => 1 }, + 'missing-delay.a' => { "requested" => 0, "count" => 1 }, + 'invalid-delay.a' => { "requested" => 0, "count" => 1 }, +); + +sub rot13 { + my $str = shift; + $str =~ y/A-Za-z/N-ZA-Mn-za-m/; + return $str; +} + +print $debug "START\n"; +$debug->flush(); + +packet_initialize("git-filter", 2); + +my %remote_caps = packet_read_and_check_capabilities("clean", "smudge", "delay"); +packet_check_and_write_capabilities(\%remote_caps, @capabilities); + +print $debug "init handshake complete\n"; +$debug->flush(); + +while (1) { + my ( $res, $command ) = packet_key_val_read("command"); + if ( $res == -1 ) { + print $debug "STOP\n"; + exit(); + } + print $debug "IN: $command"; + $debug->flush(); + + if ( $command eq "list_available_blobs" ) { + # Flush + packet_compare_lists([1, ""], packet_bin_read()) || + die "bad list_available_blobs end"; + + foreach my $pathname ( sort keys %DELAY ) { + if ( $DELAY{$pathname}{"requested"} >= 1 ) { + $DELAY{$pathname}{"count"} = $DELAY{$pathname}{"count"} - 1; + if ( $pathname eq "invalid-delay.a" ) { + # Send Git a pathname that was not delayed earlier + packet_txt_write("pathname=unfiltered"); + } + if ( $pathname eq "missing-delay.a" ) { + # Do not signal Git that this file is available + } elsif ( $DELAY{$pathname}{"count"} == 0 ) { + print $debug " $pathname"; + packet_txt_write("pathname=$pathname"); + } + } + } + + packet_flush(); + + print $debug " [OK]\n"; + $debug->flush(); + packet_txt_write("status=success"); + packet_flush(); + } else { + my ( $res, $pathname ) = packet_key_val_read("pathname"); + if ( $res == -1 ) { + die "unexpected EOF while expecting pathname"; + } + print $debug " $pathname"; + $debug->flush(); + + # Read until flush + my ( $done, $buffer ) = packet_txt_read(); + while ( $buffer ne '' ) { + if ( $buffer eq "can-delay=1" ) { + if ( exists $DELAY{$pathname} and $DELAY{$pathname}{"requested"} == 0 ) { + $DELAY{$pathname}{"requested"} = 1; + } + } else { + die "Unknown message '$buffer'"; + } + + ( $done, $buffer ) = packet_txt_read(); + } + if ( $done == -1 ) { + die "unexpected EOF after pathname '$pathname'"; + } + + my $input = ""; + { + binmode(STDIN); + my $buffer; + my $done = 0; + while ( !$done ) { + ( $done, $buffer ) = packet_bin_read(); + $input .= $buffer; + } + if ( $done == -1 ) { + die "unexpected EOF while reading input for '$pathname'"; + } + print $debug " " . length($input) . " [OK] -- "; + $debug->flush(); + } + + my $output; + if ( exists $DELAY{$pathname} and exists $DELAY{$pathname}{"output"} ) { + $output = $DELAY{$pathname}{"output"} + } elsif ( $pathname eq "error.r" or $pathname eq "abort.r" ) { + $output = ""; + } elsif ( $command eq "clean" and grep( /^clean$/, @capabilities ) ) { + $output = rot13($input); + } elsif ( $command eq "smudge" and grep( /^smudge$/, @capabilities ) ) { + $output = rot13($input); + } else { + die "bad command '$command'"; + } + + if ( $pathname eq "error.r" ) { + print $debug "[ERROR]\n"; + $debug->flush(); + packet_txt_write("status=error"); + packet_flush(); + } elsif ( $pathname eq "abort.r" ) { + print $debug "[ABORT]\n"; + $debug->flush(); + packet_txt_write("status=abort"); + packet_flush(); + } elsif ( $command eq "smudge" and + exists $DELAY{$pathname} and + $DELAY{$pathname}{"requested"} == 1 ) { + print $debug "[DELAYED]\n"; + $debug->flush(); + packet_txt_write("status=delayed"); + packet_flush(); + $DELAY{$pathname}{"requested"} = 2; + $DELAY{$pathname}{"output"} = $output; + } else { + packet_txt_write("status=success"); + packet_flush(); + + if ( $pathname eq "${command}-write-fail.r" ) { + print $debug "[WRITE FAIL]\n"; + $debug->flush(); + die "${command} write error"; + } + + print $debug "OUT: " . length($output) . " "; + $debug->flush(); + + while ( length($output) > 0 ) { + my $packet = substr( $output, 0, $MAX_PACKET_CONTENT_SIZE ); + packet_bin_write($packet); + # dots represent the number of packets + print $debug "."; + if ( length($output) > $MAX_PACKET_CONTENT_SIZE ) { + $output = substr( $output, $MAX_PACKET_CONTENT_SIZE ); + } else { + $output = ""; + } + } + packet_flush(); + print $debug " [OK]\n"; + $debug->flush(); + packet_flush(); + } + } +} diff --git a/t/t0025-crlf-auto.sh b/t/t0025-crlf-auto.sh deleted file mode 100755 index c164b4662a..0000000000 --- a/t/t0025-crlf-auto.sh +++ /dev/null @@ -1,155 +0,0 @@ -#!/bin/sh - -test_description='CRLF conversion' - -. ./test-lib.sh - -has_cr() { - tr '\015' Q <"$1" | grep Q >/dev/null -} - -test_expect_success setup ' - - git config core.autocrlf false && - - for w in Hello world how are you; do echo $w; done >LFonly && - for w in I am very very fine thank you; do echo ${w}Q; done | q_to_cr >CRLFonly && - for w in Oh here is a QNUL byte how alarming; do echo ${w}; done | q_to_nul >LFwithNUL && - git add . && - - git commit -m initial && - - LFonly=$(git rev-parse HEAD:LFonly) && - CRLFonly=$(git rev-parse HEAD:CRLFonly) && - LFwithNUL=$(git rev-parse HEAD:LFwithNUL) && - - echo happy. -' - -test_expect_success 'default settings cause no changes' ' - - rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL && - git read-tree --reset -u HEAD && - - ! has_cr LFonly && - has_cr CRLFonly && - LFonlydiff=$(git diff LFonly) && - CRLFonlydiff=$(git diff CRLFonly) && - LFwithNULdiff=$(git diff LFwithNUL) && - test -z "$LFonlydiff" -a -z "$CRLFonlydiff" -a -z "$LFwithNULdiff" -' - -test_expect_success 'crlf=true causes a CRLF file to be normalized' ' - - # Backwards compatibility check - rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL && - echo "CRLFonly crlf" > .gitattributes && - git read-tree --reset -u HEAD && - - # Note, "normalized" means that git will normalize it if added - has_cr CRLFonly && - CRLFonlydiff=$(git diff CRLFonly) && - test -n "$CRLFonlydiff" -' - -test_expect_success 'text=true causes a CRLF file to be normalized' ' - - rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL && - echo "CRLFonly text" > .gitattributes && - git read-tree --reset -u HEAD && - - # Note, "normalized" means that git will normalize it if added - has_cr CRLFonly && - CRLFonlydiff=$(git diff CRLFonly) && - test -n "$CRLFonlydiff" -' - -test_expect_success 'eol=crlf gives a normalized file CRLFs with autocrlf=false' ' - - rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL && - git config core.autocrlf false && - echo "LFonly eol=crlf" > .gitattributes && - git read-tree --reset -u HEAD && - - has_cr LFonly && - LFonlydiff=$(git diff LFonly) && - test -z "$LFonlydiff" -' - -test_expect_success 'eol=crlf gives a normalized file CRLFs with autocrlf=input' ' - - rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL && - git config core.autocrlf input && - echo "LFonly eol=crlf" > .gitattributes && - git read-tree --reset -u HEAD && - - has_cr LFonly && - LFonlydiff=$(git diff LFonly) && - test -z "$LFonlydiff" -' - -test_expect_success 'eol=lf gives a normalized file LFs with autocrlf=true' ' - - rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL && - git config core.autocrlf true && - echo "LFonly eol=lf" > .gitattributes && - git read-tree --reset -u HEAD && - - ! has_cr LFonly && - LFonlydiff=$(git diff LFonly) && - test -z "$LFonlydiff" -' - -test_expect_success 'autocrlf=true does not normalize CRLF files' ' - - rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL && - git config core.autocrlf true && - git read-tree --reset -u HEAD && - - has_cr LFonly && - has_cr CRLFonly && - LFonlydiff=$(git diff LFonly) && - CRLFonlydiff=$(git diff CRLFonly) && - LFwithNULdiff=$(git diff LFwithNUL) && - test -z "$LFonlydiff" -a -z "$CRLFonlydiff" -a -z "$LFwithNULdiff" -' - -test_expect_success 'text=auto, autocrlf=true _does_ normalize CRLF files' ' - - rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL && - git config core.autocrlf true && - echo "* text=auto" > .gitattributes && - git read-tree --reset -u HEAD && - - has_cr LFonly && - has_cr CRLFonly && - LFonlydiff=$(git diff LFonly) && - CRLFonlydiff=$(git diff CRLFonly) && - LFwithNULdiff=$(git diff LFwithNUL) && - test -z "$LFonlydiff" -a -n "$CRLFonlydiff" -a -z "$LFwithNULdiff" -' - -test_expect_success 'text=auto, autocrlf=true does not normalize binary files' ' - - rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL && - git config core.autocrlf true && - echo "* text=auto" > .gitattributes && - git read-tree --reset -u HEAD && - - ! has_cr LFwithNUL && - LFwithNULdiff=$(git diff LFwithNUL) && - test -z "$LFwithNULdiff" -' - -test_expect_success 'eol=crlf _does_ normalize binary files' ' - - rm -f .gitattributes tmp LFonly CRLFonly LFwithNUL && - echo "LFwithNUL eol=crlf" > .gitattributes && - git read-tree --reset -u HEAD && - - has_cr LFwithNUL && - LFwithNULdiff=$(git diff LFwithNUL) && - test -z "$LFwithNULdiff" -' - -test_done diff --git a/t/t0025-crlf-renormalize.sh b/t/t0025-crlf-renormalize.sh new file mode 100755 index 0000000000..9d9e02a211 --- /dev/null +++ b/t/t0025-crlf-renormalize.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +test_description='CRLF renormalization' + +. ./test-lib.sh + +test_expect_success setup ' + git config core.autocrlf false && + printf "LINEONE\nLINETWO\nLINETHREE\n" >LF.txt && + printf "LINEONE\r\nLINETWO\r\nLINETHREE\r\n" >CRLF.txt && + printf "LINEONE\r\nLINETWO\nLINETHREE\n" >CRLF_mix_LF.txt && + git add . && + git commit -m initial +' + +test_expect_success 'renormalize CRLF in repo' ' + echo "*.txt text=auto" >.gitattributes && + git add --renormalize "*.txt" && + cat >expect <<-\EOF && + i/lf w/crlf attr/text=auto CRLF.txt + i/lf w/lf attr/text=auto LF.txt + i/lf w/mixed attr/text=auto CRLF_mix_LF.txt + EOF + git ls-files --eol | + sed -e "s/ / /g" -e "s/ */ /g" | + sort >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t0027-auto-crlf.sh b/t/t0027-auto-crlf.sh index 1a56e5e82e..beb5927f77 100755 --- a/t/t0027-auto-crlf.sh +++ b/t/t0027-auto-crlf.sh @@ -4,15 +4,9 @@ test_description='CRLF conversion all combinations' . ./test-lib.sh -if ! test_have_prereq EXPENSIVE -then - skip_all="EXPENSIVE not set" - test_done -fi - compare_files () { tr '\015\000' QN <"$1" >"$1".expect && - tr '\015\000' QN <"$2" >"$2".actual && + tr '\015\000' QN <"$2" | tr -d 'Z' >"$2".actual && test_cmp "$1".expect "$2".actual && rm "$1".expect "$2".actual } @@ -21,38 +15,62 @@ compare_ws_file () { pfx=$1 exp=$2.expect act=$pfx.actual.$3 - tr '\015\000' QN <"$2" >"$exp" && - tr '\015\000' QN <"$3" >"$act" && - test_cmp $exp $act && - rm $exp $act + tr '\015\000abcdef0123456789' QN00000000000000000 <"$2" >"$exp" && + tr '\015\000abcdef0123456789' QN00000000000000000 <"$3" >"$act" && + test_cmp "$exp" "$act" && + rm "$exp" "$act" } create_gitattributes () { - attr=$1 - case "$attr" in - auto) - echo "*.txt text=auto" >.gitattributes - ;; - text) - echo "*.txt text" >.gitattributes - ;; - -text) - echo "*.txt -text" >.gitattributes - ;; - crlf) - echo "*.txt eol=crlf" >.gitattributes - ;; - lf) - echo "*.txt eol=lf" >.gitattributes - ;; - "") - echo >.gitattributes - ;; - *) - echo >&2 invalid attribute: $attr - exit 1 - ;; - esac + { + while test "$#" != 0 + do + case "$1" in + auto) echo '*.txt text=auto' ;; + ident) echo '*.txt ident' ;; + text) echo '*.txt text' ;; + -text) echo '*.txt -text' ;; + crlf) echo '*.txt eol=crlf' ;; + lf) echo '*.txt eol=lf' ;; + "") ;; + *) + echo >&2 invalid attribute: "$1" + exit 1 + ;; + esac && + shift + done + } >.gitattributes +} + +# Create 2 sets of files: +# The NNO files are "Not NOrmalized in the repo. We use CRLF_mix_LF and store +# it under different names for the different test cases, see ${pfx} +# Depending on .gitattributes they are normalized at the next commit (or not) +# The MIX files have different contents in the repo. +# Depending on its contents, the "new safer autocrlf" may kick in. +create_NNO_MIX_files () { + for crlf in false true input + do + for attr in "" auto text -text + do + for aeol in "" lf crlf + do + pfx=NNO_attr_${attr}_aeol_${aeol}_${crlf} && + cp CRLF_mix_LF ${pfx}_LF.txt && + cp CRLF_mix_LF ${pfx}_CRLF.txt && + cp CRLF_mix_LF ${pfx}_CRLF_mix_LF.txt && + cp CRLF_mix_LF ${pfx}_LF_mix_CR.txt && + cp CRLF_mix_LF ${pfx}_CRLF_nul.txt && + pfx=MIX_attr_${attr}_aeol_${aeol}_${crlf} && + cp LF ${pfx}_LF.txt && + cp CRLF ${pfx}_CRLF.txt && + cp CRLF_mix_LF ${pfx}_CRLF_mix_LF.txt && + cp LF_mix_CR ${pfx}_LF_mix_CR.txt && + cp CRLF_nul ${pfx}_CRLF_nul.txt + done + done + done } check_warning () { @@ -62,8 +80,8 @@ check_warning () { '') >"$2".expect ;; *) echo >&2 "Illegal 1": "$1" ; return false ;; esac - grep "will be replaced by" "$2" | sed -e "s/\(.*\) in [^ ]*$/\1/" >"$2".actual - test_cmp "$2".expect "$2".actual + grep "will be replaced by" "$2" | sed -e "s/\(.*\) in [^ ]*$/\1/" | uniq >"$2".actual + test_i18ncmp "$2".expect "$2".actual } commit_check_warn () { @@ -71,21 +89,12 @@ commit_check_warn () { attr=$2 lfname=$3 crlfname=$4 - repoMIX=$5 - lfmixcrlf=$6 - lfmixcr=$7 - crlfnul=$8 + lfmixcrlf=$5 + lfmixcr=$6 + crlfnul=$7 pfx=crlf_${crlf}_attr_${attr} - # Special handling for repoMIX: It should already be in the repo - # with CRLF - f=repoMIX - fname=${pfx}_$f.txt - echo >.gitattributes && - cp $f $fname && - git -c core.autocrlf=false add $fname 2>"${pfx}_$f.err" && - git commit -m "repoMIX" && create_gitattributes "$attr" && - for f in LF CRLF repoMIX LF_mix_CR CRLF_mix_LF LF_nul CRLF_nul + for f in LF CRLF LF_mix_CR CRLF_mix_LF LF_nul CRLF_nul do fname=${pfx}_$f.txt && cp $f $fname && @@ -99,6 +108,130 @@ commit_check_warn () { check_warning "$crlfnul" ${pfx}_CRLF_nul.err } +commit_chk_wrnNNO () { + attr=$1 ; shift + aeol=$1 ; shift + crlf=$1 ; shift + lfwarn=$1 ; shift + crlfwarn=$1 ; shift + lfmixcrlf=$1 ; shift + lfmixcr=$1 ; shift + crlfnul=$1 ; shift + pfx=NNO_attr_${attr}_aeol_${aeol}_${crlf} + #Commit files on top of existing file + create_gitattributes "$attr" $aeol && + for f in LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + do + fname=${pfx}_$f.txt && + cp $f $fname && + printf Z >>"$fname" && + git -c core.autocrlf=$crlf add $fname 2>"${pfx}_$f.err" + done + + test_expect_success "commit NNO files crlf=$crlf attr=$attr LF" ' + check_warning "$lfwarn" ${pfx}_LF.err + ' + test_expect_success "commit NNO files attr=$attr aeol=$aeol crlf=$crlf CRLF" ' + check_warning "$crlfwarn" ${pfx}_CRLF.err + ' + + test_expect_success "commit NNO files attr=$attr aeol=$aeol crlf=$crlf CRLF_mix_LF" ' + check_warning "$lfmixcrlf" ${pfx}_CRLF_mix_LF.err + ' + + test_expect_success "commit NNO files attr=$attr aeol=$aeol crlf=$crlf LF_mix_cr" ' + check_warning "$lfmixcr" ${pfx}_LF_mix_CR.err + ' + + test_expect_success "commit NNO files attr=$attr aeol=$aeol crlf=$crlf CRLF_nul" ' + check_warning "$crlfnul" ${pfx}_CRLF_nul.err + ' +} + +# Commit a file with mixed line endings on top of different files +# in the index. Check for warnings +commit_MIX_chkwrn () { + attr=$1 ; shift + aeol=$1 ; shift + crlf=$1 ; shift + lfwarn=$1 ; shift + crlfwarn=$1 ; shift + lfmixcrlf=$1 ; shift + lfmixcr=$1 ; shift + crlfnul=$1 ; shift + pfx=MIX_attr_${attr}_aeol_${aeol}_${crlf} + #Commit file with CLRF_mix_LF on top of existing file + create_gitattributes "$attr" $aeol && + for f in LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + do + fname=${pfx}_$f.txt && + cp CRLF_mix_LF $fname && + printf Z >>"$fname" && + git -c core.autocrlf=$crlf add $fname 2>"${pfx}_$f.err" + done + + test_expect_success "commit file with mixed EOL onto LF crlf=$crlf attr=$attr" ' + check_warning "$lfwarn" ${pfx}_LF.err + ' + test_expect_success "commit file with mixed EOL onto CLRF attr=$attr aeol=$aeol crlf=$crlf" ' + check_warning "$crlfwarn" ${pfx}_CRLF.err + ' + + test_expect_success "commit file with mixed EOL onto CRLF_mix_LF attr=$attr aeol=$aeol crlf=$crlf" ' + check_warning "$lfmixcrlf" ${pfx}_CRLF_mix_LF.err + ' + + test_expect_success "commit file with mixed EOL onto LF_mix_cr attr=$attr aeol=$aeol crlf=$crlf " ' + check_warning "$lfmixcr" ${pfx}_LF_mix_CR.err + ' + + test_expect_success "commit file with mixed EOL onto CRLF_nul attr=$attr aeol=$aeol crlf=$crlf" ' + check_warning "$crlfnul" ${pfx}_CRLF_nul.err + ' +} + + +stats_ascii () { + case "$1" in + LF) + echo lf + ;; + CRLF) + echo crlf + ;; + CRLF_mix_LF) + echo mixed + ;; + LF_mix_CR|CRLF_nul|LF_nul|CRLF_mix_CR) + echo "-text" + ;; + *) + echo error_invalid $1 + ;; + esac + +} + + +# contruct the attr/ returned by git ls-files --eol +# Take none (=empty), one or two args +# convert.c: eol=XX overrides text=auto +attr_ascii () { + case $1,$2 in + -text,*) echo "-text" ;; + text,) echo "text" ;; + text,lf) echo "text eol=lf" ;; + text,crlf) echo "text eol=crlf" ;; + auto,) echo "text=auto" ;; + auto,lf) echo "text=auto eol=lf" ;; + auto,crlf) echo "text=auto eol=crlf" ;; + lf,) echo "text eol=lf" ;; + crlf,) echo "text eol=crlf" ;; + ,) echo "" ;; + *) echo invalid_attr "$1,$2" ;; + esac +} + check_files_in_repo () { crlf=$1 attr=$2 @@ -115,61 +248,140 @@ check_files_in_repo () { compare_files $crlfnul ${pfx}CRLF_nul.txt } +check_in_repo_NNO () { + attr=$1 ; shift + aeol=$1 ; shift + crlf=$1 ; shift + lfname=$1 ; shift + crlfname=$1 ; shift + lfmixcrlf=$1 ; shift + lfmixcr=$1 ; shift + crlfnul=$1 ; shift + pfx=NNO_attr_${attr}_aeol_${aeol}_${crlf} + test_expect_success "compare_files $lfname ${pfx}_LF.txt" ' + compare_files $lfname ${pfx}_LF.txt + ' + test_expect_success "compare_files $crlfname ${pfx}_CRLF.txt" ' + compare_files $crlfname ${pfx}_CRLF.txt + ' + test_expect_success "compare_files $lfmixcrlf ${pfx}_CRLF_mix_LF.txt" ' + compare_files $lfmixcrlf ${pfx}_CRLF_mix_LF.txt + ' + test_expect_success "compare_files $lfmixcr ${pfx}_LF_mix_CR.txt" ' + compare_files $lfmixcr ${pfx}_LF_mix_CR.txt + ' + test_expect_success "compare_files $crlfnul ${pfx}_CRLF_nul.txt" ' + compare_files $crlfnul ${pfx}_CRLF_nul.txt + ' +} checkout_files () { - eol=$1 - crlf=$2 - attr=$3 - lfname=$4 - crlfname=$5 - lfmixcrlf=$6 - lfmixcr=$7 - crlfnul=$8 - create_gitattributes $attr && + attr=$1 ; shift + ident=$1; shift + aeol=$1 ; shift + crlf=$1 ; shift + ceol=$1 ; shift + lfname=$1 ; shift + crlfname=$1 ; shift + lfmixcrlf=$1 ; shift + lfmixcr=$1 ; shift + crlfnul=$1 ; shift + create_gitattributes "$attr" $ident $aeol && git config core.autocrlf $crlf && - pfx=eol_${eol}_crlf_${crlf}_attr_${attr}_ && - src=crlf_false_attr__ && + pfx=eol_${ceol}_crlf_${crlf}_attr_${attr}_ && for f in LF CRLF LF_mix_CR CRLF_mix_LF LF_nul do - rm $src$f.txt && - if test -z "$eol"; then - git checkout $src$f.txt + rm crlf_false_attr__$f.txt && + if test -z "$ceol"; then + git checkout crlf_false_attr__$f.txt else - git -c core.eol=$eol checkout $src$f.txt + git -c core.eol=$ceol checkout crlf_false_attr__$f.txt fi done - test_expect_success "checkout core.eol=$eol core.autocrlf=$crlf gitattributes=$attr file=LF" " - compare_ws_file $pfx $lfname ${src}LF.txt + test_expect_success "ls-files --eol attr=$attr $ident aeol=$aeol core.autocrlf=$crlf core.eol=$ceol" ' + test_when_finished "rm expect actual" && + sort <<-EOF >expect && + i/crlf w/$(stats_ascii $crlfname) attr/$(attr_ascii $attr $aeol) crlf_false_attr__CRLF.txt + i/mixed w/$(stats_ascii $lfmixcrlf) attr/$(attr_ascii $attr $aeol) crlf_false_attr__CRLF_mix_LF.txt + i/lf w/$(stats_ascii $lfname) attr/$(attr_ascii $attr $aeol) crlf_false_attr__LF.txt + i/-text w/$(stats_ascii $lfmixcr) attr/$(attr_ascii $attr $aeol) crlf_false_attr__LF_mix_CR.txt + i/-text w/$(stats_ascii $crlfnul) attr/$(attr_ascii $attr $aeol) crlf_false_attr__CRLF_nul.txt + i/-text w/$(stats_ascii $crlfnul) attr/$(attr_ascii $attr $aeol) crlf_false_attr__LF_nul.txt + EOF + git ls-files --eol crlf_false_attr__* | + sed -e "s/ / /g" -e "s/ */ /g" | + sort >actual && + test_cmp expect actual + ' + test_expect_success "checkout attr=$attr $ident aeol=$aeol core.autocrlf=$crlf core.eol=$ceol file=LF" " + compare_ws_file $pfx $lfname crlf_false_attr__LF.txt " - test_expect_success "checkout core.eol=$eol core.autocrlf=$crlf gitattributes=$attr file=CRLF" " - compare_ws_file $pfx $crlfname ${src}CRLF.txt + test_expect_success "checkout attr=$attr $ident aeol=$aeol core.autocrlf=$crlf core.eol=$ceol file=CRLF" " + compare_ws_file $pfx $crlfname crlf_false_attr__CRLF.txt " - test_expect_success "checkout core.eol=$eol core.autocrlf=$crlf gitattributes=$attr file=CRLF_mix_LF" " - compare_ws_file $pfx $lfmixcrlf ${src}CRLF_mix_LF.txt + test_expect_success "checkout attr=$attr $ident aeol=$aeol core.autocrlf=$crlf core.eol=$ceol file=CRLF_mix_LF" " + compare_ws_file $pfx $lfmixcrlf crlf_false_attr__CRLF_mix_LF.txt " - test_expect_success "checkout core.eol=$eol core.autocrlf=$crlf gitattributes=$attr file=LF_mix_CR" " - compare_ws_file $pfx $lfmixcr ${src}LF_mix_CR.txt + test_expect_success "checkout attr=$attr $ident aeol=$aeol core.autocrlf=$crlf core.eol=$ceol file=LF_mix_CR" " + compare_ws_file $pfx $lfmixcr crlf_false_attr__LF_mix_CR.txt " - test_expect_success "checkout core.eol=$eol core.autocrlf=$crlf gitattributes=$attr file=LF_nul" " - compare_ws_file $pfx $crlfnul ${src}LF_nul.txt + test_expect_success "checkout attr=$attr $ident aeol=$aeol core.autocrlf=$crlf core.eol=$ceol file=LF_nul" " + compare_ws_file $pfx $crlfnul crlf_false_attr__LF_nul.txt " } -####### +# Test control characters +# NUL SOH CR EOF==^Z +test_expect_success 'ls-files --eol -o Text/Binary' ' + test_when_finished "rm expect actual TeBi_*" && + STRT=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA && + STR=$STRT$STRT$STRT$STRT && + printf "${STR}BBB\001" >TeBi_127_S && + printf "${STR}BBBB\001">TeBi_128_S && + printf "${STR}BBB\032" >TeBi_127_E && + printf "\032${STR}BBB" >TeBi_E_127 && + printf "${STR}BBBB\000">TeBi_128_N && + printf "${STR}BBB\012">TeBi_128_L && + printf "${STR}BBB\015">TeBi_127_C && + printf "${STR}BB\015\012" >TeBi_126_CL && + printf "${STR}BB\015\012\015" >TeBi_126_CLC && + sort <<-\EOF >expect && + i/ w/-text TeBi_127_S + i/ w/none TeBi_128_S + i/ w/none TeBi_127_E + i/ w/-text TeBi_E_127 + i/ w/-text TeBi_128_N + i/ w/lf TeBi_128_L + i/ w/-text TeBi_127_C + i/ w/crlf TeBi_126_CL + i/ w/-text TeBi_126_CLC + EOF + git ls-files --eol -o | + sed -n -e "/TeBi_/{s!attr/[ ]*!!g + s! ! !g + s! *! !g + p + }" | sort >actual && + test_cmp expect actual +' + test_expect_success 'setup master' ' echo >.gitattributes && git checkout -b master && git add .gitattributes && - git commit -m "add .gitattributes" "" && - printf "line1\nline2\nline3" >LF && - printf "line1\r\nline2\r\nline3" >CRLF && - printf "line1\r\nline2\nline3" >repoMIX && - printf "line1\r\nline2\nline3" >CRLF_mix_LF && - printf "line1\nline2\rline3" >LF_mix_CR && - printf "line1\r\nline2\rline3" >CRLF_mix_CR && - printf "line1Q\r\nline2\r\nline3" | q_to_nul >CRLF_nul && - printf "line1Q\nline2\nline3" | q_to_nul >LF_nul + git commit -m "add .gitattributes" . && + printf "\$Id: 0000000000000000000000000000000000000000 \$\nLINEONE\nLINETWO\nLINETHREE" >LF && + printf "\$Id: 0000000000000000000000000000000000000000 \$\r\nLINEONE\r\nLINETWO\r\nLINETHREE" >CRLF && + printf "\$Id: 0000000000000000000000000000000000000000 \$\nLINEONE\r\nLINETWO\nLINETHREE" >CRLF_mix_LF && + printf "\$Id: 0000000000000000000000000000000000000000 \$\nLINEONE\nLINETWO\rLINETHREE" >LF_mix_CR && + printf "\$Id: 0000000000000000000000000000000000000000 \$\r\nLINEONE\r\nLINETWO\rLINETHREE" >CRLF_mix_CR && + printf "\$Id: 0000000000000000000000000000000000000000 \$\r\nLINEONEQ\r\nLINETWO\r\nLINETHREE" | q_to_nul >CRLF_nul && + printf "\$Id: 0000000000000000000000000000000000000000 \$\nLINEONEQ\nLINETWO\nLINETHREE" | q_to_nul >LF_nul && + create_NNO_MIX_files && + git -c core.autocrlf=false add NNO_*.txt MIX_*.txt && + git commit -m "mixed line endings" && + test_tick ' @@ -191,46 +403,84 @@ else WAMIX=CRLF_LF fi -# attr LF CRLF repoMIX CRLFmixLF LFmixCR CRLFNUL +# attr LF CRLF CRLFmixLF LFmixCR CRLFNUL test_expect_success 'commit files empty attr' ' - commit_check_warn false "" "" "" "" "" "" "" && - commit_check_warn true "" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" "" && - commit_check_warn input "" "" "CRLF_LF" "CRLF_LF" "CRLF_LF" "" "" + commit_check_warn false "" "" "" "" "" "" && + commit_check_warn true "" "LF_CRLF" "" "LF_CRLF" "" "" && + commit_check_warn input "" "" "CRLF_LF" "CRLF_LF" "" "" ' test_expect_success 'commit files attr=auto' ' - commit_check_warn false "auto" "$WILC" "$WICL" "$WAMIX" "$WAMIX" "" "" && - commit_check_warn true "auto" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" "" && - commit_check_warn input "auto" "" "CRLF_LF" "CRLF_LF" "CRLF_LF" "" "" + commit_check_warn false "auto" "$WILC" "$WICL" "$WAMIX" "" "" && + commit_check_warn true "auto" "LF_CRLF" "" "LF_CRLF" "" "" && + commit_check_warn input "auto" "" "CRLF_LF" "CRLF_LF" "" "" ' test_expect_success 'commit files attr=text' ' - commit_check_warn false "text" "$WILC" "$WICL" "$WAMIX" "$WAMIX" "$WILC" "$WICL" && - commit_check_warn true "text" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "LF_CRLF" "" && - commit_check_warn input "text" "" "CRLF_LF" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" + commit_check_warn false "text" "$WILC" "$WICL" "$WAMIX" "$WILC" "$WICL" && + commit_check_warn true "text" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" && + commit_check_warn input "text" "" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" ' test_expect_success 'commit files attr=-text' ' - commit_check_warn false "-text" "" "" "" "" "" "" && - commit_check_warn true "-text" "" "" "" "" "" "" && - commit_check_warn input "-text" "" "" "" "" "" "" + commit_check_warn false "-text" "" "" "" "" "" && + commit_check_warn true "-text" "" "" "" "" "" && + commit_check_warn input "-text" "" "" "" "" "" ' test_expect_success 'commit files attr=lf' ' - commit_check_warn false "lf" "" "CRLF_LF" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" && - commit_check_warn true "lf" "" "CRLF_LF" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" && - commit_check_warn input "lf" "" "CRLF_LF" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" + commit_check_warn false "lf" "" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" && + commit_check_warn true "lf" "" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" && + commit_check_warn input "lf" "" "CRLF_LF" "CRLF_LF" "" "CRLF_LF" ' test_expect_success 'commit files attr=crlf' ' - commit_check_warn false "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "LF_CRLF" "" && - commit_check_warn true "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "LF_CRLF" "" && - commit_check_warn input "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "LF_CRLF" "" + commit_check_warn false "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" && + commit_check_warn true "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" && + commit_check_warn input "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" ' -test_expect_success 'create files cleanup' ' +# Commit "CRLFmixLF" on top of these files already in the repo: +# mixed mixed mixed mixed mixed +# onto onto onto onto onto +# attr LF CRLF CRLFmixLF LF_mix_CR CRLFNUL +commit_MIX_chkwrn "" "" false "" "" "" "" "" +commit_MIX_chkwrn "" "" true "LF_CRLF" "" "" "LF_CRLF" "LF_CRLF" +commit_MIX_chkwrn "" "" input "CRLF_LF" "" "" "CRLF_LF" "CRLF_LF" + +commit_MIX_chkwrn "auto" "" false "$WAMIX" "" "" "$WAMIX" "$WAMIX" +commit_MIX_chkwrn "auto" "" true "LF_CRLF" "" "" "LF_CRLF" "LF_CRLF" +commit_MIX_chkwrn "auto" "" input "CRLF_LF" "" "" "CRLF_LF" "CRLF_LF" + +# attr LF CRLF CRLFmixLF LF_mix_CR CRLFNUL +commit_chk_wrnNNO "" "" false "" "" "" "" "" +commit_chk_wrnNNO "" "" true LF_CRLF "" "" "" "" +commit_chk_wrnNNO "" "" input "" "" "" "" "" + +commit_chk_wrnNNO "auto" "" false "$WILC" "" "" "" "" +commit_chk_wrnNNO "auto" "" true LF_CRLF "" "" "" "" +commit_chk_wrnNNO "auto" "" input "" "" "" "" "" +for crlf in true false input +do + commit_chk_wrnNNO -text "" $crlf "" "" "" "" "" + commit_chk_wrnNNO -text lf $crlf "" "" "" "" "" + commit_chk_wrnNNO -text crlf $crlf "" "" "" "" "" + commit_chk_wrnNNO "" lf $crlf "" CRLF_LF CRLF_LF "" CRLF_LF + commit_chk_wrnNNO "" crlf $crlf LF_CRLF "" LF_CRLF LF_CRLF "" + commit_chk_wrnNNO auto lf $crlf "" "" "" "" "" + commit_chk_wrnNNO auto crlf $crlf LF_CRLF "" "" "" "" + commit_chk_wrnNNO text lf $crlf "" CRLF_LF CRLF_LF "" CRLF_LF + commit_chk_wrnNNO text crlf $crlf LF_CRLF "" LF_CRLF LF_CRLF "" +done + +commit_chk_wrnNNO "text" "" false "$WILC" "$WICL" "$WAMIX" "$WILC" "$WICL" +commit_chk_wrnNNO "text" "" true LF_CRLF "" LF_CRLF LF_CRLF "" +commit_chk_wrnNNO "text" "" input "" CRLF_LF CRLF_LF "" CRLF_LF + +test_expect_success 'commit NNO and cleanup' ' + git commit -m "commit files on top of NNO" && rm -f *.txt && - git reset --hard + git -c core.autocrlf=false reset --hard ' test_expect_success 'commit empty gitattribues' ' @@ -257,28 +507,37 @@ test_expect_success 'commit -text' ' check_files_in_repo input "-text" LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul ' +for crlf in true false input +do + # attr aeol LF CRLF CRLF_mix_LF LF_mix_CR CRLFNUL + check_in_repo_NNO "" "" $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + check_in_repo_NNO -text "" $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + check_in_repo_NNO -text lf $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + check_in_repo_NNO -text crlf $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + check_in_repo_NNO auto "" $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + check_in_repo_NNO auto lf $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + check_in_repo_NNO auto crlf $crlf LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul + check_in_repo_NNO text "" $crlf LF LF LF LF_mix_CR LF_nul + check_in_repo_NNO text lf $crlf LF LF LF LF_mix_CR LF_nul + check_in_repo_NNO text crlf $crlf LF LF LF LF_mix_CR LF_nul +done ################################################################################ # Check how files in the repo are changed when they are checked out # How to read the table below: # - checkout_files will check multiple files with a combination of settings # and attributes (core.autocrlf=input is forbidden with core.eol=crlf) -# - parameter $1 : core.eol lf | crlf -# - parameter $2 : core.autocrlf false | true | input -# - parameter $3 : text in .gitattributs "" (empty) | auto | text | -text -# - parameter $4 : reference for a file with only LF in the repo -# - parameter $5 : reference for a file with only CRLF in the repo -# - parameter $6 : reference for a file with mixed LF and CRLF in the repo -# - parameter $7 : reference for a file with LF and CR in the repo (does somebody uses this ?) -# - parameter $8 : reference for a file with CRLF and a NUL (should be handled as binary when auto) - -# What we have in the repo: -# ----------------- EOL in repo ---------------- -# LF CRLF CRLF_mix_LF LF_mix_CR CRLF_nul -# settings with checkout: -# core. core. .gitattr -# eol acrlf -# ---------------------------------------------- -# What we want to have in the working tree: +# +# - parameter $1 : text in .gitattributs "" (empty) | auto | text | -text +# - parameter $2 : ident "" | i (i == ident) +# - parameter $3 : eol in .gitattributs "" (empty) | lf | crlf +# - parameter $4 : core.autocrlf false | true | input +# - parameter $5 : core.eol "" | lf | crlf | "native" +# - parameter $6 : reference for a file with only LF in the repo +# - parameter $7 : reference for a file with only CRLF in the repo +# - parameter $8 : reference for a file with mixed LF and CRLF in the repo +# - parameter $9 : reference for a file with LF and CR in the repo +# - parameter $10 : reference for a file with CRLF and a NUL (should be handled as binary when auto) + if test_have_prereq NATIVE_CRLF then MIX_CRLF_LF=CRLF @@ -293,68 +552,62 @@ LFNUL=LF_nul fi export CRLF_MIX_LF_CR MIX NL -checkout_files lf false "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files lf true "" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files lf input "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files lf false "auto" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files lf true "auto" CRLF CRLF CRLF LF_mix_CR LF_nul -checkout_files lf input "auto" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files lf false "text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files lf true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul -checkout_files lf input "text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files lf false "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files lf true "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files lf input "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files lf false "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files lf true "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files lf input "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files lf false "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul -checkout_files lf true "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul -checkout_files lf input "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul - -checkout_files crlf false "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files crlf true "" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files crlf false "auto" CRLF CRLF CRLF LF_mix_CR LF_nul -checkout_files crlf true "auto" CRLF CRLF CRLF LF_mix_CR LF_nul -checkout_files crlf false "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul -checkout_files crlf true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul -checkout_files crlf false "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files crlf true "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files crlf false "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files crlf true "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files crlf false "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul -checkout_files crlf true "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul - -checkout_files "" false "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" true "" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" input "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" false "auto" $NL CRLF $MIX_CRLF_LF LF_mix_CR LF_nul -checkout_files "" true "auto" CRLF CRLF CRLF LF_mix_CR LF_nul -checkout_files "" input "auto" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" false "text" $NL CRLF $MIX_CRLF_LF $MIX_LF_CR $LFNUL -checkout_files "" true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul -checkout_files "" input "text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" false "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" true "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" input "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" false "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" true "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" input "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files "" false "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul -checkout_files "" true "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul -checkout_files "" input "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul - -checkout_files native false "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files native true "" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files native false "auto" $NL CRLF $MIX_CRLF_LF LF_mix_CR LF_nul -checkout_files native true "auto" CRLF CRLF CRLF LF_mix_CR LF_nul -checkout_files native false "text" $NL CRLF $MIX_CRLF_LF $MIX_LF_CR $LFNUL -checkout_files native true "text" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul -checkout_files native false "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files native true "-text" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files native false "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files native true "lf" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul -checkout_files native false "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul -checkout_files native true "crlf" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul +# Same handling with and without ident +for id in "" ident +do + for ceol in lf crlf native + do + for crlf in true false input + do + # -text overrides core.autocrlf and core.eol + # text and eol=crlf or eol=lf override core.autocrlf and core.eol + checkout_files -text "$id" "" "$crlf" "$ceol" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + checkout_files -text "$id" "lf" "$crlf" "$ceol" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + checkout_files -text "$id" "crlf" "$crlf" "$ceol" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + # text + checkout_files text "$id" "lf" "$crlf" "$ceol" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + checkout_files text "$id" "crlf" "$crlf" "$ceol" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul + # currently the same as text, eol=XXX + checkout_files auto "$id" "lf" "$crlf" "$ceol" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + checkout_files auto "$id" "crlf" "$crlf" "$ceol" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul + done + + # core.autocrlf false, different core.eol + checkout_files "" "$id" "" false "$ceol" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + # core.autocrlf true + checkout_files "" "$id" "" true "$ceol" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul + # text: core.autocrlf = true overrides core.eol + checkout_files auto "$id" "" true "$ceol" CRLF CRLF CRLF_mix_LF LF_mix_CR LF_nul + checkout_files text "$id" "" true "$ceol" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul + # text: core.autocrlf = input overrides core.eol + checkout_files text "$id" "" input "$ceol" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + checkout_files auto "$id" "" input "$ceol" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + # text=auto + eol=XXX + done + # text: core.autocrlf=false uses core.eol + checkout_files text "$id" "" false crlf CRLF CRLF CRLF CRLF_mix_CR CRLF_nul + checkout_files text "$id" "" false lf LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + # text: core.autocrlf=false and core.eol unset(or native) uses native eol + checkout_files text "$id" "" false "" $NL CRLF $MIX_CRLF_LF $MIX_LF_CR $LFNUL + checkout_files text "$id" "" false native $NL CRLF $MIX_CRLF_LF $MIX_LF_CR $LFNUL + # auto: core.autocrlf=false and core.eol unset(or native) uses native eol + checkout_files auto "$id" "" false "" $NL CRLF CRLF_mix_LF LF_mix_CR LF_nul + checkout_files auto "$id" "" false native $NL CRLF CRLF_mix_LF LF_mix_CR LF_nul +done + +# Should be the last test case: remove some files from the worktree +test_expect_success 'ls-files --eol -d -z' ' + rm crlf_false_attr__CRLF.txt crlf_false_attr__CRLF_mix_LF.txt crlf_false_attr__LF.txt .gitattributes && + cat >expect <<-\EOF && + i/crlf w/ crlf_false_attr__CRLF.txt + i/lf w/ .gitattributes + i/lf w/ crlf_false_attr__LF.txt + i/mixed w/ crlf_false_attr__CRLF_mix_LF.txt + EOF + git ls-files --eol -d | + sed -e "s!attr/[^ ]*!!g" -e "s/ / /g" -e "s/ */ /g" | + sort >actual && + test_cmp expect actual +' test_done diff --git a/t/t0030-stripspace.sh b/t/t0030-stripspace.sh index 29e91d861c..bbf3e39e3d 100755 --- a/t/t0030-stripspace.sh +++ b/t/t0030-stripspace.sh @@ -432,6 +432,15 @@ test_expect_success '-c with changed comment char' ' test_cmp expect actual ' +test_expect_success '-c with comment char defined in .git/config' ' + test_config core.commentchar = && + printf "= foo\n" >expect && + printf "foo" | ( + mkdir sub && cd sub && git stripspace -c + ) >actual && + test_cmp expect actual +' + test_expect_success 'avoid SP-HT sequence in commented line' ' printf "#\tone\n#\n# two\n" >expect && printf "\tone\n\ntwo\n" | git stripspace -c >actual && diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index b044785175..04d474c84f 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -7,9 +7,11 @@ test_description='our own option parser' . ./test-lib.sh -cat > expect << EOF +cat >expect <<\EOF usage: test-parse-options <options> + A helper function for the parse-options API. + --yes get a boolean -D, --no-doubt begins with 'no-' -B, --no-fear be brave @@ -19,6 +21,7 @@ usage: test-parse-options <options> -i, --integer <n> get a integer -j <n> get a integer, too + -m, --magnitude <n> get a magnitude --set23 set integer to 23 -t <time> get timestamp of <time> -L, --length <str> get length of <str> @@ -44,62 +47,24 @@ Standard options -v, --verbose be verbose -n, --dry-run dry run -q, --quiet be quiet + --expect <string> expected output in the variable dump EOF test_expect_success 'test help' ' - test_must_fail test-parse-options -h > output 2> output.err && + test_must_fail test-parse-options -h >output 2>output.err && test_must_be_empty output.err && test_i18ncmp expect output ' mv expect expect.err -cat >expect.template <<EOF -boolean: 0 -integer: 0 -timestamp: 0 -string: (not set) -abbrev: 7 -verbose: 0 -quiet: no -dry run: no -file: (not set) -EOF - -check() { - what="$1" && - shift && - expect="$1" && - shift && - sed "s/^$what .*/$what $expect/" <expect.template >expect && - test-parse-options $* >output 2>output.err && - test_must_be_empty output.err && - test_cmp expect output -} - -check_i18n() { +check () { what="$1" && shift && expect="$1" && shift && - sed "s/^$what .*/$what $expect/" <expect.template >expect && - test-parse-options $* >output 2>output.err && - test_must_be_empty output.err && - test_i18ncmp expect output -} - -check_unknown() { - case "$1" in - --*) - echo error: unknown option \`${1#--}\' >expect ;; - -*) - echo error: unknown switch \`${1#-}\' >expect ;; - esac && - cat expect.err >>expect && - test_must_fail test-parse-options $* >output 2>output.err && - test_must_be_empty output && - test_cmp expect output.err + test-parse-options --expect="$what $expect" "$@" } check_unknown_i18n() { @@ -132,41 +97,66 @@ test_expect_success 'OPT_BOOL() no negation #2' 'check_unknown_i18n --no-no-fear test_expect_success 'OPT_BOOL() positivation' 'check boolean: 0 -D --doubt' -cat > expect << EOF +test_expect_success 'OPT_INT() negative' 'check integer: -2345 -i -2345' + +test_expect_success 'OPT_MAGNITUDE() simple' ' + check magnitude: 2345678 -m 2345678 +' + +test_expect_success 'OPT_MAGNITUDE() kilo' ' + check magnitude: 239616 -m 234k +' + +test_expect_success 'OPT_MAGNITUDE() mega' ' + check magnitude: 104857600 -m 100m +' + +test_expect_success 'OPT_MAGNITUDE() giga' ' + check magnitude: 1073741824 -m 1g +' + +test_expect_success 'OPT_MAGNITUDE() 3giga' ' + check magnitude: 3221225472 -m 3g +' + +cat >expect <<\EOF boolean: 2 integer: 1729 +magnitude: 16384 timestamp: 0 string: 123 abbrev: 7 verbose: 2 -quiet: no +quiet: 0 dry run: yes file: prefix/my.file EOF test_expect_success 'short options' ' - test-parse-options -s123 -b -i 1729 -b -vv -n -F my.file \ - > output 2> output.err && + test-parse-options -s123 -b -i 1729 -m 16k -b -vv -n -F my.file \ + >output 2>output.err && test_cmp expect output && test_must_be_empty output.err ' -cat > expect << EOF +cat >expect <<\EOF boolean: 2 integer: 1729 +magnitude: 16384 timestamp: 0 string: 321 abbrev: 10 verbose: 2 -quiet: no +quiet: 0 dry run: no file: prefix/fi.le EOF test_expect_success 'long options' ' - test-parse-options --boolean --integer 1729 --boolean --string2=321 \ - --verbose --verbose --no-dry-run --abbrev=10 --file fi.le\ - --obsolete > output 2> output.err && + test-parse-options --boolean --integer 1729 --magnitude 16k \ + --boolean --string2=321 --verbose --verbose --no-dry-run \ + --abbrev=10 --file fi.le --obsolete \ + >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' @@ -177,14 +167,15 @@ test_expect_success 'missing required value' ' test_expect_code 129 test-parse-options --file ' -cat > expect << EOF +cat >expect <<\EOF boolean: 1 integer: 13 +magnitude: 0 timestamp: 0 string: 123 abbrev: 7 -verbose: 0 -quiet: no +verbose: -1 +quiet: 0 dry run: no file: (not set) arg 00: a1 @@ -194,104 +185,75 @@ EOF test_expect_success 'intermingled arguments' ' test-parse-options a1 --string 123 b1 --boolean -j 13 -- --boolean \ - > output 2> output.err && + >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' -cat > expect << EOF +cat >expect <<\EOF boolean: 0 integer: 2 +magnitude: 0 timestamp: 0 string: (not set) abbrev: 7 -verbose: 0 -quiet: no +verbose: -1 +quiet: 0 dry run: no file: (not set) EOF test_expect_success 'unambiguously abbreviated option' ' - test-parse-options --int 2 --boolean --no-bo > output 2> output.err && + test-parse-options --int 2 --boolean --no-bo >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' test_expect_success 'unambiguously abbreviated option with "="' ' - test-parse-options --int=2 > output 2> output.err && - test_must_be_empty output.err && - test_cmp expect output + test-parse-options --expect="integer: 2" --int=2 ' test_expect_success 'ambiguously abbreviated option' ' test_expect_code 129 test-parse-options --strin 123 ' -cat > expect << EOF -boolean: 0 -integer: 0 -timestamp: 0 -string: 123 -abbrev: 7 -verbose: 0 -quiet: no -dry run: no -file: (not set) -EOF - test_expect_success 'non ambiguous option (after two options it abbreviates)' ' - test-parse-options --st 123 > output 2> output.err && - test_must_be_empty output.err && - test_cmp expect output + test-parse-options --expect="string: 123" --st 123 ' -cat > typo.err << EOF -error: did you mean \`--boolean\` (with two dashes ?) +cat >typo.err <<\EOF +error: did you mean `--boolean` (with two dashes ?) EOF test_expect_success 'detect possible typos' ' - test_must_fail test-parse-options -boolean > output 2> output.err && + test_must_fail test-parse-options -boolean >output 2>output.err && test_must_be_empty output && test_cmp typo.err output.err ' -cat > typo.err << EOF -error: did you mean \`--ambiguous\` (with two dashes ?) +cat >typo.err <<\EOF +error: did you mean `--ambiguous` (with two dashes ?) EOF test_expect_success 'detect possible typos' ' - test_must_fail test-parse-options -ambiguous > output 2> output.err && + test_must_fail test-parse-options -ambiguous >output 2>output.err && test_must_be_empty output && test_cmp typo.err output.err ' -cat > expect <<EOF -boolean: 0 -integer: 0 -timestamp: 0 -string: (not set) -abbrev: 7 -verbose: 0 -quiet: no -dry run: no -file: (not set) -arg 00: --quux -EOF - test_expect_success 'keep some options as arguments' ' - test-parse-options --quux > output 2> output.err && - test_must_be_empty output.err && - test_cmp expect output + test-parse-options --expect="arg 00: --quux" --quux ' -cat > expect <<EOF +cat >expect <<\EOF boolean: 0 integer: 0 +magnitude: 0 timestamp: 1 string: (not set) abbrev: 7 -verbose: 0 -quiet: yes +verbose: -1 +quiet: 1 dry run: no file: (not set) arg 00: foo @@ -299,120 +261,89 @@ EOF test_expect_success 'OPT_DATE() works' ' test-parse-options -t "1970-01-01 00:00:01 +0000" \ - foo -q > output 2> output.err && + foo -q >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' -cat > expect <<EOF +cat >expect <<\EOF Callback: "four", 0 boolean: 5 integer: 4 +magnitude: 0 timestamp: 0 string: (not set) abbrev: 7 -verbose: 0 -quiet: no +verbose: -1 +quiet: 0 dry run: no file: (not set) EOF test_expect_success 'OPT_CALLBACK() and OPT_BIT() work' ' - test-parse-options --length=four -b -4 > output 2> output.err && + test-parse-options --length=four -b -4 >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' -cat > expect <<EOF -Callback: "not set", 1 -EOF +>expect test_expect_success 'OPT_CALLBACK() and callback errors work' ' - test_must_fail test-parse-options --no-length > output 2> output.err && + test_must_fail test-parse-options --no-length >output 2>output.err && test_i18ncmp expect output && - test_i18ncmp expect.err output.err + test_must_be_empty output.err ' -cat > expect <<EOF +cat >expect <<\EOF boolean: 1 integer: 23 +magnitude: 0 timestamp: 0 string: (not set) abbrev: 7 -verbose: 0 -quiet: no +verbose: -1 +quiet: 0 dry run: no file: (not set) EOF test_expect_success 'OPT_BIT() and OPT_SET_INT() work' ' - test-parse-options --set23 -bbbbb --no-or4 > output 2> output.err && + test-parse-options --set23 -bbbbb --no-or4 >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' test_expect_success 'OPT_NEGBIT() and OPT_SET_INT() work' ' - test-parse-options --set23 -bbbbb --neg-or4 > output 2> output.err && + test-parse-options --set23 -bbbbb --neg-or4 >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' -cat > expect <<EOF -boolean: 6 -integer: 0 -timestamp: 0 -string: (not set) -abbrev: 7 -verbose: 0 -quiet: no -dry run: no -file: (not set) -EOF - test_expect_success 'OPT_BIT() works' ' - test-parse-options -bb --or4 > output 2> output.err && - test_must_be_empty output.err && - test_cmp expect output + test-parse-options --expect="boolean: 6" -bb --or4 ' test_expect_success 'OPT_NEGBIT() works' ' - test-parse-options -bb --no-neg-or4 > output 2> output.err && - test_must_be_empty output.err && - test_cmp expect output + test-parse-options --expect="boolean: 6" -bb --no-neg-or4 ' test_expect_success 'OPT_COUNTUP() with PARSE_OPT_NODASH works' ' - test-parse-options + + + + + + > output 2> output.err && - test_must_be_empty output.err && - test_cmp expect output + test-parse-options --expect="boolean: 6" + + + + + + ' -cat > expect <<EOF -boolean: 0 -integer: 12345 -timestamp: 0 -string: (not set) -abbrev: 7 -verbose: 0 -quiet: no -dry run: no -file: (not set) -EOF - test_expect_success 'OPT_NUMBER_CALLBACK() works' ' - test-parse-options -12345 > output 2> output.err && - test_must_be_empty output.err && - test_cmp expect output + test-parse-options --expect="integer: 12345" -12345 ' -cat >expect <<EOF +cat >expect <<\EOF boolean: 0 integer: 0 +magnitude: 0 timestamp: 0 string: (not set) abbrev: 7 -verbose: 0 -quiet: no +verbose: -1 +quiet: 0 dry run: no file: (not set) EOF @@ -423,7 +354,7 @@ test_expect_success 'negation of OPT_NONEG flags is not ambiguous' ' test_cmp expect output ' -cat >>expect <<'EOF' +cat >>expect <<\EOF list: foo list: bar list: baz @@ -439,4 +370,28 @@ test_expect_success '--no-list resets list' ' test_cmp expect output ' +test_expect_success 'multiple quiet levels' ' + test-parse-options --expect="quiet: 3" -q -q -q +' + +test_expect_success 'multiple verbose levels' ' + test-parse-options --expect="verbose: 3" -v -v -v +' + +test_expect_success '--no-quiet sets --quiet to 0' ' + test-parse-options --expect="quiet: 0" --no-quiet +' + +test_expect_success '--no-quiet resets multiple -q to 0' ' + test-parse-options --expect="quiet: 0" -q -q -q --no-quiet +' + +test_expect_success '--no-verbose sets verbose to 0' ' + test-parse-options --expect="verbose: 0" --no-verbose +' + +test_expect_success '--no-verbose resets multiple verbose to 0' ' + test-parse-options --expect="verbose: 0" -v -v -v --no-verbose +' + test_done diff --git a/t/t0041-usage.sh b/t/t0041-usage.sh new file mode 100755 index 0000000000..5b927b76fe --- /dev/null +++ b/t/t0041-usage.sh @@ -0,0 +1,107 @@ +#!/bin/sh + +test_description='Test commands behavior when given invalid argument value' + +. ./test-lib.sh + +test_expect_success 'setup ' ' + test_commit "v1.0" +' + +test_expect_success 'tag --contains <existent_tag>' ' + git tag --contains "v1.0" >actual 2>actual.err && + grep "v1.0" actual && + test_line_count = 0 actual.err +' + +test_expect_success 'tag --contains <inexistent_tag>' ' + test_must_fail git tag --contains "notag" >actual 2>actual.err && + test_line_count = 0 actual && + test_i18ngrep "error" actual.err && + test_i18ngrep ! "usage" actual.err +' + +test_expect_success 'tag --no-contains <existent_tag>' ' + git tag --no-contains "v1.0" >actual 2>actual.err && + test_line_count = 0 actual && + test_line_count = 0 actual.err +' + +test_expect_success 'tag --no-contains <inexistent_tag>' ' + test_must_fail git tag --no-contains "notag" >actual 2>actual.err && + test_line_count = 0 actual && + test_i18ngrep "error" actual.err && + test_i18ngrep ! "usage" actual.err +' + +test_expect_success 'tag usage error' ' + test_must_fail git tag --noopt >actual 2>actual.err && + test_line_count = 0 actual && + test_i18ngrep "usage" actual.err +' + +test_expect_success 'branch --contains <existent_commit>' ' + git branch --contains "master" >actual 2>actual.err && + test_i18ngrep "master" actual && + test_line_count = 0 actual.err +' + +test_expect_success 'branch --contains <inexistent_commit>' ' + test_must_fail git branch --no-contains "nocommit" >actual 2>actual.err && + test_line_count = 0 actual && + test_i18ngrep "error" actual.err && + test_i18ngrep ! "usage" actual.err +' + +test_expect_success 'branch --no-contains <existent_commit>' ' + git branch --no-contains "master" >actual 2>actual.err && + test_line_count = 0 actual && + test_line_count = 0 actual.err +' + +test_expect_success 'branch --no-contains <inexistent_commit>' ' + test_must_fail git branch --no-contains "nocommit" >actual 2>actual.err && + test_line_count = 0 actual && + test_i18ngrep "error" actual.err && + test_i18ngrep ! "usage" actual.err +' + +test_expect_success 'branch usage error' ' + test_must_fail git branch --noopt >actual 2>actual.err && + test_line_count = 0 actual && + test_i18ngrep "usage" actual.err +' + +test_expect_success 'for-each-ref --contains <existent_object>' ' + git for-each-ref --contains "master" >actual 2>actual.err && + test_line_count = 2 actual && + test_line_count = 0 actual.err +' + +test_expect_success 'for-each-ref --contains <inexistent_object>' ' + test_must_fail git for-each-ref --no-contains "noobject" >actual 2>actual.err && + test_line_count = 0 actual && + test_i18ngrep "error" actual.err && + test_i18ngrep ! "usage" actual.err +' + +test_expect_success 'for-each-ref --no-contains <existent_object>' ' + git for-each-ref --no-contains "master" >actual 2>actual.err && + test_line_count = 0 actual && + test_line_count = 0 actual.err +' + +test_expect_success 'for-each-ref --no-contains <inexistent_object>' ' + test_must_fail git for-each-ref --no-contains "noobject" >actual 2>actual.err && + test_line_count = 0 actual && + test_i18ngrep "error" actual.err && + test_i18ngrep ! "usage" actual.err +' + +test_expect_success 'for-each-ref usage error' ' + test_must_fail git for-each-ref --noopt >actual 2>actual.err && + test_line_count = 0 actual && + test_i18ngrep "usage" actual.err +' + +test_done diff --git a/t/t0050-filesystem.sh b/t/t0050-filesystem.sh index b29d749bb7..192c94eccd 100755 --- a/t/t0050-filesystem.sh +++ b/t/t0050-filesystem.sh @@ -80,7 +80,21 @@ test_expect_success 'merge (case change)' ' git merge topic ' - +test_expect_success CASE_INSENSITIVE_FS 'add directory (with different case)' ' + git reset --hard initial && + mkdir -p dir1/dir2 && + echo >dir1/dir2/a && + echo >dir1/dir2/b && + git add dir1/dir2/a && + git add dir1/DIR2/b && + git ls-files >actual && + cat >expected <<-\EOF && + camelcase + dir1/dir2/a + dir1/dir2/b + EOF + test_cmp expected actual +' test_expect_failure CASE_INSENSITIVE_FS 'add (with different case)' ' git reset --hard initial && diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index 93605f42f2..f46e3c4995 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -8,15 +8,22 @@ test_description='Test various path utilities' . ./test-lib.sh norm_path() { - expected=$(test-path-utils print_path "$2") + expected=$(test-tool path-utils print_path "$2") test_expect_success $3 "normalize path: $1 => $2" \ - "test \"\$(test-path-utils normalize_path_copy '$1')\" = '$expected'" + "test \"\$(test-tool path-utils normalize_path_copy '$1')\" = '$expected'" } relative_path() { - expected=$(test-path-utils print_path "$3") + expected=$(test-tool path-utils print_path "$3") test_expect_success $4 "relative path: $1 $2 => $3" \ - "test \"\$(test-path-utils relative_path '$1' '$2')\" = '$expected'" + "test \"\$(test-tool path-utils relative_path '$1' '$2')\" = '$expected'" +} + +test_submodule_relative_url() { + test_expect_success "test_submodule_relative_url: $1 $2 $3 => $4" " + actual=\$(git submodule--helper resolve-relative-url-test '$1' '$2' '$3') && + test \"\$actual\" = '$4' + " } test_git_path() { @@ -30,22 +37,31 @@ test_git_path() { # On Windows, we are using MSYS's bash, which mangles the paths. # Absolute paths are anchored at the MSYS installation directory, # which means that the path / accounts for this many characters: -rootoff=$(test-path-utils normalize_path_copy / | wc -c) +rootoff=$(test-tool path-utils normalize_path_copy / | wc -c) # Account for the trailing LF: if test $rootoff = 2; then rootoff= # we are on Unix else rootoff=$(($rootoff-1)) + # In MSYS2, the root directory "/" is translated into a Windows + # directory *with* trailing slash. Let's test for that and adjust + # our expected longest ancestor length accordingly. + case "$(test-tool path-utils print_path /)" in + */) rootslash=1;; + *) rootslash=0;; + esac fi ancestor() { # We do some math with the expected ancestor length. expected=$3 if test -n "$rootoff" && test "x$expected" != x-1; then + expected=$(($expected-$rootslash)) + test $expected -lt 0 || expected=$(($expected+$rootoff)) fi test_expect_success "longest ancestor: $1 $2 => $expected" \ - "actual=\$(test-path-utils longest_ancestor_length '$1' '$2') && + "actual=\$(test-tool path-utils longest_ancestor_length '$1' '$2') && test \"\$actual\" = '$expected'" } @@ -54,11 +70,16 @@ ancestor() { case $(uname -s) in *MINGW*) ;; +*CYGWIN*) + ;; *) test_set_prereq POSIX ;; esac +test_expect_success basename 'test-tool path-utils basename' +test_expect_success dirname 'test-tool path-utils dirname' + norm_path "" "" norm_path . "" norm_path ./ "" @@ -136,48 +157,48 @@ ancestor /foo/bar /foo:/bar 4 ancestor /foo/bar /bar -1 test_expect_success 'strip_path_suffix' ' - test c:/msysgit = $(test-path-utils strip_path_suffix \ + test c:/msysgit = $(test-tool path-utils strip_path_suffix \ c:/msysgit/libexec//git-core libexec/git-core) ' test_expect_success 'absolute path rejects the empty string' ' - test_must_fail test-path-utils absolute_path "" + test_must_fail test-tool path-utils absolute_path "" ' test_expect_success 'real path rejects the empty string' ' - test_must_fail test-path-utils real_path "" + test_must_fail test-tool path-utils real_path "" ' test_expect_success POSIX 'real path works on absolute paths 1' ' nopath="hopefully-absent-path" && - test "/" = "$(test-path-utils real_path "/")" && - test "/$nopath" = "$(test-path-utils real_path "/$nopath")" + test "/" = "$(test-tool path-utils real_path "/")" && + test "/$nopath" = "$(test-tool path-utils real_path "/$nopath")" ' test_expect_success 'real path works on absolute paths 2' ' nopath="hopefully-absent-path" && # Find an existing top-level directory for the remaining tests: d=$(pwd -P | sed -e "s|^\([^/]*/[^/]*\)/.*|\1|") && - test "$d" = "$(test-path-utils real_path "$d")" && - test "$d/$nopath" = "$(test-path-utils real_path "$d/$nopath")" + test "$d" = "$(test-tool path-utils real_path "$d")" && + test "$d/$nopath" = "$(test-tool path-utils real_path "$d/$nopath")" ' test_expect_success POSIX 'real path removes extra leading slashes' ' nopath="hopefully-absent-path" && - test "/" = "$(test-path-utils real_path "///")" && - test "/$nopath" = "$(test-path-utils real_path "///$nopath")" && + test "/" = "$(test-tool path-utils real_path "///")" && + test "/$nopath" = "$(test-tool path-utils real_path "///$nopath")" && # Find an existing top-level directory for the remaining tests: d=$(pwd -P | sed -e "s|^\([^/]*/[^/]*\)/.*|\1|") && - test "$d" = "$(test-path-utils real_path "//$d")" && - test "$d/$nopath" = "$(test-path-utils real_path "//$d/$nopath")" + test "$d" = "$(test-tool path-utils real_path "//$d")" && + test "$d/$nopath" = "$(test-tool path-utils real_path "//$d/$nopath")" ' test_expect_success 'real path removes other extra slashes' ' nopath="hopefully-absent-path" && # Find an existing top-level directory for the remaining tests: d=$(pwd -P | sed -e "s|^\([^/]*/[^/]*\)/.*|\1|") && - test "$d" = "$(test-path-utils real_path "$d///")" && - test "$d/$nopath" = "$(test-path-utils real_path "$d///$nopath")" + test "$d" = "$(test-tool path-utils real_path "$d///")" && + test "$d/$nopath" = "$(test-tool path-utils real_path "$d///$nopath")" ' test_expect_success SYMLINKS 'real path works on symlinks' ' @@ -188,35 +209,35 @@ test_expect_success SYMLINKS 'real path works on symlinks' ' mkdir third && dir="$(cd .git; pwd -P)" && dir2=third/../second/other/.git && - test "$dir" = "$(test-path-utils real_path $dir2)" && + test "$dir" = "$(test-tool path-utils real_path $dir2)" && file="$dir"/index && - test "$file" = "$(test-path-utils real_path $dir2/index)" && + test "$file" = "$(test-tool path-utils real_path $dir2/index)" && basename=blub && - test "$dir/$basename" = "$(cd .git && test-path-utils real_path "$basename")" && + test "$dir/$basename" = "$(cd .git && test-tool path-utils real_path "$basename")" && ln -s ../first/file .git/syml && sym="$(cd first; pwd -P)"/file && - test "$sym" = "$(test-path-utils real_path "$dir2/syml")" + test "$sym" = "$(test-tool path-utils real_path "$dir2/syml")" ' test_expect_success SYMLINKS 'prefix_path works with absolute paths to work tree symlinks' ' ln -s target symlink && - test "$(test-path-utils prefix_path prefix "$(pwd)/symlink")" = "symlink" + test "$(test-tool path-utils prefix_path prefix "$(pwd)/symlink")" = "symlink" ' test_expect_success 'prefix_path works with only absolute path to work tree' ' echo "" >expected && - test-path-utils prefix_path prefix "$(pwd)" >actual && + test-tool path-utils prefix_path prefix "$(pwd)" >actual && test_cmp expected actual ' test_expect_success 'prefix_path rejects absolute path to dir with same beginning as work tree' ' - test_must_fail test-path-utils prefix_path prefix "$(pwd)a" + test_must_fail test-tool path-utils prefix_path prefix "$(pwd)a" ' test_expect_success SYMLINKS 'prefix_path works with absolute path to a symlink to work tree having same beginning as work tree' ' git init repo && ln -s repo repolink && - test "a" = "$(cd repo && test-path-utils prefix_path prefix "$(pwd)/../repolink/a")" + test "a" = "$(cd repo && test-tool path-utils prefix_path prefix "$(pwd)/../repolink/a")" ' relative_path /foo/a/b/c/ /foo/a/b/ c/ @@ -266,18 +287,66 @@ test_expect_success 'setup common repository' 'git --git-dir=bar init' test_git_path GIT_COMMON_DIR=bar index .git/index test_git_path GIT_COMMON_DIR=bar HEAD .git/HEAD test_git_path GIT_COMMON_DIR=bar logs/HEAD .git/logs/HEAD +test_git_path GIT_COMMON_DIR=bar logs/refs/bisect/foo .git/logs/refs/bisect/foo +test_git_path GIT_COMMON_DIR=bar logs/refs/bisec/foo bar/logs/refs/bisec/foo +test_git_path GIT_COMMON_DIR=bar logs/refs/bisec bar/logs/refs/bisec +test_git_path GIT_COMMON_DIR=bar logs/refs/bisectfoo bar/logs/refs/bisectfoo test_git_path GIT_COMMON_DIR=bar objects bar/objects test_git_path GIT_COMMON_DIR=bar objects/bar bar/objects/bar test_git_path GIT_COMMON_DIR=bar info/exclude bar/info/exclude test_git_path GIT_COMMON_DIR=bar info/grafts bar/info/grafts test_git_path GIT_COMMON_DIR=bar info/sparse-checkout .git/info/sparse-checkout +test_git_path GIT_COMMON_DIR=bar info//sparse-checkout .git/info//sparse-checkout test_git_path GIT_COMMON_DIR=bar remotes/bar bar/remotes/bar test_git_path GIT_COMMON_DIR=bar branches/bar bar/branches/bar test_git_path GIT_COMMON_DIR=bar logs/refs/heads/master bar/logs/refs/heads/master test_git_path GIT_COMMON_DIR=bar refs/heads/master bar/refs/heads/master +test_git_path GIT_COMMON_DIR=bar refs/bisect/foo .git/refs/bisect/foo test_git_path GIT_COMMON_DIR=bar hooks/me bar/hooks/me test_git_path GIT_COMMON_DIR=bar config bar/config test_git_path GIT_COMMON_DIR=bar packed-refs bar/packed-refs test_git_path GIT_COMMON_DIR=bar shallow bar/shallow +# In the tests below, $(pwd) must be used because it is a native path on +# Windows and avoids MSYS's path mangling (which simplifies "foo/../bar" and +# strips the dot from trailing "/."). + +test_submodule_relative_url "../" "../foo" "../submodule" "../../submodule" +test_submodule_relative_url "../" "../foo/bar" "../submodule" "../../foo/submodule" +test_submodule_relative_url "../" "../foo/submodule" "../submodule" "../../foo/submodule" +test_submodule_relative_url "../" "./foo" "../submodule" "../submodule" +test_submodule_relative_url "../" "./foo/bar" "../submodule" "../foo/submodule" +test_submodule_relative_url "../../../" "../foo/bar" "../sub/a/b/c" "../../../../foo/sub/a/b/c" +test_submodule_relative_url "../" "$(pwd)/addtest" "../repo" "$(pwd)/repo" +test_submodule_relative_url "../" "foo/bar" "../submodule" "../foo/submodule" +test_submodule_relative_url "../" "foo" "../submodule" "../submodule" + +test_submodule_relative_url "(null)" "../foo/bar" "../sub/a/b/c" "../foo/sub/a/b/c" +test_submodule_relative_url "(null)" "../foo/bar" "../sub/a/b/c/" "../foo/sub/a/b/c" +test_submodule_relative_url "(null)" "../foo/bar/" "../sub/a/b/c" "../foo/sub/a/b/c" +test_submodule_relative_url "(null)" "../foo/bar" "../submodule" "../foo/submodule" +test_submodule_relative_url "(null)" "../foo/submodule" "../submodule" "../foo/submodule" +test_submodule_relative_url "(null)" "../foo" "../submodule" "../submodule" +test_submodule_relative_url "(null)" "./foo/bar" "../submodule" "foo/submodule" +test_submodule_relative_url "(null)" "./foo" "../submodule" "submodule" +test_submodule_relative_url "(null)" "//somewhere else/repo" "../subrepo" "//somewhere else/subrepo" +test_submodule_relative_url "(null)" "$(pwd)/subsuper_update_r" "../subsubsuper_update_r" "$(pwd)/subsubsuper_update_r" +test_submodule_relative_url "(null)" "$(pwd)/super_update_r2" "../subsuper_update_r" "$(pwd)/subsuper_update_r" +test_submodule_relative_url "(null)" "$(pwd)/." "../." "$(pwd)/." +test_submodule_relative_url "(null)" "$(pwd)" "./." "$(pwd)/." +test_submodule_relative_url "(null)" "$(pwd)/addtest" "../repo" "$(pwd)/repo" +test_submodule_relative_url "(null)" "$(pwd)" "./Ã¥ äö" "$(pwd)/Ã¥ äö" +test_submodule_relative_url "(null)" "$(pwd)/." "../submodule" "$(pwd)/submodule" +test_submodule_relative_url "(null)" "$(pwd)/submodule" "../submodule" "$(pwd)/submodule" +test_submodule_relative_url "(null)" "$(pwd)/home2/../remote" "../bundle1" "$(pwd)/home2/../bundle1" +test_submodule_relative_url "(null)" "$(pwd)/submodule_update_repo" "./." "$(pwd)/submodule_update_repo/." +test_submodule_relative_url "(null)" "file:///tmp/repo" "../subrepo" "file:///tmp/subrepo" +test_submodule_relative_url "(null)" "foo/bar" "../submodule" "foo/submodule" +test_submodule_relative_url "(null)" "foo" "../submodule" "submodule" +test_submodule_relative_url "(null)" "helper:://hostname/repo" "../subrepo" "helper:://hostname/subrepo" +test_submodule_relative_url "(null)" "ssh://hostname/repo" "../subrepo" "ssh://hostname/subrepo" +test_submodule_relative_url "(null)" "ssh://hostname:22/repo" "../subrepo" "ssh://hostname:22/subrepo" +test_submodule_relative_url "(null)" "user@host:path/to/repo" "../subrepo" "user@host:path/to/subrepo" +test_submodule_relative_url "(null)" "user@host:repo" "../subrepo" "user@host:subrepo" + test_done diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh index 9acf628726..d03149be9f 100755 --- a/t/t0061-run-command.sh +++ b/t/t0061-run-command.sh @@ -14,22 +14,63 @@ EOF >empty test_expect_success 'start_command reports ENOENT' ' - test-run-command start-command-ENOENT ./does-not-exist + test-tool run-command start-command-ENOENT ./does-not-exist ' test_expect_success 'run_command can run a command' ' cat hello-script >hello.sh && chmod +x hello.sh && - test-run-command run-command ./hello.sh >actual 2>err && + test-tool run-command run-command ./hello.sh >actual 2>err && test_cmp hello-script actual && test_cmp empty err ' +test_expect_success !MINGW 'run_command can run a script without a #! line' ' + cat >hello <<-\EOF && + cat hello-script + EOF + chmod +x hello && + test-tool run-command run-command ./hello >actual 2>err && + + test_cmp hello-script actual && + test_cmp empty err +' + +test_expect_success 'run_command does not try to execute a directory' ' + test_when_finished "rm -rf bin1 bin2" && + mkdir -p bin1/greet bin2 && + write_script bin2/greet <<-\EOF && + cat bin2/greet + EOF + + PATH=$PWD/bin1:$PWD/bin2:$PATH \ + test-tool run-command run-command greet >actual 2>err && + test_cmp bin2/greet actual && + test_cmp empty err +' + +test_expect_success POSIXPERM 'run_command passes over non-executable file' ' + test_when_finished "rm -rf bin1 bin2" && + mkdir -p bin1 bin2 && + write_script bin1/greet <<-\EOF && + cat bin1/greet + EOF + chmod -x bin1/greet && + write_script bin2/greet <<-\EOF && + cat bin2/greet + EOF + + PATH=$PWD/bin1:$PWD/bin2:$PATH \ + test-tool run-command run-command greet >actual 2>err && + test_cmp bin2/greet actual && + test_cmp empty err +' + test_expect_success POSIXPERM 'run_command reports EACCES' ' cat hello-script >hello.sh && chmod -x hello.sh && - test_must_fail test-run-command run-command ./hello.sh 2>err && + test_must_fail test-tool run-command run-command ./hello.sh 2>err && grep "fatal: cannot exec.*hello.sh" err ' @@ -47,4 +88,94 @@ test_expect_success POSIXPERM,SANITY 'unreadable directory in PATH' ' test_cmp expect actual ' +cat >expect <<-EOF +preloaded output of a child +Hello +World +preloaded output of a child +Hello +World +preloaded output of a child +Hello +World +preloaded output of a child +Hello +World +EOF + +test_expect_success 'run_command runs in parallel with more jobs available than tasks' ' + test-tool run-command run-command-parallel 5 sh -c "printf \"%s\n%s\n\" Hello World" 2>actual && + test_cmp expect actual +' + +test_expect_success 'run_command runs in parallel with as many jobs as tasks' ' + test-tool run-command run-command-parallel 4 sh -c "printf \"%s\n%s\n\" Hello World" 2>actual && + test_cmp expect actual +' + +test_expect_success 'run_command runs in parallel with more tasks than jobs available' ' + test-tool run-command run-command-parallel 3 sh -c "printf \"%s\n%s\n\" Hello World" 2>actual && + test_cmp expect actual +' + +cat >expect <<-EOF +preloaded output of a child +asking for a quick stop +preloaded output of a child +asking for a quick stop +preloaded output of a child +asking for a quick stop +EOF + +test_expect_success 'run_command is asked to abort gracefully' ' + test-tool run-command run-command-abort 3 false 2>actual && + test_cmp expect actual +' + +cat >expect <<-EOF +no further jobs available +EOF + +test_expect_success 'run_command outputs ' ' + test-tool run-command run-command-no-jobs 3 sh -c "printf \"%s\n%s\n\" Hello World" 2>actual && + test_cmp expect actual +' + +test_trace () { + expect="$1" + shift + GIT_TRACE=1 test-tool run-command "$@" run-command true 2>&1 >/dev/null | \ + sed 's/.* run_command: //' >actual && + echo "$expect true" >expect && + test_cmp expect actual +} + +test_expect_success 'GIT_TRACE with environment variables' ' + test_trace "abc=1 def=2" env abc=1 env def=2 && + test_trace "abc=2" env abc env abc=1 env abc=2 && + test_trace "abc=2" env abc env abc=2 && + ( + abc=1 && export abc && + test_trace "def=1" env abc=1 env def=1 + ) && + ( + abc=1 && export abc && + test_trace "def=1" env abc env abc=1 env def=1 + ) && + test_trace "def=1" env non-exist env def=1 && + test_trace "abc=2" env abc=1 env abc env abc=2 && + ( + abc=1 def=2 && export abc def && + test_trace "unset abc def;" env abc env def + ) && + ( + abc=1 def=2 && export abc def && + test_trace "unset def; abc=3" env abc env def env abc=3 + ) && + ( + abc=1 && export abc && + test_trace "unset abc;" env abc=2 env abc + ) +' + test_done diff --git a/t/t0062-revision-walking.sh b/t/t0062-revision-walking.sh index 113c728e67..8e215867b8 100755 --- a/t/t0062-revision-walking.sh +++ b/t/t0062-revision-walking.sh @@ -26,7 +26,7 @@ test_expect_success 'setup' ' ' test_expect_success 'revision walking can be done twice' ' - test-revision-walking run-twice >run_twice_actual && + test-tool revision-walking run-twice >run_twice_actual && test_cmp run_twice_expected run_twice_actual ' diff --git a/t/t0063-string-list.sh b/t/t0063-string-list.sh index dbfc05ebdc..c6ee9f66b1 100755 --- a/t/t0063-string-list.sh +++ b/t/t0063-string-list.sh @@ -10,9 +10,9 @@ test_description='Test string list functionality' test_split () { cat >expected && test_expect_success "split $1 at $2, max $3" " - test-string-list split '$1' '$2' '$3' >actual && + test-tool string-list split '$1' '$2' '$3' >actual && test_cmp expected actual && - test-string-list split_in_place '$1' '$2' '$3' >actual && + test-tool string-list split_in_place '$1' '$2' '$3' >actual && test_cmp expected actual " } @@ -61,31 +61,31 @@ test_split ":" ":" "-1" <<EOF EOF test_expect_success "test filter_string_list" ' - test "x-" = "x$(test-string-list filter - y)" && - test "x-" = "x$(test-string-list filter no y)" && - test yes = "$(test-string-list filter yes y)" && - test yes = "$(test-string-list filter no:yes y)" && - test yes = "$(test-string-list filter yes:no y)" && - test y1:y2 = "$(test-string-list filter y1:y2 y)" && - test y2:y1 = "$(test-string-list filter y2:y1 y)" && - test "x-" = "x$(test-string-list filter x1:x2 y)" + test "x-" = "x$(test-tool string-list filter - y)" && + test "x-" = "x$(test-tool string-list filter no y)" && + test yes = "$(test-tool string-list filter yes y)" && + test yes = "$(test-tool string-list filter no:yes y)" && + test yes = "$(test-tool string-list filter yes:no y)" && + test y1:y2 = "$(test-tool string-list filter y1:y2 y)" && + test y2:y1 = "$(test-tool string-list filter y2:y1 y)" && + test "x-" = "x$(test-tool string-list filter x1:x2 y)" ' test_expect_success "test remove_duplicates" ' - test "x-" = "x$(test-string-list remove_duplicates -)" && - test "x" = "x$(test-string-list remove_duplicates "")" && - test a = "$(test-string-list remove_duplicates a)" && - test a = "$(test-string-list remove_duplicates a:a)" && - test a = "$(test-string-list remove_duplicates a:a:a:a:a)" && - test a:b = "$(test-string-list remove_duplicates a:b)" && - test a:b = "$(test-string-list remove_duplicates a:a:b)" && - test a:b = "$(test-string-list remove_duplicates a:b:b)" && - test a:b:c = "$(test-string-list remove_duplicates a:b:c)" && - test a:b:c = "$(test-string-list remove_duplicates a:a:b:c)" && - test a:b:c = "$(test-string-list remove_duplicates a:b:b:c)" && - test a:b:c = "$(test-string-list remove_duplicates a:b:c:c)" && - test a:b:c = "$(test-string-list remove_duplicates a:a:b:b:c:c)" && - test a:b:c = "$(test-string-list remove_duplicates a:a:a:b:b:b:c:c:c)" + test "x-" = "x$(test-tool string-list remove_duplicates -)" && + test "x" = "x$(test-tool string-list remove_duplicates "")" && + test a = "$(test-tool string-list remove_duplicates a)" && + test a = "$(test-tool string-list remove_duplicates a:a)" && + test a = "$(test-tool string-list remove_duplicates a:a:a:a:a)" && + test a:b = "$(test-tool string-list remove_duplicates a:b)" && + test a:b = "$(test-tool string-list remove_duplicates a:a:b)" && + test a:b = "$(test-tool string-list remove_duplicates a:b:b)" && + test a:b:c = "$(test-tool string-list remove_duplicates a:b:c)" && + test a:b:c = "$(test-tool string-list remove_duplicates a:a:b:c)" && + test a:b:c = "$(test-tool string-list remove_duplicates a:b:b:c)" && + test a:b:c = "$(test-tool string-list remove_duplicates a:b:c:c)" && + test a:b:c = "$(test-tool string-list remove_duplicates a:a:b:b:c:c)" && + test a:b:c = "$(test-tool string-list remove_duplicates a:a:a:b:b:b:c:c:c)" ' test_done diff --git a/t/t0064-sha1-array.sh b/t/t0064-sha1-array.sh index 50b31ffe75..67484502a0 100755 --- a/t/t0064-sha1-array.sh +++ b/t/t0064-sha1-array.sh @@ -18,7 +18,7 @@ test_expect_success 'ordered enumeration' ' { echo20 append 88 44 aa 55 && echo for_each_unique - } | test-sha1-array >actual && + } | test-tool sha1-array >actual && test_cmp expect actual ' @@ -28,7 +28,7 @@ test_expect_success 'ordered enumeration with duplicate suppression' ' echo20 append 88 44 aa 55 && echo20 append 88 44 aa 55 && echo for_each_unique - } | test-sha1-array >actual && + } | test-tool sha1-array >actual && test_cmp expect actual ' @@ -36,7 +36,7 @@ test_expect_success 'lookup' ' { echo20 append 88 44 aa 55 && echo20 lookup 55 - } | test-sha1-array >actual && + } | test-tool sha1-array >actual && n=$(cat actual) && test "$n" -eq 1 ' @@ -45,7 +45,7 @@ test_expect_success 'lookup non-existing entry' ' { echo20 append 88 44 aa 55 && echo20 lookup 33 - } | test-sha1-array >actual && + } | test-tool sha1-array >actual && n=$(cat actual) && test "$n" -lt 0 ' @@ -55,7 +55,7 @@ test_expect_success 'lookup with duplicates' ' echo20 append 88 44 aa 55 && echo20 append 88 44 aa 55 && echo20 lookup 55 - } | test-sha1-array >actual && + } | test-tool sha1-array >actual && n=$(cat actual) && test "$n" -ge 2 && test "$n" -le 3 @@ -66,7 +66,7 @@ test_expect_success 'lookup non-existing entry with duplicates' ' echo20 append 88 44 aa 55 && echo20 append 88 44 aa 55 && echo20 lookup 66 - } | test-sha1-array >actual && + } | test-tool sha1-array >actual && n=$(cat actual) && test "$n" -lt 0 ' @@ -76,7 +76,7 @@ test_expect_success 'lookup with almost duplicate values' ' echo "append 5555555555555555555555555555555555555555" && echo "append 555555555555555555555555555555555555555f" && echo20 lookup 55 - } | test-sha1-array >actual && + } | test-tool sha1-array >actual && n=$(cat actual) && test "$n" -eq 0 ' @@ -85,7 +85,7 @@ test_expect_success 'lookup with single duplicate value' ' { echo20 append 55 55 && echo20 lookup 55 - } | test-sha1-array >actual && + } | test-tool sha1-array >actual && n=$(cat actual) && test "$n" -ge 0 && test "$n" -le 1 diff --git a/t/t0065-strcmp-offset.sh b/t/t0065-strcmp-offset.sh new file mode 100755 index 0000000000..91fa639c4a --- /dev/null +++ b/t/t0065-strcmp-offset.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +test_description='Test strcmp_offset functionality' + +. ./test-lib.sh + +while read s1 s2 expect +do + test_expect_success "strcmp_offset($s1, $s2)" ' + echo "$expect" >expect && + test-tool strcmp-offset "$s1" "$s2" >actual && + test_cmp expect actual + ' +done <<-EOF +abc abc 0 3 +abc def -1 0 +abc abz -1 2 +abc abcdef -1 3 +EOF + +test_done diff --git a/t/t0070-fundamental.sh b/t/t0070-fundamental.sh index 5ed69a6f56..23fbe6434a 100755 --- a/t/t0070-fundamental.sh +++ b/t/t0070-fundamental.sh @@ -9,11 +9,11 @@ Verify wrappers and compatibility functions. . ./test-lib.sh test_expect_success 'character classes (isspace, isalpha etc.)' ' - test-ctype + test-tool ctype ' test_expect_success 'mktemp to nonexistent directory prints filename' ' - test_must_fail test-mktemp doesnotexist/testXXXXXX 2>err && + test_must_fail test-tool mktemp doesnotexist/testXXXXXX 2>err && grep "doesnotexist/test" err ' @@ -21,7 +21,7 @@ test_expect_success POSIXPERM,SANITY 'mktemp to unwritable directory prints file mkdir cannotwrite && chmod -w cannotwrite && test_when_finished "chmod +w cannotwrite" && - test_must_fail test-mktemp cannotwrite/testXXXXXX 2>err && + test_must_fail test-tool mktemp cannotwrite/testXXXXXX 2>err && grep "cannotwrite/test" err ' @@ -31,7 +31,7 @@ test_expect_success 'git_mkstemps_mode does not fail if fd 0 is not open' ' test_expect_success 'check for a bug in the regex routines' ' # if this test fails, re-build git with NO_REGEX=1 - test-regex + test-tool regex --bug ' test_done diff --git a/t/t0090-cache-tree.sh b/t/t0090-cache-tree.sh index 601d02d71f..4ae0995cd9 100755 --- a/t/t0090-cache-tree.sh +++ b/t/t0090-cache-tree.sh @@ -8,13 +8,13 @@ cache-tree extension. . ./test-lib.sh cmp_cache_tree () { - test-dump-cache-tree | sed -e '/#(ref)/d' >actual && + test-tool dump-cache-tree | sed -e '/#(ref)/d' >actual && sed "s/$_x40/SHA/" <actual >filtered && test_cmp "$1" filtered } # We don't bother with actually checking the SHA1: -# test-dump-cache-tree already verifies that all existing data is +# test-tool dump-cache-tree already verifies that all existing data is # correct. generate_expected_cache_tree_rec () { dir="$1${1:+/}" && @@ -47,7 +47,7 @@ test_cache_tree () { test_invalid_cache_tree () { printf "invalid %s ()\n" "" "$@" >expect && - test-dump-cache-tree | + test-tool dump-cache-tree | sed -n -e "s/[0-9]* subtrees//" -e '/#(ref)/d' -e '/^invalid /p' >actual && test_cmp expect actual } @@ -115,14 +115,14 @@ test_expect_success 'update-index invalidates cache-tree' ' ' test_expect_success 'write-tree establishes cache-tree' ' - test-scrap-cache-tree && + test-tool scrap-cache-tree && git write-tree && test_cache_tree ' -test_expect_success 'test-scrap-cache-tree works' ' +test_expect_success 'test-tool scrap-cache-tree works' ' git read-tree HEAD && - test-scrap-cache-tree && + test-tool scrap-cache-tree && test_no_cache_tree ' @@ -170,7 +170,7 @@ test_expect_success 'commit in child dir has cache-tree' ' ' test_expect_success 'reset --hard gives cache-tree' ' - test-scrap-cache-tree && + test-tool scrap-cache-tree && git reset --hard && test_cache_tree ' @@ -199,6 +199,30 @@ test_expect_success 'checkout -B gives cache-tree' ' test_cache_tree ' +test_expect_success 'merge --ff-only maintains cache-tree' ' + git checkout current && + git checkout -b changes && + test_commit llamas && + test_commit pachyderm && + test_cache_tree && + git checkout current && + test_cache_tree && + git merge --ff-only changes && + test_cache_tree +' + +test_expect_success 'merge maintains cache-tree' ' + git checkout current && + git checkout -b changes2 && + test_commit alpacas && + test_cache_tree && + git checkout current && + test_commit struthio && + test_cache_tree && + git merge changes2 && + test_cache_tree +' + test_expect_success 'partial commit gives cache-tree' ' git checkout -b partial no-children && test_commit one && @@ -218,4 +242,14 @@ test_expect_success 'no phantom error when switching trees' ' ! test -s errors ' +test_expect_success 'switching trees does not invalidate shared index' ' + git update-index --split-index && + >split && + git add split && + test-tool dump-split-index .git/index | grep -v ^own >before && + git commit -m "as-is" && + test-tool dump-split-index .git/index | grep -v ^own >after && + test_cmp before after +' + test_done diff --git a/t/t0100-previous.sh b/t/t0100-previous.sh index e0a6940232..58c0b7e9b6 100755 --- a/t/t0100-previous.sh +++ b/t/t0100-previous.sh @@ -56,5 +56,13 @@ test_expect_success 'merge @{-100} before checking out that many branches yet' ' test_must_fail git merge @{-100} ' +test_expect_success 'log -g @{-1}' ' + git checkout -b last_branch && + git checkout -b new_branch && + echo "last_branch@{0}" >expect && + git log -g --format=%gd @{-1} >actual && + test_cmp expect actual +' + test_done diff --git a/t/t0110-urlmatch-normalization.sh b/t/t0110-urlmatch-normalization.sh index 410d5768ca..f99529d838 100755 --- a/t/t0110-urlmatch-normalization.sh +++ b/t/t0110-urlmatch-normalization.sh @@ -9,172 +9,172 @@ tu="$TEST_DIRECTORY/t0110/url" # Note that only file: URLs should be allowed without a host test_expect_success 'url scheme' ' - ! test-urlmatch-normalization "" && - ! test-urlmatch-normalization "_" && - ! test-urlmatch-normalization "scheme" && - ! test-urlmatch-normalization "scheme:" && - ! test-urlmatch-normalization "scheme:/" && - ! test-urlmatch-normalization "scheme://" && - ! test-urlmatch-normalization "file" && - ! test-urlmatch-normalization "file:" && - ! test-urlmatch-normalization "file:/" && - test-urlmatch-normalization "file://" && - ! test-urlmatch-normalization "://acme.co" && - ! test-urlmatch-normalization "x_test://acme.co" && - ! test-urlmatch-normalization "-test://acme.co" && - ! test-urlmatch-normalization "0test://acme.co" && - ! test-urlmatch-normalization "+test://acme.co" && - ! test-urlmatch-normalization ".test://acme.co" && - ! test-urlmatch-normalization "schem%6e://" && - test-urlmatch-normalization "x-Test+v1.0://acme.co" && - test "$(test-urlmatch-normalization -p "AbCdeF://x.Y")" = "abcdef://x.y/" + ! test-tool urlmatch-normalization "" && + ! test-tool urlmatch-normalization "_" && + ! test-tool urlmatch-normalization "scheme" && + ! test-tool urlmatch-normalization "scheme:" && + ! test-tool urlmatch-normalization "scheme:/" && + ! test-tool urlmatch-normalization "scheme://" && + ! test-tool urlmatch-normalization "file" && + ! test-tool urlmatch-normalization "file:" && + ! test-tool urlmatch-normalization "file:/" && + test-tool urlmatch-normalization "file://" && + ! test-tool urlmatch-normalization "://acme.co" && + ! test-tool urlmatch-normalization "x_test://acme.co" && + ! test-tool urlmatch-normalization "-test://acme.co" && + ! test-tool urlmatch-normalization "0test://acme.co" && + ! test-tool urlmatch-normalization "+test://acme.co" && + ! test-tool urlmatch-normalization ".test://acme.co" && + ! test-tool urlmatch-normalization "schem%6e://" && + test-tool urlmatch-normalization "x-Test+v1.0://acme.co" && + test "$(test-tool urlmatch-normalization -p "AbCdeF://x.Y")" = "abcdef://x.y/" ' test_expect_success 'url authority' ' - ! test-urlmatch-normalization "scheme://user:pass@" && - ! test-urlmatch-normalization "scheme://?" && - ! test-urlmatch-normalization "scheme://#" && - ! test-urlmatch-normalization "scheme:///" && - ! test-urlmatch-normalization "scheme://:" && - ! test-urlmatch-normalization "scheme://:555" && - test-urlmatch-normalization "file://user:pass@" && - test-urlmatch-normalization "file://?" && - test-urlmatch-normalization "file://#" && - test-urlmatch-normalization "file:///" && - test-urlmatch-normalization "file://:" && - ! test-urlmatch-normalization "file://:555" && - test-urlmatch-normalization "scheme://user:pass@host" && - test-urlmatch-normalization "scheme://@host" && - test-urlmatch-normalization "scheme://%00@host" && - ! test-urlmatch-normalization "scheme://%%@host" && - ! test-urlmatch-normalization "scheme://host_" && - test-urlmatch-normalization "scheme://user:pass@host/" && - test-urlmatch-normalization "scheme://@host/" && - test-urlmatch-normalization "scheme://host/" && - test-urlmatch-normalization "scheme://host?x" && - test-urlmatch-normalization "scheme://host#x" && - test-urlmatch-normalization "scheme://host/@" && - test-urlmatch-normalization "scheme://host?@x" && - test-urlmatch-normalization "scheme://host#@x" && - test-urlmatch-normalization "scheme://[::1]" && - test-urlmatch-normalization "scheme://[::1]/" && - ! test-urlmatch-normalization "scheme://hos%41/" && - test-urlmatch-normalization "scheme://[invalid....:/" && - test-urlmatch-normalization "scheme://invalid....:]/" && - ! test-urlmatch-normalization "scheme://invalid....:[/" && - ! test-urlmatch-normalization "scheme://invalid....:[" + ! test-tool urlmatch-normalization "scheme://user:pass@" && + ! test-tool urlmatch-normalization "scheme://?" && + ! test-tool urlmatch-normalization "scheme://#" && + ! test-tool urlmatch-normalization "scheme:///" && + ! test-tool urlmatch-normalization "scheme://:" && + ! test-tool urlmatch-normalization "scheme://:555" && + test-tool urlmatch-normalization "file://user:pass@" && + test-tool urlmatch-normalization "file://?" && + test-tool urlmatch-normalization "file://#" && + test-tool urlmatch-normalization "file:///" && + test-tool urlmatch-normalization "file://:" && + ! test-tool urlmatch-normalization "file://:555" && + test-tool urlmatch-normalization "scheme://user:pass@host" && + test-tool urlmatch-normalization "scheme://@host" && + test-tool urlmatch-normalization "scheme://%00@host" && + ! test-tool urlmatch-normalization "scheme://%%@host" && + ! test-tool urlmatch-normalization "scheme://host_" && + test-tool urlmatch-normalization "scheme://user:pass@host/" && + test-tool urlmatch-normalization "scheme://@host/" && + test-tool urlmatch-normalization "scheme://host/" && + test-tool urlmatch-normalization "scheme://host?x" && + test-tool urlmatch-normalization "scheme://host#x" && + test-tool urlmatch-normalization "scheme://host/@" && + test-tool urlmatch-normalization "scheme://host?@x" && + test-tool urlmatch-normalization "scheme://host#@x" && + test-tool urlmatch-normalization "scheme://[::1]" && + test-tool urlmatch-normalization "scheme://[::1]/" && + ! test-tool urlmatch-normalization "scheme://hos%41/" && + test-tool urlmatch-normalization "scheme://[invalid....:/" && + test-tool urlmatch-normalization "scheme://invalid....:]/" && + ! test-tool urlmatch-normalization "scheme://invalid....:[/" && + ! test-tool urlmatch-normalization "scheme://invalid....:[" ' test_expect_success 'url port checks' ' - test-urlmatch-normalization "xyz://q@some.host:" && - test-urlmatch-normalization "xyz://q@some.host:456/" && - ! test-urlmatch-normalization "xyz://q@some.host:0" && - ! test-urlmatch-normalization "xyz://q@some.host:0000000" && - test-urlmatch-normalization "xyz://q@some.host:0000001?" && - test-urlmatch-normalization "xyz://q@some.host:065535#" && - test-urlmatch-normalization "xyz://q@some.host:65535" && - ! test-urlmatch-normalization "xyz://q@some.host:65536" && - ! test-urlmatch-normalization "xyz://q@some.host:99999" && - ! test-urlmatch-normalization "xyz://q@some.host:100000" && - ! test-urlmatch-normalization "xyz://q@some.host:100001" && - test-urlmatch-normalization "http://q@some.host:80" && - test-urlmatch-normalization "https://q@some.host:443" && - test-urlmatch-normalization "http://q@some.host:80/" && - test-urlmatch-normalization "https://q@some.host:443?" && - ! test-urlmatch-normalization "http://q@:8008" && - ! test-urlmatch-normalization "http://:8080" && - ! test-urlmatch-normalization "http://:" && - test-urlmatch-normalization "xyz://q@some.host:456/" && - test-urlmatch-normalization "xyz://[::1]:456/" && - test-urlmatch-normalization "xyz://[::1]:/" && - ! test-urlmatch-normalization "xyz://[::1]:000/" && - ! test-urlmatch-normalization "xyz://[::1]:0%300/" && - ! test-urlmatch-normalization "xyz://[::1]:0x80/" && - ! test-urlmatch-normalization "xyz://[::1]:4294967297/" && - ! test-urlmatch-normalization "xyz://[::1]:030f/" + test-tool urlmatch-normalization "xyz://q@some.host:" && + test-tool urlmatch-normalization "xyz://q@some.host:456/" && + ! test-tool urlmatch-normalization "xyz://q@some.host:0" && + ! test-tool urlmatch-normalization "xyz://q@some.host:0000000" && + test-tool urlmatch-normalization "xyz://q@some.host:0000001?" && + test-tool urlmatch-normalization "xyz://q@some.host:065535#" && + test-tool urlmatch-normalization "xyz://q@some.host:65535" && + ! test-tool urlmatch-normalization "xyz://q@some.host:65536" && + ! test-tool urlmatch-normalization "xyz://q@some.host:99999" && + ! test-tool urlmatch-normalization "xyz://q@some.host:100000" && + ! test-tool urlmatch-normalization "xyz://q@some.host:100001" && + test-tool urlmatch-normalization "http://q@some.host:80" && + test-tool urlmatch-normalization "https://q@some.host:443" && + test-tool urlmatch-normalization "http://q@some.host:80/" && + test-tool urlmatch-normalization "https://q@some.host:443?" && + ! test-tool urlmatch-normalization "http://q@:8008" && + ! test-tool urlmatch-normalization "http://:8080" && + ! test-tool urlmatch-normalization "http://:" && + test-tool urlmatch-normalization "xyz://q@some.host:456/" && + test-tool urlmatch-normalization "xyz://[::1]:456/" && + test-tool urlmatch-normalization "xyz://[::1]:/" && + ! test-tool urlmatch-normalization "xyz://[::1]:000/" && + ! test-tool urlmatch-normalization "xyz://[::1]:0%300/" && + ! test-tool urlmatch-normalization "xyz://[::1]:0x80/" && + ! test-tool urlmatch-normalization "xyz://[::1]:4294967297/" && + ! test-tool urlmatch-normalization "xyz://[::1]:030f/" ' test_expect_success 'url port normalization' ' - test "$(test-urlmatch-normalization -p "http://x:800")" = "http://x:800/" && - test "$(test-urlmatch-normalization -p "http://x:0800")" = "http://x:800/" && - test "$(test-urlmatch-normalization -p "http://x:00000800")" = "http://x:800/" && - test "$(test-urlmatch-normalization -p "http://x:065535")" = "http://x:65535/" && - test "$(test-urlmatch-normalization -p "http://x:1")" = "http://x:1/" && - test "$(test-urlmatch-normalization -p "http://x:80")" = "http://x/" && - test "$(test-urlmatch-normalization -p "http://x:080")" = "http://x/" && - test "$(test-urlmatch-normalization -p "http://x:000000080")" = "http://x/" && - test "$(test-urlmatch-normalization -p "https://x:443")" = "https://x/" && - test "$(test-urlmatch-normalization -p "https://x:0443")" = "https://x/" && - test "$(test-urlmatch-normalization -p "https://x:000000443")" = "https://x/" + test "$(test-tool urlmatch-normalization -p "http://x:800")" = "http://x:800/" && + test "$(test-tool urlmatch-normalization -p "http://x:0800")" = "http://x:800/" && + test "$(test-tool urlmatch-normalization -p "http://x:00000800")" = "http://x:800/" && + test "$(test-tool urlmatch-normalization -p "http://x:065535")" = "http://x:65535/" && + test "$(test-tool urlmatch-normalization -p "http://x:1")" = "http://x:1/" && + test "$(test-tool urlmatch-normalization -p "http://x:80")" = "http://x/" && + test "$(test-tool urlmatch-normalization -p "http://x:080")" = "http://x/" && + test "$(test-tool urlmatch-normalization -p "http://x:000000080")" = "http://x/" && + test "$(test-tool urlmatch-normalization -p "https://x:443")" = "https://x/" && + test "$(test-tool urlmatch-normalization -p "https://x:0443")" = "https://x/" && + test "$(test-tool urlmatch-normalization -p "https://x:000000443")" = "https://x/" ' test_expect_success 'url general escapes' ' - ! test-urlmatch-normalization "http://x.y?%fg" && - test "$(test-urlmatch-normalization -p "X://W/%7e%41^%3a")" = "x://w/~A%5E%3A" && - test "$(test-urlmatch-normalization -p "X://W/:/?#[]@")" = "x://w/:/?#[]@" && - test "$(test-urlmatch-normalization -p "X://W/$&()*+,;=")" = "x://w/$&()*+,;=" && - test "$(test-urlmatch-normalization -p "X://W/'\''")" = "x://w/'\''" && - test "$(test-urlmatch-normalization -p "X://W?'\!'")" = "x://w/?'\!'" + ! test-tool urlmatch-normalization "http://x.y?%fg" && + test "$(test-tool urlmatch-normalization -p "X://W/%7e%41^%3a")" = "x://w/~A%5E%3A" && + test "$(test-tool urlmatch-normalization -p "X://W/:/?#[]@")" = "x://w/:/?#[]@" && + test "$(test-tool urlmatch-normalization -p "X://W/$&()*+,;=")" = "x://w/$&()*+,;=" && + test "$(test-tool urlmatch-normalization -p "X://W/'\''")" = "x://w/'\''" && + test "$(test-tool urlmatch-normalization -p "X://W?'\!'")" = "x://w/?'\!'" ' test_expect_success !MINGW 'url high-bit escapes' ' - test "$(test-urlmatch-normalization -p "$(cat "$tu-1")")" = "x://q/%01%02%03%04%05%06%07%08%0E%0F%10%11%12" && - test "$(test-urlmatch-normalization -p "$(cat "$tu-2")")" = "x://q/%13%14%15%16%17%18%19%1B%1C%1D%1E%1F%7F" && - test "$(test-urlmatch-normalization -p "$(cat "$tu-3")")" = "x://q/%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F" && - test "$(test-urlmatch-normalization -p "$(cat "$tu-4")")" = "x://q/%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F" && - test "$(test-urlmatch-normalization -p "$(cat "$tu-5")")" = "x://q/%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF" && - test "$(test-urlmatch-normalization -p "$(cat "$tu-6")")" = "x://q/%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF" && - test "$(test-urlmatch-normalization -p "$(cat "$tu-7")")" = "x://q/%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF" && - test "$(test-urlmatch-normalization -p "$(cat "$tu-8")")" = "x://q/%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF" && - test "$(test-urlmatch-normalization -p "$(cat "$tu-9")")" = "x://q/%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF" && - test "$(test-urlmatch-normalization -p "$(cat "$tu-10")")" = "x://q/%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF" + test "$(test-tool urlmatch-normalization -p "$(cat "$tu-1")")" = "x://q/%01%02%03%04%05%06%07%08%0E%0F%10%11%12" && + test "$(test-tool urlmatch-normalization -p "$(cat "$tu-2")")" = "x://q/%13%14%15%16%17%18%19%1B%1C%1D%1E%1F%7F" && + test "$(test-tool urlmatch-normalization -p "$(cat "$tu-3")")" = "x://q/%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%8F" && + test "$(test-tool urlmatch-normalization -p "$(cat "$tu-4")")" = "x://q/%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F" && + test "$(test-tool urlmatch-normalization -p "$(cat "$tu-5")")" = "x://q/%A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF" && + test "$(test-tool urlmatch-normalization -p "$(cat "$tu-6")")" = "x://q/%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%BD%BE%BF" && + test "$(test-tool urlmatch-normalization -p "$(cat "$tu-7")")" = "x://q/%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF" && + test "$(test-tool urlmatch-normalization -p "$(cat "$tu-8")")" = "x://q/%D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF" && + test "$(test-tool urlmatch-normalization -p "$(cat "$tu-9")")" = "x://q/%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%EB%EC%ED%EE%EF" && + test "$(test-tool urlmatch-normalization -p "$(cat "$tu-10")")" = "x://q/%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF" ' test_expect_success 'url utf-8 escapes' ' - test "$(test-urlmatch-normalization -p "$(cat "$tu-11")")" = "x://q/%C2%80%DF%BF%E0%A0%80%EF%BF%BD%F0%90%80%80%F0%AF%BF%BD" + test "$(test-tool urlmatch-normalization -p "$(cat "$tu-11")")" = "x://q/%C2%80%DF%BF%E0%A0%80%EF%BF%BD%F0%90%80%80%F0%AF%BF%BD" ' test_expect_success 'url username/password escapes' ' - test "$(test-urlmatch-normalization -p "x://%41%62(^):%70+d@foo")" = "x://Ab(%5E):p+d@foo/" + test "$(test-tool urlmatch-normalization -p "x://%41%62(^):%70+d@foo")" = "x://Ab(%5E):p+d@foo/" ' test_expect_success 'url normalized lengths' ' - test "$(test-urlmatch-normalization -l "Http://%4d%65:%4d^%70@The.Host")" = 25 && - test "$(test-urlmatch-normalization -l "http://%41:%42@x.y/%61/")" = 17 && - test "$(test-urlmatch-normalization -l "http://@x.y/^")" = 15 + test "$(test-tool urlmatch-normalization -l "Http://%4d%65:%4d^%70@The.Host")" = 25 && + test "$(test-tool urlmatch-normalization -l "http://%41:%42@x.y/%61/")" = 17 && + test "$(test-tool urlmatch-normalization -l "http://@x.y/^")" = 15 ' test_expect_success 'url . and .. segments' ' - test "$(test-urlmatch-normalization -p "x://y/.")" = "x://y/" && - test "$(test-urlmatch-normalization -p "x://y/./")" = "x://y/" && - test "$(test-urlmatch-normalization -p "x://y/a/.")" = "x://y/a" && - test "$(test-urlmatch-normalization -p "x://y/a/./")" = "x://y/a/" && - test "$(test-urlmatch-normalization -p "x://y/.?")" = "x://y/?" && - test "$(test-urlmatch-normalization -p "x://y/./?")" = "x://y/?" && - test "$(test-urlmatch-normalization -p "x://y/a/.?")" = "x://y/a?" && - test "$(test-urlmatch-normalization -p "x://y/a/./?")" = "x://y/a/?" && - test "$(test-urlmatch-normalization -p "x://y/a/./b/.././../c")" = "x://y/c" && - test "$(test-urlmatch-normalization -p "x://y/a/./b/../.././c/")" = "x://y/c/" && - test "$(test-urlmatch-normalization -p "x://y/a/./b/.././../c/././.././.")" = "x://y/" && - ! test-urlmatch-normalization "x://y/a/./b/.././../c/././.././.." && - test "$(test-urlmatch-normalization -p "x://y/a/./?/././..")" = "x://y/a/?/././.." && - test "$(test-urlmatch-normalization -p "x://y/%2e/")" = "x://y/" && - test "$(test-urlmatch-normalization -p "x://y/%2E/")" = "x://y/" && - test "$(test-urlmatch-normalization -p "x://y/a/%2e./")" = "x://y/" && - test "$(test-urlmatch-normalization -p "x://y/b/.%2E/")" = "x://y/" && - test "$(test-urlmatch-normalization -p "x://y/c/%2e%2E/")" = "x://y/" + test "$(test-tool urlmatch-normalization -p "x://y/.")" = "x://y/" && + test "$(test-tool urlmatch-normalization -p "x://y/./")" = "x://y/" && + test "$(test-tool urlmatch-normalization -p "x://y/a/.")" = "x://y/a" && + test "$(test-tool urlmatch-normalization -p "x://y/a/./")" = "x://y/a/" && + test "$(test-tool urlmatch-normalization -p "x://y/.?")" = "x://y/?" && + test "$(test-tool urlmatch-normalization -p "x://y/./?")" = "x://y/?" && + test "$(test-tool urlmatch-normalization -p "x://y/a/.?")" = "x://y/a?" && + test "$(test-tool urlmatch-normalization -p "x://y/a/./?")" = "x://y/a/?" && + test "$(test-tool urlmatch-normalization -p "x://y/a/./b/.././../c")" = "x://y/c" && + test "$(test-tool urlmatch-normalization -p "x://y/a/./b/../.././c/")" = "x://y/c/" && + test "$(test-tool urlmatch-normalization -p "x://y/a/./b/.././../c/././.././.")" = "x://y/" && + ! test-tool urlmatch-normalization "x://y/a/./b/.././../c/././.././.." && + test "$(test-tool urlmatch-normalization -p "x://y/a/./?/././..")" = "x://y/a/?/././.." && + test "$(test-tool urlmatch-normalization -p "x://y/%2e/")" = "x://y/" && + test "$(test-tool urlmatch-normalization -p "x://y/%2E/")" = "x://y/" && + test "$(test-tool urlmatch-normalization -p "x://y/a/%2e./")" = "x://y/" && + test "$(test-tool urlmatch-normalization -p "x://y/b/.%2E/")" = "x://y/" && + test "$(test-tool urlmatch-normalization -p "x://y/c/%2e%2E/")" = "x://y/" ' # http://@foo specifies an empty user name but does not specify a password # http://foo specifies neither a user name nor a password # So they should not be equivalent test_expect_success 'url equivalents' ' - test-urlmatch-normalization "httP://x" "Http://X/" && - test-urlmatch-normalization "Http://%4d%65:%4d^%70@The.Host" "hTTP://Me:%4D^p@the.HOST:80/" && - ! test-urlmatch-normalization "https://@x.y/^" "httpS://x.y:443/^" && - test-urlmatch-normalization "https://@x.y/^" "httpS://@x.y:0443/^" && - test-urlmatch-normalization "https://@x.y/^/../abc" "httpS://@x.y:0443/abc" && - test-urlmatch-normalization "https://@x.y/^/.." "httpS://@x.y:0443/" + test-tool urlmatch-normalization "httP://x" "Http://X/" && + test-tool urlmatch-normalization "Http://%4d%65:%4d^%70@The.Host" "hTTP://Me:%4D^p@the.HOST:80/" && + ! test-tool urlmatch-normalization "https://@x.y/^" "httpS://x.y:443/^" && + test-tool urlmatch-normalization "https://@x.y/^" "httpS://@x.y:0443/^" && + test-tool urlmatch-normalization "https://@x.y/^/../abc" "httpS://@x.y:0443/abc" && + test-tool urlmatch-normalization "https://@x.y/^/.." "httpS://@x.y:0443/" ' test_done diff --git a/t/t0202/test.pl b/t/t0202/test.pl index 2c10cb4693..2cbf7b9590 100755 --- a/t/t0202/test.pl +++ b/t/t0202/test.pl @@ -4,7 +4,7 @@ use lib (split(/:/, $ENV{GITPERLLIB})); use strict; use warnings; use POSIX qw(:locale_h); -use Test::More tests => 8; +use Test::More tests => 13; use Git::I18N; my $has_gettext_library = $Git::I18N::__HAS_LIBRARY; @@ -31,6 +31,8 @@ is_deeply(\@Git::I18N::EXPORT, \@Git::I18N::EXPORT_OK, "sanity: Git::I18N export # more gettext wrapper functions. my %prototypes = (qw( __ $ + __n $$$ + N__ $ )); while (my ($sub, $proto) = each %prototypes) { is(prototype(\&{"Git::I18N::$sub"}), $proto, "sanity: $sub has a $proto prototype"); @@ -46,6 +48,16 @@ is_deeply(\@Git::I18N::EXPORT, \@Git::I18N::EXPORT_OK, "sanity: Git::I18N export my ($got, $expect) = (('TEST: A Perl test string') x 2); is(__($got), $expect, "Passing a string through __() in the C locale works"); + + my ($got_singular, $got_plural, $expect_singular, $expect_plural) = + (('TEST: 1 file', 'TEST: n files') x 2); + + is(__n($got_singular, $got_plural, 1), $expect_singular, + "Get singular string through __n() in C locale"); + is(__n($got_singular, $got_plural, 2), $expect_plural, + "Get plural string through __n() in C locale"); + + is(N__($got), $expect, "Passing a string through N__() in the C locale works"); } # Test a basic message on different locales diff --git a/t/t0203-gettext-setlocale-sanity.sh b/t/t0203-gettext-setlocale-sanity.sh index a212460081..71b0d74b4d 100755 --- a/t/t0203-gettext-setlocale-sanity.sh +++ b/t/t0203-gettext-setlocale-sanity.sh @@ -8,7 +8,7 @@ test_description="The Git C functions aren't broken by setlocale(3)" . ./lib-gettext.sh test_expect_success 'git show a ISO-8859-1 commit under C locale' ' - . "$TEST_DIRECTORY"/t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && test_commit "iso-c-commit" iso-under-c && git show >out 2>err && ! test -s err && @@ -16,7 +16,7 @@ test_expect_success 'git show a ISO-8859-1 commit under C locale' ' ' test_expect_success GETTEXT_LOCALE 'git show a ISO-8859-1 commit under a UTF-8 locale' ' - . "$TEST_DIRECTORY"/t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && test_commit "iso-utf8-commit" iso-under-utf8 && LANGUAGE=is LC_ALL="$is_IS_locale" git show >out 2>err && ! test -s err && diff --git a/t/t0205-gettext-poison.sh b/t/t0205-gettext-poison.sh index 2361590d54..438e778d6a 100755 --- a/t/t0205-gettext-poison.sh +++ b/t/t0205-gettext-poison.sh @@ -7,10 +7,6 @@ test_description='Gettext Shell poison' . ./lib-gettext.sh -test_expect_success GETTEXT_POISON "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" ' - test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME" -' - test_expect_success GETTEXT_POISON 'sanity: $GIT_INTERNAL_GETTEXT_SH_SCHEME" is poison' ' test "$GIT_INTERNAL_GETTEXT_SH_SCHEME" = "poison" ' diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh index d7ef44b4a2..03bd31e9f2 100755 --- a/t/t0300-credentials.sh +++ b/t/t0300-credentials.sh @@ -298,4 +298,15 @@ test_expect_success 'helpers can abort the process' ' test_cmp expect stdout ' +test_expect_success 'empty helper spec resets helper list' ' + test_config credential.helper "verbatim file file" && + check fill "" "verbatim cmdline cmdline" <<-\EOF + -- + username=cmdline + password=cmdline + -- + verbatim: get + EOF +' + test_done diff --git a/t/t0301-credential-cache.sh b/t/t0301-credential-cache.sh index 82c8411210..fd92533acf 100755 --- a/t/t0301-credential-cache.sh +++ b/t/t0301-credential-cache.sh @@ -12,7 +12,100 @@ test -z "$NO_UNIX_SOCKETS" || { # don't leave a stale daemon running trap 'code=$?; git credential-cache exit; (exit $code); die' EXIT +# test that the daemon works with no special setup helper_test cache + +test_expect_success 'socket defaults to ~/.cache/git/credential/socket' ' + test_when_finished " + git credential-cache exit && + rmdir -p .cache/git/credential/ + " && + test_path_is_missing "$HOME/.git-credential-cache" && + test -S "$HOME/.cache/git/credential/socket" +' + +XDG_CACHE_HOME="$HOME/xdg" +export XDG_CACHE_HOME +# test behavior when XDG_CACHE_HOME is set +helper_test cache + +test_expect_success "use custom XDG_CACHE_HOME if set and default sockets are not created" ' + test_when_finished "git credential-cache exit" && + test -S "$XDG_CACHE_HOME/git/credential/socket" && + test_path_is_missing "$HOME/.git-credential-cache/socket" && + test_path_is_missing "$HOME/.cache/git/credential/socket" +' +unset XDG_CACHE_HOME + +test_expect_success 'credential-cache --socket option overrides default location' ' + test_when_finished " + git credential-cache exit --socket \"\$HOME/dir/socket\" && + rmdir \"\$HOME/dir\" + " && + check approve "cache --socket \"\$HOME/dir/socket\"" <<-\EOF && + protocol=https + host=example.com + username=store-user + password=store-pass + EOF + test -S "$HOME/dir/socket" +' + +test_expect_success "use custom XDG_CACHE_HOME even if xdg socket exists" ' + test_when_finished " + git credential-cache exit && + sane_unset XDG_CACHE_HOME + " && + check approve cache <<-\EOF && + protocol=https + host=example.com + username=store-user + password=store-pass + EOF + test -S "$HOME/.cache/git/credential/socket" && + XDG_CACHE_HOME="$HOME/xdg" && + export XDG_CACHE_HOME && + check approve cache <<-\EOF && + protocol=https + host=example.com + username=store-user + password=store-pass + EOF + test -S "$XDG_CACHE_HOME/git/credential/socket" +' + +test_expect_success 'use user socket if user directory exists' ' + test_when_finished " + git credential-cache exit && + rmdir \"\$HOME/.git-credential-cache/\" + " && + mkdir -p -m 700 "$HOME/.git-credential-cache/" && + check approve cache <<-\EOF && + protocol=https + host=example.com + username=store-user + password=store-pass + EOF + test -S "$HOME/.git-credential-cache/socket" +' + +test_expect_success SYMLINKS 'use user socket if user directory is a symlink to a directory' ' + test_when_finished " + git credential-cache exit && + rmdir \"\$HOME/dir/\" && + rm \"\$HOME/.git-credential-cache\" + " && + mkdir -p -m 700 "$HOME/dir/" && + ln -s "$HOME/dir" "$HOME/.git-credential-cache" && + check approve cache <<-\EOF && + protocol=https + host=example.com + username=store-user + password=store-pass + EOF + test -S "$HOME/.git-credential-cache/socket" +' + helper_test_timeout cache --timeout=1 # we can't rely on our "trap" above working after test_done, diff --git a/t/t0302-credential-store.sh b/t/t0302-credential-store.sh index 0979df93a1..d6b54e8c65 100755 --- a/t/t0302-credential-store.sh +++ b/t/t0302-credential-store.sh @@ -37,7 +37,7 @@ helper_test store unset XDG_CONFIG_HOME test_expect_success 'if custom xdg file exists, home and xdg files not created' ' - test_when_finished "rm -f $HOME/xdg/git/credentials" && + test_when_finished "rm -f \"$HOME/xdg/git/credentials\"" && test -s "$HOME/xdg/git/credentials" && test_path_is_missing "$HOME/.git-credentials" && test_path_is_missing "$HOME/.config/git/credentials" @@ -75,7 +75,7 @@ test_expect_success 'get: use xdg file if home file has no matches' ' EOF ' -test_expect_success POSIXPERM 'get: use xdg file if home file is unreadable' ' +test_expect_success POSIXPERM,SANITY 'get: use xdg file if home file is unreadable' ' echo "https://home-user:home-pass@example.com" >"$HOME/.git-credentials" && chmod -r "$HOME/.git-credentials" && mkdir -p "$HOME/.config/git" && diff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh new file mode 100755 index 0000000000..cc18b75c03 --- /dev/null +++ b/t/t0410-partial-clone.sh @@ -0,0 +1,343 @@ +#!/bin/sh + +test_description='partial clone' + +. ./test-lib.sh + +delete_object () { + rm $1/.git/objects/$(echo $2 | sed -e 's|^..|&/|') +} + +pack_as_from_promisor () { + HASH=$(git -C repo pack-objects .git/objects/pack/pack) && + >repo/.git/objects/pack/pack-$HASH.promisor && + echo $HASH +} + +promise_and_delete () { + HASH=$(git -C repo rev-parse "$1") && + git -C repo tag -a -m message my_annotated_tag "$HASH" && + git -C repo rev-parse my_annotated_tag | pack_as_from_promisor && + # tag -d prints a message to stdout, so redirect it + git -C repo tag -d my_annotated_tag >/dev/null && + delete_object repo "$HASH" +} + +test_expect_success 'missing reflog object, but promised by a commit, passes fsck' ' + test_create_repo repo && + test_commit -C repo my_commit && + + A=$(git -C repo commit-tree -m a HEAD^{tree}) && + C=$(git -C repo commit-tree -m c -p $A HEAD^{tree}) && + + # Reference $A only from reflog, and delete it + git -C repo branch my_branch "$A" && + git -C repo branch -f my_branch my_commit && + delete_object repo "$A" && + + # State that we got $C, which refers to $A, from promisor + printf "$C\n" | pack_as_from_promisor && + + # Normally, it fails + test_must_fail git -C repo fsck && + + # But with the extension, it succeeds + git -C repo config core.repositoryformatversion 1 && + git -C repo config extensions.partialclone "arbitrary string" && + git -C repo fsck +' + +test_expect_success 'missing reflog object, but promised by a tag, passes fsck' ' + rm -rf repo && + test_create_repo repo && + test_commit -C repo my_commit && + + A=$(git -C repo commit-tree -m a HEAD^{tree}) && + git -C repo tag -a -m d my_tag_name $A && + T=$(git -C repo rev-parse my_tag_name) && + git -C repo tag -d my_tag_name && + + # Reference $A only from reflog, and delete it + git -C repo branch my_branch "$A" && + git -C repo branch -f my_branch my_commit && + delete_object repo "$A" && + + # State that we got $T, which refers to $A, from promisor + printf "$T\n" | pack_as_from_promisor && + + git -C repo config core.repositoryformatversion 1 && + git -C repo config extensions.partialclone "arbitrary string" && + git -C repo fsck +' + +test_expect_success 'missing reflog object alone fails fsck, even with extension set' ' + rm -rf repo && + test_create_repo repo && + test_commit -C repo my_commit && + + A=$(git -C repo commit-tree -m a HEAD^{tree}) && + B=$(git -C repo commit-tree -m b HEAD^{tree}) && + + # Reference $A only from reflog, and delete it + git -C repo branch my_branch "$A" && + git -C repo branch -f my_branch my_commit && + delete_object repo "$A" && + + git -C repo config core.repositoryformatversion 1 && + git -C repo config extensions.partialclone "arbitrary string" && + test_must_fail git -C repo fsck +' + +test_expect_success 'missing ref object, but promised, passes fsck' ' + rm -rf repo && + test_create_repo repo && + test_commit -C repo my_commit && + + A=$(git -C repo commit-tree -m a HEAD^{tree}) && + + # Reference $A only from ref + git -C repo branch my_branch "$A" && + promise_and_delete "$A" && + + git -C repo config core.repositoryformatversion 1 && + git -C repo config extensions.partialclone "arbitrary string" && + git -C repo fsck +' + +test_expect_success 'missing object, but promised, passes fsck' ' + rm -rf repo && + test_create_repo repo && + test_commit -C repo 1 && + test_commit -C repo 2 && + test_commit -C repo 3 && + git -C repo tag -a annotated_tag -m "annotated tag" && + + C=$(git -C repo rev-parse 1) && + T=$(git -C repo rev-parse 2^{tree}) && + B=$(git hash-object repo/3.t) && + AT=$(git -C repo rev-parse annotated_tag) && + + promise_and_delete "$C" && + promise_and_delete "$T" && + promise_and_delete "$B" && + promise_and_delete "$AT" && + + git -C repo config core.repositoryformatversion 1 && + git -C repo config extensions.partialclone "arbitrary string" && + git -C repo fsck +' + +test_expect_success 'missing CLI object, but promised, passes fsck' ' + rm -rf repo && + test_create_repo repo && + test_commit -C repo my_commit && + + A=$(git -C repo commit-tree -m a HEAD^{tree}) && + promise_and_delete "$A" && + + git -C repo config core.repositoryformatversion 1 && + git -C repo config extensions.partialclone "arbitrary string" && + git -C repo fsck "$A" +' + +test_expect_success 'fetching of missing objects' ' + rm -rf repo && + test_create_repo server && + test_commit -C server foo && + git -C server repack -a -d --write-bitmap-index && + + git clone "file://$(pwd)/server" repo && + HASH=$(git -C repo rev-parse foo) && + rm -rf repo/.git/objects/* && + + git -C repo config core.repositoryformatversion 1 && + git -C repo config extensions.partialclone "origin" && + git -C repo cat-file -p "$HASH" && + + # Ensure that the .promisor file is written, and check that its + # associated packfile contains the object + ls repo/.git/objects/pack/pack-*.promisor >promisorlist && + test_line_count = 1 promisorlist && + IDX=$(cat promisorlist | sed "s/promisor$/idx/") && + git verify-pack --verbose "$IDX" | grep "$HASH" +' + +test_expect_success 'rev-list stops traversal at missing and promised commit' ' + rm -rf repo && + test_create_repo repo && + test_commit -C repo foo && + test_commit -C repo bar && + + FOO=$(git -C repo rev-parse foo) && + promise_and_delete "$FOO" && + + git -C repo config core.repositoryformatversion 1 && + git -C repo config extensions.partialclone "arbitrary string" && + git -C repo rev-list --exclude-promisor-objects --objects bar >out && + grep $(git -C repo rev-parse bar) out && + ! grep $FOO out +' + +test_expect_success 'rev-list stops traversal at missing and promised tree' ' + rm -rf repo && + test_create_repo repo && + test_commit -C repo foo && + mkdir repo/a_dir && + echo something >repo/a_dir/something && + git -C repo add a_dir/something && + git -C repo commit -m bar && + + # foo^{tree} (tree referenced from commit) + TREE=$(git -C repo rev-parse foo^{tree}) && + + # a tree referenced by HEAD^{tree} (tree referenced from tree) + TREE2=$(git -C repo ls-tree HEAD^{tree} | grep " tree " | head -1 | cut -b13-52) && + + promise_and_delete "$TREE" && + promise_and_delete "$TREE2" && + + git -C repo config core.repositoryformatversion 1 && + git -C repo config extensions.partialclone "arbitrary string" && + git -C repo rev-list --exclude-promisor-objects --objects HEAD >out && + grep $(git -C repo rev-parse foo) out && + ! grep $TREE out && + grep $(git -C repo rev-parse HEAD) out && + ! grep $TREE2 out +' + +test_expect_success 'rev-list stops traversal at missing and promised blob' ' + rm -rf repo && + test_create_repo repo && + echo something >repo/something && + git -C repo add something && + git -C repo commit -m foo && + + BLOB=$(git -C repo hash-object -w something) && + promise_and_delete "$BLOB" && + + git -C repo config core.repositoryformatversion 1 && + git -C repo config extensions.partialclone "arbitrary string" && + git -C repo rev-list --exclude-promisor-objects --objects HEAD >out && + grep $(git -C repo rev-parse HEAD) out && + ! grep $BLOB out +' + +test_expect_success 'rev-list stops traversal at promisor commit, tree, and blob' ' + rm -rf repo && + test_create_repo repo && + test_commit -C repo foo && + test_commit -C repo bar && + test_commit -C repo baz && + + COMMIT=$(git -C repo rev-parse foo) && + TREE=$(git -C repo rev-parse bar^{tree}) && + BLOB=$(git hash-object repo/baz.t) && + printf "%s\n%s\n%s\n" $COMMIT $TREE $BLOB | pack_as_from_promisor && + + git -C repo config core.repositoryformatversion 1 && + git -C repo config extensions.partialclone "arbitrary string" && + git -C repo rev-list --exclude-promisor-objects --objects HEAD >out && + ! grep $COMMIT out && + ! grep $TREE out && + ! grep $BLOB out && + grep $(git -C repo rev-parse bar) out # sanity check that some walking was done +' + +test_expect_success 'rev-list accepts missing and promised objects on command line' ' + rm -rf repo && + test_create_repo repo && + test_commit -C repo foo && + test_commit -C repo bar && + test_commit -C repo baz && + + COMMIT=$(git -C repo rev-parse foo) && + TREE=$(git -C repo rev-parse bar^{tree}) && + BLOB=$(git hash-object repo/baz.t) && + + promise_and_delete $COMMIT && + promise_and_delete $TREE && + promise_and_delete $BLOB && + + git -C repo config core.repositoryformatversion 1 && + git -C repo config extensions.partialclone "arbitrary string" && + git -C repo rev-list --exclude-promisor-objects --objects "$COMMIT" "$TREE" "$BLOB" +' + +test_expect_success 'gc does not repack promisor objects' ' + rm -rf repo && + test_create_repo repo && + test_commit -C repo my_commit && + + TREE_HASH=$(git -C repo rev-parse HEAD^{tree}) && + HASH=$(printf "$TREE_HASH\n" | pack_as_from_promisor) && + + git -C repo config core.repositoryformatversion 1 && + git -C repo config extensions.partialclone "arbitrary string" && + git -C repo gc && + + # Ensure that the promisor packfile still exists, and remove it + test -e repo/.git/objects/pack/pack-$HASH.pack && + rm repo/.git/objects/pack/pack-$HASH.* && + + # Ensure that the single other pack contains the commit, but not the tree + ls repo/.git/objects/pack/pack-*.pack >packlist && + test_line_count = 1 packlist && + git verify-pack repo/.git/objects/pack/pack-*.pack -v >out && + grep "$(git -C repo rev-parse HEAD)" out && + ! grep "$TREE_HASH" out +' + +test_expect_success 'gc stops traversal when a missing but promised object is reached' ' + rm -rf repo && + test_create_repo repo && + test_commit -C repo my_commit && + + TREE_HASH=$(git -C repo rev-parse HEAD^{tree}) && + HASH=$(promise_and_delete $TREE_HASH) && + + git -C repo config core.repositoryformatversion 1 && + git -C repo config extensions.partialclone "arbitrary string" && + git -C repo gc && + + # Ensure that the promisor packfile still exists, and remove it + test -e repo/.git/objects/pack/pack-$HASH.pack && + rm repo/.git/objects/pack/pack-$HASH.* && + + # Ensure that the single other pack contains the commit, but not the tree + ls repo/.git/objects/pack/pack-*.pack >packlist && + test_line_count = 1 packlist && + git verify-pack repo/.git/objects/pack/pack-*.pack -v >out && + grep "$(git -C repo rev-parse HEAD)" out && + ! grep "$TREE_HASH" out +' + +LIB_HTTPD_PORT=12345 # default port, 410, cannot be used as non-root +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +test_expect_success 'fetching of missing objects from an HTTP server' ' + rm -rf repo && + SERVER="$HTTPD_DOCUMENT_ROOT_PATH/server" && + test_create_repo "$SERVER" && + test_commit -C "$SERVER" foo && + git -C "$SERVER" repack -a -d --write-bitmap-index && + + git clone $HTTPD_URL/smart/server repo && + HASH=$(git -C repo rev-parse foo) && + rm -rf repo/.git/objects/* && + + git -C repo config core.repositoryformatversion 1 && + git -C repo config extensions.partialclone "origin" && + git -C repo cat-file -p "$HASH" && + + # Ensure that the .promisor file is written, and check that its + # associated packfile contains the object + ls repo/.git/objects/pack/pack-*.promisor >promisorlist && + test_line_count = 1 promisorlist && + IDX=$(cat promisorlist | sed "s/promisor$/idx/") && + git verify-pack --verbose "$IDX" | grep "$HASH" +' + +stop_httpd + +test_done diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh index a0b79b4839..3c4d2d6045 100755 --- a/t/t1000-read-tree-m-3way.sh +++ b/t/t1000-read-tree-m-3way.sh @@ -128,29 +128,29 @@ cat >expected <<\EOF EOF check_result () { - git ls-files --stage | sed -e 's/ '"$_x40"' / X /' >current && - test_cmp expected current + git ls-files --stage | sed -e 's/ '"$_x40"' / X /' >current && + test_cmp expected current } # This is done on an empty work directory, which is the normal # merge person behaviour. -test_expect_success \ - '3-way merge with git read-tree -m, empty cache' \ - "rm -fr [NDMALTS][NDMALTSF] Z && - rm .git/index && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" +test_expect_success '3-way merge with git read-tree -m, empty cache' ' + rm -fr [NDMALTS][NDMALTSF] Z && + rm .git/index && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' # This starts out with the first head, which is the normal # patch submitter behaviour. -test_expect_success \ - '3-way merge with git read-tree -m, match H' \ - "rm -fr [NDMALTS][NDMALTSF] Z && - rm .git/index && - read_tree_must_succeed $tree_A && - git checkout-index -f -u -a && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" +test_expect_success '3-way merge with git read-tree -m, match H' ' + rm -fr [NDMALTS][NDMALTSF] Z && + rm .git/index && + read_tree_must_succeed $tree_A && + git checkout-index -f -u -a && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' : <<\END_OF_CASE_TABLE @@ -208,322 +208,304 @@ DF (file) when tree B require DF to be a directory by having DF/DF END_OF_CASE_TABLE -test_expect_success '1 - must not have an entry not in A.' " - rm -f .git/index XX && - echo XX >XX && - git update-index --add XX && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '2 - must match B in !O && !A && B case.' \ - "rm -f .git/index NA && - cp .orig-B/NA NA && - git update-index --add NA && - read_tree_must_succeed -m $tree_O $tree_A $tree_B" - -test_expect_success \ - '2 - matching B alone is OK in !O && !A && B case.' \ - "rm -f .git/index NA && - cp .orig-B/NA NA && - git update-index --add NA && - echo extra >>NA && - read_tree_must_succeed -m $tree_O $tree_A $tree_B" - -test_expect_success \ - '3 - must match A in !O && A && !B case.' \ - "rm -f .git/index AN && - cp .orig-A/AN AN && - git update-index --add AN && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '3 - matching A alone is OK in !O && A && !B case.' \ - "rm -f .git/index AN && - cp .orig-A/AN AN && - git update-index --add AN && - echo extra >>AN && - read_tree_must_succeed -m $tree_O $tree_A $tree_B" - -test_expect_success \ - '3 (fail) - must match A in !O && A && !B case.' " - rm -f .git/index AN && - cp .orig-A/AN AN && - echo extra >>AN && - git update-index --add AN && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '4 - must match and be up-to-date in !O && A && B && A!=B case.' \ - "rm -f .git/index AA && - cp .orig-A/AA AA && - git update-index --add AA && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' " - rm -f .git/index AA && - cp .orig-A/AA AA && - git update-index --add AA && - echo extra >>AA && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' " - rm -f .git/index AA && - cp .orig-A/AA AA && - echo extra >>AA && - git update-index --add AA && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '5 - must match in !O && A && B && A==B case.' \ - "rm -f .git/index LL && - cp .orig-A/LL LL && - git update-index --add LL && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '5 - must match in !O && A && B && A==B case.' \ - "rm -f .git/index LL && - cp .orig-A/LL LL && - git update-index --add LL && - echo extra >>LL && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '5 (fail) - must match A in !O && A && B && A==B case.' " - rm -f .git/index LL && - cp .orig-A/LL LL && - echo extra >>LL && - git update-index --add LL && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '6 - must not exist in O && !A && !B case' " - rm -f .git/index DD && - echo DD >DD && - git update-index --add DD && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '7 - must not exist in O && !A && B && O!=B case' " - rm -f .git/index DM && - cp .orig-B/DM DM && - git update-index --add DM && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '8 - must not exist in O && !A && B && O==B case' " - rm -f .git/index DN && - cp .orig-B/DN DN && - git update-index --add DN && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '9 - must match and be up-to-date in O && A && !B && O!=A case' \ - "rm -f .git/index MD && - cp .orig-A/MD MD && - git update-index --add MD && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' " - rm -f .git/index MD && - cp .orig-A/MD MD && - git update-index --add MD && - echo extra >>MD && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' " - rm -f .git/index MD && - cp .orig-A/MD MD && - echo extra >>MD && - git update-index --add MD && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '10 - must match and be up-to-date in O && A && !B && O==A case' \ - "rm -f .git/index ND && - cp .orig-A/ND ND && - git update-index --add ND && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' " - rm -f .git/index ND && - cp .orig-A/ND ND && - git update-index --add ND && - echo extra >>ND && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' " - rm -f .git/index ND && - cp .orig-A/ND ND && - echo extra >>ND && - git update-index --add ND && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '11 - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \ - "rm -f .git/index MM && - cp .orig-A/MM MM && - git update-index --add MM && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' " - rm -f .git/index MM && - cp .orig-A/MM MM && - git update-index --add MM && - echo extra >>MM && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' " - rm -f .git/index MM && - cp .orig-A/MM MM && - echo extra >>MM && - git update-index --add MM && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '12 - must match A in O && A && B && O!=A && A==B case' \ - "rm -f .git/index SS && - cp .orig-A/SS SS && - git update-index --add SS && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '12 - must match A in O && A && B && O!=A && A==B case' \ - "rm -f .git/index SS && - cp .orig-A/SS SS && - git update-index --add SS && - echo extra >>SS && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '12 (fail) - must match A in O && A && B && O!=A && A==B case' " - rm -f .git/index SS && - cp .orig-A/SS SS && - echo extra >>SS && - git update-index --add SS && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '13 - must match A in O && A && B && O!=A && O==B case' \ - "rm -f .git/index MN && - cp .orig-A/MN MN && - git update-index --add MN && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '13 - must match A in O && A && B && O!=A && O==B case' \ - "rm -f .git/index MN && - cp .orig-A/MN MN && - git update-index --add MN && - echo extra >>MN && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '14 - must match and be up-to-date in O && A && B && O==A && O!=B case' \ - "rm -f .git/index NM && - cp .orig-A/NM NM && - git update-index --add NM && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '14 - may match B in O && A && B && O==A && O!=B case' \ - "rm -f .git/index NM && - cp .orig-B/NM NM && - git update-index --add NM && - echo extra >>NM && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' " - rm -f .git/index NM && - cp .orig-A/NM NM && - git update-index --add NM && - echo extra >>NM && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' " - rm -f .git/index NM && - cp .orig-A/NM NM && - echo extra >>NM && - git update-index --add NM && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -test_expect_success \ - '15 - must match A in O && A && B && O==A && O==B case' \ - "rm -f .git/index NN && - cp .orig-A/NN NN && - git update-index --add NN && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '15 - must match A in O && A && B && O==A && O==B case' \ - "rm -f .git/index NN && - cp .orig-A/NN NN && - git update-index --add NN && - echo extra >>NN && - read_tree_must_succeed -m $tree_O $tree_A $tree_B && - check_result" - -test_expect_success \ - '15 (fail) - must match A in O && A && B && O==A && O==B case' " - rm -f .git/index NN && - cp .orig-A/NN NN && - echo extra >>NN && - git update-index --add NN && - read_tree_must_fail -m $tree_O $tree_A $tree_B -" - -# #16 -test_expect_success \ - '16 - A matches in one and B matches in another.' \ - 'rm -f .git/index F16 && - echo F16 >F16 && - git update-index --add F16 && - tree0=$(git write-tree) && - echo E16 >F16 && - git update-index F16 && - tree1=$(git write-tree) && - read_tree_must_succeed -m $tree0 $tree1 $tree1 $tree0 && - git ls-files --stage' +test_expect_success '1 - must not have an entry not in A.' ' + rm -f .git/index XX && + echo XX >XX && + git update-index --add XX && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '2 - must match B in !O && !A && B case.' ' + rm -f .git/index NA && + cp .orig-B/NA NA && + git update-index --add NA && + read_tree_must_succeed -m $tree_O $tree_A $tree_B +' + +test_expect_success '2 - matching B alone is OK in !O && !A && B case.' ' + rm -f .git/index NA && + cp .orig-B/NA NA && + git update-index --add NA && + echo extra >>NA && + read_tree_must_succeed -m $tree_O $tree_A $tree_B +' + +test_expect_success '3 - must match A in !O && A && !B case.' ' + rm -f .git/index AN && + cp .orig-A/AN AN && + git update-index --add AN && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '3 - matching A alone is OK in !O && A && !B case.' ' + rm -f .git/index AN && + cp .orig-A/AN AN && + git update-index --add AN && + echo extra >>AN && + read_tree_must_succeed -m $tree_O $tree_A $tree_B +' + +test_expect_success '3 (fail) - must match A in !O && A && !B case.' ' + rm -f .git/index AN && + cp .orig-A/AN AN && + echo extra >>AN && + git update-index --add AN && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '4 - must match and be up-to-date in !O && A && B && A!=B case.' ' + rm -f .git/index AA && + cp .orig-A/AA AA && + git update-index --add AA && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' ' + rm -f .git/index AA && + cp .orig-A/AA AA && + git update-index --add AA && + echo extra >>AA && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' ' + rm -f .git/index AA && + cp .orig-A/AA AA && + echo extra >>AA && + git update-index --add AA && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '5 - must match in !O && A && B && A==B case.' ' + rm -f .git/index LL && + cp .orig-A/LL LL && + git update-index --add LL && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '5 - must match in !O && A && B && A==B case.' ' + rm -f .git/index LL && + cp .orig-A/LL LL && + git update-index --add LL && + echo extra >>LL && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '5 (fail) - must match A in !O && A && B && A==B case.' ' + rm -f .git/index LL && + cp .orig-A/LL LL && + echo extra >>LL && + git update-index --add LL && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '6 - must not exist in O && !A && !B case' ' + rm -f .git/index DD && + echo DD >DD && + git update-index --add DD && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '7 - must not exist in O && !A && B && O!=B case' ' + rm -f .git/index DM && + cp .orig-B/DM DM && + git update-index --add DM && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '8 - must not exist in O && !A && B && O==B case' ' + rm -f .git/index DN && + cp .orig-B/DN DN && + git update-index --add DN && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '9 - must match and be up-to-date in O && A && !B && O!=A case' ' + rm -f .git/index MD && + cp .orig-A/MD MD && + git update-index --add MD && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' ' + rm -f .git/index MD && + cp .orig-A/MD MD && + git update-index --add MD && + echo extra >>MD && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' ' + rm -f .git/index MD && + cp .orig-A/MD MD && + echo extra >>MD && + git update-index --add MD && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '10 - must match and be up-to-date in O && A && !B && O==A case' ' + rm -f .git/index ND && + cp .orig-A/ND ND && + git update-index --add ND && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' ' + rm -f .git/index ND && + cp .orig-A/ND ND && + git update-index --add ND && + echo extra >>ND && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' ' + rm -f .git/index ND && + cp .orig-A/ND ND && + echo extra >>ND && + git update-index --add ND && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '11 - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' ' + rm -f .git/index MM && + cp .orig-A/MM MM && + git update-index --add MM && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' ' + rm -f .git/index MM && + cp .orig-A/MM MM && + git update-index --add MM && + echo extra >>MM && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' ' + rm -f .git/index MM && + cp .orig-A/MM MM && + echo extra >>MM && + git update-index --add MM && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '12 - must match A in O && A && B && O!=A && A==B case' ' + rm -f .git/index SS && + cp .orig-A/SS SS && + git update-index --add SS && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '12 - must match A in O && A && B && O!=A && A==B case' ' + rm -f .git/index SS && + cp .orig-A/SS SS && + git update-index --add SS && + echo extra >>SS && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '12 (fail) - must match A in O && A && B && O!=A && A==B case' ' + rm -f .git/index SS && + cp .orig-A/SS SS && + echo extra >>SS && + git update-index --add SS && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '13 - must match A in O && A && B && O!=A && O==B case' ' + rm -f .git/index MN && + cp .orig-A/MN MN && + git update-index --add MN && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '13 - must match A in O && A && B && O!=A && O==B case' ' + rm -f .git/index MN && + cp .orig-A/MN MN && + git update-index --add MN && + echo extra >>MN && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '14 - must match and be up-to-date in O && A && B && O==A && O!=B case' ' + rm -f .git/index NM && + cp .orig-A/NM NM && + git update-index --add NM && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '14 - may match B in O && A && B && O==A && O!=B case' ' + rm -f .git/index NM && + cp .orig-B/NM NM && + git update-index --add NM && + echo extra >>NM && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' ' + rm -f .git/index NM && + cp .orig-A/NM NM && + git update-index --add NM && + echo extra >>NM && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' ' + rm -f .git/index NM && + cp .orig-A/NM NM && + echo extra >>NM && + git update-index --add NM && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '15 - must match A in O && A && B && O==A && O==B case' ' + rm -f .git/index NN && + cp .orig-A/NN NN && + git update-index --add NN && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '15 - must match A in O && A && B && O==A && O==B case' ' + rm -f .git/index NN && + cp .orig-A/NN NN && + git update-index --add NN && + echo extra >>NN && + read_tree_must_succeed -m $tree_O $tree_A $tree_B && + check_result +' + +test_expect_success '15 (fail) - must match A in O && A && B && O==A && O==B case' ' + rm -f .git/index NN && + cp .orig-A/NN NN && + echo extra >>NN && + git update-index --add NN && + read_tree_must_fail -m $tree_O $tree_A $tree_B +' + +test_expect_success '16 - A matches in one and B matches in another.' ' + rm -f .git/index F16 && + echo F16 >F16 && + git update-index --add F16 && + tree0=$(git write-tree) && + echo E16 >F16 && + git update-index F16 && + tree1=$(git write-tree) && + read_tree_must_succeed -m $tree0 $tree1 $tree1 $tree0 && + git ls-files --stage +' test_done diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh index db1b6f5cf4..5ededd8e40 100755 --- a/t/t1001-read-tree-m-2way.sh +++ b/t/t1001-read-tree-m-2way.sh @@ -14,10 +14,10 @@ all the combinations described in the two-tree merge "carry forward" rules, found in <Documentation/git read-tree.txt>. In the test, these paths are used: - bozbar - in H, stays in M, modified from bozbar to gnusto - frotz - not in H added in M - nitfol - in H, stays in M unmodified - rezrov - in H, deleted in M + bozbar - in H, stays in M, modified from bozbar to gnusto + frotz - not in H added in M + nitfol - in H, stays in M unmodified + rezrov - in H, deleted in M yomin - not in H or M ' . ./test-lib.sh @@ -60,336 +60,343 @@ EOF sed -e 's/bozbar/gnusto (earlier bozbar)/' bozbar-old >bozbar-new -test_expect_success \ - setup \ - 'echo frotz >frotz && - echo nitfol >nitfol && - cat bozbar-old >bozbar && - echo rezrov >rezrov && - echo yomin >yomin && - git update-index --add nitfol bozbar rezrov && - treeH=$(git write-tree) && - echo treeH $treeH && - git ls-tree $treeH && - - cat bozbar-new >bozbar && - git update-index --add frotz bozbar --force-remove rezrov && - git ls-files --stage >M.out && - treeM=$(git write-tree) && - echo treeM $treeM && - git ls-tree $treeM && - git diff-tree $treeH $treeM' - -test_expect_success \ - '1, 2, 3 - no carry forward' \ - 'rm -f .git/index && - read_tree_twoway $treeH $treeM && - git ls-files --stage >1-3.out && - test_cmp M.out 1-3.out && - check_cache_at bozbar dirty && - check_cache_at frotz dirty && - check_cache_at nitfol dirty' +test_expect_success 'setup' ' + echo frotz >frotz && + echo nitfol >nitfol && + cat bozbar-old >bozbar && + echo rezrov >rezrov && + echo yomin >yomin && + git update-index --add nitfol bozbar rezrov && + treeH=$(git write-tree) && + echo treeH $treeH && + git ls-tree $treeH && + + cat bozbar-new >bozbar && + git update-index --add frotz bozbar --force-remove rezrov && + git ls-files --stage >M.out && + treeM=$(git write-tree) && + echo treeM $treeM && + git ls-tree $treeM && + git diff-tree $treeH $treeM +' +test_expect_success '1, 2, 3 - no carry forward' ' + rm -f .git/index && + read_tree_twoway $treeH $treeM && + git ls-files --stage >1-3.out && + test_cmp M.out 1-3.out && + check_cache_at bozbar dirty && + check_cache_at frotz dirty && + check_cache_at nitfol dirty +' echo '+100644 X 0 yomin' >expected -test_expect_success \ - '4 - carry forward local addition.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - git update-index --add yomin && - read_tree_twoway $treeH $treeM && - git ls-files --stage >4.out && - test_must_fail git diff --no-index M.out 4.out >4diff.out && - compare_change 4diff.out expected && - check_cache_at yomin clean' - -test_expect_success \ - '5 - carry forward local addition.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo yomin >yomin && - git update-index --add yomin && - echo yomin yomin >yomin && - read_tree_twoway $treeH $treeM && - git ls-files --stage >5.out && - test_must_fail git diff --no-index M.out 5.out >5diff.out && - compare_change 5diff.out expected && - check_cache_at yomin dirty' - -test_expect_success \ - '6 - local addition already has the same.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - git update-index --add frotz && - read_tree_twoway $treeH $treeM && - git ls-files --stage >6.out && - test_cmp M.out 6.out && - check_cache_at frotz clean' - -test_expect_success \ - '7 - local addition already has the same.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo frotz >frotz && - git update-index --add frotz && - echo frotz frotz >frotz && - read_tree_twoway $treeH $treeM && - git ls-files --stage >7.out && - test_cmp M.out 7.out && - check_cache_at frotz dirty' - -test_expect_success \ - '8 - conflicting addition.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo frotz frotz >frotz && - git update-index --add frotz && - if read_tree_twoway $treeH $treeM; then false; else :; fi' - -test_expect_success \ - '9 - conflicting addition.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo frotz frotz >frotz && - git update-index --add frotz && - echo frotz >frotz && - if read_tree_twoway $treeH $treeM; then false; else :; fi' - -test_expect_success \ - '10 - path removed.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo rezrov >rezrov && - git update-index --add rezrov && - read_tree_twoway $treeH $treeM && - git ls-files --stage >10.out && - test_cmp M.out 10.out' - -test_expect_success \ - '11 - dirty path removed.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo rezrov >rezrov && - git update-index --add rezrov && - echo rezrov rezrov >rezrov && - if read_tree_twoway $treeH $treeM; then false; else :; fi' - -test_expect_success \ - '12 - unmatching local changes being removed.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo rezrov rezrov >rezrov && - git update-index --add rezrov && - if read_tree_twoway $treeH $treeM; then false; else :; fi' - -test_expect_success \ - '13 - unmatching local changes being removed.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo rezrov rezrov >rezrov && - git update-index --add rezrov && - echo rezrov >rezrov && - if read_tree_twoway $treeH $treeM; then false; else :; fi' +test_expect_success '4 - carry forward local addition.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + git update-index --add yomin && + read_tree_twoway $treeH $treeM && + git ls-files --stage >4.out && + test_must_fail git diff --no-index M.out 4.out >4diff.out && + compare_change 4diff.out expected && + check_cache_at yomin clean +' + +test_expect_success '5 - carry forward local addition.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo yomin >yomin && + git update-index --add yomin && + echo yomin yomin >yomin && + read_tree_twoway $treeH $treeM && + git ls-files --stage >5.out && + test_must_fail git diff --no-index M.out 5.out >5diff.out && + compare_change 5diff.out expected && + check_cache_at yomin dirty +' + +test_expect_success '6 - local addition already has the same.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + git update-index --add frotz && + read_tree_twoway $treeH $treeM && + git ls-files --stage >6.out && + test_cmp M.out 6.out && + check_cache_at frotz clean +' + +test_expect_success '7 - local addition already has the same.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo frotz >frotz && + git update-index --add frotz && + echo frotz frotz >frotz && + read_tree_twoway $treeH $treeM && + git ls-files --stage >7.out && + test_cmp M.out 7.out && + check_cache_at frotz dirty +' + +test_expect_success '8 - conflicting addition.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo frotz frotz >frotz && + git update-index --add frotz && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' + +test_expect_success '9 - conflicting addition.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo frotz frotz >frotz && + git update-index --add frotz && + echo frotz >frotz && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' + +test_expect_success '10 - path removed.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo rezrov >rezrov && + git update-index --add rezrov && + read_tree_twoway $treeH $treeM && + git ls-files --stage >10.out && + test_cmp M.out 10.out +' + +test_expect_success '11 - dirty path removed.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo rezrov >rezrov && + git update-index --add rezrov && + echo rezrov rezrov >rezrov && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' + +test_expect_success '12 - unmatching local changes being removed.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo rezrov rezrov >rezrov && + git update-index --add rezrov && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' + +test_expect_success '13 - unmatching local changes being removed.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo rezrov rezrov >rezrov && + git update-index --add rezrov && + echo rezrov >rezrov && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' cat >expected <<EOF -100644 X 0 nitfol +100644 X 0 nitfol EOF -test_expect_success \ - '14 - unchanged in two heads.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo nitfol nitfol >nitfol && - git update-index --add nitfol && - read_tree_twoway $treeH $treeM && - git ls-files --stage >14.out && - test_must_fail git diff --no-index M.out 14.out >14diff.out && - compare_change 14diff.out expected && - check_cache_at nitfol clean' - -test_expect_success \ - '15 - unchanged in two heads.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo nitfol nitfol >nitfol && - git update-index --add nitfol && - echo nitfol nitfol nitfol >nitfol && - read_tree_twoway $treeH $treeM && - git ls-files --stage >15.out && - test_must_fail git diff --no-index M.out 15.out >15diff.out && - compare_change 15diff.out expected && - check_cache_at nitfol dirty' - -test_expect_success \ - '16 - conflicting local change.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo bozbar bozbar >bozbar && - git update-index --add bozbar && - if read_tree_twoway $treeH $treeM; then false; else :; fi' - -test_expect_success \ - '17 - conflicting local change.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - echo bozbar bozbar >bozbar && - git update-index --add bozbar && - echo bozbar bozbar bozbar >bozbar && - if read_tree_twoway $treeH $treeM; then false; else :; fi' - -test_expect_success \ - '18 - local change already having a good result.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - cat bozbar-new >bozbar && - git update-index --add bozbar && - read_tree_twoway $treeH $treeM && - git ls-files --stage >18.out && - test_cmp M.out 18.out && - check_cache_at bozbar clean' - -test_expect_success \ - '19 - local change already having a good result, further modified.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - cat bozbar-new >bozbar && - git update-index --add bozbar && - echo gnusto gnusto >bozbar && - read_tree_twoway $treeH $treeM && - git ls-files --stage >19.out && - test_cmp M.out 19.out && - check_cache_at bozbar dirty' - -test_expect_success \ - '20 - no local change, use new tree.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - cat bozbar-old >bozbar && - git update-index --add bozbar && - read_tree_twoway $treeH $treeM && - git ls-files --stage >20.out && - test_cmp M.out 20.out && - check_cache_at bozbar dirty' - -test_expect_success \ - '21 - no local change, dirty cache.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - cat bozbar-old >bozbar && - git update-index --add bozbar && - echo gnusto gnusto >bozbar && - if read_tree_twoway $treeH $treeM; then false; else :; fi' +test_expect_success '14 - unchanged in two heads.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo nitfol nitfol >nitfol && + git update-index --add nitfol && + read_tree_twoway $treeH $treeM && + git ls-files --stage >14.out && + test_must_fail git diff --no-index M.out 14.out >14diff.out && + compare_change 14diff.out expected && + check_cache_at nitfol clean +' + +test_expect_success '15 - unchanged in two heads.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo nitfol nitfol >nitfol && + git update-index --add nitfol && + echo nitfol nitfol nitfol >nitfol && + read_tree_twoway $treeH $treeM && + git ls-files --stage >15.out && + test_must_fail git diff --no-index M.out 15.out >15diff.out && + compare_change 15diff.out expected && + check_cache_at nitfol dirty +' + +test_expect_success '16 - conflicting local change.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo bozbar bozbar >bozbar && + git update-index --add bozbar && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' + +test_expect_success '17 - conflicting local change.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + echo bozbar bozbar >bozbar && + git update-index --add bozbar && + echo bozbar bozbar bozbar >bozbar && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' + +test_expect_success '18 - local change already having a good result.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + cat bozbar-new >bozbar && + git update-index --add bozbar && + read_tree_twoway $treeH $treeM && + git ls-files --stage >18.out && + test_cmp M.out 18.out && + check_cache_at bozbar clean +' + +test_expect_success '19 - local change already having a good result, further modified.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + cat bozbar-new >bozbar && + git update-index --add bozbar && + echo gnusto gnusto >bozbar && + read_tree_twoway $treeH $treeM && + git ls-files --stage >19.out && + test_cmp M.out 19.out && + check_cache_at bozbar dirty +' + +test_expect_success '20 - no local change, use new tree.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + cat bozbar-old >bozbar && + git update-index --add bozbar && + read_tree_twoway $treeH $treeM && + git ls-files --stage >20.out && + test_cmp M.out 20.out && + check_cache_at bozbar dirty +' + +test_expect_success '21 - no local change, dirty cache.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + cat bozbar-old >bozbar && + git update-index --add bozbar && + echo gnusto gnusto >bozbar && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' # This fails with straight two-way fast-forward. -test_expect_success \ - '22 - local change cache updated.' \ - 'rm -f .git/index && - read_tree_must_succeed $treeH && - git checkout-index -u -f -q -a && - sed -e "s/such as/SUCH AS/" bozbar-old >bozbar && - git update-index --add bozbar && - if read_tree_twoway $treeH $treeM; then false; else :; fi' +test_expect_success '22 - local change cache updated.' ' + rm -f .git/index && + read_tree_must_succeed $treeH && + git checkout-index -u -f -q -a && + sed -e "s/such as/SUCH AS/" bozbar-old >bozbar && + git update-index --add bozbar && + if read_tree_twoway $treeH $treeM; then false; else :; fi +' # Also make sure we did not break DF vs DF/DF case. -test_expect_success \ - 'DF vs DF/DF case setup.' \ - 'rm -f .git/index && - echo DF >DF && - git update-index --add DF && - treeDF=$(git write-tree) && - echo treeDF $treeDF && - git ls-tree $treeDF && - - rm -f DF && - mkdir DF && - echo DF/DF >DF/DF && - git update-index --add --remove DF DF/DF && - treeDFDF=$(git write-tree) && - echo treeDFDF $treeDFDF && - git ls-tree $treeDFDF && - git ls-files --stage >DFDF.out' - -test_expect_success \ - 'DF vs DF/DF case test.' \ - 'rm -f .git/index && - rm -fr DF && - echo DF >DF && - git update-index --add DF && - read_tree_twoway $treeDF $treeDFDF && - git ls-files --stage >DFDFcheck.out && - test_cmp DFDF.out DFDFcheck.out && - check_cache_at DF/DF dirty && - :' - -test_expect_success \ - 'a/b (untracked) vs a case setup.' \ - 'rm -f .git/index && - : >a && - git update-index --add a && - treeM=$(git write-tree) && - echo treeM $treeM && - git ls-tree $treeM && - git ls-files --stage >treeM.out && - - rm -f a && - git update-index --remove a && - mkdir a && - : >a/b && - treeH=$(git write-tree) && - echo treeH $treeH && - git ls-tree $treeH' - -test_expect_success \ - 'a/b (untracked) vs a, plus c/d case test.' \ - 'read_tree_u_must_fail -u -m "$treeH" "$treeM" && - git ls-files --stage && - test -f a/b' - -test_expect_success \ - 'a/b vs a, plus c/d case setup.' \ - 'rm -f .git/index && - rm -fr a && - : >a && - mkdir c && - : >c/d && - git update-index --add a c/d && - treeM=$(git write-tree) && - echo treeM $treeM && - git ls-tree $treeM && - git ls-files --stage >treeM.out && - - rm -f a && - mkdir a && - : >a/b && - git update-index --add --remove a a/b && - treeH=$(git write-tree) && - echo treeH $treeH && - git ls-tree $treeH' - -test_expect_success \ - 'a/b vs a, plus c/d case test.' \ - 'read_tree_u_must_succeed -u -m "$treeH" "$treeM" && - git ls-files --stage | tee >treeMcheck.out && - test_cmp treeM.out treeMcheck.out' +test_expect_success 'DF vs DF/DF case setup.' ' + rm -f .git/index && + echo DF >DF && + git update-index --add DF && + treeDF=$(git write-tree) && + echo treeDF $treeDF && + git ls-tree $treeDF && + + rm -f DF && + mkdir DF && + echo DF/DF >DF/DF && + git update-index --add --remove DF DF/DF && + treeDFDF=$(git write-tree) && + echo treeDFDF $treeDFDF && + git ls-tree $treeDFDF && + git ls-files --stage >DFDF.out +' + +test_expect_success 'DF vs DF/DF case test.' ' + rm -f .git/index && + rm -fr DF && + echo DF >DF && + git update-index --add DF && + read_tree_twoway $treeDF $treeDFDF && + git ls-files --stage >DFDFcheck.out && + test_cmp DFDF.out DFDFcheck.out && + check_cache_at DF/DF dirty && + : +' + +test_expect_success 'a/b (untracked) vs a case setup.' ' + rm -f .git/index && + : >a && + git update-index --add a && + treeM=$(git write-tree) && + echo treeM $treeM && + git ls-tree $treeM && + git ls-files --stage >treeM.out && + + rm -f a && + git update-index --remove a && + mkdir a && + : >a/b && + treeH=$(git write-tree) && + echo treeH $treeH && + git ls-tree $treeH +' + +test_expect_success 'a/b (untracked) vs a, plus c/d case test.' ' + read_tree_u_must_fail -u -m "$treeH" "$treeM" && + git ls-files --stage && + test -f a/b +' + +test_expect_success 'read-tree supports the super-prefix' ' + cat <<-EOF >expect && + error: Updating '\''fictional/a'\'' would lose untracked files in it + EOF + test_must_fail git --super-prefix fictional/ read-tree -u -m "$treeH" "$treeM" 2>actual && + test_cmp expect actual +' + +test_expect_success 'a/b vs a, plus c/d case setup.' ' + rm -f .git/index && + rm -fr a && + : >a && + mkdir c && + : >c/d && + git update-index --add a c/d && + treeM=$(git write-tree) && + echo treeM $treeM && + git ls-tree $treeM && + git ls-files --stage >treeM.out && + + rm -f a && + mkdir a && + : >a/b && + git update-index --add --remove a a/b && + treeH=$(git write-tree) && + echo treeH $treeH && + git ls-tree $treeH +' + +test_expect_success 'a/b vs a, plus c/d case test.' ' + read_tree_u_must_succeed -u -m "$treeH" "$treeM" && + git ls-files --stage | tee >treeMcheck.out && + test_cmp treeM.out treeMcheck.out +' test_expect_success '-m references the correct modified tree' ' echo >file-a && diff --git a/t/t1002-read-tree-m-u-2way.sh b/t/t1002-read-tree-m-u-2way.sh index e3bf821694..7ca2e65d10 100755 --- a/t/t1002-read-tree-m-u-2way.sh +++ b/t/t1002-read-tree-m-u-2way.sh @@ -51,7 +51,9 @@ test_expect_success \ treeM=$(git write-tree) && echo treeM $treeM && git ls-tree $treeM && - sum bozbar frotz nitfol >M.sum && + cp bozbar bozbar.M && + cp frotz frotz.M && + cp nitfol nitfol.M && git diff-tree $treeH $treeM' test_expect_success \ @@ -61,8 +63,9 @@ test_expect_success \ read_tree_u_must_succeed -m -u $treeH $treeM && git ls-files --stage >1-3.out && cmp M.out 1-3.out && - sum bozbar frotz nitfol >actual3.sum && - cmp M.sum actual3.sum && + test_cmp bozbar.M bozbar && + test_cmp frotz.M frotz && + test_cmp nitfol.M nitfol && check_cache_at bozbar clean && check_cache_at frotz clean && check_cache_at nitfol clean' @@ -79,8 +82,9 @@ test_expect_success \ test_might_fail git diff -U0 --no-index M.out 4.out >4diff.out && compare_change 4diff.out expected && check_cache_at yomin clean && - sum bozbar frotz nitfol >actual4.sum && - cmp M.sum actual4.sum && + test_cmp bozbar.M bozbar && + test_cmp frotz.M frotz && + test_cmp nitfol.M nitfol && echo yomin >yomin1 && diff yomin yomin1 && rm -f yomin1' @@ -98,8 +102,9 @@ test_expect_success \ test_might_fail git diff -U0 --no-index M.out 5.out >5diff.out && compare_change 5diff.out expected && check_cache_at yomin dirty && - sum bozbar frotz nitfol >actual5.sum && - cmp M.sum actual5.sum && + test_cmp bozbar.M bozbar && + test_cmp frotz.M frotz && + test_cmp nitfol.M nitfol && : dirty index should have prevented -u from checking it out. && echo yomin yomin >yomin1 && diff yomin yomin1 && @@ -115,8 +120,9 @@ test_expect_success \ git ls-files --stage >6.out && test_cmp M.out 6.out && check_cache_at frotz clean && - sum bozbar frotz nitfol >actual3.sum && - cmp M.sum actual3.sum && + test_cmp bozbar.M bozbar && + test_cmp frotz.M frotz && + test_cmp nitfol.M nitfol && echo frotz >frotz1 && diff frotz frotz1 && rm -f frotz1' @@ -132,8 +138,8 @@ test_expect_success \ git ls-files --stage >7.out && test_cmp M.out 7.out && check_cache_at frotz dirty && - sum bozbar frotz nitfol >actual7.sum && - if cmp M.sum actual7.sum; then false; else :; fi && + test_cmp bozbar.M bozbar && + test_cmp nitfol.M nitfol && : dirty index should have prevented -u from checking it out. && echo frotz frotz >frotz1 && diff frotz frotz1 && @@ -165,8 +171,10 @@ test_expect_success \ read_tree_u_must_succeed -m -u $treeH $treeM && git ls-files --stage >10.out && cmp M.out 10.out && - sum bozbar frotz nitfol >actual10.sum && - cmp M.sum actual10.sum' + test_cmp bozbar.M bozbar && + test_cmp frotz.M frotz && + test_cmp nitfol.M nitfol +' test_expect_success \ '11 - dirty path removed.' \ @@ -209,11 +217,8 @@ test_expect_success \ git ls-files --stage >14.out && test_must_fail git diff -U0 --no-index M.out 14.out >14diff.out && compare_change 14diff.out expected && - sum bozbar frotz >actual14.sum && - grep -v nitfol M.sum > expected14.sum && - cmp expected14.sum actual14.sum && - sum bozbar frotz nitfol >actual14a.sum && - if cmp M.sum actual14a.sum; then false; else :; fi && + test_cmp bozbar.M bozbar && + test_cmp frotz.M frotz && check_cache_at nitfol clean && echo nitfol nitfol >nitfol1 && diff nitfol nitfol1 && @@ -231,11 +236,8 @@ test_expect_success \ test_must_fail git diff -U0 --no-index M.out 15.out >15diff.out && compare_change 15diff.out expected && check_cache_at nitfol dirty && - sum bozbar frotz >actual15.sum && - grep -v nitfol M.sum > expected15.sum && - cmp expected15.sum actual15.sum && - sum bozbar frotz nitfol >actual15a.sum && - if cmp M.sum actual15a.sum; then false; else :; fi && + test_cmp bozbar.M bozbar && + test_cmp frotz.M frotz && echo nitfol nitfol nitfol >nitfol1 && diff nitfol nitfol1 && rm -f nitfol1' @@ -267,8 +269,10 @@ test_expect_success \ git ls-files --stage >18.out && test_cmp M.out 18.out && check_cache_at bozbar clean && - sum bozbar frotz nitfol >actual18.sum && - cmp M.sum actual18.sum' + test_cmp bozbar.M bozbar && + test_cmp frotz.M frotz && + test_cmp nitfol.M nitfol +' test_expect_success \ '19 - local change already having a good result, further modified.' \ @@ -281,11 +285,8 @@ test_expect_success \ git ls-files --stage >19.out && test_cmp M.out 19.out && check_cache_at bozbar dirty && - sum frotz nitfol >actual19.sum && - grep -v bozbar M.sum > expected19.sum && - cmp expected19.sum actual19.sum && - sum bozbar frotz nitfol >actual19a.sum && - if cmp M.sum actual19a.sum; then false; else :; fi && + test_cmp frotz.M frotz && + test_cmp nitfol.M nitfol && echo gnusto gnusto >bozbar1 && diff bozbar bozbar1 && rm -f bozbar1' @@ -300,8 +301,10 @@ test_expect_success \ git ls-files --stage >20.out && test_cmp M.out 20.out && check_cache_at bozbar clean && - sum bozbar frotz nitfol >actual20.sum && - cmp M.sum actual20.sum' + test_cmp bozbar.M bozbar && + test_cmp frotz.M frotz && + test_cmp nitfol.M nitfol +' test_expect_success \ '21 - no local change, dirty cache.' \ diff --git a/t/t1004-read-tree-m-u-wf.sh b/t/t1004-read-tree-m-u-wf.sh index c70cf42300..c7ce5d8bb5 100755 --- a/t/t1004-read-tree-m-u-wf.sh +++ b/t/t1004-read-tree-m-u-wf.sh @@ -218,7 +218,7 @@ test_expect_success 'D/F' ' echo "100644 $a 2 subdir/file2" echo "100644 $b 3 subdir/file2/another" ) >expect && - test_cmp actual expect + test_cmp expect actual ' diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index 93a4794930..2ac3b940c6 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -231,7 +231,7 @@ $tag_content | git cat-file --batch)" ' -test_expect_success "--batch-check for an emtpy line" ' +test_expect_success "--batch-check for an empty line" ' test " missing" = "$(echo | git cat-file --batch-check)" ' @@ -282,7 +282,7 @@ test_expect_success "--batch-check with multiple sha1s gives correct format" ' ' test_expect_success 'setup blobs which are likely to delta' ' - test-genrandom foo 10240 >foo && + test-tool genrandom foo 10240 >foo && { cat foo; echo plus; } >foo-plus && git add foo foo-plus && git commit -m foo && @@ -547,4 +547,30 @@ test_expect_success 'git cat-file --batch --follow-symlink returns correct sha a test_cmp expect actual ' +test_expect_success 'cat-file --batch-all-objects shows all objects' ' + # make new repos so we know the full set of objects; we will + # also make sure that there are some packed and some loose + # objects, some referenced and some not, and that there are + # some available only via alternates. + git init all-one && + ( + cd all-one && + echo content >file && + git add file && + git commit -qm base && + git rev-parse HEAD HEAD^{tree} HEAD:file && + git repack -ad && + echo not-cloned | git hash-object -w --stdin + ) >expect.unsorted && + git clone -s all-one all-two && + ( + cd all-two && + echo local-unref | git hash-object -w --stdin + ) >>expect.unsorted && + sort <expect.unsorted >expect && + git -C all-two cat-file --batch-all-objects \ + --batch-check="%(objectname)" >actual && + test_cmp expect actual +' + test_done diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index 7d2baa15bb..532682f51c 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -101,7 +101,7 @@ test_expect_success 'git hash-object --stdin file1 <file0 first operates on file test "$obname1" = "$obname1new" ' -test_expect_success 'check that appropriate filter is invoke when --path is used' ' +test_expect_success 'set up crlf tests' ' echo fooQ | tr Q "\\015" >file0 && cp file0 file1 && echo "file0 -crlf" >.gitattributes && @@ -109,7 +109,10 @@ test_expect_success 'check that appropriate filter is invoke when --path is used git config core.autocrlf true && file0_sha=$(git hash-object file0) && file1_sha=$(git hash-object file1) && - test "$file0_sha" != "$file1_sha" && + test "$file0_sha" != "$file1_sha" +' + +test_expect_success 'check that appropriate filter is invoke when --path is used' ' path1_sha=$(git hash-object --path=file1 file0) && path0_sha=$(git hash-object --path=file0 file1) && test "$file0_sha" = "$path0_sha" && @@ -117,38 +120,40 @@ test_expect_success 'check that appropriate filter is invoke when --path is used path1_sha=$(cat file0 | git hash-object --path=file1 --stdin) && path0_sha=$(cat file1 | git hash-object --path=file0 --stdin) && test "$file0_sha" = "$path0_sha" && - test "$file1_sha" = "$path1_sha" && - git config --unset core.autocrlf + test "$file1_sha" = "$path1_sha" +' + +test_expect_success 'gitattributes also work in a subdirectory' ' + mkdir subdir && + ( + cd subdir && + subdir_sha0=$(git hash-object ../file0) && + subdir_sha1=$(git hash-object ../file1) && + test "$file0_sha" = "$subdir_sha0" && + test "$file1_sha" = "$subdir_sha1" + ) +' + +test_expect_success '--path works in a subdirectory' ' + ( + cd subdir && + path1_sha=$(git hash-object --path=../file1 ../file0) && + path0_sha=$(git hash-object --path=../file0 ../file1) && + test "$file0_sha" = "$path0_sha" && + test "$file1_sha" = "$path1_sha" + ) ' test_expect_success 'check that --no-filters option works' ' - echo fooQ | tr Q "\\015" >file0 && - cp file0 file1 && - echo "file0 -crlf" >.gitattributes && - echo "file1 crlf" >>.gitattributes && - git config core.autocrlf true && - file0_sha=$(git hash-object file0) && - file1_sha=$(git hash-object file1) && - test "$file0_sha" != "$file1_sha" && nofilters_file1=$(git hash-object --no-filters file1) && test "$file0_sha" = "$nofilters_file1" && nofilters_file1=$(cat file1 | git hash-object --stdin) && - test "$file0_sha" = "$nofilters_file1" && - git config --unset core.autocrlf + test "$file0_sha" = "$nofilters_file1" ' test_expect_success 'check that --no-filters option works with --stdin-paths' ' - echo fooQ | tr Q "\\015" >file0 && - cp file0 file1 && - echo "file0 -crlf" >.gitattributes && - echo "file1 crlf" >>.gitattributes && - git config core.autocrlf true && - file0_sha=$(git hash-object file0) && - file1_sha=$(git hash-object file1) && - test "$file0_sha" != "$file1_sha" && nofilters_file1=$(echo "file1" | git hash-object --stdin-paths --no-filters) && - test "$file0_sha" = "$nofilters_file1" && - git config --unset core.autocrlf + test "$file0_sha" = "$nofilters_file1" ' pop_repo @@ -188,9 +193,30 @@ for args in "-w --stdin-paths" "--stdin-paths -w"; do pop_repo done -test_expect_success 'corrupt tree' ' +test_expect_success 'too-short tree' ' echo abc >malformed-tree && - test_must_fail git hash-object -t tree malformed-tree + test_must_fail git hash-object -t tree malformed-tree 2>err && + test_i18ngrep "too-short tree object" err +' + +hex2oct() { + perl -ne 'printf "\\%03o", hex for /../g' +} + +test_expect_success 'malformed mode in tree' ' + hex_sha1=$(echo foo | git hash-object --stdin -w) && + bin_sha1=$(echo $hex_sha1 | hex2oct) && + printf "9100644 \0$bin_sha1" >tree-with-malformed-mode && + test_must_fail git hash-object -t tree tree-with-malformed-mode 2>err && + test_i18ngrep "malformed mode in tree entry" err +' + +test_expect_success 'empty filename in tree' ' + hex_sha1=$(echo foo | git hash-object --stdin -w) && + bin_sha1=$(echo $hex_sha1 | hex2oct) && + printf "100644 \0$bin_sha1" >tree-with-empty-filename && + test_must_fail git hash-object -t tree tree-with-empty-filename 2>err && + test_i18ngrep "empty filename in tree entry" err ' test_expect_success 'corrupt commit' ' diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh index 0c74beedd2..0c6f48f302 100755 --- a/t/t1011-read-tree-sparse-checkout.sh +++ b/t/t1011-read-tree-sparse-checkout.sh @@ -15,11 +15,14 @@ test_description='sparse checkout tests . "$TEST_DIRECTORY"/lib-read-tree.sh test_expect_success 'setup' ' - cat >expected <<-\EOF && - 100644 77f0ba1734ed79d12881f81b36ee134de6a3327b 0 init.t - 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sub/added - 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 sub/addedtoo - 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 subsub/added + test_commit init && + echo modified >>init.t && + + cat >expected <<-EOF && + 100644 $(git hash-object init.t) 0 init.t + 100644 $EMPTY_BLOB 0 sub/added + 100644 $EMPTY_BLOB 0 sub/addedtoo + 100644 $EMPTY_BLOB 0 subsub/added EOF cat >expected.swt <<-\EOF && H init.t @@ -28,8 +31,6 @@ test_expect_success 'setup' ' H subsub/added EOF - test_commit init && - echo modified >>init.t && mkdir sub subsub && touch sub/added sub/addedtoo subsub/added && git add init.t sub/added sub/addedtoo subsub/added && @@ -244,10 +245,10 @@ test_expect_success 'print errors when failed to update worktree' ' error: The following untracked working tree files would be overwritten by checkout: sub/added sub/addedtoo -Please move or remove them before you can switch branches. +Please move or remove them before you switch branches. Aborting EOF - test_cmp expected actual + test_i18ncmp expected actual ' test_expect_success 'checkout without --ignore-skip-worktree-bits' ' diff --git a/t/t1013-read-tree-submodule.sh b/t/t1013-read-tree-submodule.sh index 20526aed34..91a6fafcb4 100755 --- a/t/t1013-read-tree-submodule.sh +++ b/t/t1013-read-tree-submodule.sh @@ -5,6 +5,13 @@ test_description='read-tree can handle submodules' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh +KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1 +KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1 + +test_submodule_switch_recursing_with_args "read-tree -u -m" + +test_submodule_forced_switch_recursing_with_args "read-tree -u --reset" + test_submodule_switch "git read-tree -u -m" test_submodule_forced_switch "git read-tree -u --reset" diff --git a/t/t1020-subdirectory.sh b/t/t1020-subdirectory.sh index 8e22b03cdd..df3183ea1a 100755 --- a/t/t1020-subdirectory.sh +++ b/t/t1020-subdirectory.sh @@ -141,13 +141,13 @@ test_expect_success 'GIT_PREFIX for !alias' ' test_expect_success 'GIT_PREFIX for built-ins' ' # Use GIT_EXTERNAL_DIFF to test that the "diff" built-in # receives the GIT_PREFIX variable. - printf "dir/" >expect && - printf "#!/bin/sh\n" >diff && - printf "printf \"\$GIT_PREFIX\"" >>diff && - chmod +x diff && + echo "dir/" >expect && + write_script diff <<-\EOF && + printf "%s\n" "$GIT_PREFIX" + EOF ( cd dir && - printf "change" >two && + echo "change" >two && GIT_EXTERNAL_DIFF=./diff git diff >../actual git checkout -- two ) && diff --git a/t/t1050-large.sh b/t/t1050-large.sh index f9f3d1391f..f9eb143f43 100755 --- a/t/t1050-large.sh +++ b/t/t1050-large.sh @@ -5,6 +5,12 @@ test_description='adding and checking out large blobs' . ./test-lib.sh +# This should be moved to test-lib.sh together with the +# copy in t0021 after both topics have graduated to 'master'. +file_size () { + perl -e 'print -s $ARGV[0]' "$1" +} + test_expect_success setup ' # clone does not allow us to pass core.bigfilethreshold to # new repos, so set core.bigfilethreshold globally @@ -17,6 +23,29 @@ test_expect_success setup ' export GIT_ALLOC_LIMIT ' +# add a large file with different settings +while read expect config +do + test_expect_success "add with $config" ' + test_when_finished "rm -f .git/objects/pack/pack-*.* .git/index" && + git $config add large1 && + sz=$(file_size .git/objects/pack/pack-*.pack) && + case "$expect" in + small) test "$sz" -le 100000 ;; + large) test "$sz" -ge 100000 ;; + esac + ' +done <<\EOF +large -c core.compression=0 +small -c core.compression=9 +large -c core.compression=0 -c pack.compression=0 +large -c core.compression=9 -c pack.compression=0 +small -c core.compression=0 -c pack.compression=9 +small -c core.compression=9 -c pack.compression=9 +large -c pack.compression=0 +small -c pack.compression=9 +EOF + test_expect_success 'add a large file or two' ' git add large1 huge large2 && # make sure we got a single packfile and no loose objects @@ -74,9 +103,9 @@ test_expect_success 'packsize limit' ' # mid1 and mid2 will fit within 256k limit but # appending mid3 will bust the limit and will # result in a separate packfile. - test-genrandom "a" $(( 66 * 1024 )) >mid1 && - test-genrandom "b" $(( 80 * 1024 )) >mid2 && - test-genrandom "c" $(( 128 * 1024 )) >mid3 && + test-tool genrandom "a" $(( 66 * 1024 )) >mid1 && + test-tool genrandom "b" $(( 80 * 1024 )) >mid2 && + test-tool genrandom "c" $(( 128 * 1024 )) >mid3 && git add mid1 mid2 mid3 && count=0 @@ -177,10 +206,9 @@ test_expect_success 'zip achiving, deflate' ' git archive --format=zip HEAD >/dev/null ' -test_expect_success 'fsck' ' - test_must_fail git fsck 2>err && - n=$(grep "error: attempting to allocate .* over limit" err | wc -l) && - test "$n" -gt 1 +test_expect_success 'fsck large blobs' ' + git fsck 2>err && + test_must_be_empty err ' test_done diff --git a/t/t1060-object-corruption.sh b/t/t1060-object-corruption.sh index 3f8705139d..ac1f189fd2 100755 --- a/t/t1060-object-corruption.sh +++ b/t/t1060-object-corruption.sh @@ -21,6 +21,14 @@ test_expect_success 'setup corrupt repo' ' cd bit-error && test_commit content && corrupt_byte HEAD:content.t 10 + ) && + git init no-bit-error && + ( + # distinct commit from bit-error, but containing a + # non-corrupted version of the same blob + cd no-bit-error && + test_tick && + test_commit content ) ' @@ -53,6 +61,13 @@ test_expect_success 'streaming a corrupt blob fails' ' ) ' +test_expect_success 'getting type of a corrupt blob fails' ' + ( + cd bit-error && + test_must_fail git cat-file -s HEAD:content.t + ) +' + test_expect_success 'read-tree -u detects bit-errors in blobs' ' ( cd bit-error && @@ -101,4 +116,13 @@ test_expect_failure 'clone --local detects misnamed objects' ' test_must_fail git clone --local misnamed misnamed-checkout ' +test_expect_success 'fetch into corrupted repo with index-pack' ' + ( + cd bit-error && + test_must_fail git -c transfer.unpackLimit=1 \ + fetch ../no-bit-error 2>stderr && + test_i18ngrep ! -i collision stderr + ) +' + test_done diff --git a/t/t1090-sparse-checkout-scope.sh b/t/t1090-sparse-checkout-scope.sh new file mode 100755 index 0000000000..1f61eb3e88 --- /dev/null +++ b/t/t1090-sparse-checkout-scope.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +test_description='sparse checkout scope tests' + +. ./test-lib.sh + +test_expect_success 'setup' ' + echo "initial" >a && + echo "initial" >b && + echo "initial" >c && + git add a b c && + git commit -m "initial commit" +' + +test_expect_success 'create feature branch' ' + git checkout -b feature && + echo "modified" >b && + echo "modified" >c && + git add b c && + git commit -m "modification" +' + +test_expect_success 'perform sparse checkout of master' ' + git config --local --bool core.sparsecheckout true && + echo "!/*" >.git/info/sparse-checkout && + echo "/a" >>.git/info/sparse-checkout && + echo "/c" >>.git/info/sparse-checkout && + git checkout master && + test_path_is_file a && + test_path_is_missing b && + test_path_is_file c +' + +test_expect_success 'merge feature branch into sparse checkout of master' ' + git merge feature && + test_path_is_file a && + test_path_is_missing b && + test_path_is_file c && + test "$(cat c)" = "modified" +' + +test_expect_success 'return to full checkout of master' ' + git checkout feature && + echo "/*" >.git/info/sparse-checkout && + git checkout master && + test_path_is_file a && + test_path_is_file b && + test_path_is_file c && + test "$(cat b)" = "modified" +' + +test_done diff --git a/t/t1100-commit-tree-options.sh b/t/t1100-commit-tree-options.sh index f8457f9d14..ae66ba5bab 100755 --- a/t/t1100-commit-tree-options.sh +++ b/t/t1100-commit-tree-options.sh @@ -15,7 +15,7 @@ Also make sure that command line parser understands the normal . ./test-lib.sh cat >expected <<EOF -tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 +tree $EMPTY_TREE author Author Name <author@email> 1117148400 +0000 committer Committer Name <committer@email> 1117150200 +0000 @@ -35,11 +35,11 @@ test_expect_success \ GIT_COMMITTER_NAME="Committer Name" \ GIT_COMMITTER_EMAIL="committer@email" \ GIT_COMMITTER_DATE="2005-05-26 23:30" \ - TZ=GMT git commit-tree `cat treeid` >commitid 2>/dev/null' + TZ=GMT git commit-tree $(cat treeid) >commitid 2>/dev/null' test_expect_success \ 'read commit' \ - 'git cat-file commit `cat commitid` >commit' + 'git cat-file commit $(cat commitid) >commit' test_expect_success \ 'compare commit' \ diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh deleted file mode 100755 index 397ccb6909..0000000000 --- a/t/t1200-tutorial.sh +++ /dev/null @@ -1,268 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2005 Johannes Schindelin -# - -test_description='A simple turial in the form of a test case' - -. ./test-lib.sh - -test_expect_success 'blob' ' - echo "Hello World" > hello && - echo "Silly example" > example && - - git update-index --add hello example && - - test blob = "$(git cat-file -t 557db03)" -' - -test_expect_success 'blob 557db03' ' - test "Hello World" = "$(git cat-file blob 557db03)" -' - -echo "It's a new day for git" >>hello -cat > diff.expect << EOF -diff --git a/hello b/hello -index 557db03..263414f 100644 ---- a/hello -+++ b/hello -@@ -1 +1,2 @@ - Hello World -+It's a new day for git -EOF - -test_expect_success 'git diff-files -p' ' - git diff-files -p > diff.output && - test_cmp diff.expect diff.output -' - -test_expect_success 'git diff' ' - git diff > diff.output && - test_cmp diff.expect diff.output -' - -test_expect_success 'tree' ' - tree=$(git write-tree 2>/dev/null) && - test 8988da15d077d4829fc51d8544c097def6644dbb = $tree -' - -test_expect_success 'git diff-index -p HEAD' ' - test_tick && - tree=$(git write-tree) && - commit=$(echo "Initial commit" | git commit-tree $tree) && - git update-ref HEAD $commit && - git diff-index -p HEAD > diff.output && - test_cmp diff.expect diff.output -' - -test_expect_success 'git diff HEAD' ' - git diff HEAD > diff.output && - test_cmp diff.expect diff.output -' - -cat > whatchanged.expect << EOF -commit VARIABLE -Author: VARIABLE -Date: VARIABLE - - Initial commit - -diff --git a/example b/example -new file mode 100644 -index 0000000..f24c74a ---- /dev/null -+++ b/example -@@ -0,0 +1 @@ -+Silly example -diff --git a/hello b/hello -new file mode 100644 -index 0000000..557db03 ---- /dev/null -+++ b/hello -@@ -0,0 +1 @@ -+Hello World -EOF - -test_expect_success 'git whatchanged -p --root' ' - git whatchanged -p --root | - sed -e "1s/^\(.\{7\}\).\{40\}/\1VARIABLE/" \ - -e "2,3s/^\(.\{8\}\).*$/\1VARIABLE/" \ - > whatchanged.output && - test_cmp whatchanged.expect whatchanged.output -' - -test_expect_success 'git tag my-first-tag' ' - git tag my-first-tag && - test_cmp .git/refs/heads/master .git/refs/tags/my-first-tag -' - -test_expect_success 'git checkout -b mybranch' ' - git checkout -b mybranch && - test_cmp .git/refs/heads/master .git/refs/heads/mybranch -' - -cat > branch.expect <<EOF - master -* mybranch -EOF - -test_expect_success 'git branch' ' - git branch > branch.output && - test_cmp branch.expect branch.output -' - -test_expect_success 'git resolve now fails' ' - git checkout mybranch && - echo "Work, work, work" >>hello && - test_tick && - git commit -m "Some work." -i hello && - - git checkout master && - - echo "Play, play, play" >>hello && - echo "Lots of fun" >>example && - test_tick && - git commit -m "Some fun." -i hello example && - - test_must_fail git merge -m "Merge work in mybranch" mybranch -' - -cat > hello << EOF -Hello World -It's a new day for git -Play, play, play -Work, work, work -EOF - -cat > show-branch.expect << EOF -* [master] Merge work in mybranch - ! [mybranch] Some work. --- -- [master] Merge work in mybranch -*+ [mybranch] Some work. -* [master^] Some fun. -EOF - -test_expect_success 'git show-branch' ' - test_tick && - git commit -m "Merge work in mybranch" -i hello && - git show-branch --topo-order --more=1 master mybranch \ - > show-branch.output && - test_cmp show-branch.expect show-branch.output -' - -cat > resolve.expect << EOF -Updating VARIABLE..VARIABLE -FASTFORWARD (no commit created; -m option ignored) - example | 1 + - hello | 1 + - 2 files changed, 2 insertions(+) -EOF - -test_expect_success 'git resolve' ' - git checkout mybranch && - git merge -m "Merge upstream changes." master | - sed -e "1s/[0-9a-f]\{7\}/VARIABLE/g" \ - -e "s/^Fast[- ]forward /FASTFORWARD /" >resolve.output -' - -test_expect_success 'git resolve output' ' - test_i18ncmp resolve.expect resolve.output -' - -cat > show-branch2.expect << EOF -! [master] Merge work in mybranch - * [mybranch] Merge work in mybranch --- --- [master] Merge work in mybranch -EOF - -test_expect_success 'git show-branch (part 2)' ' - git show-branch --topo-order master mybranch > show-branch2.output && - test_cmp show-branch2.expect show-branch2.output -' - -cat > show-branch3.expect << EOF -! [master] Merge work in mybranch - * [mybranch] Merge work in mybranch --- --- [master] Merge work in mybranch -+* [master^2] Some work. -+* [master^] Some fun. -EOF - -test_expect_success 'git show-branch (part 3)' ' - git show-branch --topo-order --more=2 master mybranch \ - > show-branch3.output && - test_cmp show-branch3.expect show-branch3.output -' - -test_expect_success 'rewind to "Some fun." and "Some work."' ' - git checkout mybranch && - git reset --hard master^2 && - git checkout master && - git reset --hard master^ -' - -cat > show-branch4.expect << EOF -* [master] Some fun. - ! [mybranch] Some work. --- -* [master] Some fun. - + [mybranch] Some work. -*+ [master^] Initial commit -EOF - -test_expect_success 'git show-branch (part 4)' ' - git show-branch --topo-order > show-branch4.output && - test_cmp show-branch4.expect show-branch4.output -' - -test_expect_success 'manual merge' ' - mb=$(git merge-base HEAD mybranch) && - git name-rev --name-only --tags $mb > name-rev.output && - test "my-first-tag" = $(cat name-rev.output) && - - git read-tree -m -u $mb HEAD mybranch -' - -cat > ls-files.expect << EOF -100644 7f8b141b65fdcee47321e399a2598a235a032422 0 example -100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello -100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello -100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello -EOF - -test_expect_success 'git ls-files --stage' ' - git ls-files --stage > ls-files.output && - test_cmp ls-files.expect ls-files.output -' - -cat > ls-files-unmerged.expect << EOF -100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1 hello -100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2 hello -100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello -EOF - -test_expect_success 'git ls-files --unmerged' ' - git ls-files --unmerged > ls-files-unmerged.output && - test_cmp ls-files-unmerged.expect ls-files-unmerged.output -' - -test_expect_success 'git-merge-index' ' - test_must_fail git merge-index git-merge-one-file hello -' - -test_expect_success 'git ls-files --stage (part 2)' ' - git ls-files --stage > ls-files.output2 && - test_cmp ls-files.expect ls-files.output2 -' - -test_expect_success 'git repack' 'git repack' -test_expect_success 'git prune-packed' 'git prune-packed' -test_expect_success '-> only packed objects' ' - git prune && # Remove conflict marked blobs - test $(find .git/objects/[0-9a-f][0-9a-f] -type f -print 2>/dev/null | wc -l) = 0 -' - -test_done diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 66dd28644f..e95b1e67da 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -353,6 +353,18 @@ test_expect_success '--list without repo produces empty output' ' ' cat > expect << EOF +beta.noindent +nextsection.nonewline +123456.a123 +version.1.2.3eX.alpha +EOF + +test_expect_success '--name-only --list' ' + git config --name-only --list >output && + test_cmp expect output +' + +cat > expect << EOF beta.noindent sillyValue nextsection.nonewline wow2 for me EOF @@ -363,6 +375,16 @@ test_expect_success '--get-regexp' ' ' cat > expect << EOF +beta.noindent +nextsection.nonewline +EOF + +test_expect_success '--name-only --get-regexp' ' + git config --name-only --get-regexp in >output && + test_cmp expect output +' + +cat > expect << EOF wow2 for me wow4 for you EOF @@ -677,11 +699,19 @@ test_expect_success 'invalid unit' ' echo 1auto >expect && git config aninvalid.unit >actual && test_cmp expect actual && - cat >expect <<-\EOF && - fatal: bad numeric config value '\''1auto'\'' for '\''aninvalid.unit'\'' in .git/config: invalid unit - EOF test_must_fail git config --int --get aninvalid.unit 2>actual && - test_i18ncmp expect actual + test_i18ngrep "bad numeric config value .1auto. for .aninvalid.unit. in file .git/config: invalid unit" actual +' + +test_expect_success 'line number is reported correctly' ' + printf "[bool]\n\tvar\n" >invalid && + test_must_fail git config -f invalid --path bool.var 2>actual && + test_i18ngrep "line 2" actual +' + +test_expect_success 'invalid stdin config' ' + echo "[broken" | test_must_fail git config --list --file - >output 2>&1 && + test_i18ngrep "bad config line 1 in standard input" output ' cat > expect << EOF @@ -862,7 +892,7 @@ test_expect_success !MINGW 'get --path copes with unset $HOME' ' git config --get --path path.normal >>result && git config --get --path path.trailingtilde >>result ) && - grep "[Ff]ailed to expand.*~/" msg && + test_i18ngrep "[Ff]ailed to expand.*~/" msg && test_cmp expect result ' @@ -871,6 +901,36 @@ test_expect_success 'get --path barfs on boolean variable' ' test_must_fail git config --get --path path.bool ' +test_expect_success 'get --expiry-date' ' + rel="3.weeks.5.days.00:00" && + rel_out="$rel ->" && + cat >.git/config <<-\EOF && + [date] + valid1 = "3.weeks.5.days 00:00" + valid2 = "Fri Jun 4 15:46:55 2010" + valid3 = "2017/11/11 11:11:11PM" + valid4 = "2017/11/10 09:08:07 PM" + valid5 = "never" + invalid1 = "abc" + EOF + cat >expect <<-EOF && + $(test-tool date timestamp $rel) + 1275666415 + 1510441871 + 1510348087 + 0 + EOF + { + echo "$rel_out $(git config --expiry-date date.valid1)" + git config --expiry-date date.valid2 && + git config --expiry-date date.valid3 && + git config --expiry-date date.valid4 && + git config --expiry-date date.valid5 + } >actual && + test_cmp expect actual && + test_must_fail git config --expiry-date date.invalid1 +' + cat > expect << EOF [quote] leading = " test" @@ -935,13 +995,15 @@ Qsection.sub=section.val4 Qsection.sub=section.val5Q EOF test_expect_success '--null --list' ' - git config --null --list | nul_to_q >result && + git config --null --list >result.raw && + nul_to_q <result.raw >result && echo >>result && test_cmp expect result ' test_expect_success '--null --get-regexp' ' - git config --null --get-regexp "val[0-9]" | nul_to_q >result && + git config --null --get-regexp "val[0-9]" >result.raw && + nul_to_q <result.raw >result && echo >>result && test_cmp expect result ' @@ -1043,6 +1105,13 @@ test_expect_success 'git -c works with aliases of builtins' ' test_cmp expect actual ' +test_expect_success 'aliases can be CamelCased' ' + test_config alias.CamelCased "rev-parse HEAD" && + git CamelCased >out && + git rev-parse HEAD >expect && + test_cmp expect out +' + test_expect_success 'git -c does not split values on equals' ' echo "value with = in it" >expect && git -c core.foo="value with = in it" config core.foo >actual && @@ -1061,6 +1130,105 @@ test_expect_success 'git -c complains about empty key and value' ' test_must_fail git -c "" rev-parse ' +test_expect_success 'multiple git -c appends config' ' + test_config alias.x "!git -c x.two=2 config --get-regexp ^x\.*" && + cat >expect <<-\EOF && + x.one 1 + x.two 2 + EOF + git -c x.one=1 x >actual && + test_cmp expect actual +' + +test_expect_success 'last one wins: two level vars' ' + + # sec.var and sec.VAR are the same variable, as the first + # and the last level of a configuration variable name is + # case insensitive. + + echo VAL >expect && + + git -c sec.var=val -c sec.VAR=VAL config --get sec.var >actual && + test_cmp expect actual && + git -c SEC.var=val -c sec.var=VAL config --get sec.var >actual && + test_cmp expect actual && + + git -c sec.var=val -c sec.VAR=VAL config --get SEC.var >actual && + test_cmp expect actual && + git -c SEC.var=val -c sec.var=VAL config --get sec.VAR >actual && + test_cmp expect actual +' + +test_expect_success 'last one wins: three level vars' ' + + # v.a.r and v.A.r are not the same variable, as the middle + # level of a three-level configuration variable name is + # case sensitive. + + echo val >expect && + git -c v.a.r=val -c v.A.r=VAL config --get v.a.r >actual && + test_cmp expect actual && + git -c v.a.r=val -c v.A.r=VAL config --get V.a.R >actual && + test_cmp expect actual && + + # v.a.r and V.a.R are the same variable, as the first + # and the last level of a configuration variable name is + # case insensitive. + + echo VAL >expect && + git -c v.a.r=val -c v.a.R=VAL config --get v.a.r >actual && + test_cmp expect actual && + git -c v.a.r=val -c V.a.r=VAL config --get v.a.r >actual && + test_cmp expect actual && + git -c v.a.r=val -c v.a.R=VAL config --get V.a.R >actual && + test_cmp expect actual && + git -c v.a.r=val -c V.a.r=VAL config --get V.a.R >actual && + test_cmp expect actual +' + +for VAR in a .a a. a.0b a."b c". a."b c".0d +do + test_expect_success "git -c $VAR=VAL rejects invalid '$VAR'" ' + test_must_fail git -c "$VAR=VAL" config -l + ' +done + +for VAR in a.b a."b c".d +do + test_expect_success "git -c $VAR=VAL works with valid '$VAR'" ' + echo VAL >expect && + git -c "$VAR=VAL" config --get "$VAR" >actual && + test_cmp expect actual + ' +done + +test_expect_success 'git -c is not confused by empty environment' ' + GIT_CONFIG_PARAMETERS="" git -c x.one=1 config --list +' + +sq="'" +test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' ' + cat >expect <<-\EOF && + env.one one + env.two two + EOF + GIT_CONFIG_PARAMETERS="${sq}env.one=one${sq} ${sq}env.two=two${sq}" \ + git config --get-regexp "env.*" >actual && + test_cmp expect actual && + + cat >expect <<-EOF && + env.one one${sq} + env.two two + EOF + GIT_CONFIG_PARAMETERS="${sq}env.one=one${sq}\\$sq$sq$sq ${sq}env.two=two${sq}" \ + git config --get-regexp "env.*" >actual && + test_cmp expect actual && + + test_must_fail env \ + GIT_CONFIG_PARAMETERS="${sq}env.one=one${sq}\\$sq ${sq}env.two=two${sq}" \ + git config --get-regexp "env.*" +' + test_expect_success 'git config --edit works' ' git config -f tmp test.value no && echo test.value=yes >expect && @@ -1086,7 +1254,7 @@ test_expect_success 'barf on syntax error' ' key garbage EOF test_must_fail git config --get section.key >actual 2>error && - grep " line 3 " error + test_i18ngrep " line 3 " error ' test_expect_success 'barf on incomplete section header' ' @@ -1096,7 +1264,7 @@ test_expect_success 'barf on incomplete section header' ' key = value EOF test_must_fail git config --get section.key >actual 2>error && - grep " line 2 " error + test_i18ngrep " line 2 " error ' test_expect_success 'barf on incomplete string' ' @@ -1106,7 +1274,7 @@ test_expect_success 'barf on incomplete string' ' key = "value string EOF test_must_fail git config --get section.key >actual 2>error && - grep " line 3 " error + test_i18ngrep " line 3 " error ' test_expect_success 'urlmatch' ' @@ -1118,6 +1286,9 @@ test_expect_success 'urlmatch' ' cookieFile = /tmp/cookie.txt EOF + test_expect_code 1 git config --bool --get-urlmatch doesnt.exist https://good.example.com >actual && + test_must_be_empty actual && + echo true >expect && git config --bool --get-urlmatch http.SSLverify https://good.example.com >actual && test_cmp expect actual && @@ -1134,6 +1305,111 @@ test_expect_success 'urlmatch' ' test_cmp expect actual ' +test_expect_success 'urlmatch favors more specific URLs' ' + cat >.git/config <<-\EOF && + [http "https://example.com/"] + cookieFile = /tmp/root.txt + [http "https://example.com/subdirectory"] + cookieFile = /tmp/subdirectory.txt + [http "https://user@example.com/"] + cookieFile = /tmp/user.txt + [http "https://averylonguser@example.com/"] + cookieFile = /tmp/averylonguser.txt + [http "https://preceding.example.com"] + cookieFile = /tmp/preceding.txt + [http "https://*.example.com"] + cookieFile = /tmp/wildcard.txt + [http "https://*.example.com/wildcardwithsubdomain"] + cookieFile = /tmp/wildcardwithsubdomain.txt + [http "https://trailing.example.com"] + cookieFile = /tmp/trailing.txt + [http "https://user@*.example.com/"] + cookieFile = /tmp/wildcardwithuser.txt + [http "https://sub.example.com/"] + cookieFile = /tmp/sub.txt + EOF + + echo http.cookiefile /tmp/root.txt >expect && + git config --get-urlmatch HTTP https://example.com >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/subdirectory.txt >expect && + git config --get-urlmatch HTTP https://example.com/subdirectory >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/subdirectory.txt >expect && + git config --get-urlmatch HTTP https://example.com/subdirectory/nested >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/user.txt >expect && + git config --get-urlmatch HTTP https://user@example.com/ >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/subdirectory.txt >expect && + git config --get-urlmatch HTTP https://averylonguser@example.com/subdirectory >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/preceding.txt >expect && + git config --get-urlmatch HTTP https://preceding.example.com >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/wildcard.txt >expect && + git config --get-urlmatch HTTP https://wildcard.example.com >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/sub.txt >expect && + git config --get-urlmatch HTTP https://sub.example.com/wildcardwithsubdomain >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/trailing.txt >expect && + git config --get-urlmatch HTTP https://trailing.example.com >actual && + test_cmp expect actual && + + echo http.cookiefile /tmp/sub.txt >expect && + git config --get-urlmatch HTTP https://user@sub.example.com >actual && + test_cmp expect actual +' + +test_expect_success 'urlmatch with wildcard' ' + cat >.git/config <<-\EOF && + [http] + sslVerify + [http "https://*.example.com"] + sslVerify = false + cookieFile = /tmp/cookie.txt + EOF + + test_expect_code 1 git config --bool --get-urlmatch doesnt.exist https://good.example.com >actual && + test_must_be_empty actual && + + echo true >expect && + git config --bool --get-urlmatch http.SSLverify https://example.com >actual && + test_cmp expect actual && + + echo true >expect && + git config --bool --get-urlmatch http.SSLverify https://good-example.com >actual && + test_cmp expect actual && + + echo true >expect && + git config --bool --get-urlmatch http.sslverify https://deep.nested.example.com >actual && + test_cmp expect actual && + + echo false >expect && + git config --bool --get-urlmatch http.sslverify https://good.example.com >actual && + test_cmp expect actual && + + { + echo http.cookiefile /tmp/cookie.txt && + echo http.sslverify false + } >expect && + git config --get-urlmatch HTTP https://good.example.com >actual && + test_cmp expect actual && + + echo http.sslverify >expect && + git config --get-urlmatch HTTP https://more.example.com.au >actual && + test_cmp expect actual +' + # good section hygiene test_expect_failure 'unsetting the last key in a section removes header' ' cat >.git/config <<-\EOF && @@ -1179,4 +1455,160 @@ test_expect_success POSIXPERM,PERL 'preserves existing permissions' ' "die q(badrename) if ((stat(q(.git/config)))[2] & 07777) != 0600" ' +! test_have_prereq MINGW || +HOME="$(pwd)" # convert to Windows path + +test_expect_success 'set up --show-origin tests' ' + INCLUDE_DIR="$HOME/include" && + mkdir -p "$INCLUDE_DIR" && + cat >"$INCLUDE_DIR"/absolute.include <<-\EOF && + [user] + absolute = include + EOF + cat >"$INCLUDE_DIR"/relative.include <<-\EOF && + [user] + relative = include + EOF + cat >"$HOME"/.gitconfig <<-EOF && + [user] + global = true + override = global + [include] + path = "$INCLUDE_DIR/absolute.include" + EOF + cat >.git/config <<-\EOF + [user] + local = true + override = local + [include] + path = ../include/relative.include + EOF +' + +test_expect_success '--show-origin with --list' ' + cat >expect <<-EOF && + file:$HOME/.gitconfig user.global=true + file:$HOME/.gitconfig user.override=global + file:$HOME/.gitconfig include.path=$INCLUDE_DIR/absolute.include + file:$INCLUDE_DIR/absolute.include user.absolute=include + file:.git/config user.local=true + file:.git/config user.override=local + file:.git/config include.path=../include/relative.include + file:.git/../include/relative.include user.relative=include + command line: user.cmdline=true + EOF + git -c user.cmdline=true config --list --show-origin >output && + test_cmp expect output +' + +test_expect_success '--show-origin with --list --null' ' + cat >expect <<-EOF && + file:$HOME/.gitconfigQuser.global + trueQfile:$HOME/.gitconfigQuser.override + globalQfile:$HOME/.gitconfigQinclude.path + $INCLUDE_DIR/absolute.includeQfile:$INCLUDE_DIR/absolute.includeQuser.absolute + includeQfile:.git/configQuser.local + trueQfile:.git/configQuser.override + localQfile:.git/configQinclude.path + ../include/relative.includeQfile:.git/../include/relative.includeQuser.relative + includeQcommand line:Quser.cmdline + trueQ + EOF + git -c user.cmdline=true config --null --list --show-origin >output.raw && + nul_to_q <output.raw >output && + # The here-doc above adds a newline that the --null output would not + # include. Add it here to make the two comparable. + echo >>output && + test_cmp expect output +' + +test_expect_success '--show-origin with single file' ' + cat >expect <<-\EOF && + file:.git/config user.local=true + file:.git/config user.override=local + file:.git/config include.path=../include/relative.include + EOF + git config --local --list --show-origin >output && + test_cmp expect output +' + +test_expect_success '--show-origin with --get-regexp' ' + cat >expect <<-EOF && + file:$HOME/.gitconfig user.global true + file:.git/config user.local true + EOF + git config --show-origin --get-regexp "user\.[g|l].*" >output && + test_cmp expect output +' + +test_expect_success '--show-origin getting a single key' ' + cat >expect <<-\EOF && + file:.git/config local + EOF + git config --show-origin user.override >output && + test_cmp expect output +' + +test_expect_success 'set up custom config file' ' + CUSTOM_CONFIG_FILE="file\" (dq) and spaces.conf" && + cat >"$CUSTOM_CONFIG_FILE" <<-\EOF + [user] + custom = true + EOF +' + +test_expect_success !MINGW '--show-origin escape special file name characters' ' + cat >expect <<-\EOF && + file:"file\" (dq) and spaces.conf" user.custom=true + EOF + git config --file "$CUSTOM_CONFIG_FILE" --show-origin --list >output && + test_cmp expect output +' + +test_expect_success '--show-origin stdin' ' + cat >expect <<-\EOF && + standard input: user.custom=true + EOF + git config --file - --show-origin --list <"$CUSTOM_CONFIG_FILE" >output && + test_cmp expect output +' + +test_expect_success '--show-origin stdin with file include' ' + cat >"$INCLUDE_DIR"/stdin.include <<-EOF && + [user] + stdin = include + EOF + cat >expect <<-EOF && + file:$INCLUDE_DIR/stdin.include include + EOF + echo "[include]path=\"$INCLUDE_DIR\"/stdin.include" \ + | git config --show-origin --includes --file - user.stdin >output && + test_cmp expect output +' + +test_expect_success !MINGW '--show-origin blob' ' + blob=$(git hash-object -w "$CUSTOM_CONFIG_FILE") && + cat >expect <<-EOF && + blob:$blob user.custom=true + EOF + git config --blob=$blob --show-origin --list >output && + test_cmp expect output +' + +test_expect_success !MINGW '--show-origin blob ref' ' + cat >expect <<-\EOF && + blob:"master:file\" (dq) and spaces.conf" user.custom=true + EOF + git add "$CUSTOM_CONFIG_FILE" && + git commit -m "new config file" && + git config --blob=master:"$CUSTOM_CONFIG_FILE" --show-origin --list >output && + test_cmp expect output +' + +test_expect_success '--local requires a repo' ' + # we expect 128 to ensure that we do not simply + # fail to find anything and return code "1" + test_expect_code 128 nongit git config --local foo.bar +' + test_done diff --git a/t/t1301-shared-repo.sh b/t/t1301-shared-repo.sh index ac10875408..dfece751b5 100755 --- a/t/t1301-shared-repo.sh +++ b/t/t1301-shared-repo.sh @@ -19,10 +19,6 @@ test_expect_success 'shared = 0400 (faulty permission u-w)' ' ) ' -modebits () { - ls -l "$1" | sed -e 's|^\(..........\).*|\1|' -} - for u in 002 022 do test_expect_success POSIXPERM "shared=1 does not clear bits preset by umask $u" ' @@ -88,7 +84,7 @@ do rm -f .git/info/refs && git update-server-info && - actual="$(modebits .git/info/refs)" && + actual="$(test_modebits .git/info/refs)" && verbose test "x$actual" = "x-$y" ' @@ -98,7 +94,7 @@ do rm -f .git/info/refs && git update-server-info && - actual="$(modebits .git/info/refs)" && + actual="$(test_modebits .git/info/refs)" && verbose test "x$actual" = "x-$x" ' @@ -111,7 +107,7 @@ test_expect_success POSIXPERM 'info/refs respects umask in unshared repo' ' umask 002 && git update-server-info && echo "-rw-rw-r--" >expect && - modebits .git/info/refs >actual && + test_modebits .git/info/refs >actual && test_cmp expect actual ' @@ -172,4 +168,45 @@ test_expect_success POSIXPERM 'forced modes' ' }" actual)" ' +test_expect_success POSIXPERM 'remote init does not use config from cwd' ' + git config core.sharedrepository 0666 && + umask 0022 && + git init --bare child.git && + echo "-rw-r--r--" >expect && + test_modebits child.git/config >actual && + test_cmp expect actual +' + +test_expect_success POSIXPERM 're-init respects core.sharedrepository (local)' ' + git config core.sharedrepository 0666 && + umask 0022 && + echo whatever >templates/foo && + git init --template=templates && + echo "-rw-rw-rw-" >expect && + test_modebits .git/foo >actual && + test_cmp expect actual +' + +test_expect_success POSIXPERM 're-init respects core.sharedrepository (remote)' ' + rm -rf child.git && + umask 0022 && + git init --bare --shared=0666 child.git && + test_path_is_missing child.git/foo && + git init --bare --template=../templates child.git && + echo "-rw-rw-rw-" >expect && + test_modebits child.git/foo >actual && + test_cmp expect actual +' + +test_expect_success POSIXPERM 'template can set core.sharedrepository' ' + rm -rf child.git && + umask 0022 && + git config core.sharedrepository 0666 && + cp .git/config templates/config && + git init --bare --template=../templates child.git && + echo "-rw-rw-rw-" >expect && + test_modebits child.git/HEAD >actual && + test_cmp expect actual +' + test_done diff --git a/t/t1302-repo-version.sh b/t/t1302-repo-version.sh index 0d9388afc4..ce4cff13bb 100755 --- a/t/t1302-repo-version.sh +++ b/t/t1302-repo-version.sh @@ -25,46 +25,86 @@ test_expect_success 'setup' ' test_expect_success 'gitdir selection on normal repos' ' echo 0 >expect && git config core.repositoryformatversion >actual && - ( - cd test && - git config core.repositoryformatversion >../actual2 - ) && + git -C test config core.repositoryformatversion >actual2 && test_cmp expect actual && test_cmp expect actual2 ' test_expect_success 'gitdir selection on unsupported repo' ' # Make sure it would stop at test2, not trash - echo 99 >expect && - ( - cd test2 && - git config core.repositoryformatversion >../actual - ) && - test_cmp expect actual + test_expect_code 1 git -C test2 config core.repositoryformatversion >actual ' test_expect_success 'gitdir not required mode' ' git apply --stat test.patch && - ( - cd test && - git apply --stat ../test.patch - ) && - ( - cd test2 && - git apply --stat ../test.patch - ) + git -C test apply --stat ../test.patch && + git -C test2 apply --stat ../test.patch ' test_expect_success 'gitdir required mode' ' git apply --check --index test.patch && - ( - cd test && - git apply --check --index ../test.patch - ) && - ( - cd test2 && - test_must_fail git apply --check --index ../test.patch - ) + git -C test apply --check --index ../test.patch && + test_must_fail git -C test2 apply --check --index ../test.patch +' + +check_allow () { + git rev-parse --git-dir >actual && + echo .git >expect && + test_cmp expect actual +} + +check_abort () { + test_must_fail git rev-parse --git-dir +} + +# avoid git-config, since it cannot be trusted to run +# in a repository with a broken version +mkconfig () { + echo '[core]' && + echo "repositoryformatversion = $1" && + shift && + + if test $# -gt 0; then + echo '[extensions]' && + for i in "$@"; do + echo "$i" + done + fi +} + +while read outcome version extensions; do + test_expect_success "$outcome version=$version $extensions" " + mkconfig $version $extensions >.git/config && + check_${outcome} + " +done <<\EOF +allow 0 +allow 1 +allow 1 noop +abort 1 no-such-extension +allow 0 no-such-extension +EOF + +test_expect_success 'precious-objects allowed' ' + mkconfig 1 preciousObjects >.git/config && + check_allow +' + +test_expect_success 'precious-objects blocks destructive repack' ' + test_must_fail git repack -ad +' + +test_expect_success 'other repacks are OK' ' + test_commit foo && + git repack +' + +test_expect_success 'precious-objects blocks prune' ' + test_must_fail git prune +' + +test_expect_success 'gc runs without complaint' ' + git gc ' test_done diff --git a/t/t1304-default-acl.sh b/t/t1304-default-acl.sh index f5422f1d33..335d3f3211 100755 --- a/t/t1304-default-acl.sh +++ b/t/t1304-default-acl.sh @@ -54,7 +54,7 @@ test_expect_success SETFACL 'Setup test repo' ' test_expect_success SETFACL 'Objects creation does not break ACLs with restrictive umask' ' # SHA1 for empty blob - check_perms_and_acl .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 + check_perms_and_acl .git/objects/$(echo $EMPTY_BLOB | sed -e "s,^\(..\),\1/,") ' test_expect_success SETFACL 'git gc does not break ACLs with restrictive umask' ' diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh index 9ba2ba11c3..f035ee40a3 100755 --- a/t/t1305-config-include.sh +++ b/t/t1305-config-include.sh @@ -3,6 +3,16 @@ test_description='test config file include directives' . ./test-lib.sh +# Force setup_explicit_git_dir() to run until the end. This is needed +# by some tests to make sure real_path() is called on $GIT_DIR. The +# caller needs to make sure git commands are run from a subdirectory +# though or real_path() will not be called. +force_setup_explicit_git_dir() { + GIT_DIR="$(pwd)/.git" + GIT_WORK_TREE="$(pwd)" + export GIT_DIR GIT_WORK_TREE +} + test_expect_success 'include file by absolute path' ' echo "[test]one = 1" >one && echo "[include]path = \"$(pwd)/one\"" >.gitconfig && @@ -102,7 +112,7 @@ test_expect_success 'config modification does not affect includes' ' test_expect_success 'missing include files are ignored' ' cat >.gitconfig <<-\EOF && - [include]path = foo + [include]path = non-existent [test]value = yes EOF echo yes >expect && @@ -152,6 +162,140 @@ test_expect_success 'relative includes from stdin line fail' ' test_must_fail git config --file - test.one ' +test_expect_success 'conditional include, both unanchored' ' + git init foo && + ( + cd foo && + echo "[includeIf \"gitdir:foo/\"]path=bar" >>.git/config && + echo "[test]one=1" >.git/bar && + echo 1 >expect && + git config test.one >actual && + test_cmp expect actual + ) +' + +test_expect_success 'conditional include, $HOME expansion' ' + ( + cd foo && + echo "[includeIf \"gitdir:~/foo/\"]path=bar2" >>.git/config && + echo "[test]two=2" >.git/bar2 && + echo 2 >expect && + git config test.two >actual && + test_cmp expect actual + ) +' + +test_expect_success 'conditional include, full pattern' ' + ( + cd foo && + echo "[includeIf \"gitdir:**/foo/**\"]path=bar3" >>.git/config && + echo "[test]three=3" >.git/bar3 && + echo 3 >expect && + git config test.three >actual && + test_cmp expect actual + ) +' + +test_expect_success 'conditional include, relative path' ' + echo "[includeIf \"gitdir:./foo/.git\"]path=bar4" >>.gitconfig && + echo "[test]four=4" >bar4 && + ( + cd foo && + echo 4 >expect && + git config test.four >actual && + test_cmp expect actual + ) +' + +test_expect_success 'conditional include, both unanchored, icase' ' + ( + cd foo && + echo "[includeIf \"gitdir/i:FOO/\"]path=bar5" >>.git/config && + echo "[test]five=5" >.git/bar5 && + echo 5 >expect && + git config test.five >actual && + test_cmp expect actual + ) +' + +test_expect_success 'conditional include, early config reading' ' + ( + cd foo && + echo "[includeIf \"gitdir:foo/\"]path=bar6" >>.git/config && + echo "[test]six=6" >.git/bar6 && + echo 6 >expect && + test-tool config read_early_config test.six >actual && + test_cmp expect actual + ) +' + +test_expect_success SYMLINKS 'conditional include, set up symlinked $HOME' ' + mkdir real-home && + ln -s real-home home && + ( + HOME="$TRASH_DIRECTORY/home" && + export HOME && + cd "$HOME" && + + git init foo && + cd foo && + mkdir sub + ) +' + +test_expect_success SYMLINKS 'conditional include, $HOME expansion with symlinks' ' + ( + HOME="$TRASH_DIRECTORY/home" && + export HOME && + cd "$HOME"/foo && + + echo "[includeIf \"gitdir:~/foo/\"]path=bar2" >>.git/config && + echo "[test]two=2" >.git/bar2 && + echo 2 >expect && + force_setup_explicit_git_dir && + git -C sub config test.two >actual && + test_cmp expect actual + ) +' + +test_expect_success SYMLINKS 'conditional include, relative path with symlinks' ' + echo "[includeIf \"gitdir:./foo/.git\"]path=bar4" >home/.gitconfig && + echo "[test]four=4" >home/bar4 && + ( + HOME="$TRASH_DIRECTORY/home" && + export HOME && + cd "$HOME"/foo && + + echo 4 >expect && + force_setup_explicit_git_dir && + git -C sub config test.four >actual && + test_cmp expect actual + ) +' + +test_expect_success SYMLINKS 'conditional include, gitdir matching symlink' ' + ln -s foo bar && + ( + cd bar && + echo "[includeIf \"gitdir:bar/\"]path=bar7" >>.git/config && + echo "[test]seven=7" >.git/bar7 && + echo 7 >expect && + git config test.seven >actual && + test_cmp expect actual + ) +' + +test_expect_success SYMLINKS 'conditional include, gitdir matching symlink, icase' ' + ( + cd bar && + echo "[includeIf \"gitdir/i:BAR/\"]path=bar8" >>.git/config && + echo "[test]eight=8" >.git/bar8 && + echo 8 >expect && + git config test.eight >actual && + test_cmp expect actual + ) +' + test_expect_success 'include cycles are detected' ' cat >.gitconfig <<-\EOF && [test]value = gitconfig diff --git a/t/t1307-config-blob.sh b/t/t1307-config-blob.sh index 3c6791e6be..eed31ffa30 100755 --- a/t/t1307-config-blob.sh +++ b/t/t1307-config-blob.sh @@ -61,10 +61,7 @@ test_expect_success 'parse errors in blobs are properly attributed' ' git commit -m broken && test_must_fail git config --blob=HEAD:config some.value 2>err && - - # just grep for our token as the exact error message is likely to - # change or be internationalized - grep "HEAD:config" err + test_i18ngrep "HEAD:config" err ' test_expect_success 'can parse blob ending with CR' ' diff --git a/t/t1308-config-set.sh b/t/t1308-config-set.sh index 91235b76ba..3e00d1af01 100755 --- a/t/t1308-config-set.sh +++ b/t/t1308-config-set.sh @@ -18,7 +18,7 @@ check_config () { then printf "%s\n" "$@" fi >expect && - test_expect_code $expect_code test-config "$op" "$key" >actual && + test_expect_code $expect_code test-tool config "$op" "$key" >actual && test_cmp expect actual } @@ -125,7 +125,7 @@ test_expect_success 'find string value for a key' ' ' test_expect_success 'check line error when NULL string is queried' ' - test_expect_code 128 test-config get_string case.foo 2>result && + test_expect_code 128 test-tool config get_string case.foo 2>result && test_i18ngrep "fatal: .*case\.foo.*\.git/config.*line 7" result ' @@ -155,13 +155,13 @@ test_expect_success 'find value from a configset' ' baz = ball EOF echo silk >expect && - test-config configset_get_value my.new config2 .git/config >actual && + test-tool config configset_get_value my.new config2 .git/config >actual && test_cmp expect actual ' test_expect_success 'find value with highest priority from a configset' ' echo hask >expect && - test-config configset_get_value case.baz config2 .git/config >actual && + test-tool config configset_get_value case.baz config2 .git/config >actual && test_cmp expect actual ' @@ -173,13 +173,22 @@ test_expect_success 'find value_list for a key from a configset' ' lama ball EOF - test-config configset_get_value case.baz config2 .git/config >actual && + test-tool config configset_get_value case.baz config2 .git/config >actual && test_cmp expect actual ' test_expect_success 'proper error on non-existent files' ' echo "Error (-1) reading configuration file non-existent-file." >expect && - test_expect_code 2 test-config configset_get_value foo.bar non-existent-file 2>actual && + test_expect_code 2 test-tool config configset_get_value foo.bar non-existent-file 2>actual && + test_cmp expect actual +' + +test_expect_success 'proper error on directory "files"' ' + echo "Error (-1) reading configuration file a-directory." >expect && + mkdir a-directory && + test_expect_code 2 test-tool config configset_get_value foo.bar a-directory 2>output && + grep "^warning:" output && + grep "^Error" output >actual && test_cmp expect actual ' @@ -187,7 +196,9 @@ test_expect_success POSIXPERM,SANITY 'proper error on non-accessible files' ' chmod -r .git/config && test_when_finished "chmod +r .git/config" && echo "Error (-1) reading configuration file .git/config." >expect && - test_expect_code 2 test-config configset_get_value foo.bar .git/config 2>actual && + test_expect_code 2 test-tool config configset_get_value foo.bar .git/config 2>output && + grep "^warning:" output && + grep "^Error" output >actual && test_cmp expect actual ' @@ -195,16 +206,16 @@ test_expect_success 'proper error on error in default config files' ' cp .git/config .git/config.old && test_when_finished "mv .git/config.old .git/config" && echo "[" >>.git/config && - echo "fatal: bad config file line 34 in .git/config" >expect && - test_expect_code 128 test-config get_value foo.bar 2>actual && - test_cmp expect actual + echo "fatal: bad config line 34 in file .git/config" >expect && + test_expect_code 128 test-tool config get_value foo.bar 2>actual && + test_i18ncmp expect actual ' test_expect_success 'proper error on error in custom config files' ' echo "[" >>syntax-error && - echo "fatal: bad config file line 1 in syntax-error" >expect && - test_expect_code 128 test-config configset_get_value foo.bar syntax-error 2>actual && - test_cmp expect actual + echo "fatal: bad config line 1 in file syntax-error" >expect && + test_expect_code 128 test-tool config configset_get_value foo.bar syntax-error 2>actual && + test_i18ncmp expect actual ' test_expect_success 'check line errors for malformed values' ' @@ -215,7 +226,49 @@ test_expect_success 'check line errors for malformed values' ' br EOF test_expect_code 128 git br 2>result && - test_i18ngrep "fatal: .*alias\.br.*\.git/config.*line 2" result + test_i18ngrep "missing value for .alias\.br" result && + test_i18ngrep "fatal: .*\.git/config" result && + test_i18ngrep "fatal: .*line 2" result +' + +test_expect_success 'error on modifying repo config without repo' ' + nongit test_must_fail git config a.b c 2>err && + grep "not in a git directory" err +' + +cmdline_config="'foo.bar=from-cmdline'" +test_expect_success 'iteration shows correct origins' ' + echo "[foo]bar = from-repo" >.git/config && + echo "[foo]bar = from-home" >.gitconfig && + if test_have_prereq MINGW + then + # Use Windows path (i.e. *not* $HOME) + HOME_GITCONFIG=$(pwd)/.gitconfig + else + # Do not get fooled by symbolic links, i.e. $HOME != $(pwd) + HOME_GITCONFIG=$HOME/.gitconfig + fi && + cat >expect <<-EOF && + key=foo.bar + value=from-home + origin=file + name=$HOME_GITCONFIG + scope=global + + key=foo.bar + value=from-repo + origin=file + name=.git/config + scope=repo + + key=foo.bar + value=from-cmdline + origin=command line + name= + scope=cmdline + EOF + GIT_CONFIG_PARAMETERS=$cmdline_config test-tool config iterate >actual && + test_cmp expect actual ' test_done diff --git a/t/t1309-early-config.sh b/t/t1309-early-config.sh new file mode 100755 index 0000000000..413642aa56 --- /dev/null +++ b/t/t1309-early-config.sh @@ -0,0 +1,92 @@ +#!/bin/sh + +test_description='Test read_early_config()' + +. ./test-lib.sh + +test_expect_success 'read early config' ' + test_config early.config correct && + test-tool config read_early_config early.config >output && + test correct = "$(cat output)" +' + +test_expect_success 'in a sub-directory' ' + test_config early.config sub && + mkdir -p sub && + ( + cd sub && + test-tool config read_early_config early.config + ) >output && + test sub = "$(cat output)" +' + +test_expect_success 'ceiling' ' + test_config early.config ceiling && + mkdir -p sub && + ( + GIT_CEILING_DIRECTORIES="$PWD" && + export GIT_CEILING_DIRECTORIES && + cd sub && + test-tool config read_early_config early.config + ) >output && + test -z "$(cat output)" +' + +test_expect_success 'ceiling #2' ' + mkdir -p xdg/git && + git config -f xdg/git/config early.config xdg && + test_config early.config ceiling && + mkdir -p sub && + ( + XDG_CONFIG_HOME="$PWD"/xdg && + GIT_CEILING_DIRECTORIES="$PWD" && + export GIT_CEILING_DIRECTORIES XDG_CONFIG_HOME && + cd sub && + test-tool config read_early_config early.config + ) >output && + test xdg = "$(cat output)" +' + +cmdline_config="'test.source=cmdline'" +test_expect_success 'read config file in right order' ' + echo "[test]source = home" >>.gitconfig && + git init foo && + ( + cd foo && + echo "[test]source = repo" >>.git/config && + GIT_CONFIG_PARAMETERS=$cmdline_config test-tool config \ + read_early_config test.source >actual && + cat >expected <<-\EOF && + home + repo + cmdline + EOF + test_cmp expected actual + ) +' + +test_with_config () { + rm -rf throwaway && + git init throwaway && + ( + cd throwaway && + echo "$*" >.git/config && + test-tool config read_early_config early.config + ) +} + +test_expect_success 'ignore .git/ with incompatible repository version' ' + test_with_config "[core]repositoryformatversion = 999999" 2>err && + test_i18ngrep "warning:.* Expected git repo version <= [1-9]" err +' + +test_expect_failure 'ignore .git/ with invalid repository version' ' + test_with_config "[core]repositoryformatversion = invalid" +' + + +test_expect_failure 'ignore .git/ with invalid config' ' + test_with_config "[" +' + +test_done diff --git a/t/t1350-config-hooks-path.sh b/t/t1350-config-hooks-path.sh new file mode 100755 index 0000000000..f1f9aee9f5 --- /dev/null +++ b/t/t1350-config-hooks-path.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +test_description='Test the core.hooksPath configuration variable' + +. ./test-lib.sh + +test_expect_success 'set up a pre-commit hook in core.hooksPath' ' + mkdir -p .git/custom-hooks .git/hooks && + write_script .git/custom-hooks/pre-commit <<-\EOF && + echo CUSTOM >>actual + EOF + write_script .git/hooks/pre-commit <<-\EOF + echo NORMAL >>actual + EOF +' + +test_expect_success 'Check that various forms of specifying core.hooksPath work' ' + test_commit no_custom_hook && + git config core.hooksPath .git/custom-hooks && + test_commit have_custom_hook && + git config core.hooksPath .git/custom-hooks/ && + test_commit have_custom_hook_trailing_slash && + git config core.hooksPath "$PWD/.git/custom-hooks" && + test_commit have_custom_hook_abs_path && + git config core.hooksPath "$PWD/.git/custom-hooks/" && + test_commit have_custom_hook_abs_path_trailing_slash && + cat >expect <<-\EOF && + NORMAL + CUSTOM + CUSTOM + CUSTOM + CUSTOM + EOF + test_cmp expect actual +' + +test_expect_success 'git rev-parse --git-path hooks' ' + git config core.hooksPath .git/custom-hooks && + git rev-parse --git-path hooks/abc >actual && + test .git/custom-hooks/abc = "$(cat actual)" +' + +test_done diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index d787bf50f8..664a3a4e4e 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -8,106 +8,209 @@ test_description='Test git update-ref and basic ref logging' Z=$_z40 -test_expect_success setup ' +m=refs/heads/master +n_dir=refs/heads/gu +n=$n_dir/fixes +outside=refs/foo +bare=bare-repo +create_test_commits () +{ + prfx="$1" for name in A B C D E F do test_tick && T=$(git write-tree) && sha1=$(echo $name | git commit-tree $T) && - eval $name=$sha1 + eval $prfx$name=$sha1 done +} +test_expect_success setup ' + create_test_commits "" && + mkdir $bare && + cd $bare && + git init --bare && + create_test_commits "bare" && + cd - ' -m=refs/heads/master -n_dir=refs/heads/gu -n=$n_dir/fixes - -test_expect_success \ - "create $m" \ - "git update-ref $m $A && - test $A"' = $(cat .git/'"$m"')' -test_expect_success \ - "create $m" \ - "git update-ref $m $B $A && - test $B"' = $(cat .git/'"$m"')' +test_expect_success "create $m" ' + git update-ref $m $A && + test $A = $(cat .git/$m) +' +test_expect_success "create $m with oldvalue verification" ' + git update-ref $m $B $A && + test $B = $(cat .git/$m) +' test_expect_success "fail to delete $m with stale ref" ' test_must_fail git update-ref -d $m $A && test $B = "$(cat .git/$m)" ' test_expect_success "delete $m" ' + test_when_finished "rm -f .git/$m" && git update-ref -d $m $B && - ! test -f .git/$m + test_path_is_missing .git/$m ' -rm -f .git/$m -test_expect_success "delete $m without oldvalue verification" " +test_expect_success "delete $m without oldvalue verification" ' + test_when_finished "rm -f .git/$m" && git update-ref $m $A && - test $A = \$(cat .git/$m) && + test $A = $(cat .git/$m) && git update-ref -d $m && - ! test -f .git/$m -" -rm -f .git/$m - -test_expect_success \ - "fail to create $n" \ - "touch .git/$n_dir && - test_must_fail git update-ref $n $A >out 2>err" -rm -f .git/$n_dir out err - -test_expect_success \ - "create $m (by HEAD)" \ - "git update-ref HEAD $A && - test $A"' = $(cat .git/'"$m"')' -test_expect_success \ - "create $m (by HEAD)" \ - "git update-ref HEAD $B $A && - test $B"' = $(cat .git/'"$m"')' + test_path_is_missing .git/$m +' + +test_expect_success "fail to create $n" ' + test_when_finished "rm -f .git/$n_dir" && + touch .git/$n_dir && + test_must_fail git update-ref $n $A +' + +test_expect_success "create $m (by HEAD)" ' + git update-ref HEAD $A && + test $A = $(cat .git/$m) +' +test_expect_success "create $m (by HEAD) with oldvalue verification" ' + git update-ref HEAD $B $A && + test $B = $(cat .git/$m) +' test_expect_success "fail to delete $m (by HEAD) with stale ref" ' test_must_fail git update-ref -d HEAD $A && test $B = $(cat .git/$m) ' test_expect_success "delete $m (by HEAD)" ' + test_when_finished "rm -f .git/$m" && git update-ref -d HEAD $B && - ! test -f .git/$m -' -rm -f .git/$m - -test_expect_success \ - "create $m (by HEAD)" \ - "git update-ref HEAD $A && - test $A"' = $(cat .git/'"$m"')' -test_expect_success \ - "pack refs" \ - "git pack-refs --all" -test_expect_success \ - "move $m (by HEAD)" \ - "git update-ref HEAD $B $A && - test $B"' = $(cat .git/'"$m"')' + test_path_is_missing .git/$m +' + +test_expect_success "deleting current branch adds message to HEAD's log" ' + test_when_finished "rm -f .git/$m" && + git update-ref $m $A && + git symbolic-ref HEAD $m && + git update-ref -m delete-$m -d $m && + test_path_is_missing .git/$m && + grep "delete-$m$" .git/logs/HEAD +' + +test_expect_success "deleting by HEAD adds message to HEAD's log" ' + test_when_finished "rm -f .git/$m" && + git update-ref $m $A && + git symbolic-ref HEAD $m && + git update-ref -m delete-by-head -d HEAD && + test_path_is_missing .git/$m && + grep "delete-by-head$" .git/logs/HEAD +' + +test_expect_success 'update-ref does not create reflogs by default' ' + test_when_finished "git update-ref -d $outside" && + git update-ref $outside $A && + git rev-parse $A >expect && + git rev-parse $outside >actual && + test_cmp expect actual && + test_must_fail git reflog exists $outside +' + +test_expect_success 'update-ref creates reflogs with --create-reflog' ' + test_when_finished "git update-ref -d $outside" && + git update-ref --create-reflog $outside $A && + git rev-parse $A >expect && + git rev-parse $outside >actual && + test_cmp expect actual && + git reflog exists $outside +' + +test_expect_success 'creates no reflog in bare repository' ' + git -C $bare update-ref $m $bareA && + git -C $bare rev-parse $bareA >expect && + git -C $bare rev-parse $m >actual && + test_cmp expect actual && + test_must_fail git -C $bare reflog exists $m +' + +test_expect_success 'core.logAllRefUpdates=true creates reflog in bare repository' ' + test_when_finished "git -C $bare config --unset core.logAllRefUpdates && \ + rm $bare/logs/$m" && + git -C $bare config core.logAllRefUpdates true && + git -C $bare update-ref $m $bareB && + git -C $bare rev-parse $bareB >expect && + git -C $bare rev-parse $m >actual && + test_cmp expect actual && + git -C $bare reflog exists $m +' + +test_expect_success 'core.logAllRefUpdates=true does not create reflog by default' ' + test_config core.logAllRefUpdates true && + test_when_finished "git update-ref -d $outside" && + git update-ref $outside $A && + git rev-parse $A >expect && + git rev-parse $outside >actual && + test_cmp expect actual && + test_must_fail git reflog exists $outside +' + +test_expect_success 'core.logAllRefUpdates=always creates reflog by default' ' + test_config core.logAllRefUpdates always && + test_when_finished "git update-ref -d $outside" && + git update-ref $outside $A && + git rev-parse $A >expect && + git rev-parse $outside >actual && + test_cmp expect actual && + git reflog exists $outside +' + +test_expect_success 'core.logAllRefUpdates=always creates no reflog for ORIG_HEAD' ' + test_config core.logAllRefUpdates always && + git update-ref ORIG_HEAD $A && + test_must_fail git reflog exists ORIG_HEAD +' + +test_expect_success '--no-create-reflog overrides core.logAllRefUpdates=always' ' + test_config core.logAllRefUpdates true && + test_when_finished "git update-ref -d $outside" && + git update-ref --no-create-reflog $outside $A && + git rev-parse $A >expect && + git rev-parse $outside >actual && + test_cmp expect actual && + test_must_fail git reflog exists $outside +' + +test_expect_success "create $m (by HEAD)" ' + git update-ref HEAD $A && + test $A = $(cat .git/$m) +' +test_expect_success 'pack refs' ' + git pack-refs --all +' +test_expect_success "move $m (by HEAD)" ' + git update-ref HEAD $B $A && + test $B = $(cat .git/$m) +' test_expect_success "delete $m (by HEAD) should remove both packed and loose $m" ' + test_when_finished "rm -f .git/$m" && git update-ref -d HEAD $B && ! grep "$m" .git/packed-refs && - ! test -f .git/$m + test_path_is_missing .git/$m ' -rm -f .git/$m cp -f .git/HEAD .git/HEAD.orig -test_expect_success "delete symref without dereference" ' +test_expect_success 'delete symref without dereference' ' + test_when_finished "cp -f .git/HEAD.orig .git/HEAD" && git update-ref --no-deref -d HEAD && - ! test -f .git/HEAD + test_path_is_missing .git/HEAD ' -cp -f .git/HEAD.orig .git/HEAD -test_expect_success "delete symref without dereference when the referred ref is packed" ' +test_expect_success 'delete symref without dereference when the referred ref is packed' ' + test_when_finished "cp -f .git/HEAD.orig .git/HEAD" && echo foo >foo.c && git add foo.c && git commit -m foo && git pack-refs --all && git update-ref --no-deref -d HEAD && - ! test -f .git/HEAD + test_path_is_missing .git/HEAD ' -cp -f .git/HEAD.orig .git/HEAD + git update-ref -d $m test_expect_success 'update-ref -d is not confused by self-reference' ' @@ -136,83 +239,110 @@ test_expect_success 'update-ref --no-deref -d can delete reference to bad ref' ' test_path_is_missing .git/refs/heads/ref-to-bad ' -test_expect_success '(not) create HEAD with old sha1' " +test_expect_success '(not) create HEAD with old sha1' ' test_must_fail git update-ref HEAD $A $B -" -test_expect_success "(not) prior created .git/$m" " - ! test -f .git/$m -" -rm -f .git/$m - -test_expect_success \ - "create HEAD" \ - "git update-ref HEAD $A" -test_expect_success '(not) change HEAD with wrong SHA1' " +' +test_expect_success "(not) prior created .git/$m" ' + test_when_finished "rm -f .git/$m" && + test_path_is_missing .git/$m +' + +test_expect_success 'create HEAD' ' + git update-ref HEAD $A +' +test_expect_success '(not) change HEAD with wrong SHA1' ' test_must_fail git update-ref HEAD $B $Z -" -test_expect_success "(not) changed .git/$m" " - ! test $B"' = $(cat .git/'"$m"') -' -rm -f .git/$m - -: a repository with working tree always has reflog these days... -: >.git/logs/refs/heads/master -test_expect_success \ - "create $m (logged by touch)" \ - 'GIT_COMMITTER_DATE="2005-05-26 23:30" \ - git update-ref HEAD '"$A"' -m "Initial Creation" && - test '"$A"' = $(cat .git/'"$m"')' -test_expect_success \ - "update $m (logged by touch)" \ - 'GIT_COMMITTER_DATE="2005-05-26 23:31" \ - git update-ref HEAD'" $B $A "'-m "Switch" && - test '"$B"' = $(cat .git/'"$m"')' -test_expect_success \ - "set $m (logged by touch)" \ - 'GIT_COMMITTER_DATE="2005-05-26 23:41" \ - git update-ref HEAD'" $A && - test $A"' = $(cat .git/'"$m"')' +' +test_expect_success "(not) changed .git/$m" ' + test_when_finished "rm -f .git/$m" && + ! test $B = $(cat .git/$m) +' + +rm -f .git/logs/refs/heads/master +test_expect_success "create $m (logged by touch)" ' + test_config core.logAllRefUpdates false && + GIT_COMMITTER_DATE="2005-05-26 23:30" \ + git update-ref --create-reflog HEAD $A -m "Initial Creation" && + test $A = $(cat .git/$m) +' +test_expect_success "update $m (logged by touch)" ' + test_config core.logAllRefUpdates false && + GIT_COMMITTER_DATE="2005-05-26 23:31" \ + git update-ref HEAD $B $A -m "Switch" && + test $B = $(cat .git/$m) +' +test_expect_success "set $m (logged by touch)" ' + test_config core.logAllRefUpdates false && + GIT_COMMITTER_DATE="2005-05-26 23:41" \ + git update-ref HEAD $A && + test $A = $(cat .git/$m) +' + +test_expect_success 'empty directory removal' ' + git branch d1/d2/r1 HEAD && + git branch d1/r2 HEAD && + test_path_is_file .git/refs/heads/d1/d2/r1 && + test_path_is_file .git/logs/refs/heads/d1/d2/r1 && + git branch -d d1/d2/r1 && + test_path_is_missing .git/refs/heads/d1/d2 && + test_path_is_missing .git/logs/refs/heads/d1/d2 && + test_path_is_file .git/refs/heads/d1/r2 && + test_path_is_file .git/logs/refs/heads/d1/r2 +' + +test_expect_success 'symref empty directory removal' ' + git branch e1/e2/r1 HEAD && + git branch e1/r2 HEAD && + git checkout e1/e2/r1 && + test_when_finished "git checkout master" && + test_path_is_file .git/refs/heads/e1/e2/r1 && + test_path_is_file .git/logs/refs/heads/e1/e2/r1 && + git update-ref -d HEAD && + test_path_is_missing .git/refs/heads/e1/e2 && + test_path_is_missing .git/logs/refs/heads/e1/e2 && + test_path_is_file .git/refs/heads/e1/r2 && + test_path_is_file .git/logs/refs/heads/e1/r2 && + test_path_is_file .git/logs/HEAD +' cat >expect <<EOF $Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 Initial Creation $A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150260 +0000 Switch $B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000 EOF -test_expect_success \ - "verifying $m's log" \ - "test_cmp expect .git/logs/$m" -rm -rf .git/$m .git/logs expect - -test_expect_success \ - 'enable core.logAllRefUpdates' \ - 'git config core.logAllRefUpdates true && - test true = $(git config --bool --get core.logAllRefUpdates)' - -test_expect_success \ - "create $m (logged by config)" \ - 'GIT_COMMITTER_DATE="2005-05-26 23:32" \ - git update-ref HEAD'" $A "'-m "Initial Creation" && - test '"$A"' = $(cat .git/'"$m"')' -test_expect_success \ - "update $m (logged by config)" \ - 'GIT_COMMITTER_DATE="2005-05-26 23:33" \ - git update-ref HEAD'" $B $A "'-m "Switch" && - test '"$B"' = $(cat .git/'"$m"')' -test_expect_success \ - "set $m (logged by config)" \ - 'GIT_COMMITTER_DATE="2005-05-26 23:43" \ - git update-ref HEAD '"$A && - test $A"' = $(cat .git/'"$m"')' +test_expect_success "verifying $m's log (logged by touch)" ' + test_when_finished "rm -rf .git/$m .git/logs expect" && + test_cmp expect .git/logs/$m +' + +test_expect_success "create $m (logged by config)" ' + test_config core.logAllRefUpdates true && + GIT_COMMITTER_DATE="2005-05-26 23:32" \ + git update-ref HEAD $A -m "Initial Creation" && + test $A = $(cat .git/$m) +' +test_expect_success "update $m (logged by config)" ' + test_config core.logAllRefUpdates true && + GIT_COMMITTER_DATE="2005-05-26 23:33" \ + git update-ref HEAD'" $B $A "'-m "Switch" && + test $B = $(cat .git/$m) +' +test_expect_success "set $m (logged by config)" ' + test_config core.logAllRefUpdates true && + GIT_COMMITTER_DATE="2005-05-26 23:43" \ + git update-ref HEAD $A && + test $A = $(cat .git/$m) +' cat >expect <<EOF $Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 +0000 Initial Creation $A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 +0000 Switch $B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 +0000 EOF -test_expect_success \ - "verifying $m's log" \ - 'test_cmp expect .git/logs/$m' -rm -f .git/$m .git/logs/$m expect +test_expect_success "verifying $m's log (logged by config)" ' + test_when_finished "rm -f .git/$m .git/logs/$m expect" && + test_cmp expect .git/logs/$m +' git update-ref $m $D cat >.git/logs/$m <<EOF @@ -226,86 +356,85 @@ EOF ed="Thu, 26 May 2005 18:32:00 -0500" gd="Thu, 26 May 2005 18:33:00 -0500" ld="Thu, 26 May 2005 18:43:00 -0500" -test_expect_success \ - 'Query "master@{May 25 2005}" (before history)' \ - 'rm -f o e && - git rev-parse --verify "master@{May 25 2005}" >o 2>e && - test '"$C"' = $(cat o) && - test "warning: Log for '\'master\'' only goes back to $ed." = "$(cat e)"' -test_expect_success \ - "Query master@{2005-05-25} (before history)" \ - 'rm -f o e && - git rev-parse --verify master@{2005-05-25} >o 2>e && - test '"$C"' = $(cat o) && - echo test "warning: Log for '\'master\'' only goes back to $ed." = "$(cat e)"' -test_expect_success \ - 'Query "master@{May 26 2005 23:31:59}" (1 second before history)' \ - 'rm -f o e && - git rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e && - test '"$C"' = $(cat o) && - test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)"' -test_expect_success \ - 'Query "master@{May 26 2005 23:32:00}" (exactly history start)' \ - 'rm -f o e && - git rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e && - test '"$C"' = $(cat o) && - test "" = "$(cat e)"' -test_expect_success \ - 'Query "master@{May 26 2005 23:32:30}" (first non-creation change)' \ - 'rm -f o e && - git rev-parse --verify "master@{May 26 2005 23:32:30}" >o 2>e && - test '"$A"' = $(cat o) && - test "" = "$(cat e)"' -test_expect_success \ - 'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' \ - 'rm -f o e && - git rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e && - test '"$B"' = $(cat o) && - test "warning: Log for ref '"$m has gap after $gd"'." = "$(cat e)"' -test_expect_success \ - 'Query "master@{2005-05-26 23:38:00}" (middle of history)' \ - 'rm -f o e && - git rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e && - test '"$Z"' = $(cat o) && - test "" = "$(cat e)"' -test_expect_success \ - 'Query "master@{2005-05-26 23:43:00}" (exact end of history)' \ - 'rm -f o e && - git rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e && - test '"$E"' = $(cat o) && - test "" = "$(cat e)"' -test_expect_success \ - 'Query "master@{2005-05-28}" (past end of history)' \ - 'rm -f o e && - git rev-parse --verify "master@{2005-05-28}" >o 2>e && - test '"$D"' = $(cat o) && - test "warning: Log for ref '"$m unexpectedly ended on $ld"'." = "$(cat e)"' - +test_expect_success 'Query "master@{May 25 2005}" (before history)' ' + test_when_finished "rm -f o e" && + git rev-parse --verify "master@{May 25 2005}" >o 2>e && + test $C = $(cat o) && + test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)" +' +test_expect_success 'Query master@{2005-05-25} (before history)' ' + test_when_finished "rm -f o e" && + git rev-parse --verify master@{2005-05-25} >o 2>e && + test $C = $(cat o) && + echo test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)" +' +test_expect_success 'Query "master@{May 26 2005 23:31:59}" (1 second before history)' ' + test_when_finished "rm -f o e" && + git rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e && + test $C = $(cat o) && + test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)" +' +test_expect_success 'Query "master@{May 26 2005 23:32:00}" (exactly history start)' ' + test_when_finished "rm -f o e" && + git rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e && + test $C = $(cat o) && + test "" = "$(cat e)" +' +test_expect_success 'Query "master@{May 26 2005 23:32:30}" (first non-creation change)' ' + test_when_finished "rm -f o e" && + git rev-parse --verify "master@{May 26 2005 23:32:30}" >o 2>e && + test $A = $(cat o) && + test "" = "$(cat e)" +' +test_expect_success 'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' ' + test_when_finished "rm -f o e" && + git rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e && + test $B = $(cat o) && + test "warning: Log for ref $m has gap after $gd." = "$(cat e)" +' +test_expect_success 'Query "master@{2005-05-26 23:38:00}" (middle of history)' ' + test_when_finished "rm -f o e" && + git rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e && + test $Z = $(cat o) && + test "" = "$(cat e)" +' +test_expect_success 'Query "master@{2005-05-26 23:43:00}" (exact end of history)' ' + test_when_finished "rm -f o e" && + git rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e && + test $E = $(cat o) && + test "" = "$(cat e)" +' +test_expect_success 'Query "master@{2005-05-28}" (past end of history)' ' + test_when_finished "rm -f o e" && + git rev-parse --verify "master@{2005-05-28}" >o 2>e && + test $D = $(cat o) && + test "warning: Log for ref $m unexpectedly ended on $ld." = "$(cat e)" +' rm -f .git/$m .git/logs/$m expect -test_expect_success \ - 'creating initial files' \ - 'echo TEST >F && - git add F && - GIT_AUTHOR_DATE="2005-05-26 23:30" \ - GIT_COMMITTER_DATE="2005-05-26 23:30" git commit -m add -a && - h_TEST=$(git rev-parse --verify HEAD) && - echo The other day this did not work. >M && - echo And then Bob told me how to fix it. >>M && - echo OTHER >F && - GIT_AUTHOR_DATE="2005-05-26 23:41" \ - GIT_COMMITTER_DATE="2005-05-26 23:41" git commit -F M -a && - h_OTHER=$(git rev-parse --verify HEAD) && - GIT_AUTHOR_DATE="2005-05-26 23:44" \ - GIT_COMMITTER_DATE="2005-05-26 23:44" git commit --amend && - h_FIXED=$(git rev-parse --verify HEAD) && - echo Merged initial commit and a later commit. >M && - echo $h_TEST >.git/MERGE_HEAD && - GIT_AUTHOR_DATE="2005-05-26 23:45" \ - GIT_COMMITTER_DATE="2005-05-26 23:45" git commit -F M && - h_MERGED=$(git rev-parse --verify HEAD) && - rm -f M' +test_expect_success 'creating initial files' ' + test_when_finished rm -f M && + echo TEST >F && + git add F && + GIT_AUTHOR_DATE="2005-05-26 23:30" \ + GIT_COMMITTER_DATE="2005-05-26 23:30" git commit -m add -a && + h_TEST=$(git rev-parse --verify HEAD) && + echo The other day this did not work. >M && + echo And then Bob told me how to fix it. >>M && + echo OTHER >F && + GIT_AUTHOR_DATE="2005-05-26 23:41" \ + GIT_COMMITTER_DATE="2005-05-26 23:41" git commit -F M -a && + h_OTHER=$(git rev-parse --verify HEAD) && + GIT_AUTHOR_DATE="2005-05-26 23:44" \ + GIT_COMMITTER_DATE="2005-05-26 23:44" git commit --amend && + h_FIXED=$(git rev-parse --verify HEAD) && + echo Merged initial commit and a later commit. >M && + echo $h_TEST >.git/MERGE_HEAD && + GIT_AUTHOR_DATE="2005-05-26 23:45" \ + GIT_COMMITTER_DATE="2005-05-26 23:45" git commit -F M && + h_MERGED=$(git rev-parse --verify HEAD) +' cat >expect <<EOF $Z $h_TEST $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 commit (initial): add @@ -313,20 +442,20 @@ $h_TEST $h_OTHER $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000 com $h_OTHER $h_FIXED $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117151040 +0000 commit (amend): The other day this did not work. $h_FIXED $h_MERGED $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117151100 +0000 commit (merge): Merged initial commit and a later commit. EOF -test_expect_success \ - 'git commit logged updates' \ - "test_cmp expect .git/logs/$m" +test_expect_success 'git commit logged updates' ' + test_cmp expect .git/logs/$m +' unset h_TEST h_OTHER h_FIXED h_MERGED -test_expect_success \ - 'git cat-file blob master:F (expect OTHER)' \ - 'test OTHER = $(git cat-file blob master:F)' -test_expect_success \ - 'git cat-file blob master@{2005-05-26 23:30}:F (expect TEST)' \ - 'test TEST = $(git cat-file blob "master@{2005-05-26 23:30}:F")' -test_expect_success \ - 'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \ - 'test OTHER = $(git cat-file blob "master@{2005-05-26 23:42}:F")' +test_expect_success 'git cat-file blob master:F (expect OTHER)' ' + test OTHER = $(git cat-file blob master:F) +' +test_expect_success 'git cat-file blob master@{2005-05-26 23:30}:F (expect TEST)' ' + test TEST = $(git cat-file blob "master@{2005-05-26 23:30}:F") +' +test_expect_success 'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' ' + test OTHER = $(git cat-file blob "master@{2005-05-26 23:42}:F") +' a=refs/heads/a b=refs/heads/b @@ -343,7 +472,7 @@ test_expect_success 'stdin test setup' ' test_expect_success '-z fails without --stdin' ' test_must_fail git update-ref -z $m $m $m 2>err && - grep "usage: git update-ref" err + test_i18ngrep "usage: git update-ref" err ' test_expect_success 'stdin works with no input' ' @@ -461,7 +590,7 @@ test_expect_success 'stdin fails with duplicate refs' ' create $a $m EOF test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: Multiple updates for ref '"'"'$a'"'"' not allowed." err + grep "fatal: multiple updates for ref '"'"'$a'"'"' not allowed." err ' test_expect_success 'stdin create ref works' ' @@ -472,6 +601,26 @@ test_expect_success 'stdin create ref works' ' test_cmp expect actual ' +test_expect_success 'stdin does not create reflogs by default' ' + test_when_finished "git update-ref -d $outside" && + echo "create $outside $m" >stdin && + git update-ref --stdin <stdin && + git rev-parse $m >expect && + git rev-parse $outside >actual && + test_cmp expect actual && + test_must_fail git reflog exists $outside +' + +test_expect_success 'stdin creates reflogs with --create-reflog' ' + test_when_finished "git update-ref -d $outside" && + echo "create $outside $m" >stdin && + git update-ref --create-reflog --stdin <stdin && + git rev-parse $m >expect && + git rev-parse $outside >actual && + test_cmp expect actual && + git reflog exists $outside +' + test_expect_success 'stdin succeeds with quoted argument' ' git update-ref -d $a && echo "create $a \"$m\"" >stdin && @@ -843,7 +992,7 @@ test_expect_success 'stdin -z fails option with unknown name' ' test_expect_success 'stdin -z fails with duplicate refs' ' printf $F "create $a" "$m" "create $b" "$m" "create $a" "$m" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: Multiple updates for ref '"'"'$a'"'"' not allowed." err + grep "fatal: multiple updates for ref '"'"'$a'"'"' not allowed." err ' test_expect_success 'stdin -z create ref works' ' @@ -1065,11 +1214,49 @@ test_expect_success 'stdin -z delete refs works with packed and loose refs' ' test_must_fail git rev-parse --verify -q $c ' +test_expect_success 'fails with duplicate HEAD update' ' + git branch target1 $A && + git checkout target1 && + cat >stdin <<-EOF && + update refs/heads/target1 $C + option no-deref + update HEAD $B + EOF + test_must_fail git update-ref --stdin <stdin 2>err && + grep "fatal: multiple updates for '\''HEAD'\'' (including one via its referent .refs/heads/target1.) are not allowed" err && + echo "refs/heads/target1" >expect && + git symbolic-ref HEAD >actual && + test_cmp expect actual && + echo "$A" >expect && + git rev-parse refs/heads/target1 >actual && + test_cmp expect actual +' + +test_expect_success 'fails with duplicate ref update via symref' ' + git branch target2 $A && + git symbolic-ref refs/heads/symref2 refs/heads/target2 && + cat >stdin <<-EOF && + update refs/heads/target2 $C + update refs/heads/symref2 $B + EOF + test_must_fail git update-ref --stdin <stdin 2>err && + grep "fatal: multiple updates for '\''refs/heads/target2'\'' (including one via symref .refs/heads/symref2.) are not allowed" err && + echo "refs/heads/target2" >expect && + git symbolic-ref refs/heads/symref2 >actual && + test_cmp expect actual && + echo "$A" >expect && + git rev-parse refs/heads/target2 >actual && + test_cmp expect actual +' + run_with_limited_open_files () { (ulimit -n 32 && "$@") } -test_lazy_prereq ULIMIT_FILE_DESCRIPTORS 'run_with_limited_open_files true' +test_lazy_prereq ULIMIT_FILE_DESCRIPTORS ' + test_have_prereq !MINGW,!CYGWIN && + run_with_limited_open_files true +' test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction creating branches does not burst open file limit' ' ( @@ -1093,4 +1280,23 @@ test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction deleting branches ) ' +test_expect_success 'handle per-worktree refs in refs/bisect' ' + git commit --allow-empty -m "initial commit" && + git worktree add -b branch worktree && + ( + cd worktree && + git commit --allow-empty -m "test commit" && + git for-each-ref >for-each-ref.out && + ! grep refs/bisect for-each-ref.out && + git update-ref refs/bisect/something HEAD && + git rev-parse refs/bisect/something >../worktree-head && + git for-each-ref | grep refs/bisect/something + ) && + test_path_is_missing .git/refs/bisect && + test_must_fail git rev-parse refs/bisect/something && + git update-ref refs/bisect/something HEAD && + git rev-parse refs/bisect/something >main-head && + ! test_cmp main-head worktree-head +' + test_done diff --git a/t/t1401-symbolic-ref.sh b/t/t1401-symbolic-ref.sh index 36378b0e3f..9e782a8122 100755 --- a/t/t1401-symbolic-ref.sh +++ b/t/t1401-symbolic-ref.sh @@ -29,22 +29,29 @@ reset_to_sane test_expect_success 'symbolic-ref refuses bare sha1' ' echo content >file && git add file && git commit -m one && - test_must_fail git symbolic-ref HEAD `git rev-parse HEAD` + test_must_fail git symbolic-ref HEAD $(git rev-parse HEAD) ' reset_to_sane -test_expect_success 'symbolic-ref deletes HEAD' ' - git symbolic-ref -d HEAD && +test_expect_success 'HEAD cannot be removed' ' + test_must_fail git symbolic-ref -d HEAD +' + +reset_to_sane + +test_expect_success 'symbolic-ref can be deleted' ' + git symbolic-ref NOTHEAD refs/heads/foo && + git symbolic-ref -d NOTHEAD && test_path_is_file .git/refs/heads/foo && - test_path_is_missing .git/HEAD + test_path_is_missing .git/NOTHEAD ' reset_to_sane -test_expect_success 'symbolic-ref deletes dangling HEAD' ' - git symbolic-ref HEAD refs/heads/missing && - git symbolic-ref -d HEAD && +test_expect_success 'symbolic-ref can delete dangling symref' ' + git symbolic-ref NOTHEAD refs/heads/missing && + git symbolic-ref -d NOTHEAD && test_path_is_missing .git/refs/heads/missing && - test_path_is_missing .git/HEAD + test_path_is_missing .git/NOTHEAD ' reset_to_sane @@ -63,4 +70,94 @@ test_expect_success 'symbolic-ref fails to delete real ref' ' ' reset_to_sane +test_expect_success 'create large ref name' ' + # make 256+ character ref; some systems may not handle that, + # so be gentle + long=0123456789abcdef && + long=$long/$long/$long/$long && + long=$long/$long/$long/$long && + long_ref=refs/heads/$long && + tree=$(git write-tree) && + commit=$(echo foo | git commit-tree $tree) && + if git update-ref $long_ref $commit; then + test_set_prereq LONG_REF + else + echo >&2 "long refs not supported" + fi +' + +test_expect_success LONG_REF 'symbolic-ref can point to large ref name' ' + git symbolic-ref HEAD $long_ref && + echo $long_ref >expect && + git symbolic-ref HEAD >actual && + test_cmp expect actual +' + +test_expect_success LONG_REF 'we can parse long symbolic ref' ' + echo $commit >expect && + git rev-parse --verify HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'symbolic-ref reports failure in exit code' ' + test_when_finished "rm -f .git/HEAD.lock" && + >.git/HEAD.lock && + test_must_fail git symbolic-ref HEAD refs/heads/whatever +' + +test_expect_success 'symbolic-ref writes reflog entry' ' + git checkout -b log1 && + test_commit one && + git checkout -b log2 && + test_commit two && + git checkout --orphan orphan && + git symbolic-ref -m create HEAD refs/heads/log1 && + git symbolic-ref -m update HEAD refs/heads/log2 && + cat >expect <<-\EOF && + update + create + EOF + git log --format=%gs -g -2 >actual && + test_cmp expect actual +' + +test_expect_success 'symbolic-ref does not create ref d/f conflicts' ' + git checkout -b df && + test_commit df && + test_must_fail git symbolic-ref refs/heads/df/conflict refs/heads/df && + git pack-refs --all --prune && + test_must_fail git symbolic-ref refs/heads/df/conflict refs/heads/df +' + +test_expect_success 'symbolic-ref can overwrite pointer to invalid name' ' + test_when_finished reset_to_sane && + head=$(git rev-parse HEAD) && + git symbolic-ref HEAD refs/heads/outer && + test_when_finished "git update-ref -d refs/heads/outer/inner" && + git update-ref refs/heads/outer/inner $head && + git symbolic-ref HEAD refs/heads/unrelated +' + +test_expect_success 'symbolic-ref can resolve d/f name (EISDIR)' ' + test_when_finished reset_to_sane && + head=$(git rev-parse HEAD) && + git symbolic-ref HEAD refs/heads/outer/inner && + test_when_finished "git update-ref -d refs/heads/outer" && + git update-ref refs/heads/outer $head && + echo refs/heads/outer/inner >expect && + git symbolic-ref HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'symbolic-ref can resolve d/f name (ENOTDIR)' ' + test_when_finished reset_to_sane && + head=$(git rev-parse HEAD) && + git symbolic-ref HEAD refs/heads/outer && + test_when_finished "git update-ref -d refs/heads/outer/inner" && + git update-ref refs/heads/outer/inner $head && + echo refs/heads/outer >expect && + git symbolic-ref HEAD >actual && + test_cmp expect actual +' + test_done diff --git a/t/t1402-check-ref-format.sh b/t/t1402-check-ref-format.sh index e5dc62e9ef..98e4a8613b 100755 --- a/t/t1402-check-ref-format.sh +++ b/t/t1402-check-ref-format.sh @@ -62,9 +62,11 @@ invalid_ref 'heads/foo\bar' invalid_ref "$(printf 'heads/foo\t')" invalid_ref "$(printf 'heads/foo\177')" valid_ref "$(printf 'heads/fu\303\237')" -invalid_ref 'heads/*foo/bar' --refspec-pattern -invalid_ref 'heads/foo*/bar' --refspec-pattern -invalid_ref 'heads/f*o/bar' --refspec-pattern +valid_ref 'heads/*foo/bar' --refspec-pattern +valid_ref 'heads/foo*/bar' --refspec-pattern +valid_ref 'heads/f*o/bar' --refspec-pattern +invalid_ref 'heads/f*o*/bar' --refspec-pattern +invalid_ref 'heads/foo*/bar*' --refspec-pattern ref='foo' invalid_ref "$ref" @@ -142,6 +144,11 @@ test_expect_success "check-ref-format --branch @{-1}" ' refname2=$(git check-ref-format --branch @{-2}) && test "$refname2" = master' +test_expect_success 'check-ref-format --branch -naster' ' + test_must_fail git check-ref-format --branch -naster >actual && + test_must_be_empty actual +' + test_expect_success 'check-ref-format --branch from subdir' ' mkdir subdir && @@ -159,6 +166,17 @@ test_expect_success 'check-ref-format --branch from subdir' ' test "$refname" = "$sha1" ' +test_expect_success 'check-ref-format --branch @{-1} from non-repo' ' + nongit test_must_fail git check-ref-format --branch @{-1} >actual && + test_must_be_empty actual +' + +test_expect_success 'check-ref-format --branch master from non-repo' ' + echo master >expect && + nongit git check-ref-format --branch master >actual && + test_cmp expect actual +' + valid_ref_normalized() { prereq= case $1 in diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh index 7e10bcfe39..30354fd26c 100755 --- a/t/t1403-show-ref.sh +++ b/t/t1403-show-ref.sh @@ -97,6 +97,9 @@ test_expect_success 'show-ref -d' ' git show-ref -d refs/tags/A refs/tags/C >actual && test_cmp expect actual && + git show-ref --verify -d refs/tags/A refs/tags/C >actual && + test_cmp expect actual && + echo $(git rev-parse refs/heads/master) refs/heads/master >expect && git show-ref -d master >actual && test_cmp expect actual && @@ -116,6 +119,12 @@ test_expect_success 'show-ref -d' ' test_cmp expect actual && test_must_fail git show-ref -d --verify heads/master >actual && + test_cmp expect actual && + + test_must_fail git show-ref --verify -d A C >actual && + test_cmp expect actual && + + test_must_fail git show-ref --verify -d tags/A tags/C >actual && test_cmp expect actual ' @@ -164,4 +173,37 @@ test_expect_success 'show-ref --heads, --tags, --head, pattern' ' test_cmp expect actual ' +test_expect_success 'show-ref --verify HEAD' ' + echo $(git rev-parse HEAD) HEAD >expect && + git show-ref --verify HEAD >actual && + test_cmp expect actual && + + >expect && + + git show-ref --verify -q HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'show-ref --verify with dangling ref' ' + sha1_file() { + echo "$*" | sed "s#..#.git/objects/&/#" + } && + + remove_object() { + file=$(sha1_file "$*") && + test -e "$file" && + rm -f "$file" + } && + + test_when_finished "rm -rf dangling" && + ( + git init dangling && + cd dangling && + test_commit dangling && + sha=$(git rev-parse refs/tags/dangling) && + remove_object $sha && + test_must_fail git show-ref --verify refs/tags/dangling + ) +' + test_done diff --git a/t/t1404-update-ref-df-conflicts.sh b/t/t1404-update-ref-df-conflicts.sh deleted file mode 100755 index 66bafb5cf4..0000000000 --- a/t/t1404-update-ref-df-conflicts.sh +++ /dev/null @@ -1,107 +0,0 @@ -#!/bin/sh - -test_description='Test git update-ref with D/F conflicts' -. ./test-lib.sh - -test_update_rejected () { - prefix="$1" && - before="$2" && - pack="$3" && - create="$4" && - error="$5" && - printf "create $prefix/%s $C\n" $before | - git update-ref --stdin && - git for-each-ref $prefix >unchanged && - if $pack - then - git pack-refs --all - fi && - printf "create $prefix/%s $C\n" $create >input && - test_must_fail git update-ref --stdin <input 2>output.err && - grep -F "$error" output.err && - git for-each-ref $prefix >actual && - test_cmp unchanged actual -} - -Q="'" - -test_expect_success 'setup' ' - - git commit --allow-empty -m Initial && - C=$(git rev-parse HEAD) - -' - -test_expect_success 'existing loose ref is a simple prefix of new' ' - - prefix=refs/1l && - test_update_rejected $prefix "a c e" false "b c/x d" \ - "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x$Q" - -' - -test_expect_success 'existing packed ref is a simple prefix of new' ' - - prefix=refs/1p && - test_update_rejected $prefix "a c e" true "b c/x d" \ - "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x$Q" - -' - -test_expect_success 'existing loose ref is a deeper prefix of new' ' - - prefix=refs/2l && - test_update_rejected $prefix "a c e" false "b c/x/y d" \ - "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x/y$Q" - -' - -test_expect_success 'existing packed ref is a deeper prefix of new' ' - - prefix=refs/2p && - test_update_rejected $prefix "a c e" true "b c/x/y d" \ - "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x/y$Q" - -' - -test_expect_success 'new ref is a simple prefix of existing loose' ' - - prefix=refs/3l && - test_update_rejected $prefix "a c/x e" false "b c d" \ - "$Q$prefix/c/x$Q exists; cannot create $Q$prefix/c$Q" - -' - -test_expect_success 'new ref is a simple prefix of existing packed' ' - - prefix=refs/3p && - test_update_rejected $prefix "a c/x e" true "b c d" \ - "$Q$prefix/c/x$Q exists; cannot create $Q$prefix/c$Q" - -' - -test_expect_success 'new ref is a deeper prefix of existing loose' ' - - prefix=refs/4l && - test_update_rejected $prefix "a c/x/y e" false "b c d" \ - "$Q$prefix/c/x/y$Q exists; cannot create $Q$prefix/c$Q" - -' - -test_expect_success 'new ref is a deeper prefix of existing packed' ' - - prefix=refs/4p && - test_update_rejected $prefix "a c/x/y e" true "b c d" \ - "$Q$prefix/c/x/y$Q exists; cannot create $Q$prefix/c$Q" - -' - -test_expect_success 'one new ref is a simple prefix of another' ' - - prefix=refs/5 && - test_update_rejected $prefix "a e" false "b c c/x d" \ - "cannot process $Q$prefix/c$Q and $Q$prefix/c/x$Q at the same time" - -' - -test_done diff --git a/t/t1404-update-ref-errors.sh b/t/t1404-update-ref-errors.sh new file mode 100755 index 0000000000..3a887b5113 --- /dev/null +++ b/t/t1404-update-ref-errors.sh @@ -0,0 +1,621 @@ +#!/bin/sh + +test_description='Test git update-ref error handling' +. ./test-lib.sh + +# Create some references, perhaps run pack-refs --all, then try to +# create some more references. Ensure that the second creation fails +# with the correct error message. +# Usage: test_update_rejected <before> <pack> <create> <error> +# <before> is a ws-separated list of refs to create before the test +# <pack> (true or false) tells whether to pack the refs before the test +# <create> is a list of variables to attempt creating +# <error> is a string to look for in the stderr of update-ref. +# All references are created in the namespace specified by the current +# value of $prefix. +test_update_rejected () { + before="$1" && + pack="$2" && + create="$3" && + error="$4" && + printf "create $prefix/%s $C\n" $before | + git update-ref --stdin && + git for-each-ref $prefix >unchanged && + if $pack + then + git pack-refs --all + fi && + printf "create $prefix/%s $C\n" $create >input && + test_must_fail git update-ref --stdin <input 2>output.err && + grep -F "$error" output.err && + git for-each-ref $prefix >actual && + test_cmp unchanged actual +} + +Q="'" + +# Test adding and deleting D/F-conflicting references in a single +# transaction. +df_test() { + prefix="$1" + pack=: symadd=false symdel=false add_del=false addref= delref= + shift + while test $# -gt 0 + do + case "$1" in + --pack) + pack="git pack-refs --all" + shift + ;; + --sym-add) + # Perform the add via a symbolic reference + symadd=true + shift + ;; + --sym-del) + # Perform the del via a symbolic reference + symdel=true + shift + ;; + --del-add) + # Delete first reference then add second + add_del=false + delref="$prefix/r/$2" + addref="$prefix/r/$3" + shift 3 + ;; + --add-del) + # Add first reference then delete second + add_del=true + addref="$prefix/r/$2" + delref="$prefix/r/$3" + shift 3 + ;; + *) + echo 1>&2 "Extra args to df_test: $*" + return 1 + ;; + esac + done + git update-ref "$delref" $C && + if $symadd + then + addname="$prefix/s/symadd" && + git symbolic-ref "$addname" "$addref" + else + addname="$addref" + fi && + if $symdel + then + delname="$prefix/s/symdel" && + git symbolic-ref "$delname" "$delref" + else + delname="$delref" + fi && + cat >expected-err <<-EOF && + fatal: cannot lock ref $Q$addname$Q: $Q$delref$Q exists; cannot create $Q$addref$Q + EOF + $pack && + if $add_del + then + printf "%s\n" "create $addname $D" "delete $delname" + else + printf "%s\n" "delete $delname" "create $addname $D" + fi >commands && + test_must_fail git update-ref --stdin <commands 2>output.err && + test_cmp expected-err output.err && + printf "%s\n" "$C $delref" >expected-refs && + git for-each-ref --format="%(objectname) %(refname)" $prefix/r >actual-refs && + test_cmp expected-refs actual-refs +} + +test_expect_success 'setup' ' + + git commit --allow-empty -m Initial && + C=$(git rev-parse HEAD) && + git commit --allow-empty -m Second && + D=$(git rev-parse HEAD) && + git commit --allow-empty -m Third && + E=$(git rev-parse HEAD) +' + +test_expect_success 'existing loose ref is a simple prefix of new' ' + + prefix=refs/1l && + test_update_rejected "a c e" false "b c/x d" \ + "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x$Q" + +' + +test_expect_success 'existing packed ref is a simple prefix of new' ' + + prefix=refs/1p && + test_update_rejected "a c e" true "b c/x d" \ + "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x$Q" + +' + +test_expect_success 'existing loose ref is a deeper prefix of new' ' + + prefix=refs/2l && + test_update_rejected "a c e" false "b c/x/y d" \ + "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x/y$Q" + +' + +test_expect_success 'existing packed ref is a deeper prefix of new' ' + + prefix=refs/2p && + test_update_rejected "a c e" true "b c/x/y d" \ + "$Q$prefix/c$Q exists; cannot create $Q$prefix/c/x/y$Q" + +' + +test_expect_success 'new ref is a simple prefix of existing loose' ' + + prefix=refs/3l && + test_update_rejected "a c/x e" false "b c d" \ + "$Q$prefix/c/x$Q exists; cannot create $Q$prefix/c$Q" + +' + +test_expect_success 'new ref is a simple prefix of existing packed' ' + + prefix=refs/3p && + test_update_rejected "a c/x e" true "b c d" \ + "$Q$prefix/c/x$Q exists; cannot create $Q$prefix/c$Q" + +' + +test_expect_success 'new ref is a deeper prefix of existing loose' ' + + prefix=refs/4l && + test_update_rejected "a c/x/y e" false "b c d" \ + "$Q$prefix/c/x/y$Q exists; cannot create $Q$prefix/c$Q" + +' + +test_expect_success 'new ref is a deeper prefix of existing packed' ' + + prefix=refs/4p && + test_update_rejected "a c/x/y e" true "b c d" \ + "$Q$prefix/c/x/y$Q exists; cannot create $Q$prefix/c$Q" + +' + +test_expect_success 'one new ref is a simple prefix of another' ' + + prefix=refs/5 && + test_update_rejected "a e" false "b c c/x d" \ + "cannot process $Q$prefix/c$Q and $Q$prefix/c/x$Q at the same time" + +' + +test_expect_success 'empty directory should not fool rev-parse' ' + prefix=refs/e-rev-parse && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + echo "$C" >expected && + git rev-parse $prefix/foo >actual && + test_cmp expected actual +' + +test_expect_success 'empty directory should not fool for-each-ref' ' + prefix=refs/e-for-each-ref && + git update-ref $prefix/foo $C && + git for-each-ref $prefix >expected && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + git for-each-ref $prefix >actual && + test_cmp expected actual +' + +test_expect_success 'empty directory should not fool create' ' + prefix=refs/e-create && + mkdir -p .git/$prefix/foo/bar/baz && + printf "create %s $C\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'empty directory should not fool verify' ' + prefix=refs/e-verify && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + printf "verify %s $C\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'empty directory should not fool 1-arg update' ' + prefix=refs/e-update-1 && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + printf "update %s $D\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'empty directory should not fool 2-arg update' ' + prefix=refs/e-update-2 && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + printf "update %s $D $C\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'empty directory should not fool 0-arg delete' ' + prefix=refs/e-delete-0 && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + printf "delete %s\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'empty directory should not fool 1-arg delete' ' + prefix=refs/e-delete-1 && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + printf "delete %s $C\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'D/F conflict prevents add long + delete short' ' + df_test refs/df-al-ds --add-del foo/bar foo +' + +test_expect_success 'D/F conflict prevents add short + delete long' ' + df_test refs/df-as-dl --add-del foo foo/bar +' + +test_expect_success 'D/F conflict prevents delete long + add short' ' + df_test refs/df-dl-as --del-add foo/bar foo +' + +test_expect_success 'D/F conflict prevents delete short + add long' ' + df_test refs/df-ds-al --del-add foo foo/bar +' + +test_expect_success 'D/F conflict prevents add long + delete short packed' ' + df_test refs/df-al-dsp --pack --add-del foo/bar foo +' + +test_expect_success 'D/F conflict prevents add short + delete long packed' ' + df_test refs/df-as-dlp --pack --add-del foo foo/bar +' + +test_expect_success 'D/F conflict prevents delete long packed + add short' ' + df_test refs/df-dlp-as --pack --del-add foo/bar foo +' + +test_expect_success 'D/F conflict prevents delete short packed + add long' ' + df_test refs/df-dsp-al --pack --del-add foo foo/bar +' + +# Try some combinations involving symbolic refs... + +test_expect_success 'D/F conflict prevents indirect add long + delete short' ' + df_test refs/df-ial-ds --sym-add --add-del foo/bar foo +' + +test_expect_success 'D/F conflict prevents indirect add long + indirect delete short' ' + df_test refs/df-ial-ids --sym-add --sym-del --add-del foo/bar foo +' + +test_expect_success 'D/F conflict prevents indirect add short + indirect delete long' ' + df_test refs/df-ias-idl --sym-add --sym-del --add-del foo foo/bar +' + +test_expect_success 'D/F conflict prevents indirect delete long + indirect add short' ' + df_test refs/df-idl-ias --sym-add --sym-del --del-add foo/bar foo +' + +test_expect_success 'D/F conflict prevents indirect add long + delete short packed' ' + df_test refs/df-ial-dsp --sym-add --pack --add-del foo/bar foo +' + +test_expect_success 'D/F conflict prevents indirect add long + indirect delete short packed' ' + df_test refs/df-ial-idsp --sym-add --sym-del --pack --add-del foo/bar foo +' + +test_expect_success 'D/F conflict prevents add long + indirect delete short packed' ' + df_test refs/df-al-idsp --sym-del --pack --add-del foo/bar foo +' + +test_expect_success 'D/F conflict prevents indirect delete long packed + indirect add short' ' + df_test refs/df-idlp-ias --sym-add --sym-del --pack --del-add foo/bar foo +' + +# Test various errors when reading the old values of references... + +test_expect_success 'missing old value blocks update' ' + prefix=refs/missing-update && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/foo$Q: unable to resolve reference $Q$prefix/foo$Q + EOF + printf "%s\n" "update $prefix/foo $E $D" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'incorrect old value blocks update' ' + prefix=refs/incorrect-update && + git update-ref $prefix/foo $C && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/foo$Q: is at $C but expected $D + EOF + printf "%s\n" "update $prefix/foo $E $D" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'existing old value blocks create' ' + prefix=refs/existing-create && + git update-ref $prefix/foo $C && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/foo$Q: reference already exists + EOF + printf "%s\n" "create $prefix/foo $E" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'incorrect old value blocks delete' ' + prefix=refs/incorrect-delete && + git update-ref $prefix/foo $C && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/foo$Q: is at $C but expected $D + EOF + printf "%s\n" "delete $prefix/foo $D" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'missing old value blocks indirect update' ' + prefix=refs/missing-indirect-update && + git symbolic-ref $prefix/symref $prefix/foo && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: unable to resolve reference $Q$prefix/foo$Q + EOF + printf "%s\n" "update $prefix/symref $E $D" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'incorrect old value blocks indirect update' ' + prefix=refs/incorrect-indirect-update && + git symbolic-ref $prefix/symref $prefix/foo && + git update-ref $prefix/foo $C && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: is at $C but expected $D + EOF + printf "%s\n" "update $prefix/symref $E $D" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'existing old value blocks indirect create' ' + prefix=refs/existing-indirect-create && + git symbolic-ref $prefix/symref $prefix/foo && + git update-ref $prefix/foo $C && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: reference already exists + EOF + printf "%s\n" "create $prefix/symref $E" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'incorrect old value blocks indirect delete' ' + prefix=refs/incorrect-indirect-delete && + git symbolic-ref $prefix/symref $prefix/foo && + git update-ref $prefix/foo $C && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: is at $C but expected $D + EOF + printf "%s\n" "delete $prefix/symref $D" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'missing old value blocks indirect no-deref update' ' + prefix=refs/missing-noderef-update && + git symbolic-ref $prefix/symref $prefix/foo && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: reference is missing but expected $D + EOF + printf "%s\n" "option no-deref" "update $prefix/symref $E $D" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'incorrect old value blocks indirect no-deref update' ' + prefix=refs/incorrect-noderef-update && + git symbolic-ref $prefix/symref $prefix/foo && + git update-ref $prefix/foo $C && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: is at $C but expected $D + EOF + printf "%s\n" "option no-deref" "update $prefix/symref $E $D" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'existing old value blocks indirect no-deref create' ' + prefix=refs/existing-noderef-create && + git symbolic-ref $prefix/symref $prefix/foo && + git update-ref $prefix/foo $C && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: reference already exists + EOF + printf "%s\n" "option no-deref" "create $prefix/symref $E" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'incorrect old value blocks indirect no-deref delete' ' + prefix=refs/incorrect-noderef-delete && + git symbolic-ref $prefix/symref $prefix/foo && + git update-ref $prefix/foo $C && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: is at $C but expected $D + EOF + printf "%s\n" "option no-deref" "delete $prefix/symref $D" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'non-empty directory blocks create' ' + prefix=refs/ne-create && + mkdir -p .git/$prefix/foo/bar && + : >.git/$prefix/foo/bar/baz.lock && + test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/foo$Q: there is a non-empty directory $Q.git/$prefix/foo$Q blocking reference $Q$prefix/foo$Q + EOF + printf "%s\n" "update $prefix/foo $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/foo$Q: unable to resolve reference $Q$prefix/foo$Q + EOF + printf "%s\n" "update $prefix/foo $D $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'broken reference blocks create' ' + prefix=refs/broken-create && + mkdir -p .git/$prefix && + echo "gobbledigook" >.git/$prefix/foo && + test_when_finished "rm -f .git/$prefix/foo" && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/foo$Q: unable to resolve reference $Q$prefix/foo$Q: reference broken + EOF + printf "%s\n" "update $prefix/foo $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/foo$Q: unable to resolve reference $Q$prefix/foo$Q: reference broken + EOF + printf "%s\n" "update $prefix/foo $D $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'non-empty directory blocks indirect create' ' + prefix=refs/ne-indirect-create && + git symbolic-ref $prefix/symref $prefix/foo && + mkdir -p .git/$prefix/foo/bar && + : >.git/$prefix/foo/bar/baz.lock && + test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: there is a non-empty directory $Q.git/$prefix/foo$Q blocking reference $Q$prefix/foo$Q + EOF + printf "%s\n" "update $prefix/symref $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: unable to resolve reference $Q$prefix/foo$Q + EOF + printf "%s\n" "update $prefix/symref $D $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'broken reference blocks indirect create' ' + prefix=refs/broken-indirect-create && + git symbolic-ref $prefix/symref $prefix/foo && + echo "gobbledigook" >.git/$prefix/foo && + test_when_finished "rm -f .git/$prefix/foo" && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: unable to resolve reference $Q$prefix/foo$Q: reference broken + EOF + printf "%s\n" "update $prefix/symref $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err && + cat >expected <<-EOF && + fatal: cannot lock ref $Q$prefix/symref$Q: unable to resolve reference $Q$prefix/foo$Q: reference broken + EOF + printf "%s\n" "update $prefix/symref $D $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'no bogus intermediate values during delete' ' + prefix=refs/slow-transaction && + # Set up a reference with differing loose and packed versions: + git update-ref $prefix/foo $C && + git pack-refs --all && + git update-ref $prefix/foo $D && + git for-each-ref $prefix >unchanged && + # Now try to update the reference, but hold the `packed-refs` lock + # for a while to see what happens while the process is blocked: + : >.git/packed-refs.lock && + test_when_finished "rm -f .git/packed-refs.lock" && + { + # Note: the following command is intentionally run in the + # background. We increase the timeout so that `update-ref` + # attempts to acquire the `packed-refs` lock for longer than + # it takes for us to do the check then delete it: + git -c core.packedrefstimeout=3000 update-ref -d $prefix/foo & + } && + pid2=$! && + # Give update-ref plenty of time to get to the point where it tries + # to lock packed-refs: + sleep 1 && + # Make sure that update-ref did not complete despite the lock: + kill -0 $pid2 && + # Verify that the reference still has its old value: + sha1=$(git rev-parse --verify --quiet $prefix/foo || echo undefined) && + case "$sha1" in + $D) + # This is what we hope for; it means that nothing + # user-visible has changed yet. + : ;; + undefined) + # This is not correct; it means the deletion has happened + # already even though update-ref should not have been + # able to acquire the lock yet. + echo "$prefix/foo deleted prematurely" && + break + ;; + $C) + # This value should never be seen. Probably the loose + # reference has been deleted but the packed reference + # is still there: + echo "$prefix/foo incorrectly observed to be C" && + break + ;; + *) + # WTF? + echo "unexpected value observed for $prefix/foo: $sha1" && + break + ;; + esac >out && + rm -f .git/packed-refs.lock && + wait $pid2 && + test_must_be_empty out && + test_must_fail git rev-parse --verify --quiet $prefix/foo +' + +test_expect_success 'delete fails cleanly if packed-refs file is locked' ' + prefix=refs/locked-packed-refs && + # Set up a reference with differing loose and packed versions: + git update-ref $prefix/foo $C && + git pack-refs --all && + git update-ref $prefix/foo $D && + git for-each-ref $prefix >unchanged && + # Now try to delete it while the `packed-refs` lock is held: + : >.git/packed-refs.lock && + test_when_finished "rm -f .git/packed-refs.lock" && + test_must_fail git update-ref -d $prefix/foo >out 2>err && + git for-each-ref $prefix >actual && + test_i18ngrep "Unable to create $Q.*packed-refs.lock$Q: File exists" err && + test_cmp unchanged actual +' + +test_done diff --git a/t/t1405-main-ref-store.sh b/t/t1405-main-ref-store.sh new file mode 100755 index 0000000000..a74c38b5fb --- /dev/null +++ b/t/t1405-main-ref-store.sh @@ -0,0 +1,129 @@ +#!/bin/sh + +test_description='test main ref store api' + +. ./test-lib.sh + +RUN="test-tool ref-store main" + +test_expect_success 'pack_refs(PACK_REFS_ALL | PACK_REFS_PRUNE)' ' + test_commit one && + N=`find .git/refs -type f | wc -l` && + test "$N" != 0 && + $RUN pack-refs 3 && + N=`find .git/refs -type f | wc -l` +' + +test_expect_success 'peel_ref(new-tag)' ' + git rev-parse HEAD >expected && + git tag -a -m new-tag new-tag HEAD && + $RUN peel-ref refs/tags/new-tag >actual && + test_cmp expected actual +' + +test_expect_success 'create_symref(FOO, refs/heads/master)' ' + $RUN create-symref FOO refs/heads/master nothing && + echo refs/heads/master >expected && + git symbolic-ref FOO >actual && + test_cmp expected actual +' + +test_expect_success 'delete_refs(FOO, refs/tags/new-tag)' ' + git rev-parse FOO -- && + git rev-parse refs/tags/new-tag -- && + $RUN delete-refs 0 nothing FOO refs/tags/new-tag && + test_must_fail git rev-parse FOO -- && + test_must_fail git rev-parse refs/tags/new-tag -- +' + +test_expect_success 'rename_refs(master, new-master)' ' + git rev-parse master >expected && + $RUN rename-ref refs/heads/master refs/heads/new-master && + git rev-parse new-master >actual && + test_cmp expected actual && + test_commit recreate-master +' + +test_expect_success 'for_each_ref(refs/heads/)' ' + $RUN for-each-ref refs/heads/ | cut -d" " -f 2- >actual && + cat >expected <<-\EOF && + master 0x0 + new-master 0x0 + EOF + test_cmp expected actual +' + +test_expect_success 'for_each_ref() is sorted' ' + $RUN for-each-ref refs/heads/ | cut -c 42- >actual && + sort actual > expected && + test_cmp expected actual +' + +test_expect_success 'resolve_ref(new-master)' ' + SHA1=`git rev-parse new-master` && + echo "$SHA1 refs/heads/new-master 0x0" >expected && + $RUN resolve-ref refs/heads/new-master 0 >actual && + test_cmp expected actual +' + +test_expect_success 'verify_ref(new-master)' ' + $RUN verify-ref refs/heads/new-master +' + +test_expect_success 'for_each_reflog()' ' + $RUN for-each-reflog | sort -k2 | cut -c 42- >actual && + cat >expected <<-\EOF && + HEAD 0x1 + refs/heads/master 0x0 + refs/heads/new-master 0x0 + EOF + test_cmp expected actual +' + +test_expect_success 'for_each_reflog_ent()' ' + $RUN for-each-reflog-ent HEAD >actual && + head -n1 actual | grep one && + tail -n2 actual | head -n1 | grep recreate-master +' + +test_expect_success 'for_each_reflog_ent_reverse()' ' + $RUN for-each-reflog-ent-reverse HEAD >actual && + head -n1 actual | grep recreate-master && + tail -n2 actual | head -n1 | grep one +' + +test_expect_success 'reflog_exists(HEAD)' ' + $RUN reflog-exists HEAD +' + +test_expect_success 'delete_reflog(HEAD)' ' + $RUN delete-reflog HEAD && + ! test -f .git/logs/HEAD +' + +test_expect_success 'create-reflog(HEAD)' ' + $RUN create-reflog HEAD 1 && + test -f .git/logs/HEAD +' + +test_expect_success 'delete_ref(refs/heads/foo)' ' + git checkout -b foo && + FOO_SHA1=`git rev-parse foo` && + git checkout --detach && + test_commit bar-commit && + git checkout -b bar && + BAR_SHA1=`git rev-parse bar` && + $RUN update-ref updating refs/heads/foo $BAR_SHA1 $FOO_SHA1 0 && + echo $BAR_SHA1 >expected && + git rev-parse refs/heads/foo >actual && + test_cmp expected actual +' + +test_expect_success 'delete_ref(refs/heads/foo)' ' + SHA1=`git rev-parse foo` && + git checkout --detach && + $RUN delete-ref msg refs/heads/foo $SHA1 0 && + test_must_fail git rev-parse refs/heads/foo -- +' + +test_done diff --git a/t/t1406-submodule-ref-store.sh b/t/t1406-submodule-ref-store.sh new file mode 100755 index 0000000000..e093782cc3 --- /dev/null +++ b/t/t1406-submodule-ref-store.sh @@ -0,0 +1,101 @@ +#!/bin/sh + +test_description='test submodule ref store api' + +. ./test-lib.sh + +RUN="test-tool ref-store submodule:sub" + +test_expect_success 'setup' ' + git init sub && + ( + cd sub && + test_commit first && + git checkout -b new-master + ) +' + +test_expect_success 'pack_refs() not allowed' ' + test_must_fail $RUN pack-refs 3 +' + +test_expect_success 'peel_ref(new-tag)' ' + git -C sub rev-parse HEAD >expected && + git -C sub tag -a -m new-tag new-tag HEAD && + $RUN peel-ref refs/tags/new-tag >actual && + test_cmp expected actual +' + +test_expect_success 'create_symref() not allowed' ' + test_must_fail $RUN create-symref FOO refs/heads/master nothing +' + +test_expect_success 'delete_refs() not allowed' ' + test_must_fail $RUN delete-refs 0 nothing FOO refs/tags/new-tag +' + +test_expect_success 'rename_refs() not allowed' ' + test_must_fail $RUN rename-ref refs/heads/master refs/heads/new-master +' + +test_expect_success 'for_each_ref(refs/heads/)' ' + $RUN for-each-ref refs/heads/ | cut -c 42- >actual && + cat >expected <<-\EOF && + master 0x0 + new-master 0x0 + EOF + test_cmp expected actual +' + +test_expect_success 'for_each_ref() is sorted' ' + $RUN for-each-ref refs/heads/ | cut -c 42- >actual && + sort actual > expected && + test_cmp expected actual +' + +test_expect_success 'resolve_ref(master)' ' + SHA1=`git -C sub rev-parse master` && + echo "$SHA1 refs/heads/master 0x0" >expected && + $RUN resolve-ref refs/heads/master 0 >actual && + test_cmp expected actual +' + +test_expect_success 'verify_ref(new-master)' ' + $RUN verify-ref refs/heads/new-master +' + +test_expect_success 'for_each_reflog()' ' + $RUN for-each-reflog | sort | cut -c 42- >actual && + cat >expected <<-\EOF && + HEAD 0x1 + refs/heads/master 0x0 + refs/heads/new-master 0x0 + EOF + test_cmp expected actual +' + +test_expect_success 'for_each_reflog_ent()' ' + $RUN for-each-reflog-ent HEAD >actual && cat actual && + head -n1 actual | grep first && + tail -n2 actual | head -n1 | grep master.to.new +' + +test_expect_success 'for_each_reflog_ent_reverse()' ' + $RUN for-each-reflog-ent-reverse HEAD >actual && + head -n1 actual | grep master.to.new && + tail -n2 actual | head -n1 | grep first +' + +test_expect_success 'reflog_exists(HEAD)' ' + $RUN reflog-exists HEAD +' + +test_expect_success 'delete_reflog() not allowed' ' + test_must_fail $RUN delete-reflog HEAD +' + +test_expect_success 'create-reflog() not allowed' ' + test_must_fail $RUN create-reflog HEAD 1 +' + +test_done diff --git a/t/t1407-worktree-ref-store.sh b/t/t1407-worktree-ref-store.sh new file mode 100755 index 0000000000..2211f9831f --- /dev/null +++ b/t/t1407-worktree-ref-store.sh @@ -0,0 +1,82 @@ +#!/bin/sh + +test_description='test worktree ref store api' + +. ./test-lib.sh + +RWT="test-tool ref-store worktree:wt" +RMAIN="test-tool ref-store worktree:main" + +test_expect_success 'setup' ' + test_commit first && + git worktree add -b wt-master wt && + ( + cd wt && + test_commit second + ) +' + +test_expect_success 'resolve_ref(<shared-ref>)' ' + SHA1=`git rev-parse master` && + echo "$SHA1 refs/heads/master 0x0" >expected && + $RWT resolve-ref refs/heads/master 0 >actual && + test_cmp expected actual && + $RMAIN resolve-ref refs/heads/master 0 >actual && + test_cmp expected actual +' + +test_expect_success 'resolve_ref(<per-worktree-ref>)' ' + SHA1=`git -C wt rev-parse HEAD` && + echo "$SHA1 refs/heads/wt-master 0x1" >expected && + $RWT resolve-ref HEAD 0 >actual && + test_cmp expected actual && + + SHA1=`git rev-parse HEAD` && + echo "$SHA1 refs/heads/master 0x1" >expected && + $RMAIN resolve-ref HEAD 0 >actual && + test_cmp expected actual +' + +test_expect_success 'create_symref(FOO, refs/heads/master)' ' + $RWT create-symref FOO refs/heads/master nothing && + echo refs/heads/master >expected && + git -C wt symbolic-ref FOO >actual && + test_cmp expected actual && + + $RMAIN create-symref FOO refs/heads/wt-master nothing && + echo refs/heads/wt-master >expected && + git symbolic-ref FOO >actual && + test_cmp expected actual +' + +test_expect_success 'for_each_reflog()' ' + echo $_z40 > .git/logs/PSEUDO-MAIN && + mkdir -p .git/logs/refs/bisect && + echo $_z40 > .git/logs/refs/bisect/random && + + echo $_z40 > .git/worktrees/wt/logs/PSEUDO-WT && + mkdir -p .git/worktrees/wt/logs/refs/bisect && + echo $_z40 > .git/worktrees/wt/logs/refs/bisect/wt-random && + + $RWT for-each-reflog | cut -c 42- | sort >actual && + cat >expected <<-\EOF && + HEAD 0x1 + PSEUDO-WT 0x0 + refs/bisect/wt-random 0x0 + refs/heads/master 0x0 + refs/heads/wt-master 0x0 + EOF + test_cmp expected actual && + + $RMAIN for-each-reflog | cut -c 42- | sort >actual && + cat >expected <<-\EOF && + HEAD 0x1 + PSEUDO-MAIN 0x0 + refs/bisect/random 0x0 + refs/heads/master 0x0 + refs/heads/wt-master 0x0 + EOF + test_cmp expected actual +' + +test_done diff --git a/t/t1408-packed-refs.sh b/t/t1408-packed-refs.sh new file mode 100755 index 0000000000..1e44a17eea --- /dev/null +++ b/t/t1408-packed-refs.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +test_description='packed-refs entries are covered by loose refs' + +. ./test-lib.sh + +test_expect_success setup ' + test_tick && + git commit --allow-empty -m one && + one=$(git rev-parse HEAD) && + git for-each-ref >actual && + echo "$one commit refs/heads/master" >expect && + test_cmp expect actual && + + git pack-refs --all && + git for-each-ref >actual && + echo "$one commit refs/heads/master" >expect && + test_cmp expect actual && + + git checkout --orphan another && + test_tick && + git commit --allow-empty -m two && + two=$(git rev-parse HEAD) && + git checkout -B master && + git branch -D another && + + git for-each-ref >actual && + echo "$two commit refs/heads/master" >expect && + test_cmp expect actual && + + git reflog expire --expire=now --all && + git prune && + git tag -m v1.0 v1.0 master +' + +test_expect_success 'no error from stale entry in packed-refs' ' + git describe master >actual 2>&1 && + echo "v1.0" >expect && + test_cmp expect actual +' + +test_done diff --git a/t/t1409-avoid-packing-refs.sh b/t/t1409-avoid-packing-refs.sh new file mode 100755 index 0000000000..e5cb8a252d --- /dev/null +++ b/t/t1409-avoid-packing-refs.sh @@ -0,0 +1,118 @@ +#!/bin/sh + +test_description='avoid rewriting packed-refs unnecessarily' + +. ./test-lib.sh + +# Add an identifying mark to the packed-refs file header line. This +# shouldn't upset readers, and it should be omitted if the file is +# ever rewritten. +mark_packed_refs () { + sed -e "s/^\(#.*\)/\1 t1409 /" <.git/packed-refs >.git/packed-refs.new && + mv .git/packed-refs.new .git/packed-refs +} + +# Verify that the packed-refs file is still marked. +check_packed_refs_marked () { + grep -q '^#.* t1409 ' .git/packed-refs +} + +test_expect_success 'setup' ' + git commit --allow-empty -m "Commit A" && + A=$(git rev-parse HEAD) && + git commit --allow-empty -m "Commit B" && + B=$(git rev-parse HEAD) && + git commit --allow-empty -m "Commit C" && + C=$(git rev-parse HEAD) +' + +test_expect_success 'do not create packed-refs file gratuitously' ' + test_must_fail test -f .git/packed-refs && + git update-ref refs/heads/foo $A && + test_must_fail test -f .git/packed-refs && + git update-ref refs/heads/foo $B && + test_must_fail test -f .git/packed-refs && + git update-ref refs/heads/foo $C $B && + test_must_fail test -f .git/packed-refs && + git update-ref -d refs/heads/foo && + test_must_fail test -f .git/packed-refs +' + +test_expect_success 'check that marking the packed-refs file works' ' + git for-each-ref >expected && + git pack-refs --all && + mark_packed_refs && + check_packed_refs_marked && + git for-each-ref >actual && + test_cmp expected actual && + git pack-refs --all && + test_must_fail check_packed_refs_marked && + git for-each-ref >actual2 && + test_cmp expected actual2 +' + +test_expect_success 'leave packed-refs untouched on update of packed' ' + git update-ref refs/heads/packed-update $A && + git pack-refs --all && + mark_packed_refs && + git update-ref refs/heads/packed-update $B && + check_packed_refs_marked +' + +test_expect_success 'leave packed-refs untouched on checked update of packed' ' + git update-ref refs/heads/packed-checked-update $A && + git pack-refs --all && + mark_packed_refs && + git update-ref refs/heads/packed-checked-update $B $A && + check_packed_refs_marked +' + +test_expect_success 'leave packed-refs untouched on verify of packed' ' + git update-ref refs/heads/packed-verify $A && + git pack-refs --all && + mark_packed_refs && + echo "verify refs/heads/packed-verify $A" | git update-ref --stdin && + check_packed_refs_marked +' + +test_expect_success 'touch packed-refs on delete of packed' ' + git update-ref refs/heads/packed-delete $A && + git pack-refs --all && + mark_packed_refs && + git update-ref -d refs/heads/packed-delete && + test_must_fail check_packed_refs_marked +' + +test_expect_success 'leave packed-refs untouched on update of loose' ' + git pack-refs --all && + git update-ref refs/heads/loose-update $A && + mark_packed_refs && + git update-ref refs/heads/loose-update $B && + check_packed_refs_marked +' + +test_expect_success 'leave packed-refs untouched on checked update of loose' ' + git pack-refs --all && + git update-ref refs/heads/loose-checked-update $A && + mark_packed_refs && + git update-ref refs/heads/loose-checked-update $B $A && + check_packed_refs_marked +' + +test_expect_success 'leave packed-refs untouched on verify of loose' ' + git pack-refs --all && + git update-ref refs/heads/loose-verify $A && + mark_packed_refs && + echo "verify refs/heads/loose-verify $A" | git update-ref --stdin && + check_packed_refs_marked +' + +test_expect_success 'leave packed-refs untouched on delete of loose' ' + git pack-refs --all && + git update-ref refs/heads/loose-delete $A && + mark_packed_refs && + git update-ref -d refs/heads/loose-delete && + check_packed_refs_marked +' + +test_done diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh index 779d4e3829..553e26d9ce 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh @@ -62,18 +62,18 @@ test_expect_success setup ' git add . && test_tick && git commit -m rabbit && - H=`git rev-parse --verify HEAD` && - A=`git rev-parse --verify HEAD:A` && - B=`git rev-parse --verify HEAD:A/B` && - C=`git rev-parse --verify HEAD:C` && - D=`git rev-parse --verify HEAD:A/D` && - E=`git rev-parse --verify HEAD:A/B/E` && + H=$(git rev-parse --verify HEAD) && + A=$(git rev-parse --verify HEAD:A) && + B=$(git rev-parse --verify HEAD:A/B) && + C=$(git rev-parse --verify HEAD:C) && + D=$(git rev-parse --verify HEAD:A/D) && + E=$(git rev-parse --verify HEAD:A/B/E) && check_fsck && test_chmod +x C && git add C && test_tick && git commit -m dragon && - L=`git rev-parse --verify HEAD` && + L=$(git rev-parse --verify HEAD) && check_fsck && rm -f C A/B/E && @@ -81,15 +81,15 @@ test_expect_success setup ' echo horse >A/G && git add F A/G && test_tick && git commit -a -m sheep && - F=`git rev-parse --verify HEAD:F` && - G=`git rev-parse --verify HEAD:A/G` && - I=`git rev-parse --verify HEAD:A` && - J=`git rev-parse --verify HEAD` && + F=$(git rev-parse --verify HEAD:F) && + G=$(git rev-parse --verify HEAD:A/G) && + I=$(git rev-parse --verify HEAD:A) && + J=$(git rev-parse --verify HEAD) && check_fsck && rm -f A/G && test_tick && git commit -a -m monkey && - K=`git rev-parse --verify HEAD` && + K=$(git rev-parse --verify HEAD) && check_fsck && check_have A B C D E F G H I J K L && @@ -100,7 +100,8 @@ test_expect_success setup ' check_fsck && - test_line_count = 4 .git/logs/refs/heads/master + git reflog refs/heads/master >output && + test_line_count = 4 output ' test_expect_success rewind ' @@ -116,7 +117,8 @@ test_expect_success rewind ' check_have A B C D E F G H I J K L && - test_line_count = 5 .git/logs/refs/heads/master + git reflog refs/heads/master >output && + test_line_count = 5 output ' test_expect_success 'corrupt and check' ' @@ -134,7 +136,8 @@ test_expect_success 'reflog expire --dry-run should not touch reflog' ' --stale-fix \ --all && - test_line_count = 5 .git/logs/refs/heads/master && + git reflog refs/heads/master >output && + test_line_count = 5 output && check_fsck "missing blob $F" ' @@ -147,7 +150,8 @@ test_expect_success 'reflog expire' ' --stale-fix \ --all && - test_line_count = 2 .git/logs/refs/heads/master && + git reflog refs/heads/master >output && + test_line_count = 2 output && check_fsck "dangling commit $K" ' @@ -213,7 +217,8 @@ test_expect_success 'delete' ' test_expect_success 'rewind2' ' test_tick && git reset --hard HEAD~2 && - test_line_count = 4 .git/logs/refs/heads/master + git reflog refs/heads/master >output && + test_line_count = 4 output ' test_expect_success '--expire=never' ' @@ -222,7 +227,8 @@ test_expect_success '--expire=never' ' --expire=never \ --expire-unreachable=never \ --all && - test_line_count = 4 .git/logs/refs/heads/master + git reflog refs/heads/master >output && + test_line_count = 4 output ' test_expect_success 'gc.reflogexpire=never' ' @@ -230,7 +236,8 @@ test_expect_success 'gc.reflogexpire=never' ' git config gc.reflogexpire never && git config gc.reflogexpireunreachable never && git reflog expire --verbose --all && - test_line_count = 4 .git/logs/refs/heads/master + git reflog refs/heads/master >output && + test_line_count = 4 output ' test_expect_success 'gc.reflogexpire=false' ' @@ -238,7 +245,8 @@ test_expect_success 'gc.reflogexpire=false' ' git config gc.reflogexpire false && git config gc.reflogexpireunreachable false && git reflog expire --verbose --all && - test_line_count = 4 .git/logs/refs/heads/master && + git reflog refs/heads/master >output && + test_line_count = 4 output && git config --unset gc.reflogexpire && git config --unset gc.reflogexpireunreachable @@ -317,4 +325,48 @@ test_expect_success 'parsing reverse reflogs at BUFSIZ boundaries' ' test_cmp expect actual ' +test_expect_success 'no segfaults for reflog containing non-commit sha1s' ' + git update-ref --create-reflog -m "Creating ref" \ + refs/tests/tree-in-reflog HEAD && + git update-ref -m "Forcing tree" refs/tests/tree-in-reflog HEAD^{tree} && + git update-ref -m "Restoring to commit" refs/tests/tree-in-reflog HEAD && + git reflog refs/tests/tree-in-reflog +' + +test_expect_failure 'reflog with non-commit entries displays all entries' ' + git reflog refs/tests/tree-in-reflog >actual && + test_line_count = 3 actual +' + +test_expect_success 'reflog expire operates on symref not referrent' ' + git branch -l the_symref && + git branch -l referrent && + git update-ref referrent HEAD && + git symbolic-ref refs/heads/the_symref refs/heads/referrent && + test_when_finished "rm -f .git/refs/heads/referrent.lock" && + touch .git/refs/heads/referrent.lock && + git reflog expire --expire=all the_symref +' + +test_expect_success 'continue walking past root commits' ' + git init orphanage && + ( + cd orphanage && + cat >expect <<-\EOF && + HEAD@{0} commit (initial): orphan2-1 + HEAD@{1} commit: orphan1-2 + HEAD@{2} commit (initial): orphan1-1 + HEAD@{3} commit (initial): initial + EOF + test_commit initial && + git checkout --orphan orphan1 && + test_commit orphan1-1 && + test_commit orphan1-2 && + git checkout --orphan orphan2 && + test_commit orphan2-1 && + git log -g --format="%gd %gs" >actual && + test_cmp expect actual + ) +' + test_done diff --git a/t/t1411-reflog-show.sh b/t/t1411-reflog-show.sh index 6f47c0dd0e..596907758d 100755 --- a/t/t1411-reflog-show.sh +++ b/t/t1411-reflog-show.sh @@ -10,6 +10,7 @@ test_expect_success 'setup' ' git commit -m one ' +commit=$(git rev-parse --short HEAD) cat >expect <<'EOF' Reflog: HEAD@{0} (C O Mitter <committer@example.com>) Reflog message: commit (initial): one @@ -20,8 +21,8 @@ test_expect_success 'log -g shows reflog headers' ' test_cmp expect actual ' -cat >expect <<'EOF' -e46513e HEAD@{0}: commit (initial): one +cat >expect <<EOF +$commit HEAD@{0}: commit (initial): one EOF test_expect_success 'oneline reflog format' ' git log -g -1 --oneline >actual && @@ -33,8 +34,8 @@ test_expect_success 'reflog default format' ' test_cmp expect actual ' -cat >expect <<'EOF' -commit e46513e +cat >expect <<EOF +commit $commit Reflog: HEAD@{0} (C O Mitter <committer@example.com>) Reflog message: commit (initial): one Author: A U Thor <author@example.com> @@ -56,8 +57,8 @@ test_expect_success 'using @{now} syntax shows reflog date (multiline)' ' test_cmp expect actual ' -cat >expect <<'EOF' -e46513e HEAD@{Thu Apr 7 15:13:13 2005 -0700}: commit (initial): one +cat >expect <<EOF +$commit HEAD@{Thu Apr 7 15:13:13 2005 -0700}: commit (initial): one EOF test_expect_success 'using @{now} syntax shows reflog date (oneline)' ' git log -g -1 --oneline HEAD@{now} >actual && @@ -82,8 +83,8 @@ test_expect_success 'using --date= shows reflog date (multiline)' ' test_cmp expect actual ' -cat >expect <<'EOF' -e46513e HEAD@{Thu Apr 7 15:13:13 2005 -0700}: commit (initial): one +cat >expect <<EOF +$commit HEAD@{Thu Apr 7 15:13:13 2005 -0700}: commit (initial): one EOF test_expect_success 'using --date= shows reflog date (oneline)' ' git log -g -1 --oneline --date=default >actual && @@ -109,8 +110,8 @@ test_expect_success 'log.date does not invoke "--date" magic (multiline)' ' test_cmp expect actual ' -cat >expect <<'EOF' -e46513e HEAD@{0}: commit (initial): one +cat >expect <<EOF +$commit HEAD@{0}: commit (initial): one EOF test_expect_success 'log.date does not invoke "--date" magic (oneline)' ' test_config log.date raw && @@ -138,7 +139,7 @@ test_expect_success '--date magic does not override explicit @{0} syntax' ' : >expect test_expect_success 'empty reflog file' ' git branch empty && - : >.git/logs/refs/heads/empty && + git reflog expire --expire=all refs/heads/empty && git log -g empty >actual && test_cmp expect actual @@ -166,4 +167,9 @@ test_expect_success 'git log -g -p shows diffs vs. parents' ' test_cmp expect actual ' +test_expect_success 'reflog exists works' ' + git reflog exists refs/heads/master && + ! git reflog exists refs/heads/nonexistent +' + test_done diff --git a/t/t1414-reflog-walk.sh b/t/t1414-reflog-walk.sh new file mode 100755 index 0000000000..feb1efd8ff --- /dev/null +++ b/t/t1414-reflog-walk.sh @@ -0,0 +1,135 @@ +#!/bin/sh + +test_description='various tests of reflog walk (log -g) behavior' +. ./test-lib.sh + +test_expect_success 'set up some reflog entries' ' + test_commit one && + test_commit two && + git checkout -b side HEAD^ && + test_commit three && + git merge --no-commit master && + echo evil-merge-content >>one.t && + test_tick && + git commit --no-edit -a +' + +do_walk () { + git log -g --format="%gd %gs" "$@" +} + +sq="'" +test_expect_success 'set up expected reflog' ' + cat >expect.all <<-EOF + HEAD@{0} commit (merge): Merge branch ${sq}master${sq} into side + HEAD@{1} commit: three + HEAD@{2} checkout: moving from master to side + HEAD@{3} commit: two + HEAD@{4} commit (initial): one + EOF +' + +test_expect_success 'reflog walk shows expected logs' ' + do_walk >actual && + test_cmp expect.all actual +' + +test_expect_success 'reflog can limit with --no-merges' ' + grep -v merge expect.all >expect && + do_walk --no-merges >actual && + test_cmp expect actual +' + +test_expect_success 'reflog can limit with pathspecs' ' + grep two expect.all >expect && + do_walk -- two.t >actual && + test_cmp expect actual +' + +test_expect_success 'pathspec limiting handles merges' ' + # we pick up: + # - the initial commit of one + # - the checkout back to commit one + # - the evil merge which touched one + sed -n "1p;3p;5p" expect.all >expect && + do_walk -- one.t >actual && + test_cmp expect actual +' + +test_expect_success '--parents shows true parents' ' + # convert newlines to spaces + echo $(git rev-parse HEAD HEAD^1 HEAD^2) >expect && + git rev-list -g --parents -1 HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'walking multiple reflogs shows all' ' + # We expect to see all entries for all reflogs, but interleaved by + # date, with order on the command line breaking ties. We + # can use "sort" on the separate lists to generate this, + # but note two tricks: + # + # 1. We use "{" as the delimiter, which lets us skip to the reflog + # date specifier as our second field, and then our "-n" numeric + # sort ignores the bits after the timestamp. + # + # 2. POSIX leaves undefined whether this is a stable sort or not. So + # we use "-k 1" to ensure that we see HEAD before master before + # side when breaking ties. + { + do_walk --date=unix HEAD && + do_walk --date=unix side && + do_walk --date=unix master + } >expect.raw && + sort -t "{" -k 2nr -k 1 <expect.raw >expect && + do_walk --date=unix HEAD master side >actual && + test_cmp expect actual +' + +test_expect_success 'date-limiting does not interfere with other logs' ' + do_walk HEAD@{1979-01-01} HEAD >actual && + test_cmp expect.all actual +' + +test_expect_success 'min/max age uses entry date to limit' ' + # Flip between commits one and two so each ref update actually + # does something (and does not get optimized out). We know + # that the timestamps of those commits will be before our "min". + + git update-ref -m before refs/heads/minmax one && + + test_tick && + min=$test_tick && + git update-ref -m min refs/heads/minmax two && + + test_tick && + max=$test_tick && + git update-ref -m max refs/heads/minmax one && + + test_tick && + git update-ref -m after refs/heads/minmax two && + + cat >expect <<-\EOF && + max + min + EOF + git log -g --since=$min --until=$max --format=%gs minmax >actual && + test_cmp expect actual +' + +test_expect_success 'walk prefers reflog to ref tip' ' + head=$(git rev-parse HEAD) && + one=$(git rev-parse one) && + ident="$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE" && + echo "$head $one $ident broken reflog entry" >>.git/logs/HEAD && + + echo $one >expect && + git log -g --format=%H -1 >actual && + test_cmp expect actual +' + +test_expect_success 'rev-list -g complains when there are no reflogs' ' + test_must_fail git rev-list -g +' + +test_done diff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh index 16d0b8bd1a..c7878a60ed 100755 --- a/t/t1430-bad-ref-name.sh +++ b/t/t1430-bad-ref-name.sh @@ -38,18 +38,20 @@ test_expect_success 'fast-import: fail on invalid branch name "bad[branch]name"' test_must_fail git fast-import <input ' -test_expect_success 'git branch shows badly named ref' ' +test_expect_success 'git branch shows badly named ref as warning' ' cp .git/refs/heads/master .git/refs/heads/broken...ref && test_when_finished "rm -f .git/refs/heads/broken...ref" && - git branch >output && - grep -e "broken\.\.\.ref" output + git branch >output 2>error && + test_i18ngrep -e "ignoring ref with broken name refs/heads/broken\.\.\.ref" error && + ! grep -e "broken\.\.\.ref" output ' test_expect_success 'branch -d can delete badly named ref' ' cp .git/refs/heads/master .git/refs/heads/broken...ref && test_when_finished "rm -f .git/refs/heads/broken...ref" && git branch -d broken...ref && - git branch >output && + git branch >output 2>error && + ! grep -e "broken\.\.\.ref" error && ! grep -e "broken\.\.\.ref" output ' @@ -57,7 +59,8 @@ test_expect_success 'branch -D can delete badly named ref' ' cp .git/refs/heads/master .git/refs/heads/broken...ref && test_when_finished "rm -f .git/refs/heads/broken...ref" && git branch -D broken...ref && - git branch >output && + git branch >output 2>error && + ! grep -e "broken\.\.\.ref" error && ! grep -e "broken\.\.\.ref" output ' @@ -85,7 +88,8 @@ test_expect_success 'branch -D cannot delete absolute path' ' test_expect_success 'git branch cannot create a badly named ref' ' test_when_finished "rm -f .git/refs/heads/broken...ref" && test_must_fail git branch broken...ref && - git branch >output && + git branch >output 2>error && + ! grep -e "broken\.\.\.ref" error && ! grep -e "broken\.\.\.ref" output ' @@ -95,7 +99,8 @@ test_expect_success 'branch -m cannot rename to a bad ref name' ' git branch goodref && test_must_fail git branch -m goodref broken...ref && test_cmp_rev master goodref && - git branch >output && + git branch >output 2>error && + ! grep -e "broken\.\.\.ref" error && ! grep -e "broken\.\.\.ref" output ' @@ -104,18 +109,20 @@ test_expect_failure 'branch -m can rename from a bad ref name' ' test_when_finished "rm -f .git/refs/heads/broken...ref" && git branch -m broken...ref renamed && test_cmp_rev master renamed && - git branch >output && + git branch >output 2>error && + ! grep -e "broken\.\.\.ref" error && ! grep -e "broken\.\.\.ref" output ' test_expect_success 'push cannot create a badly named ref' ' test_when_finished "rm -f .git/refs/heads/broken...ref" && test_must_fail git push "file://$(pwd)" HEAD:refs/heads/broken...ref && - git branch >output && + git branch >output 2>error && + ! grep -e "broken\.\.\.ref" error && ! grep -e "broken\.\.\.ref" output ' -test_expect_failure 'push --mirror can delete badly named ref' ' +test_expect_failure C_LOCALE_OUTPUT 'push --mirror can delete badly named ref' ' top=$(pwd) && git init src && git init dest && @@ -131,7 +138,8 @@ test_expect_failure 'push --mirror can delete badly named ref' ' cp .git/refs/heads/master .git/refs/heads/broken...ref ) && git -C src push --mirror "file://$top/dest" && - git -C dest branch >output && + git -C dest branch >output 2>error && + ! grep -e "broken\.\.\.ref" error && ! grep -e "broken\.\.\.ref" output ' @@ -139,34 +147,145 @@ test_expect_success 'rev-parse skips symref pointing to broken name' ' test_when_finished "rm -f .git/refs/heads/broken...ref" && git branch shadow one && cp .git/refs/heads/master .git/refs/heads/broken...ref && - git symbolic-ref refs/tags/shadow refs/heads/broken...ref && - + printf "ref: refs/heads/broken...ref\n" >.git/refs/tags/shadow && + test_when_finished "rm -f .git/refs/tags/shadow" && git rev-parse --verify one >expect && git rev-parse --verify shadow >actual 2>err && test_cmp expect actual && - test_i18ngrep "ignoring.*refs/tags/shadow" err + test_i18ngrep "ignoring dangling symref refs/tags/shadow" err ' -test_expect_success 'update-ref --no-deref -d can delete reference to broken name' ' - git symbolic-ref refs/heads/badname refs/heads/broken...ref && +test_expect_success 'for-each-ref emits warnings for broken names' ' + cp .git/refs/heads/master .git/refs/heads/broken...ref && + test_when_finished "rm -f .git/refs/heads/broken...ref" && + printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname && test_when_finished "rm -f .git/refs/heads/badname" && - test_path_is_file .git/refs/heads/badname && - git update-ref --no-deref -d refs/heads/badname && - test_path_is_missing .git/refs/heads/badname + printf "ref: refs/heads/master\n" >.git/refs/heads/broken...symref && + test_when_finished "rm -f .git/refs/heads/broken...symref" && + git for-each-ref >output 2>error && + ! grep -e "broken\.\.\.ref" output && + ! grep -e "badname" output && + ! grep -e "broken\.\.\.symref" output && + test_i18ngrep "ignoring ref with broken name refs/heads/broken\.\.\.ref" error && + test_i18ngrep "ignoring broken ref refs/heads/badname" error && + test_i18ngrep "ignoring ref with broken name refs/heads/broken\.\.\.symref" error ' test_expect_success 'update-ref -d can delete broken name' ' cp .git/refs/heads/master .git/refs/heads/broken...ref && test_when_finished "rm -f .git/refs/heads/broken...ref" && - git update-ref -d refs/heads/broken...ref && - git branch >output && + git update-ref -d refs/heads/broken...ref >output 2>error && + test_must_be_empty output && + test_must_be_empty error && + git branch >output 2>error && + ! grep -e "broken\.\.\.ref" error && ! grep -e "broken\.\.\.ref" output ' +test_expect_success 'branch -d can delete broken name' ' + cp .git/refs/heads/master .git/refs/heads/broken...ref && + test_when_finished "rm -f .git/refs/heads/broken...ref" && + git branch -d broken...ref >output 2>error && + test_i18ngrep "Deleted branch broken...ref (was broken)" output && + test_must_be_empty error && + git branch >output 2>error && + ! grep -e "broken\.\.\.ref" error && + ! grep -e "broken\.\.\.ref" output +' + +test_expect_success 'update-ref --no-deref -d can delete symref to broken name' ' + cp .git/refs/heads/master .git/refs/heads/broken...ref && + test_when_finished "rm -f .git/refs/heads/broken...ref" && + printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname && + test_when_finished "rm -f .git/refs/heads/badname" && + git update-ref --no-deref -d refs/heads/badname >output 2>error && + test_path_is_missing .git/refs/heads/badname && + test_must_be_empty output && + test_must_be_empty error +' + +test_expect_success 'branch -d can delete symref to broken name' ' + cp .git/refs/heads/master .git/refs/heads/broken...ref && + test_when_finished "rm -f .git/refs/heads/broken...ref" && + printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname && + test_when_finished "rm -f .git/refs/heads/badname" && + git branch -d badname >output 2>error && + test_path_is_missing .git/refs/heads/badname && + test_i18ngrep "Deleted branch badname (was refs/heads/broken\.\.\.ref)" output && + test_must_be_empty error +' + +test_expect_success 'update-ref --no-deref -d can delete dangling symref to broken name' ' + printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname && + test_when_finished "rm -f .git/refs/heads/badname" && + git update-ref --no-deref -d refs/heads/badname >output 2>error && + test_path_is_missing .git/refs/heads/badname && + test_must_be_empty output && + test_must_be_empty error +' + +test_expect_success 'branch -d can delete dangling symref to broken name' ' + printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname && + test_when_finished "rm -f .git/refs/heads/badname" && + git branch -d badname >output 2>error && + test_path_is_missing .git/refs/heads/badname && + test_i18ngrep "Deleted branch badname (was refs/heads/broken\.\.\.ref)" output && + test_must_be_empty error +' + +test_expect_success 'update-ref -d can delete broken name through symref' ' + cp .git/refs/heads/master .git/refs/heads/broken...ref && + test_when_finished "rm -f .git/refs/heads/broken...ref" && + printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname && + test_when_finished "rm -f .git/refs/heads/badname" && + git update-ref -d refs/heads/badname >output 2>error && + test_path_is_missing .git/refs/heads/broken...ref && + test_must_be_empty output && + test_must_be_empty error +' + +test_expect_success 'update-ref --no-deref -d can delete symref with broken name' ' + printf "ref: refs/heads/master\n" >.git/refs/heads/broken...symref && + test_when_finished "rm -f .git/refs/heads/broken...symref" && + git update-ref --no-deref -d refs/heads/broken...symref >output 2>error && + test_path_is_missing .git/refs/heads/broken...symref && + test_must_be_empty output && + test_must_be_empty error +' + +test_expect_success 'branch -d can delete symref with broken name' ' + printf "ref: refs/heads/master\n" >.git/refs/heads/broken...symref && + test_when_finished "rm -f .git/refs/heads/broken...symref" && + git branch -d broken...symref >output 2>error && + test_path_is_missing .git/refs/heads/broken...symref && + test_i18ngrep "Deleted branch broken...symref (was refs/heads/master)" output && + test_must_be_empty error +' + +test_expect_success 'update-ref --no-deref -d can delete dangling symref with broken name' ' + printf "ref: refs/heads/idonotexist\n" >.git/refs/heads/broken...symref && + test_when_finished "rm -f .git/refs/heads/broken...symref" && + git update-ref --no-deref -d refs/heads/broken...symref >output 2>error && + test_path_is_missing .git/refs/heads/broken...symref && + test_must_be_empty output && + test_must_be_empty error +' + +test_expect_success 'branch -d can delete dangling symref with broken name' ' + printf "ref: refs/heads/idonotexist\n" >.git/refs/heads/broken...symref && + test_when_finished "rm -f .git/refs/heads/broken...symref" && + git branch -d broken...symref >output 2>error && + test_path_is_missing .git/refs/heads/broken...symref && + test_i18ngrep "Deleted branch broken...symref (was refs/heads/idonotexist)" output && + test_must_be_empty error +' + test_expect_success 'update-ref -d cannot delete non-ref in .git dir' ' echo precious >.git/my-private-file && echo precious >expect && - test_must_fail git update-ref -d my-private-file && + test_must_fail git update-ref -d my-private-file >output 2>error && + test_must_be_empty output && + test_i18ngrep -e "refusing to update ref with bad name" error && test_cmp expect .git/my-private-file ' @@ -212,4 +331,47 @@ test_expect_success 'update-ref --stdin -z fails delete with bad ref name' ' grep "fatal: invalid ref format: ~a" err ' +test_expect_success 'branch rejects HEAD as a branch name' ' + test_must_fail git branch HEAD HEAD^ && + test_must_fail git show-ref refs/heads/HEAD +' + +test_expect_success 'checkout -b rejects HEAD as a branch name' ' + test_must_fail git checkout -B HEAD HEAD^ && + test_must_fail git show-ref refs/heads/HEAD +' + +test_expect_success 'update-ref can operate on refs/heads/HEAD' ' + git update-ref refs/heads/HEAD HEAD^ && + git show-ref refs/heads/HEAD && + git update-ref -d refs/heads/HEAD && + test_must_fail git show-ref refs/heads/HEAD +' + +test_expect_success 'branch -d can remove refs/heads/HEAD' ' + git update-ref refs/heads/HEAD HEAD^ && + git branch -d HEAD && + test_must_fail git show-ref refs/heads/HEAD +' + +test_expect_success 'branch -m can rename refs/heads/HEAD' ' + git update-ref refs/heads/HEAD HEAD^ && + git branch -m HEAD tail && + test_must_fail git show-ref refs/heads/HEAD && + git show-ref refs/heads/tail +' + +test_expect_success 'branch -d can remove refs/heads/-dash' ' + git update-ref refs/heads/-dash HEAD^ && + git branch -d -- -dash && + test_must_fail git show-ref refs/heads/-dash +' + +test_expect_success 'branch -m can rename refs/heads/-dash' ' + git update-ref refs/heads/-dash HEAD^ && + git branch -m -- -dash dash && + test_must_fail git show-ref refs/heads/-dash && + git show-ref refs/heads/dash +' + test_done diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index cfb32b6242..cb4b66e29d 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -43,13 +43,13 @@ test_expect_success 'HEAD is part of refs, valid objects appear valid' ' test_expect_success 'setup: helpers for corruption tests' ' sha1_file() { - echo "$*" | sed "s#..#.git/objects/&/#" + remainder=${1#??} && + firsttwo=${1%$remainder} && + echo ".git/objects/$firsttwo/$remainder" } && remove_object() { - file=$(sha1_file "$*") && - test -e "$file" && - rm -f "$file" + rm "$(sha1_file "$1")" } ' @@ -77,11 +77,31 @@ test_expect_success 'object with bad sha1' ' test_expect_success 'branch pointing to non-commit' ' git rev-parse HEAD^{tree} >.git/refs/heads/invalid && test_when_finished "git update-ref -d refs/heads/invalid" && - git fsck 2>out && + test_must_fail git fsck 2>out && cat out && grep "not a commit" out ' +test_expect_success 'HEAD link pointing at a funny object' ' + test_when_finished "mv .git/SAVED_HEAD .git/HEAD" && + mv .git/HEAD .git/SAVED_HEAD && + echo 0000000000000000000000000000000000000000 >.git/HEAD && + # avoid corrupt/broken HEAD from interfering with repo discovery + test_must_fail env GIT_DIR=.git git fsck 2>out && + cat out && + grep "detached HEAD points" out +' + +test_expect_success 'HEAD link pointing at a funny place' ' + test_when_finished "mv .git/SAVED_HEAD .git/HEAD" && + mv .git/HEAD .git/SAVED_HEAD && + echo "ref: refs/funny/place" >.git/HEAD && + # avoid corrupt/broken HEAD from interfering with repo discovery + test_must_fail env GIT_DIR=.git git fsck 2>out && + cat out && + grep "HEAD points to something strange" out +' + test_expect_success 'email without @ is okay' ' git cat-file commit HEAD >basis && sed "s/@/AT/" basis >okay && @@ -156,16 +176,29 @@ test_expect_success 'integer overflow in timestamps is reported' ' grep "error in commit $new.*integer overflow" out ' -test_expect_success 'malformatted tree object' ' - test_when_finished "git update-ref -d refs/tags/wrong" && - test_when_finished "remove_object \$T" && +test_expect_success 'commit with NUL in header' ' + git cat-file commit HEAD >basis && + sed "s/author ./author Q/" <basis | q_to_nul >commit-NUL-header && + new=$(git hash-object -t commit -w --stdin <commit-NUL-header) && + test_when_finished "remove_object $new" && + git update-ref refs/heads/bogus "$new" && + test_when_finished "git update-ref -d refs/heads/bogus" && + test_must_fail git fsck 2>out && + cat out && + grep "error in commit $new.*unterminated header: NUL at offset" out +' + +test_expect_success 'tree object with duplicate entries' ' + test_when_finished "for i in \$T; do remove_object \$i; done" && T=$( GIT_INDEX_FILE=test-index && export GIT_INDEX_FILE && rm -f test-index && >x && git add x && + git rev-parse :x && T=$(git write-tree) && + echo $T && ( git cat-file tree $T && git cat-file tree $T @@ -176,6 +209,41 @@ test_expect_success 'malformatted tree object' ' grep "error in tree .*contains duplicate file entries" out ' +test_expect_success 'unparseable tree object' ' + test_when_finished "git update-ref -d refs/heads/wrong" && + test_when_finished "remove_object \$tree_sha1" && + test_when_finished "remove_object \$commit_sha1" && + tree_sha1=$(printf "100644 \0twenty-bytes-of-junk" | git hash-object -t tree --stdin -w --literally) && + commit_sha1=$(git commit-tree $tree_sha1) && + git update-ref refs/heads/wrong $commit_sha1 && + test_must_fail git fsck 2>out && + test_i18ngrep "error: empty filename in tree entry" out && + test_i18ngrep "$tree_sha1" out && + test_i18ngrep ! "fatal: empty filename in tree entry" out +' + +hex2oct() { + perl -ne 'printf "\\%03o", hex for /../g' +} + +test_expect_success 'tree entry with type mismatch' ' + test_when_finished "remove_object \$blob" && + test_when_finished "remove_object \$tree" && + test_when_finished "remove_object \$commit" && + test_when_finished "git update-ref -d refs/heads/type_mismatch" && + blob=$(echo blob | git hash-object -w --stdin) && + blob_bin=$(echo $blob | hex2oct) && + tree=$( + printf "40000 dir\0${blob_bin}100644 file\0${blob_bin}" | + git hash-object -t tree --stdin -w --literally + ) && + commit=$(git commit-tree $tree) && + git update-ref refs/heads/type_mismatch $commit && + test_must_fail git fsck >out 2>&1 && + test_i18ngrep "is a blob, not a tree" out && + test_i18ngrep ! "dangling blob" out +' + test_expect_success 'tag pointing to nonexistent' ' cat >invalid-tag <<-\EOF && object ffffffffffffffffffffffffffffffffffffffff @@ -231,8 +299,8 @@ test_expect_success 'tag with incorrect tag name & missing tagger' ' git fsck --tags 2>out && cat >expect <<-EOF && - warning in tag $tag: invalid '\''tag'\'' name: wrong name format - warning in tag $tag: invalid format - expected '\''tagger'\'' line + warning in tag $tag: badTagName: invalid '\''tag'\'' name: wrong name format + warning in tag $tag: missingTaggerEntry: invalid format - expected '\''tagger'\'' line EOF test_cmp expect out ' @@ -256,6 +324,26 @@ test_expect_success 'tag with bad tagger' ' grep "error in tag .*: invalid author/committer" out ' +test_expect_success 'tag with NUL in header' ' + sha=$(git rev-parse HEAD) && + q_to_nul >tag-NUL-header <<-EOF && + object $sha + type commit + tag contains-Q-in-header + tagger T A Gger <tagger@example.com> 1234567890 -0000 + + This is an invalid tag. + EOF + + tag=$(git hash-object --literally -t tag -w --stdin <tag-NUL-header) && + test_when_finished "remove_object $tag" && + echo $tag >.git/refs/tags/wrong && + test_when_finished "git update-ref -d refs/tags/wrong" && + test_must_fail git fsck --tags 2>out && + cat out && + grep "error in tag $tag.*unterminated header: NUL at offset" out +' + test_expect_success 'cleaned up' ' git fsck >actual 2>&1 && test_cmp empty actual @@ -287,6 +375,17 @@ test_expect_success 'rev-list --verify-objects with bad sha1' ' grep -q "error: sha1 mismatch 63ffffffffffffffffffffffffffffffffffffff" out ' +test_expect_success 'force fsck to ignore double author' ' + git cat-file commit HEAD >basis && + sed "s/^author .*/&,&/" <basis | tr , \\n >multiple-authors && + new=$(git hash-object -t commit -w --stdin <multiple-authors) && + test_when_finished "remove_object $new" && + git update-ref refs/heads/bogus "$new" && + test_when_finished "git update-ref -d refs/heads/bogus" && + test_must_fail git fsck && + git -c fsck.multipleAuthors=ignore fsck +' + _bz='\0' _bz5="$_bz$_bz$_bz$_bz$_bz" _bz20="$_bz5$_bz5$_bz5$_bz5" @@ -364,6 +463,24 @@ test_expect_success 'fsck allows .Ňit' ' ) ' +test_expect_success 'NUL in commit' ' + rm -fr nul-in-commit && + git init nul-in-commit && + ( + cd nul-in-commit && + git commit --allow-empty -m "initial commitQNUL after message" && + git cat-file commit HEAD >original && + q_to_nul <original >munged && + git hash-object -w -t commit --stdin <munged >name && + git branch bad $(cat name) && + + test_must_fail git -c fsck.nulInCommit=error fsck 2>warn.1 && + grep nulInCommit warn.1 && + git fsck 2>warn.2 && + grep nulInCommit warn.2 + ) +' + # create a static test repo which is broken by omitting # one particular object ($1, which is looked up via rev-parse # in the new repository). @@ -420,4 +537,225 @@ test_expect_success 'fsck notices ref pointing to missing tag' ' test_must_fail git -C missing fsck ' +test_expect_success 'fsck --connectivity-only' ' + rm -rf connectivity-only && + git init connectivity-only && + ( + cd connectivity-only && + touch empty && + git add empty && + test_commit empty && + + # Drop the index now; we want to be sure that we + # recursively notice the broken objects + # because they are reachable from refs, not because + # they are in the index. + rm -f .git/index && + + # corrupt the blob, but in a way that we can still identify + # its type. That lets us see that --connectivity-only is + # not actually looking at the contents, but leaves it + # free to examine the type if it chooses. + empty=.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 && + blob=$(echo unrelated | git hash-object -w --stdin) && + mv -f $(sha1_file $blob) $empty && + + test_must_fail git fsck --strict && + git fsck --strict --connectivity-only && + tree=$(git rev-parse HEAD:) && + suffix=${tree#??} && + tree=.git/objects/${tree%$suffix}/$suffix && + rm -f $tree && + echo invalid >$tree && + test_must_fail git fsck --strict --connectivity-only + ) +' + +test_expect_success 'fsck --connectivity-only with explicit head' ' + rm -rf connectivity-only && + git init connectivity-only && + ( + cd connectivity-only && + test_commit foo && + rm -f .git/index && + tree=$(git rev-parse HEAD^{tree}) && + remove_object $(git rev-parse HEAD:foo.t) && + test_must_fail git fsck --connectivity-only $tree + ) +' + +test_expect_success 'fsck --name-objects' ' + rm -rf name-objects && + git init name-objects && + ( + cd name-objects && + test_commit julius caesar.t && + test_commit augustus && + test_commit caesar && + remove_object $(git rev-parse julius:caesar.t) && + test_must_fail git fsck --name-objects >out && + tree=$(git rev-parse --verify julius:) && + egrep "$tree \((refs/heads/master|HEAD)@\{[0-9]*\}:" out + ) +' + +test_expect_success 'alternate objects are correctly blamed' ' + test_when_finished "rm -rf alt.git .git/objects/info/alternates" && + git init --bare alt.git && + echo "../../alt.git/objects" >.git/objects/info/alternates && + mkdir alt.git/objects/12 && + >alt.git/objects/12/34567890123456789012345678901234567890 && + test_must_fail git fsck >out 2>&1 && + grep alt.git out +' + +test_expect_success 'fsck errors in packed objects' ' + git cat-file commit HEAD >basis && + sed "s/</one/" basis >one && + sed "s/</foo/" basis >two && + one=$(git hash-object -t commit -w one) && + two=$(git hash-object -t commit -w two) && + pack=$( + { + echo $one && + echo $two + } | git pack-objects .git/objects/pack/pack + ) && + test_when_finished "rm -f .git/objects/pack/pack-$pack.*" && + remove_object $one && + remove_object $two && + test_must_fail git fsck 2>out && + grep "error in commit $one.* - bad name" out && + grep "error in commit $two.* - bad name" out && + ! grep corrupt out +' + +test_expect_success 'fsck fails on corrupt packfile' ' + hsh=$(git commit-tree -m mycommit HEAD^{tree}) && + pack=$(echo $hsh | git pack-objects .git/objects/pack/pack) && + + # Corrupt the first byte of the first object. (It contains 3 type bits, + # at least one of which is not zero, so setting the first byte to 0 is + # sufficient.) + chmod a+w .git/objects/pack/pack-$pack.pack && + printf '\0' | dd of=.git/objects/pack/pack-$pack.pack bs=1 conv=notrunc seek=12 && + + test_when_finished "rm -f .git/objects/pack/pack-$pack.*" && + remove_object $hsh && + test_must_fail git fsck 2>out && + test_i18ngrep "checksum mismatch" out +' + +test_expect_success 'fsck finds problems in duplicate loose objects' ' + rm -rf broken-duplicate && + git init broken-duplicate && + ( + cd broken-duplicate && + test_commit duplicate && + # no "-d" here, so we end up with duplicates + git repack && + # now corrupt the loose copy + file=$(sha1_file "$(git rev-parse HEAD)") && + rm "$file" && + echo broken >"$file" && + test_must_fail git fsck + ) +' + +test_expect_success 'fsck detects trailing loose garbage (commit)' ' + git cat-file commit HEAD >basis && + echo bump-commit-sha1 >>basis && + commit=$(git hash-object -w -t commit basis) && + file=$(sha1_file $commit) && + test_when_finished "remove_object $commit" && + chmod +w "$file" && + echo garbage >>"$file" && + test_must_fail git fsck 2>out && + test_i18ngrep "garbage.*$commit" out +' + +test_expect_success 'fsck detects trailing loose garbage (blob)' ' + blob=$(echo trailing | git hash-object -w --stdin) && + file=$(sha1_file $blob) && + test_when_finished "remove_object $blob" && + chmod +w "$file" && + echo garbage >>"$file" && + test_must_fail git fsck 2>out && + test_i18ngrep "garbage.*$blob" out +' + +# for each of type, we have one version which is referenced by another object +# (and so while unreachable, not dangling), and another variant which really is +# dangling. +test_expect_success 'fsck notices dangling objects' ' + git init dangling && + ( + cd dangling && + blob=$(echo not-dangling | git hash-object -w --stdin) && + dblob=$(echo dangling | git hash-object -w --stdin) && + tree=$(printf "100644 blob %s\t%s\n" $blob one | git mktree) && + dtree=$(printf "100644 blob %s\t%s\n" $blob two | git mktree) && + commit=$(git commit-tree $tree) && + dcommit=$(git commit-tree -p $commit $tree) && + + cat >expect <<-EOF && + dangling blob $dblob + dangling commit $dcommit + dangling tree $dtree + EOF + + git fsck >actual && + # the output order is non-deterministic, as it comes from a hash + sort <actual >actual.sorted && + test_cmp expect actual.sorted + ) +' + +test_expect_success 'fsck $name notices bogus $name' ' + test_must_fail git fsck bogus && + test_must_fail git fsck $_z40 +' + +test_expect_success 'bogus head does not fallback to all heads' ' + # set up a case that will cause a reachability complaint + echo to-be-deleted >foo && + git add foo && + blob=$(git rev-parse :foo) && + test_when_finished "git rm --cached foo" && + remove_object $blob && + test_must_fail git fsck $_z40 >out 2>&1 && + ! grep $blob out +' + +# Corrupt the checksum on the index. +# Add 1 to the last byte in the SHA. +corrupt_index_checksum () { + perl -w -e ' + use Fcntl ":seek"; + open my $fh, "+<", ".git/index" or die "open: $!"; + binmode $fh; + seek $fh, -1, SEEK_END or die "seek: $!"; + read $fh, my $in_byte, 1 or die "read: $!"; + + $in_value = unpack("C", $in_byte); + $out_value = ($in_value + 1) & 255; + + $out_byte = pack("C", $out_value); + + seek $fh, -1, SEEK_END or die "seek: $!"; + print $fh $out_byte; + close $fh or die "close: $!"; + ' +} + +# Corrupt the checksum on the index and then +# verify that only fsck notices. +test_expect_success 'detect corrupt index file in fsck' ' + cp .git/index .git/index.backup && + test_when_finished "mv .git/index.backup .git/index" && + corrupt_index_checksum && + test_must_fail git fsck --cache 2>errors && + grep "bad index file" errors +' + test_done diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh index 48ee07779d..5c715fe2cf 100755 --- a/t/t1500-rev-parse.sh +++ b/t/t1500-rev-parse.sh @@ -3,85 +3,146 @@ test_description='test git rev-parse' . ./test-lib.sh -test_rev_parse() { +# usage: [options] label is-bare is-inside-git is-inside-work prefix git-dir absolute-git-dir +test_rev_parse () { + d= + bare= + gitdir= + while : + do + case "$1" in + -C) d="$2"; shift; shift ;; + -b) case "$2" in + [tfu]*) bare="$2"; shift; shift ;; + *) error "test_rev_parse: bogus core.bare value '$2'" ;; + esac ;; + -g) gitdir="$2"; shift; shift ;; + -*) error "test_rev_parse: unrecognized option '$1'" ;; + *) break ;; + esac + done + name=$1 shift - test_expect_success "$name: is-bare-repository" \ - "test '$1' = \"\$(git rev-parse --is-bare-repository)\"" - shift - [ $# -eq 0 ] && return + for o in --is-bare-repository \ + --is-inside-git-dir \ + --is-inside-work-tree \ + --show-prefix \ + --git-dir \ + --absolute-git-dir + do + test $# -eq 0 && break + expect="$1" + test_expect_success "$name: $o" ' + if test -n "$gitdir" + then + test_when_finished "unset GIT_DIR" && + GIT_DIR="$gitdir" && + export GIT_DIR + fi && + + case "$bare" in + t*) test_config ${d:+-C} ${d:+"$d"} core.bare true ;; + f*) test_config ${d:+-C} ${d:+"$d"} core.bare false ;; + u*) test_unconfig ${d:+-C} ${d:+"$d"} core.bare ;; + esac && + + echo "$expect" >expect && + git ${d:+-C} ${d:+"$d"} rev-parse $o >actual && + test_cmp expect actual + ' + shift + done +} - test_expect_success "$name: is-inside-git-dir" \ - "test '$1' = \"\$(git rev-parse --is-inside-git-dir)\"" - shift - [ $# -eq 0 ] && return +ROOT=$(pwd) - test_expect_success "$name: is-inside-work-tree" \ - "test '$1' = \"\$(git rev-parse --is-inside-work-tree)\"" - shift - [ $# -eq 0 ] && return +test_expect_success 'setup' ' + mkdir -p sub/dir work && + cp -R .git repo.git +' - test_expect_success "$name: prefix" \ - "test '$1' = \"\$(git rev-parse --show-prefix)\"" - shift - [ $# -eq 0 ] && return +test_rev_parse toplevel false false true '' .git "$ROOT/.git" - test_expect_success "$name: git-dir" \ - "test '$1' = \"\$(git rev-parse --git-dir)\"" - shift - [ $# -eq 0 ] && return -} +test_rev_parse -C .git .git/ false true false '' . "$ROOT/.git" +test_rev_parse -C .git/objects .git/objects/ false true false '' "$ROOT/.git" "$ROOT/.git" -# label is-bare is-inside-git is-inside-work prefix git-dir +test_rev_parse -C sub/dir subdirectory false false true sub/dir/ "$ROOT/.git" "$ROOT/.git" -ROOT=$(pwd) +test_rev_parse -b t 'core.bare = true' true false false + +test_rev_parse -b u 'core.bare undefined' false false true + + +test_rev_parse -C work -g ../.git -b f 'GIT_DIR=../.git, core.bare = false' false false true '' "../.git" "$ROOT/.git" + +test_rev_parse -C work -g ../.git -b t 'GIT_DIR=../.git, core.bare = true' true false false '' -test_rev_parse toplevel false false true '' .git +test_rev_parse -C work -g ../.git -b u 'GIT_DIR=../.git, core.bare undefined' false false true '' -cd .git || exit 1 -test_rev_parse .git/ false true false '' . -cd objects || exit 1 -test_rev_parse .git/objects/ false true false '' "$ROOT/.git" -cd ../.. || exit 1 -mkdir -p sub/dir || exit 1 -cd sub/dir || exit 1 -test_rev_parse subdirectory false false true sub/dir/ "$ROOT/.git" -cd ../.. || exit 1 +test_rev_parse -C work -g ../repo.git -b f 'GIT_DIR=../repo.git, core.bare = false' false false true '' "../repo.git" "$ROOT/repo.git" -git config core.bare true -test_rev_parse 'core.bare = true' true false false +test_rev_parse -C work -g ../repo.git -b t 'GIT_DIR=../repo.git, core.bare = true' true false false '' -git config --unset core.bare -test_rev_parse 'core.bare undefined' false false true +test_rev_parse -C work -g ../repo.git -b u 'GIT_DIR=../repo.git, core.bare undefined' false false true '' -mkdir work || exit 1 -cd work || exit 1 -GIT_DIR=../.git -GIT_CONFIG="$(pwd)"/../.git/config -export GIT_DIR GIT_CONFIG +test_expect_success 'git-common-dir from worktree root' ' + echo .git >expect && + git rev-parse --git-common-dir >actual && + test_cmp expect actual +' -git config core.bare false -test_rev_parse 'GIT_DIR=../.git, core.bare = false' false false true '' +test_expect_success 'git-common-dir inside sub-dir' ' + mkdir -p path/to/child && + test_when_finished "rm -rf path" && + echo "$(git -C path/to/child rev-parse --show-cdup).git" >expect && + git -C path/to/child rev-parse --git-common-dir >actual && + test_cmp expect actual +' -git config core.bare true -test_rev_parse 'GIT_DIR=../.git, core.bare = true' true false false '' +test_expect_success 'git-path from worktree root' ' + echo .git/objects >expect && + git rev-parse --git-path objects >actual && + test_cmp expect actual +' -git config --unset core.bare -test_rev_parse 'GIT_DIR=../.git, core.bare undefined' false false true '' +test_expect_success 'git-path inside sub-dir' ' + mkdir -p path/to/child && + test_when_finished "rm -rf path" && + echo "$(git -C path/to/child rev-parse --show-cdup).git/objects" >expect && + git -C path/to/child rev-parse --git-path objects >actual && + test_cmp expect actual +' -mv ../.git ../repo.git || exit 1 -GIT_DIR=../repo.git -GIT_CONFIG="$(pwd)"/../repo.git/config +test_expect_success 'rev-parse --is-shallow-repository in shallow repo' ' + test_commit test_commit && + echo true >expect && + git clone --depth 1 --no-local . shallow && + test_when_finished "rm -rf shallow" && + git -C shallow rev-parse --is-shallow-repository >actual && + test_cmp expect actual +' -git config core.bare false -test_rev_parse 'GIT_DIR=../repo.git, core.bare = false' false false true '' +test_expect_success 'rev-parse --is-shallow-repository in non-shallow repo' ' + echo false >expect && + git rev-parse --is-shallow-repository >actual && + test_cmp expect actual +' -git config core.bare true -test_rev_parse 'GIT_DIR=../repo.git, core.bare = true' true false false '' +test_expect_success 'showing the superproject correctly' ' + git rev-parse --show-superproject-working-tree >out && + test_must_be_empty out && -git config --unset core.bare -test_rev_parse 'GIT_DIR=../repo.git, core.bare undefined' false false true '' + test_create_repo super && + test_commit -C super test_commit && + test_create_repo sub && + test_commit -C sub test_commit && + git -C super submodule add ../sub dir/sub && + echo $(pwd)/super >expect && + git -C super/dir/sub rev-parse --show-superproject-working-tree >out && + test_cmp expect out +' test_done diff --git a/t/t1501-worktree.sh b/t/t1501-work-tree.sh index cc5b870e58..9c0bc65250 100755 --- a/t/t1501-worktree.sh +++ b/t/t1501-work-tree.sh @@ -341,7 +341,7 @@ test_expect_success 'make_relative_path handles double slashes in GIT_DIR' ' test_expect_success 'relative $GIT_WORK_TREE and git subprocesses' ' GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work \ - test-subprocess --setup-work-tree rev-parse --show-toplevel >actual && + test-tool subprocess --setup-work-tree rev-parse --show-toplevel >actual && echo "$(pwd)/repo.git/work" >expected && test_cmp expected actual ' @@ -360,7 +360,7 @@ test_expect_success 'GIT_DIR set (1)' ' ( cd work && GIT_DIR=../gitfile git rev-parse --git-common-dir >actual && - test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect && + test-tool path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect && test_cmp expect actual ) ' @@ -371,7 +371,7 @@ test_expect_success 'GIT_DIR set (2)' ' ( cd work && GIT_DIR=../gitfile git rev-parse --git-common-dir >actual && - test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect && + test-tool path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect && test_cmp expect actual ) ' @@ -382,7 +382,7 @@ test_expect_success 'Auto discovery' ' ( cd work && git rev-parse --git-common-dir >actual && - test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect && + test-tool path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect && test_cmp expect actual && echo haha >data1 && git add data1 && @@ -400,7 +400,7 @@ test_expect_success '$GIT_DIR/common overrides core.worktree' ' ( cd work && git rev-parse --git-common-dir >actual && - test-path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect && + test-tool path-utils real_path "$TRASH_DIRECTORY/repo.git" >expect && test_cmp expect actual && echo haha >data2 && git add data2 && @@ -423,4 +423,24 @@ test_expect_success '$GIT_WORK_TREE overrides $GIT_DIR/common' ' ) ' +test_expect_success 'error out gracefully on invalid $GIT_WORK_TREE' ' + ( + GIT_WORK_TREE=/.invalid/work/tree && + export GIT_WORK_TREE && + test_expect_code 128 git rev-parse + ) +' + +test_expect_success 'refs work with relative gitdir and work tree' ' + git init relative && + git -C relative commit --allow-empty -m one && + git -C relative commit --allow-empty -m two && + + GIT_DIR=relative/.git GIT_WORK_TREE=relative git reset HEAD^ && + + git -C relative log -1 --format=%s >actual && + echo one >expect && + test_cmp expect actual +' + test_done diff --git a/t/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh index ebe7c3b87c..a859abedf5 100755 --- a/t/t1502-rev-parse-parseopt.sh +++ b/t/t1502-rev-parse-parseopt.sh @@ -3,7 +3,62 @@ test_description='test git rev-parse --parseopt' . ./test-lib.sh -sed -e 's/^|//' >expect <<\END_EXPECT +test_expect_success 'setup optionspec' ' + sed -e "s/^|//" >optionspec <<\EOF +|some-command [options] <args>... +| +|some-command does foo and bar! +|-- +|h,help show the help +| +|foo some nifty option --foo +|bar= some cool option --bar with an argument +|b,baz a short and long option +| +| An option group Header +|C? option C with an optional argument +|d,data? short and long option with an optional argument +| +| Argument hints +|B=arg short option required argument +|bar2=arg long option required argument +|e,fuz=with-space short and long option required argument +|s?some short option optional argument +|long?data long option optional argument +|g,fluf?path short and long option optional argument +|longest=very-long-argument-hint a very long argument hint +|pair=key=value with an equals sign in the hint +|aswitch help te=t contains? fl*g characters!` +|bswitch=hint hint has trailing tab character +|cswitch switch has trailing tab character +|short-hint=a with a one symbol hint +| +|Extras +|extra1 line above used to cause a segfault but no longer does +EOF +' + +test_expect_success 'setup optionspec-no-switches' ' + sed -e "s/^|//" >optionspec_no_switches <<\EOF +|some-command [options] <args>... +| +|some-command does foo and bar! +|-- +EOF +' + +test_expect_success 'setup optionspec-only-hidden-switches' ' + sed -e "s/^|//" >optionspec_only_hidden_switches <<\EOF +|some-command [options] <args>... +| +|some-command does foo and bar! +|-- +|hidden1* A hidden switch +EOF +' + +test_expect_success 'test --parseopt help output' ' + sed -e "s/^|//" >expect <<\END_EXPECT && |cat <<\EOF |usage: some-command [options] <args>... | @@ -28,63 +83,123 @@ sed -e 's/^|//' >expect <<\END_EXPECT | -g, --fluf[=<path>] short and long option optional argument | --longest <very-long-argument-hint> | a very long argument hint +| --pair <key=value> with an equals sign in the hint +| --aswitch help te=t contains? fl*g characters!` +| --bswitch <hint> hint has trailing tab character +| --cswitch switch has trailing tab character +| --short-hint <a> with a one symbol hint | |Extras | --extra1 line above used to cause a segfault but no longer does | |EOF END_EXPECT + test_expect_code 129 git rev-parse --parseopt -- -h > output < optionspec && + test_i18ncmp expect output +' -sed -e 's/^|//' >optionspec <<\EOF -|some-command [options] <args>... +test_expect_success 'test --parseopt help output no switches' ' + sed -e "s/^|//" >expect <<\END_EXPECT && +|cat <<\EOF +|usage: some-command [options] <args>... | -|some-command does foo and bar! -|-- -|h,help show the help +| some-command does foo and bar! | -|foo some nifty option --foo -|bar= some cool option --bar with an argument -|b,baz a short and long option +|EOF +END_EXPECT + test_expect_code 129 git rev-parse --parseopt -- -h > output < optionspec_no_switches && + test_i18ncmp expect output +' + +test_expect_success 'test --parseopt help output hidden switches' ' + sed -e "s/^|//" >expect <<\END_EXPECT && +|cat <<\EOF +|usage: some-command [options] <args>... | -| An option group Header -|C? option C with an optional argument -|d,data? short and long option with an optional argument +| some-command does foo and bar! | -| Argument hints -|B=arg short option required argument -|bar2=arg long option required argument -|e,fuz=with-space short and long option required argument -|s?some short option optional argument -|long?data long option optional argument -|g,fluf?path short and long option optional argument -|longest=very-long-argument-hint a very long argument hint +|EOF +END_EXPECT + test_expect_code 129 git rev-parse --parseopt -- -h > output < optionspec_only_hidden_switches && + test_i18ncmp expect output +' + +test_expect_success 'test --parseopt help-all output hidden switches' ' + sed -e "s/^|//" >expect <<\END_EXPECT && +|cat <<\EOF +|usage: some-command [options] <args>... | -|Extras -|extra1 line above used to cause a segfault but no longer does -EOF +| some-command does foo and bar! +| +| --hidden1 A hidden switch +| +|EOF +END_EXPECT + test_expect_code 129 git rev-parse --parseopt -- --help-all > output < optionspec_only_hidden_switches && + test_i18ncmp expect output +' -test_expect_success 'test --parseopt help output' ' - test_expect_code 129 git rev-parse --parseopt -- -h > output < optionspec && +test_expect_success 'test --parseopt invalid switch help output' ' + sed -e "s/^|//" >expect <<\END_EXPECT && +|error: unknown option `does-not-exist'\'' +|usage: some-command [options] <args>... +| +| some-command does foo and bar! +| +| -h, --help show the help +| --foo some nifty option --foo +| --bar ... some cool option --bar with an argument +| -b, --baz a short and long option +| +|An option group Header +| -C[...] option C with an optional argument +| -d, --data[=...] short and long option with an optional argument +| +|Argument hints +| -B <arg> short option required argument +| --bar2 <arg> long option required argument +| -e, --fuz <with-space> +| short and long option required argument +| -s[<some>] short option optional argument +| --long[=<data>] long option optional argument +| -g, --fluf[=<path>] short and long option optional argument +| --longest <very-long-argument-hint> +| a very long argument hint +| --pair <key=value> with an equals sign in the hint +| --aswitch help te=t contains? fl*g characters!` +| --bswitch <hint> hint has trailing tab character +| --cswitch switch has trailing tab character +| --short-hint <a> with a one symbol hint +| +|Extras +| --extra1 line above used to cause a segfault but no longer does +| +END_EXPECT + test_expect_code 129 git rev-parse --parseopt -- --does-not-exist 1>/dev/null 2>output < optionspec && test_i18ncmp expect output ' -cat > expect <<EOF -set -- --foo --bar 'ham' -b -- 'arg' +test_expect_success 'setup expect.1' " + cat > expect <<EOF +set -- --foo --bar 'ham' -b --aswitch -- 'arg' EOF +" test_expect_success 'test --parseopt' ' - git rev-parse --parseopt -- --foo --bar=ham --baz arg < optionspec > output && + git rev-parse --parseopt -- --foo --bar=ham --baz --aswitch arg < optionspec > output && test_cmp expect output ' test_expect_success 'test --parseopt with mixed options and arguments' ' - git rev-parse --parseopt -- --foo arg --bar=ham --baz < optionspec > output && + git rev-parse --parseopt -- --foo arg --bar=ham --baz --aswitch < optionspec > output && test_cmp expect output ' -cat > expect <<EOF +test_expect_success 'setup expect.2' " + cat > expect <<EOF set -- --foo -- 'arg' '--bar=ham' EOF +" test_expect_success 'test --parseopt with --' ' git rev-parse --parseopt -- --foo -- arg --bar=ham < optionspec > output && @@ -96,54 +211,66 @@ test_expect_success 'test --parseopt --stop-at-non-option' ' test_cmp expect output ' -cat > expect <<EOF +test_expect_success 'setup expect.3' " + cat > expect <<EOF set -- --foo -- '--' 'arg' '--bar=ham' EOF +" test_expect_success 'test --parseopt --keep-dashdash' ' git rev-parse --parseopt --keep-dashdash -- --foo -- arg --bar=ham < optionspec > output && test_cmp expect output ' -cat >expect <<EOF +test_expect_success 'setup expect.4' " + cat >expect <<EOF set -- --foo -- '--' 'arg' '--spam=ham' EOF +" test_expect_success 'test --parseopt --keep-dashdash --stop-at-non-option with --' ' git rev-parse --parseopt --keep-dashdash --stop-at-non-option -- --foo -- arg --spam=ham <optionspec >output && test_cmp expect output ' -cat > expect <<EOF +test_expect_success 'setup expect.5' " + cat > expect <<EOF set -- --foo -- 'arg' '--spam=ham' EOF +" test_expect_success 'test --parseopt --keep-dashdash --stop-at-non-option without --' ' git rev-parse --parseopt --keep-dashdash --stop-at-non-option -- --foo arg --spam=ham <optionspec >output && test_cmp expect output ' -cat > expect <<EOF +test_expect_success 'setup expect.6' " + cat > expect <<EOF set -- --foo --bar='z' --baz -C'Z' --data='A' -- 'arg' EOF +" test_expect_success 'test --parseopt --stuck-long' ' git rev-parse --parseopt --stuck-long -- --foo --bar=z -b arg -CZ -dA <optionspec >output && test_cmp expect output ' -cat > expect <<EOF +test_expect_success 'setup expect.7' " + cat > expect <<EOF set -- --data='' -C --baz -- 'arg' EOF +" test_expect_success 'test --parseopt --stuck-long and empty optional argument' ' git rev-parse --parseopt --stuck-long -- --data= arg -C -b <optionspec >output && test_cmp expect output ' -cat > expect <<EOF +test_expect_success 'setup expect.8' " + cat > expect <<EOF set -- --data --baz -- 'arg' EOF +" test_expect_success 'test --parseopt --stuck-long and long option with unset optional argument' ' git rev-parse --parseopt --stuck-long -- --data arg -b <optionspec >output && diff --git a/t/t1503-rev-parse-verify.sh b/t/t1503-rev-parse-verify.sh index 823fe1d799..492edffa9c 100755 --- a/t/t1503-rev-parse-verify.sh +++ b/t/t1503-rev-parse-verify.sh @@ -85,8 +85,7 @@ test_expect_success 'fails silently when using -q' ' test_expect_success 'fails silently when using -q with deleted reflogs' ' ref=$(git rev-parse HEAD) && - : >.git/logs/refs/test && - git update-ref -m "message for refs/test" refs/test "$ref" && + git update-ref --create-reflog -m "message for refs/test" refs/test "$ref" && git reflog delete --updateref --rewrite refs/test@{0} && test_must_fail git rev-parse -q --verify refs/test@{0} >error 2>&1 && test_must_be_empty error @@ -94,16 +93,14 @@ test_expect_success 'fails silently when using -q with deleted reflogs' ' test_expect_success 'fails silently when using -q with not enough reflogs' ' ref=$(git rev-parse HEAD) && - : >.git/logs/refs/test2 && - git update-ref -m "message for refs/test2" refs/test2 "$ref" && + git update-ref --create-reflog -m "message for refs/test2" refs/test2 "$ref" && test_must_fail git rev-parse -q --verify refs/test2@{999} >error 2>&1 && test_must_be_empty error ' test_expect_success 'succeeds silently with -q and reflogs that do not go far back enough in time' ' ref=$(git rev-parse HEAD) && - : >.git/logs/refs/test3 && - git update-ref -m "message for refs/test3" refs/test3 "$ref" && + git update-ref --create-reflog -m "message for refs/test3" refs/test3 "$ref" && git rev-parse -q --verify refs/test3@{1.year.ago} >actual 2>error && test_must_be_empty error && echo "$ref" >expect && @@ -142,4 +139,9 @@ test_expect_success 'master@{n} for various n' ' test_must_fail git rev-parse --verify master@{$Np1} ' +test_expect_success SYMLINKS 'ref resolution not confused by broken symlinks' ' + ln -s does-not-exist .git/refs/heads/broken && + test_must_fail git rev-parse --verify broken +' + test_done diff --git a/t/t1506-rev-parse-diagnosis.sh b/t/t1506-rev-parse-diagnosis.sh index 613d9bfe1b..4ee009da66 100755 --- a/t/t1506-rev-parse-diagnosis.sh +++ b/t/t1506-rev-parse-diagnosis.sh @@ -106,7 +106,7 @@ test_expect_success 'incorrect revision id' ' test_must_fail git rev-parse foobar:file.txt 2>error && grep "Invalid object name '"'"'foobar'"'"'." error && test_must_fail git rev-parse foobar 2> error && - grep "unknown revision or path not in the working tree." error + test_i18ngrep "unknown revision or path not in the working tree." error ' test_expect_success 'incorrect file in sha1:path' ' @@ -157,7 +157,7 @@ test_expect_success 'relative path not found' ' test_expect_success 'relative path outside worktree' ' test_must_fail git rev-parse HEAD:../file.txt >output 2>error && test -z "$(cat output)" && - grep "outside repository" error + test_i18ngrep "outside repository" error ' test_expect_success 'relative path when cwd is outside worktree' ' @@ -166,11 +166,6 @@ test_expect_success 'relative path when cwd is outside worktree' ' grep "relative path syntax can.t be used outside working tree." error ' -test_expect_success 'relative path when startup_info is NULL' ' - test_must_fail test-match-trees HEAD:./file.txt HEAD:./file.txt 2>error && - grep "BUG: startup_info struct is not initialized." error -' - test_expect_success '<commit>:file correctly diagnosed after a pathname' ' test_must_fail git rev-parse file.txt HEAD:file.txt 1>actual 2>error && test_i18ngrep ! "exists on disk" error && diff --git a/t/t1507-rev-parse-upstream.sh b/t/t1507-rev-parse-upstream.sh index 46ef1f22dc..93c77eac45 100755 --- a/t/t1507-rev-parse-upstream.sh +++ b/t/t1507-rev-parse-upstream.sh @@ -42,15 +42,18 @@ commit_subject () { error_message () { (cd clone && - test_must_fail git rev-parse --verify "$@") + test_must_fail git rev-parse --verify "$@" 2>../error) } test_expect_success '@{upstream} resolves to correct full name' ' - test refs/remotes/origin/master = "$(full_name @{upstream})" + test refs/remotes/origin/master = "$(full_name @{upstream})" && + test refs/remotes/origin/master = "$(full_name @{UPSTREAM})" && + test refs/remotes/origin/master = "$(full_name @{UpSTReam})" ' test_expect_success '@{u} resolves to correct full name' ' - test refs/remotes/origin/master = "$(full_name @{u})" + test refs/remotes/origin/master = "$(full_name @{u})" && + test refs/remotes/origin/master = "$(full_name @{U})" ' test_expect_success 'my-side@{upstream} resolves to correct full name' ' @@ -60,6 +63,8 @@ test_expect_success 'my-side@{upstream} resolves to correct full name' ' test_expect_success 'upstream of branch with @ in middle' ' full_name fun@ny@{u} >actual && echo refs/remotes/origin/side >expect && + test_cmp expect actual && + full_name fun@ny@{U} >actual && test_cmp expect actual ' @@ -96,12 +101,14 @@ test_expect_success 'not-tracking@{u} fails' ' test_expect_success '<branch>@{u}@{1} resolves correctly' ' test_commit 6 && (cd clone && git fetch) && - test 5 = $(commit_subject my-side@{u}@{1}) + test 5 = $(commit_subject my-side@{u}@{1}) && + test 5 = $(commit_subject my-side@{U}@{1}) ' test_expect_success '@{u} without specifying branch fails on a detached HEAD' ' git checkout HEAD^0 && - test_must_fail git rev-parse @{u} + test_must_fail git rev-parse @{u} && + test_must_fail git rev-parse @{U} ' test_expect_success 'checkout -b new my-side@{u} forks from the same' ' @@ -152,8 +159,8 @@ test_expect_success 'branch@{u} error message when no upstream' ' cat >expect <<-EOF && fatal: no upstream configured for branch ${sq}non-tracking${sq} EOF - error_message non-tracking@{u} 2>actual && - test_i18ncmp expect actual + error_message non-tracking@{u} && + test_i18ncmp expect error ' test_expect_success '@{u} error message when no upstream' ' @@ -168,8 +175,8 @@ test_expect_success 'branch@{u} error message with misspelt branch' ' cat >expect <<-EOF && fatal: no such branch: ${sq}no-such-branch${sq} EOF - error_message no-such-branch@{u} 2>actual && - test_i18ncmp expect actual + error_message no-such-branch@{u} && + test_i18ncmp expect error ' test_expect_success '@{u} error message when not on a branch' ' @@ -185,8 +192,8 @@ test_expect_success 'branch@{u} error message if upstream branch not fetched' ' cat >expect <<-EOF && fatal: upstream branch ${sq}refs/heads/side${sq} not stored as a remote-tracking branch EOF - error_message bad-upstream@{u} 2>actual && - test_i18ncmp expect actual + error_message bad-upstream@{u} && + test_i18ncmp expect error ' test_expect_success 'pull works when tracking a local branch' ' @@ -202,8 +209,9 @@ test_expect_success '@{u} works when tracking a local branch' ' test refs/heads/master = "$(full_name @{u})" ' +commit=$(git rev-parse HEAD) cat >expect <<EOF -commit 8f489d01d0cc65c3b0f09504ec50b5ed02a70bd5 +commit $commit Reflog: master@{0} (C O Mitter <committer@example.com>) Reflog message: branch: Created from HEAD Author: A U Thor <author@example.com> @@ -217,7 +225,7 @@ test_expect_success 'log -g other@{u}' ' ' cat >expect <<EOF -commit 8f489d01d0cc65c3b0f09504ec50b5ed02a70bd5 +commit $commit Reflog: master@{Thu Apr 7 15:17:13 2005 -0700} (C O Mitter <committer@example.com>) Reflog message: branch: Created from HEAD Author: A U Thor <author@example.com> diff --git a/t/t1508-at-combinations.sh b/t/t1508-at-combinations.sh index 078e1195df..4a9964e9dc 100755 --- a/t/t1508-at-combinations.sh +++ b/t/t1508-at-combinations.sh @@ -35,7 +35,10 @@ test_expect_success 'setup' ' git checkout -b upstream-branch && test_commit upstream-one && test_commit upstream-two && - git checkout -b @/at-test && + if test_have_prereq !MINGW + then + git checkout -b @/at-test + fi && git checkout -b @@/at-test && git checkout -b @at-test && git checkout -b old-branch && @@ -64,6 +67,7 @@ check "@{-1}@{u}@{1}" commit master-one check "@" commit new-two check "@@{u}" ref refs/heads/upstream-branch check "@@/at-test" ref refs/heads/@@/at-test +test_have_prereq MINGW || check "@/at-test" ref refs/heads/@/at-test check "@at-test" ref refs/heads/@at-test nonsense "@{u}@{-1}" diff --git a/t/t1509-root-worktree.sh b/t/t1509-root-work-tree.sh index b6977d4b39..553a3f601b 100755 --- a/t/t1509-root-worktree.sh +++ b/t/t1509-root-work-tree.sh @@ -125,7 +125,7 @@ fi ONE_SHA1=d00491fd7e5bb6fa28c517a0bb32b8b506539d4d test_expect_success 'setup' ' - rm -rf /foo + rm -rf /foo && mkdir /foo && mkdir /foo/bar && echo 1 > /foo/foome && @@ -218,7 +218,7 @@ unset GIT_WORK_TREE test_expect_success 'go to /' 'cd /' test_expect_success 'setup' ' - rm -rf /.git + rm -rf /.git && echo "Initialized empty Git repository in /.git/" > expected && git init > result && test_cmp expected result @@ -241,8 +241,8 @@ say "auto bare gitdir" # DESTROYYYYY!!!!! test_expect_success 'setup' ' - rm -rf /refs /objects /info /hooks - rm /* + rm -rf /refs /objects /info /hooks && + rm -f /expected /ls.expected /me /result && cd / && echo "Initialized empty Git repository in /" > expected && git init --bare > result && diff --git a/t/t1510-repo-setup.sh b/t/t1510-repo-setup.sh index 13ae12dfa7..e6854b828e 100755 --- a/t/t1510-repo-setup.sh +++ b/t/t1510-repo-setup.sh @@ -39,6 +39,10 @@ A few rules for repo setup: 11. When user's cwd is outside worktree, cwd remains unchanged, prefix is NULL. " + +# This test heavily relies on the standard error of nested function calls. +test_untraceable=UnfortunatelyYes + . ./test-lib.sh here=$(pwd) diff --git a/t/t1511-rev-parse-caret.sh b/t/t1511-rev-parse-caret.sh index 15973f2094..e0a49a651f 100755 --- a/t/t1511-rev-parse-caret.sh +++ b/t/t1511-rev-parse-caret.sh @@ -6,11 +6,11 @@ test_description='tests for ref^{stuff}' test_expect_success 'setup' ' echo blob >a-blob && - git tag -a -m blob blob-tag `git hash-object -w a-blob` && + git tag -a -m blob blob-tag $(git hash-object -w a-blob) && mkdir a-tree && echo moreblobs >a-tree/another-blob && git add . && - TREE_SHA1=`git write-tree` && + TREE_SHA1=$(git write-tree) && git tag -a -m tree tree-tag "$TREE_SHA1" && git commit -m Initial && git tag -a -m commit commit-tag && @@ -18,7 +18,18 @@ test_expect_success 'setup' ' git checkout master && echo modified >>a-blob && git add -u && - git commit -m Modified + git commit -m Modified && + git branch modref && + echo changed! >>a-blob && + git add -u && + git commit -m !Exp && + git branch expref && + echo changed >>a-blob && + git add -u && + git commit -m Changed && + echo changed-again >>a-blob && + git add -u && + git commit -m Changed-again ' test_expect_success 'ref^{non-existent}' ' @@ -77,4 +88,44 @@ test_expect_success 'ref^{/Initial}' ' test_cmp expected actual ' +test_expect_success 'ref^{/!Exp}' ' + test_must_fail git rev-parse master^{/!Exp} +' + +test_expect_success 'ref^{/!}' ' + test_must_fail git rev-parse master^{/!} +' + +test_expect_success 'ref^{/!!Exp}' ' + git rev-parse expref >expected && + git rev-parse master^{/!!Exp} >actual && + test_cmp expected actual +' + +test_expect_success 'ref^{/!-}' ' + test_must_fail git rev-parse master^{/!-} +' + +test_expect_success 'ref^{/!-.}' ' + test_must_fail git rev-parse master^{/!-.} +' + +test_expect_success 'ref^{/!-non-existent}' ' + git rev-parse master >expected && + git rev-parse master^{/!-non-existent} >actual && + test_cmp expected actual +' + +test_expect_success 'ref^{/!-Changed}' ' + git rev-parse expref >expected && + git rev-parse master^{/!-Changed} >actual && + test_cmp expected actual +' + +test_expect_success 'ref^{/!-!Exp}' ' + git rev-parse modref >expected && + git rev-parse expref^{/!-!Exp} >actual && + test_cmp expected actual +' + test_done diff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh index 4a155c8d09..711704ba5a 100755 --- a/t/t1512-rev-parse-disambiguation.sh +++ b/t/t1512-rev-parse-disambiguation.sh @@ -42,7 +42,7 @@ test_expect_success 'blob and tree' ' test_expect_success 'warn ambiguity when no candidate matches type hint' ' test_must_fail git rev-parse --verify 000000000^{commit} 2>actual && - grep "short SHA1 000000000 is ambiguous" actual + test_i18ngrep "short SHA1 000000000 is ambiguous" actual ' test_expect_success 'disambiguate tree-ish' ' @@ -264,6 +264,13 @@ test_expect_success 'ambiguous commit-ish' ' test_must_fail git log 000000000... ' +# There are three objects with this prefix: a blob, a tree, and a tag. We know +# the blob will not pass as a treeish, but the tree and tag should (and thus +# cause an error). +test_expect_success 'ambiguous tags peel to treeish' ' + test_must_fail git rev-parse 0000000000f^{tree} +' + test_expect_success 'rev-parse --disambiguate' ' # The test creates 16 objects that share the prefix and two # commits created by commit-tree in earlier tests share a @@ -273,22 +280,85 @@ test_expect_success 'rev-parse --disambiguate' ' test "$(sed -e "s/^\(.........\).*/\1/" actual | sort -u)" = 000000000 ' +test_expect_success 'rev-parse --disambiguate drops duplicates' ' + git rev-parse --disambiguate=000000000 >expect && + git pack-objects .git/objects/pack/pack <expect && + git rev-parse --disambiguate=000000000 >actual && + test_cmp expect actual +' + test_expect_success 'ambiguous 40-hex ref' ' TREE=$(git mktree </dev/null) && - REF=`git rev-parse HEAD` && + REF=$(git rev-parse HEAD) && VAL=$(git commit-tree $TREE </dev/null) && git update-ref refs/heads/$REF $VAL && - test `git rev-parse $REF 2>err` = $REF && + test $(git rev-parse $REF 2>err) = $REF && grep "refname.*${REF}.*ambiguous" err ' test_expect_success 'ambiguous short sha1 ref' ' TREE=$(git mktree </dev/null) && - REF=`git rev-parse --short HEAD` && + REF=$(git rev-parse --short HEAD) && VAL=$(git commit-tree $TREE </dev/null) && git update-ref refs/heads/$REF $VAL && - test `git rev-parse $REF 2>err` = $VAL && + test $(git rev-parse $REF 2>err) = $VAL && grep "refname.*${REF}.*ambiguous" err ' +test_expect_success C_LOCALE_OUTPUT 'ambiguity errors are not repeated (raw)' ' + test_must_fail git rev-parse 00000 2>stderr && + grep "is ambiguous" stderr >errors && + test_line_count = 1 errors +' + +test_expect_success C_LOCALE_OUTPUT 'ambiguity errors are not repeated (treeish)' ' + test_must_fail git rev-parse 00000:foo 2>stderr && + grep "is ambiguous" stderr >errors && + test_line_count = 1 errors +' + +test_expect_success C_LOCALE_OUTPUT 'ambiguity errors are not repeated (peel)' ' + test_must_fail git rev-parse 00000^{commit} 2>stderr && + grep "is ambiguous" stderr >errors && + test_line_count = 1 errors +' + +test_expect_success C_LOCALE_OUTPUT 'ambiguity hints' ' + test_must_fail git rev-parse 000000000 2>stderr && + grep ^hint: stderr >hints && + # 16 candidates, plus one intro line + test_line_count = 17 hints +' + +test_expect_success C_LOCALE_OUTPUT 'ambiguity hints respect type' ' + test_must_fail git rev-parse 000000000^{commit} 2>stderr && + grep ^hint: stderr >hints && + # 5 commits, 1 tag (which is a commitish), plus intro line + test_line_count = 7 hints +' + +test_expect_success C_LOCALE_OUTPUT 'failed type-selector still shows hint' ' + # these two blobs share the same prefix "ee3d", but neither + # will pass for a commit + echo 851 | git hash-object --stdin -w && + echo 872 | git hash-object --stdin -w && + test_must_fail git rev-parse ee3d^{commit} 2>stderr && + grep ^hint: stderr >hints && + test_line_count = 3 hints +' + +test_expect_success 'core.disambiguate config can prefer types' ' + # ambiguous between tree and tag + sha1=0000000000f && + test_must_fail git rev-parse $sha1 && + git rev-parse $sha1^{commit} && + git -c core.disambiguate=committish rev-parse $sha1 +' + +test_expect_success 'core.disambiguate does not override context' ' + # treeish ambiguous between tag and tree + test_must_fail \ + git -c core.disambiguate=committish rev-parse $sha1^{tree} +' + test_done diff --git a/t/t1514-rev-parse-push.sh b/t/t1514-rev-parse-push.sh index 7214f5b33f..788cc91e45 100755 --- a/t/t1514-rev-parse-push.sh +++ b/t/t1514-rev-parse-push.sh @@ -24,12 +24,16 @@ test_expect_success 'setup' ' test_expect_success '@{push} with default=nothing' ' test_config push.default nothing && - test_must_fail git rev-parse master@{push} + test_must_fail git rev-parse master@{push} && + test_must_fail git rev-parse master@{PUSH} && + test_must_fail git rev-parse master@{PuSH} ' test_expect_success '@{push} with default=simple' ' test_config push.default simple && - resolve master@{push} refs/remotes/origin/master + resolve master@{push} refs/remotes/origin/master && + resolve master@{PUSH} refs/remotes/origin/master && + resolve master@{pUSh} refs/remotes/origin/master ' test_expect_success 'triangular @{push} fails with default=simple' ' @@ -60,4 +64,10 @@ test_expect_success '@{push} with push refspecs' ' resolve topic@{push} refs/remotes/origin/magic/topic ' +test_expect_success 'resolving @{push} fails with a detached HEAD' ' + git checkout HEAD^0 && + test_when_finished "git checkout -" && + test_must_fail git rev-parse @{push} +' + test_done diff --git a/t/t1515-rev-parse-outside-repo.sh b/t/t1515-rev-parse-outside-repo.sh new file mode 100755 index 0000000000..3ec2971ee5 --- /dev/null +++ b/t/t1515-rev-parse-outside-repo.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +test_description='check that certain rev-parse options work outside repo' +. ./test-lib.sh + +test_expect_success 'set up non-repo directory' ' + GIT_CEILING_DIRECTORIES=$(pwd) && + export GIT_CEILING_DIRECTORIES && + mkdir non-repo && + cd non-repo && + # confirm that git does not find a repo + test_must_fail git rev-parse --git-dir +' + +# Rather than directly test the output of sq-quote directly, +# make sure the shell can read back a tricky case, since +# that's what we really care about anyway. +tricky="really tricky with \\ and \" and '" +dump_args () { + for i in "$@"; do + echo "arg: $i" + done +} +test_expect_success 'rev-parse --sq-quote' ' + dump_args "$tricky" easy >expect && + eval "dump_args $(git rev-parse --sq-quote "$tricky" easy)" >actual && + test_cmp expect actual +' + +test_expect_success 'rev-parse --local-env-vars' ' + git rev-parse --local-env-vars >actual && + # we do not want to depend on the complete list here, + # so just look for something plausible + grep ^GIT_DIR actual +' + +test_expect_success 'rev-parse --resolve-git-dir' ' + git init --separate-git-dir repo dir && + test_must_fail git rev-parse --resolve-git-dir . && + echo "$(pwd)/repo" >expect && + git rev-parse --resolve-git-dir dir/.git >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t1600-index.sh b/t/t1600-index.sh index 079d241145..c4422312f4 100755 --- a/t/t1600-index.sh +++ b/t/t1600-index.sh @@ -68,7 +68,7 @@ test_expect_success 'GIT_INDEX_VERSION takes precedence over config' ' git config --add index.version 2 && git add a 2>&1 && echo 4 >expect && - test-index-version <.git/index >actual && + test-tool index-version <.git/index >actual && test_cmp expect actual ) ' diff --git a/t/t1601-index-bogus.sh b/t/t1601-index-bogus.sh new file mode 100755 index 0000000000..73cc9323cd --- /dev/null +++ b/t/t1601-index-bogus.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +test_description='test handling of bogus index entries' +. ./test-lib.sh + +test_expect_success 'create tree with null sha1' ' + tree=$(printf "160000 commit $_z40\\tbroken\\n" | git mktree) +' + +test_expect_success 'read-tree refuses to read null sha1' ' + test_must_fail git read-tree $tree +' + +test_expect_success 'GIT_ALLOW_NULL_SHA1 overrides refusal' ' + GIT_ALLOW_NULL_SHA1=1 git read-tree $tree +' + +test_expect_success 'git write-tree refuses to write null sha1' ' + test_must_fail git write-tree +' + +test_done diff --git a/t/t1700-split-index.sh b/t/t1700-split-index.sh index 193d55c3f4..e4f4c4df4e 100755 --- a/t/t1700-split-index.sh +++ b/t/t1700-split-index.sh @@ -6,11 +6,13 @@ test_description='split index mode tests' # We need total control of index splitting here sane_unset GIT_TEST_SPLIT_INDEX +sane_unset GIT_FSMONITOR_TEST test_expect_success 'enable split index' ' + git config splitIndex.maxPercentChange 100 && git update-index --split-index && - test-dump-split-index .git/index >actual && - indexversion=$(test-index-version <.git/index) && + test-tool dump-split-index .git/index >actual && + indexversion=$(test-tool index-version <.git/index) && if test "$indexversion" = "4" then own=432ef4b63f32193984f339431fd50ca796493569 @@ -19,12 +21,12 @@ test_expect_success 'enable split index' ' own=8299b0bcd1ac364e5f1d7768efb62fa2da79a339 base=39d890139ee5356c7ef572216cebcd27aa41f9df fi && - cat >expect <<EOF && -own $own -base $base -replacements: -deletions: -EOF + cat >expect <<-EOF && + own $own + base $base + replacements: + deletions: + EOF test_cmp expect actual ' @@ -32,51 +34,51 @@ test_expect_success 'add one file' ' : >one && git update-index --add one && git ls-files --stage >ls-files.actual && - cat >ls-files.expect <<EOF && -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one -EOF + cat >ls-files.expect <<-EOF && + 100644 $EMPTY_BLOB 0 one + EOF test_cmp ls-files.expect ls-files.actual && - test-dump-split-index .git/index | sed "/^own/d" >actual && - cat >expect <<EOF && -base $base -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one -replacements: -deletions: -EOF + test-tool dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + base $base + 100644 $EMPTY_BLOB 0 one + replacements: + deletions: + EOF test_cmp expect actual ' test_expect_success 'disable split index' ' git update-index --no-split-index && git ls-files --stage >ls-files.actual && - cat >ls-files.expect <<EOF && -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one -EOF + cat >ls-files.expect <<-EOF && + 100644 $EMPTY_BLOB 0 one + EOF test_cmp ls-files.expect ls-files.actual && - BASE=`test-dump-split-index .git/index | grep "^own" | sed "s/own/base/"` && - test-dump-split-index .git/index | sed "/^own/d" >actual && - cat >expect <<EOF && -not a split index -EOF + BASE=$(test-tool dump-split-index .git/index | grep "^own" | sed "s/own/base/") && + test-tool dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + not a split index + EOF test_cmp expect actual ' test_expect_success 'enable split index again, "one" now belongs to base index"' ' git update-index --split-index && git ls-files --stage >ls-files.actual && - cat >ls-files.expect <<EOF && -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one -EOF + cat >ls-files.expect <<-EOF && + 100644 $EMPTY_BLOB 0 one + EOF test_cmp ls-files.expect ls-files.actual && - test-dump-split-index .git/index | sed "/^own/d" >actual && - cat >expect <<EOF && -$BASE -replacements: -deletions: -EOF + test-tool dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + $BASE + replacements: + deletions: + EOF test_cmp expect actual ' @@ -84,18 +86,18 @@ test_expect_success 'modify original file, base index untouched' ' echo modified >one && git update-index one && git ls-files --stage >ls-files.actual && - cat >ls-files.expect <<EOF && -100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0 one -EOF + cat >ls-files.expect <<-EOF && + 100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0 one + EOF test_cmp ls-files.expect ls-files.actual && - test-dump-split-index .git/index | sed "/^own/d" >actual && - q_to_tab >expect <<EOF && -$BASE -100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0Q -replacements: 0 -deletions: -EOF + test-tool dump-split-index .git/index | sed "/^own/d" >actual && + q_to_tab >expect <<-EOF && + $BASE + 100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0Q + replacements: 0 + deletions: + EOF test_cmp expect actual ' @@ -103,54 +105,54 @@ test_expect_success 'add another file, which stays index' ' : >two && git update-index --add two && git ls-files --stage >ls-files.actual && - cat >ls-files.expect <<EOF && -100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0 one -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 two -EOF + cat >ls-files.expect <<-EOF && + 100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0 one + 100644 $EMPTY_BLOB 0 two + EOF test_cmp ls-files.expect ls-files.actual && - test-dump-split-index .git/index | sed "/^own/d" >actual && - q_to_tab >expect <<EOF && -$BASE -100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0Q -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 two -replacements: 0 -deletions: -EOF + test-tool dump-split-index .git/index | sed "/^own/d" >actual && + q_to_tab >expect <<-EOF && + $BASE + 100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0Q + 100644 $EMPTY_BLOB 0 two + replacements: 0 + deletions: + EOF test_cmp expect actual ' test_expect_success 'remove file not in base index' ' git update-index --force-remove two && git ls-files --stage >ls-files.actual && - cat >ls-files.expect <<EOF && -100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0 one -EOF + cat >ls-files.expect <<-EOF && + 100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0 one + EOF test_cmp ls-files.expect ls-files.actual && - test-dump-split-index .git/index | sed "/^own/d" >actual && - q_to_tab >expect <<EOF && -$BASE -100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0Q -replacements: 0 -deletions: -EOF + test-tool dump-split-index .git/index | sed "/^own/d" >actual && + q_to_tab >expect <<-EOF && + $BASE + 100644 2e0996000b7e9019eabcad29391bf0f5c7702f0b 0Q + replacements: 0 + deletions: + EOF test_cmp expect actual ' test_expect_success 'remove file in base index' ' git update-index --force-remove one && git ls-files --stage >ls-files.actual && - cat >ls-files.expect <<EOF && -EOF + cat >ls-files.expect <<-EOF && + EOF test_cmp ls-files.expect ls-files.actual && - test-dump-split-index .git/index | sed "/^own/d" >actual && - cat >expect <<EOF && -$BASE -replacements: -deletions: 0 -EOF + test-tool dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + $BASE + replacements: + deletions: 0 + EOF test_cmp expect actual ' @@ -158,18 +160,18 @@ test_expect_success 'add original file back' ' : >one && git update-index --add one && git ls-files --stage >ls-files.actual && - cat >ls-files.expect <<EOF && -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one -EOF + cat >ls-files.expect <<-EOF && + 100644 $EMPTY_BLOB 0 one + EOF test_cmp ls-files.expect ls-files.actual && - test-dump-split-index .git/index | sed "/^own/d" >actual && - cat >expect <<EOF && -$BASE -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one -replacements: -deletions: 0 -EOF + test-tool dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + $BASE + 100644 $EMPTY_BLOB 0 one + replacements: + deletions: 0 + EOF test_cmp expect actual ' @@ -177,27 +179,264 @@ test_expect_success 'add new file' ' : >two && git update-index --add two && git ls-files --stage >actual && - cat >expect <<EOF && -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 two -EOF + cat >expect <<-EOF && + 100644 $EMPTY_BLOB 0 one + 100644 $EMPTY_BLOB 0 two + EOF test_cmp expect actual ' test_expect_success 'unify index, two files remain' ' git update-index --no-split-index && git ls-files --stage >ls-files.actual && - cat >ls-files.expect <<EOF && -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one -100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 two -EOF + cat >ls-files.expect <<-EOF && + 100644 $EMPTY_BLOB 0 one + 100644 $EMPTY_BLOB 0 two + EOF test_cmp ls-files.expect ls-files.actual && - test-dump-split-index .git/index | sed "/^own/d" >actual && - cat >expect <<EOF && -not a split index -EOF + test-tool dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + not a split index + EOF + test_cmp expect actual +' + +test_expect_success 'rev-parse --shared-index-path' ' + test_create_repo split-index && + ( + cd split-index && + git update-index --split-index && + echo .git/sharedindex* >expect && + git rev-parse --shared-index-path >actual && + test_cmp expect actual && + mkdir subdirectory && + cd subdirectory && + echo ../.git/sharedindex* >expect && + git rev-parse --shared-index-path >actual && + test_cmp expect actual + ) +' + +test_expect_success 'set core.splitIndex config variable to true' ' + git config core.splitIndex true && + : >three && + git update-index --add three && + git ls-files --stage >ls-files.actual && + cat >ls-files.expect <<-EOF && + 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one + 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 three + 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 two + EOF + test_cmp ls-files.expect ls-files.actual && + BASE=$(test-tool dump-split-index .git/index | grep "^base") && + test-tool dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + $BASE + replacements: + deletions: + EOF test_cmp expect actual ' +test_expect_success 'set core.splitIndex config variable to false' ' + git config core.splitIndex false && + git update-index --force-remove three && + git ls-files --stage >ls-files.actual && + cat >ls-files.expect <<-EOF && + 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 one + 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 two + EOF + test_cmp ls-files.expect ls-files.actual && + test-tool dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + not a split index + EOF + test_cmp expect actual +' + +test_expect_success 'set core.splitIndex config variable to true' ' + git config core.splitIndex true && + : >three && + git update-index --add three && + BASE=$(test-tool dump-split-index .git/index | grep "^base") && + test-tool dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + $BASE + replacements: + deletions: + EOF + test_cmp expect actual && + : >four && + git update-index --add four && + test-tool dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + $BASE + 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 four + replacements: + deletions: + EOF + test_cmp expect actual +' + +test_expect_success 'check behavior with splitIndex.maxPercentChange unset' ' + git config --unset splitIndex.maxPercentChange && + : >five && + git update-index --add five && + BASE=$(test-tool dump-split-index .git/index | grep "^base") && + test-tool dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + $BASE + replacements: + deletions: + EOF + test_cmp expect actual && + : >six && + git update-index --add six && + test-tool dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + $BASE + 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 six + replacements: + deletions: + EOF + test_cmp expect actual +' + +test_expect_success 'check splitIndex.maxPercentChange set to 0' ' + git config splitIndex.maxPercentChange 0 && + : >seven && + git update-index --add seven && + BASE=$(test-tool dump-split-index .git/index | grep "^base") && + test-tool dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + $BASE + replacements: + deletions: + EOF + test_cmp expect actual && + : >eight && + git update-index --add eight && + BASE=$(test-tool dump-split-index .git/index | grep "^base") && + test-tool dump-split-index .git/index | sed "/^own/d" >actual && + cat >expect <<-EOF && + $BASE + replacements: + deletions: + EOF + test_cmp expect actual +' + +test_expect_success 'shared index files expire after 2 weeks by default' ' + : >ten && + git update-index --add ten && + test $(ls .git/sharedindex.* | wc -l) -gt 2 && + just_under_2_weeks_ago=$((5-14*86400)) && + test-tool chmtime =$just_under_2_weeks_ago .git/sharedindex.* && + : >eleven && + git update-index --add eleven && + test $(ls .git/sharedindex.* | wc -l) -gt 2 && + just_over_2_weeks_ago=$((-1-14*86400)) && + test-tool chmtime =$just_over_2_weeks_ago .git/sharedindex.* && + : >twelve && + git update-index --add twelve && + test $(ls .git/sharedindex.* | wc -l) -le 2 +' + +test_expect_success 'check splitIndex.sharedIndexExpire set to 16 days' ' + git config splitIndex.sharedIndexExpire "16.days.ago" && + test-tool chmtime =$just_over_2_weeks_ago .git/sharedindex.* && + : >thirteen && + git update-index --add thirteen && + test $(ls .git/sharedindex.* | wc -l) -gt 2 && + just_over_16_days_ago=$((-1-16*86400)) && + test-tool chmtime =$just_over_16_days_ago .git/sharedindex.* && + : >fourteen && + git update-index --add fourteen && + test $(ls .git/sharedindex.* | wc -l) -le 2 +' + +test_expect_success 'check splitIndex.sharedIndexExpire set to "never" and "now"' ' + git config splitIndex.sharedIndexExpire never && + just_10_years_ago=$((-365*10*86400)) && + test-tool chmtime =$just_10_years_ago .git/sharedindex.* && + : >fifteen && + git update-index --add fifteen && + test $(ls .git/sharedindex.* | wc -l) -gt 2 && + git config splitIndex.sharedIndexExpire now && + just_1_second_ago=-1 && + test-tool chmtime =$just_1_second_ago .git/sharedindex.* && + : >sixteen && + git update-index --add sixteen && + test $(ls .git/sharedindex.* | wc -l) -le 2 +' + +while read -r mode modebits +do + test_expect_success POSIXPERM "split index respects core.sharedrepository $mode" ' + # Remove existing shared index files + git config core.splitIndex false && + git update-index --force-remove one && + rm -f .git/sharedindex.* && + # Create one new shared index file + git config core.sharedrepository "$mode" && + git config core.splitIndex true && + : >one && + git update-index --add one && + echo "$modebits" >expect && + test_modebits .git/index >actual && + test_cmp expect actual && + shared=$(ls .git/sharedindex.*) && + case "$shared" in + *" "*) + # we have more than one??? + false ;; + *) + test_modebits "$shared" >actual && + test_cmp expect actual ;; + esac + ' +done <<\EOF +0666 -rw-rw-rw- +0642 -rw-r---w- +EOF + +test_expect_success POSIXPERM,SANITY 'graceful handling when splitting index is not allowed' ' + test_create_repo ro && + ( + cd ro && + test_commit initial && + git update-index --split-index && + test -f .git/sharedindex.* + ) && + cp ro/.git/index new-index && + test_when_finished "chmod u+w ro/.git" && + chmod u-w ro/.git && + GIT_INDEX_FILE="$(pwd)/new-index" git -C ro update-index --split-index && + chmod u+w ro/.git && + rm ro/.git/sharedindex.* && + GIT_INDEX_FILE=new-index git ls-files >actual && + echo initial.t >expected && + test_cmp expected actual +' + +test_expect_success 'writing split index with null sha1 does not write cache tree' ' + git config core.splitIndex true && + git config splitIndex.maxPercentChange 0 && + git commit -m "commit" && + { + git ls-tree HEAD && + printf "160000 commit $_z40\\tbroken\\n" + } >broken-tree && + echo "add broken entry" >msg && + + tree=$(git mktree <broken-tree) && + test_tick && + commit=$(git commit-tree $tree -p HEAD <msg) && + git update-ref HEAD "$commit" && + GIT_ALLOW_NULL_SHA1=1 git reset --hard && + (test-tool dump-cache-tree >cache-tree.out || true) && + test_line_count = 0 cache-tree.out +' + test_done diff --git a/t/t2010-checkout-ambiguous.sh b/t/t2010-checkout-ambiguous.sh index 87bdf9c96b..2e47fe01cf 100755 --- a/t/t2010-checkout-ambiguous.sh +++ b/t/t2010-checkout-ambiguous.sh @@ -41,6 +41,15 @@ test_expect_success 'check ambiguity' ' test_must_fail git checkout world all ' +test_expect_success 'check ambiguity in subdir' ' + mkdir sub && + # not ambiguous because sub/world does not exist + git -C sub checkout world ../all && + echo hello >sub/world && + # ambiguous because sub/world does exist + test_must_fail git -C sub checkout world ../all +' + test_expect_success 'disambiguate checking out from a tree-ish' ' echo bye > world && git checkout world -- world && @@ -49,7 +58,7 @@ test_expect_success 'disambiguate checking out from a tree-ish' ' test_expect_success 'accurate error message with more than one ref' ' test_must_fail git checkout HEAD master -- 2>actual && - grep 2 actual && + test_i18ngrep 2 actual && test_i18ngrep "one reference expected, 2 given" actual ' diff --git a/t/t2011-checkout-invalid-head.sh b/t/t2011-checkout-invalid-head.sh index 300f8bf25c..c5501b008c 100755 --- a/t/t2011-checkout-invalid-head.sh +++ b/t/t2011-checkout-invalid-head.sh @@ -19,4 +19,43 @@ test_expect_success 'checkout master from invalid HEAD' ' git checkout master -- ' +test_expect_success 'checkout notices failure to lock HEAD' ' + test_when_finished "rm -f .git/HEAD.lock" && + >.git/HEAD.lock && + test_must_fail git checkout -b other +' + +test_expect_success 'create ref directory/file conflict scenario' ' + git update-ref refs/heads/outer/inner master && + + # do not rely on symbolic-ref to get a known state, + # as it may use the same code we are testing + reset_to_df () { + echo "ref: refs/heads/outer" >.git/HEAD + } +' + +test_expect_success 'checkout away from d/f HEAD (unpacked, to branch)' ' + reset_to_df && + git checkout master +' + +test_expect_success 'checkout away from d/f HEAD (unpacked, to detached)' ' + reset_to_df && + git checkout --detach master +' + +test_expect_success 'pack refs' ' + git pack-refs --all --prune +' + +test_expect_success 'checkout away from d/f HEAD (packed, to branch)' ' + reset_to_df && + git checkout master +' + +test_expect_success 'checkout away from d/f HEAD (packed, to detached)' ' + reset_to_df && + git checkout --detach master +' test_done diff --git a/t/t2013-checkout-submodule.sh b/t/t2013-checkout-submodule.sh index 6847f75822..6ef15738e4 100755 --- a/t/t2013-checkout-submodule.sh +++ b/t/t2013-checkout-submodule.sh @@ -63,6 +63,11 @@ test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .git/ ! test -s actual ' +KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1 +test_submodule_switch_recursing_with_args "checkout" + +test_submodule_forced_switch_recursing_with_args "checkout -f" + test_submodule_switch "git checkout" test_submodule_forced_switch "git checkout -f" diff --git a/t/t2018-checkout-branch.sh b/t/t2018-checkout-branch.sh index 2741262369..2131fb2a56 100755 --- a/t/t2018-checkout-branch.sh +++ b/t/t2018-checkout-branch.sh @@ -124,7 +124,7 @@ test_expect_success 'checkout -b to @{-1} fails with the right branch name' ' git checkout branch2 && echo >expect "fatal: A branch named '\''branch1'\'' already exists." && test_must_fail git checkout -b @{-1} 2>actual && - test_cmp expect actual + test_i18ncmp expect actual ' test_expect_success 'checkout -B to an existing branch resets branch to HEAD' ' diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh index 5d68729d7a..1fa670625c 100755 --- a/t/t2020-checkout-detach.sh +++ b/t/t2020-checkout-detach.sh @@ -163,4 +163,158 @@ test_expect_success 'tracking count is accurate after orphan check' ' test_i18ncmp expect stdout ' +test_expect_success 'no advice given for explicit detached head state' ' + # baseline + test_config advice.detachedHead true && + git checkout child && git checkout HEAD^0 >expect.advice 2>&1 && + test_config advice.detachedHead false && + git checkout child && git checkout HEAD^0 >expect.no-advice 2>&1 && + test_unconfig advice.detachedHead && + # without configuration, the advice.* variables default to true + git checkout child && git checkout HEAD^0 >actual 2>&1 && + test_cmp expect.advice actual && + + # with explicit --detach + # no configuration + test_unconfig advice.detachedHead && + git checkout child && git checkout --detach HEAD^0 >actual 2>&1 && + test_cmp expect.no-advice actual && + + # explicitly decline advice + test_config advice.detachedHead false && + git checkout child && git checkout --detach HEAD^0 >actual 2>&1 && + test_cmp expect.no-advice actual +' + +# Detached HEAD tests for GIT_PRINT_SHA1_ELLIPSIS (new format) +test_expect_success 'describe_detached_head prints no SHA-1 ellipsis when not asked to' " + + commit=$(git rev-parse --short=12 master^) && + commit2=$(git rev-parse --short=12 master~2) && + commit3=$(git rev-parse --short=12 master~3) && + + # The first detach operation is more chatty than the following ones. + cat >1st_detach <<-EOF && + Note: checking out 'HEAD^'. + + You are in 'detached HEAD' state. You can look around, make experimental + changes and commit them, and you can discard any commits you make in this + state without impacting any branches by performing another checkout. + + If you want to create a new branch to retain commits you create, you may + do so (now or later) by using -b with the checkout command again. Example: + + git checkout -b <new-branch-name> + + HEAD is now at \$commit three + EOF + + # The remaining ones just show info about previous and current HEADs. + cat >2nd_detach <<-EOF && + Previous HEAD position was \$commit three + HEAD is now at \$commit2 two + EOF + + cat >3rd_detach <<-EOF && + Previous HEAD position was \$commit2 two + HEAD is now at \$commit3 one + EOF + + reset && + check_not_detached && + + # Various ways of *not* asking for ellipses + + sane_unset GIT_PRINT_SHA1_ELLIPSIS && + git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 && + check_detached && + test_i18ncmp 1st_detach actual && + + GIT_PRINT_SHA1_ELLIPSIS="no" git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 && + check_detached && + test_i18ncmp 2nd_detach actual && + + GIT_PRINT_SHA1_ELLIPSIS= git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 && + check_detached && + test_i18ncmp 3rd_detach actual && + + sane_unset GIT_PRINT_SHA1_ELLIPSIS && + + # We only have four commits, but we can re-use them + reset && + check_not_detached && + + # Make no mention of the env var at all + git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 && + check_detached && + test_i18ncmp 1st_detach actual && + + GIT_PRINT_SHA1_ELLIPSIS='nope' && + git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 && + check_detached && + test_i18ncmp 2nd_detach actual && + + GIT_PRINT_SHA1_ELLIPSIS=nein && + git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 && + check_detached && + test_i18ncmp 3rd_detach actual && + + true +" + +# Detached HEAD tests for GIT_PRINT_SHA1_ELLIPSIS (old format) +test_expect_success 'describe_detached_head does print SHA-1 ellipsis when asked to' " + + commit=$(git rev-parse --short=12 master^) && + commit2=$(git rev-parse --short=12 master~2) && + commit3=$(git rev-parse --short=12 master~3) && + + # The first detach operation is more chatty than the following ones. + cat >1st_detach <<-EOF && + Note: checking out 'HEAD^'. + + You are in 'detached HEAD' state. You can look around, make experimental + changes and commit them, and you can discard any commits you make in this + state without impacting any branches by performing another checkout. + + If you want to create a new branch to retain commits you create, you may + do so (now or later) by using -b with the checkout command again. Example: + + git checkout -b <new-branch-name> + + HEAD is now at \$commit... three + EOF + + # The remaining ones just show info about previous and current HEADs. + cat >2nd_detach <<-EOF && + Previous HEAD position was \$commit... three + HEAD is now at \$commit2... two + EOF + + cat >3rd_detach <<-EOF && + Previous HEAD position was \$commit2... two + HEAD is now at \$commit3... one + EOF + + reset && + check_not_detached && + + # Various ways of asking for ellipses... + # The user can just use any kind of quoting (including none). + + GIT_PRINT_SHA1_ELLIPSIS=yes git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 && + check_detached && + test_i18ncmp 1st_detach actual && + + GIT_PRINT_SHA1_ELLIPSIS=Yes git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 && + check_detached && + test_i18ncmp 2nd_detach actual && + + GIT_PRINT_SHA1_ELLIPSIS=YES git -c 'core.abbrev=12' checkout HEAD^ >actual 2>&1 && + check_detached && + test_i18ncmp 3rd_detach actual && + + true +" + test_done diff --git a/t/t2022-checkout-paths.sh b/t/t2022-checkout-paths.sh index f46d0499bc..e74d58b9e1 100755 --- a/t/t2022-checkout-paths.sh +++ b/t/t2022-checkout-paths.sh @@ -68,13 +68,13 @@ test_expect_success 'do not touch files that are already up-to-date' ' git add file1 file2 && git commit -m base && echo modified >file1 && - test-chmtime =1000000000 file2 && + test-tool chmtime =1000000000 file2 && git update-index -q --refresh && git checkout HEAD -- file1 file2 && echo one >expect && test_cmp expect file1 && echo "1000000000 file2" >expect && - test-chmtime -v +0 file2 >actual && + test-tool chmtime -v +0 file2 >actual && test_cmp expect actual ' diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh index 468a000e4b..3e5ac81bd2 100755 --- a/t/t2024-checkout-dwim.sh +++ b/t/t2024-checkout-dwim.sh @@ -174,6 +174,18 @@ test_expect_success 'checkout of branch with a file having the same name fails' test_branch master ' +test_expect_success 'checkout of branch with a file in subdir having the same name fails' ' + git checkout -B master && + test_might_fail git branch -D spam && + + >spam && + mkdir sub && + mv spam sub/spam && + test_must_fail git -C sub checkout spam && + test_must_fail git rev-parse --verify refs/heads/spam && + test_branch master +' + test_expect_success 'checkout <branch> -- succeeds, even if a file with the same name exists' ' git checkout -B master && test_might_fail git branch -D spam && diff --git a/t/t2025-checkout-to.sh b/t/t2025-checkout-to.sh deleted file mode 100755 index f8e4df4818..0000000000 --- a/t/t2025-checkout-to.sh +++ /dev/null @@ -1,129 +0,0 @@ -#!/bin/sh - -test_description='test git checkout --to' - -. ./test-lib.sh - -test_expect_success 'setup' ' - test_commit init -' - -test_expect_success 'checkout --to not updating paths' ' - test_must_fail git checkout --to -- init.t -' - -test_expect_success 'checkout --to an existing worktree' ' - mkdir -p existing/subtree && - test_must_fail git checkout --detach --to existing master -' - -test_expect_success 'checkout --to an existing empty worktree' ' - mkdir existing_empty && - git checkout --detach --to existing_empty master -' - -test_expect_success 'checkout --to refuses to checkout locked branch' ' - test_must_fail git checkout --to zere master && - ! test -d zere && - ! test -d .git/worktrees/zere -' - -test_expect_success 'checkout --to a new worktree' ' - git rev-parse HEAD >expect && - git checkout --detach --to here master && - ( - cd here && - test_cmp ../init.t init.t && - test_must_fail git symbolic-ref HEAD && - git rev-parse HEAD >actual && - test_cmp ../expect actual && - git fsck - ) -' - -test_expect_success 'checkout --to a new worktree from a subdir' ' - ( - mkdir sub && - cd sub && - git checkout --detach --to here master && - cd here && - test_cmp ../../init.t init.t - ) -' - -test_expect_success 'checkout --to from a linked checkout' ' - ( - cd here && - git checkout --detach --to nested-here master && - cd nested-here && - git fsck - ) -' - -test_expect_success 'checkout --to a new worktree creating new branch' ' - git checkout --to there -b newmaster master && - ( - cd there && - test_cmp ../init.t init.t && - git symbolic-ref HEAD >actual && - echo refs/heads/newmaster >expect && - test_cmp expect actual && - git fsck - ) -' - -test_expect_success 'die the same branch is already checked out' ' - ( - cd here && - test_must_fail git checkout newmaster - ) -' - -test_expect_success 'not die the same branch is already checked out' ' - ( - cd here && - git checkout --ignore-other-worktrees --to anothernewmaster newmaster - ) -' - -test_expect_success 'not die on re-checking out current branch' ' - ( - cd there && - git checkout newmaster - ) -' - -test_expect_success 'checkout --to from a bare repo' ' - ( - git clone --bare . bare && - cd bare && - git checkout --to ../there2 -b bare-master master - ) -' - -test_expect_success 'checkout from a bare repo without --to' ' - ( - cd bare && - test_must_fail git checkout master - ) -' - -test_expect_success 'checkout with grafts' ' - test_when_finished rm .git/info/grafts && - test_commit abc && - SHA1=`git rev-parse HEAD` && - test_commit def && - test_commit xyz && - echo "`git rev-parse HEAD` $SHA1" >.git/info/grafts && - cat >expected <<-\EOF && - xyz - abc - EOF - git log --format=%s -2 >actual && - test_cmp expected actual && - git checkout --detach --to grafted master && - git --git-dir=grafted/.git log --format=%s -2 >actual && - test_cmp expected actual -' - -test_done diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh new file mode 100755 index 0000000000..d0d2e4f7ec --- /dev/null +++ b/t/t2025-worktree-add.sh @@ -0,0 +1,518 @@ +#!/bin/sh + +test_description='test git worktree add' + +. ./test-lib.sh + +. "$TEST_DIRECTORY"/lib-rebase.sh + +test_expect_success 'setup' ' + test_commit init +' + +test_expect_success '"add" an existing worktree' ' + mkdir -p existing/subtree && + test_must_fail git worktree add --detach existing master +' + +test_expect_success '"add" an existing empty worktree' ' + mkdir existing_empty && + git worktree add --detach existing_empty master +' + +test_expect_success '"add" using shorthand - fails when no previous branch' ' + test_must_fail git worktree add existing_short - +' + +test_expect_success '"add" using - shorthand' ' + git checkout -b newbranch && + echo hello >myworld && + git add myworld && + git commit -m myworld && + git checkout master && + git worktree add short-hand - && + echo refs/heads/newbranch >expect && + git -C short-hand rev-parse --symbolic-full-name HEAD >actual && + test_cmp expect actual +' + +test_expect_success '"add" refuses to checkout locked branch' ' + test_must_fail git worktree add zere master && + ! test -d zere && + ! test -d .git/worktrees/zere +' + +test_expect_success 'checking out paths not complaining about linked checkouts' ' + ( + cd existing_empty && + echo dirty >>init.t && + git checkout master -- init.t + ) +' + +test_expect_success '"add" worktree' ' + git rev-parse HEAD >expect && + git worktree add --detach here master && + ( + cd here && + test_cmp ../init.t init.t && + test_must_fail git symbolic-ref HEAD && + git rev-parse HEAD >actual && + test_cmp ../expect actual && + git fsck + ) +' + +test_expect_success '"add" worktree with lock' ' + git rev-parse HEAD >expect && + git worktree add --detach --lock here-with-lock master && + test -f .git/worktrees/here-with-lock/locked +' + +test_expect_success '"add" worktree from a subdir' ' + ( + mkdir sub && + cd sub && + git worktree add --detach here master && + cd here && + test_cmp ../../init.t init.t + ) +' + +test_expect_success '"add" from a linked checkout' ' + ( + cd here && + git worktree add --detach nested-here master && + cd nested-here && + git fsck + ) +' + +test_expect_success '"add" worktree creating new branch' ' + git worktree add -b newmaster there master && + ( + cd there && + test_cmp ../init.t init.t && + git symbolic-ref HEAD >actual && + echo refs/heads/newmaster >expect && + test_cmp expect actual && + git fsck + ) +' + +test_expect_success 'die the same branch is already checked out' ' + ( + cd here && + test_must_fail git checkout newmaster + ) +' + +test_expect_success SYMLINKS 'die the same branch is already checked out (symlink)' ' + head=$(git -C there rev-parse --git-path HEAD) && + ref=$(git -C there symbolic-ref HEAD) && + rm "$head" && + ln -s "$ref" "$head" && + test_must_fail git -C here checkout newmaster +' + +test_expect_success 'not die the same branch is already checked out' ' + ( + cd here && + git worktree add --force anothernewmaster newmaster + ) +' + +test_expect_success 'not die on re-checking out current branch' ' + ( + cd there && + git checkout newmaster + ) +' + +test_expect_success '"add" from a bare repo' ' + ( + git clone --bare . bare && + cd bare && + git worktree add -b bare-master ../there2 master + ) +' + +test_expect_success 'checkout from a bare repo without "add"' ' + ( + cd bare && + test_must_fail git checkout master + ) +' + +test_expect_success '"add" default branch of a bare repo' ' + ( + git clone --bare . bare2 && + cd bare2 && + git worktree add ../there3 master + ) +' + +test_expect_success 'checkout with grafts' ' + test_when_finished rm .git/info/grafts && + test_commit abc && + SHA1=$(git rev-parse HEAD) && + test_commit def && + test_commit xyz && + echo "$(git rev-parse HEAD) $SHA1" >.git/info/grafts && + cat >expected <<-\EOF && + xyz + abc + EOF + git log --format=%s -2 >actual && + test_cmp expected actual && + git worktree add --detach grafted master && + git --git-dir=grafted/.git log --format=%s -2 >actual && + test_cmp expected actual +' + +test_expect_success '"add" from relative HEAD' ' + test_commit a && + test_commit b && + test_commit c && + git rev-parse HEAD~1 >expected && + git worktree add relhead HEAD~1 && + git -C relhead rev-parse HEAD >actual && + test_cmp expected actual +' + +test_expect_success '"add -b" with <branch> omitted' ' + git worktree add -b burble flornk && + test_cmp_rev HEAD burble +' + +test_expect_success '"add --detach" with <branch> omitted' ' + git worktree add --detach fishhook && + git rev-parse HEAD >expected && + git -C fishhook rev-parse HEAD >actual && + test_cmp expected actual && + test_must_fail git -C fishhook symbolic-ref HEAD +' + +test_expect_success '"add" with <branch> omitted' ' + git worktree add wiffle/bat && + test_cmp_rev HEAD bat +' + +test_expect_success '"add" auto-vivify does not clobber existing branch' ' + test_commit c1 && + test_commit c2 && + git branch precious HEAD~1 && + test_must_fail git worktree add precious && + test_cmp_rev HEAD~1 precious && + test_path_is_missing precious +' + +test_expect_success '"add" no auto-vivify with --detach and <branch> omitted' ' + git worktree add --detach mish/mash && + test_must_fail git rev-parse mash -- && + test_must_fail git -C mish/mash symbolic-ref HEAD +' + +test_expect_success '"add" -b/-B mutually exclusive' ' + test_must_fail git worktree add -b poodle -B poodle bamboo master +' + +test_expect_success '"add" -b/--detach mutually exclusive' ' + test_must_fail git worktree add -b poodle --detach bamboo master +' + +test_expect_success '"add" -B/--detach mutually exclusive' ' + test_must_fail git worktree add -B poodle --detach bamboo master +' + +test_expect_success '"add -B" fails if the branch is checked out' ' + git rev-parse newmaster >before && + test_must_fail git worktree add -B newmaster bamboo master && + git rev-parse newmaster >after && + test_cmp before after +' + +test_expect_success 'add -B' ' + git worktree add -B poodle bamboo2 master^ && + git -C bamboo2 symbolic-ref HEAD >actual && + echo refs/heads/poodle >expected && + test_cmp expected actual && + test_cmp_rev master^ poodle +' + +test_expect_success 'local clone from linked checkout' ' + git clone --local here here-clone && + ( cd here-clone && git fsck ) +' + +test_expect_success 'local clone --shared from linked checkout' ' + git -C bare worktree add --detach ../baretree && + git clone --local --shared baretree bare-clone && + grep /bare/ bare-clone/.git/objects/info/alternates +' + +test_expect_success '"add" worktree with --no-checkout' ' + git worktree add --no-checkout -b swamp swamp && + ! test -e swamp/init.t && + git -C swamp reset --hard && + test_cmp init.t swamp/init.t +' + +test_expect_success '"add" worktree with --checkout' ' + git worktree add --checkout -b swmap2 swamp2 && + test_cmp init.t swamp2/init.t +' + +test_expect_success 'put a worktree under rebase' ' + git worktree add under-rebase && + ( + cd under-rebase && + set_fake_editor && + FAKE_LINES="edit 1" git rebase -i HEAD^ && + git worktree list | grep "under-rebase.*detached HEAD" + ) +' + +test_expect_success 'add a worktree, checking out a rebased branch' ' + test_must_fail git worktree add new-rebase under-rebase && + ! test -d new-rebase +' + +test_expect_success 'checking out a rebased branch from another worktree' ' + git worktree add new-place && + test_must_fail git -C new-place checkout under-rebase +' + +test_expect_success 'not allow to delete a branch under rebase' ' + ( + cd under-rebase && + test_must_fail git branch -D under-rebase + ) +' + +test_expect_success 'rename a branch under rebase not allowed' ' + test_must_fail git branch -M under-rebase rebase-with-new-name +' + +test_expect_success 'check out from current worktree branch ok' ' + ( + cd under-rebase && + git checkout under-rebase && + git checkout - && + git rebase --abort + ) +' + +test_expect_success 'checkout a branch under bisect' ' + git worktree add under-bisect && + ( + cd under-bisect && + git bisect start && + git bisect bad && + git bisect good HEAD~2 && + git worktree list | grep "under-bisect.*detached HEAD" && + test_must_fail git worktree add new-bisect under-bisect && + ! test -d new-bisect + ) +' + +test_expect_success 'rename a branch under bisect not allowed' ' + test_must_fail git branch -M under-bisect bisect-with-new-name +' +# Is branch "refs/heads/$1" set to pull from "$2/$3"? +test_branch_upstream () { + printf "%s\n" "$2" "refs/heads/$3" >expect.upstream && + { + git config "branch.$1.remote" && + git config "branch.$1.merge" + } >actual.upstream && + test_cmp expect.upstream actual.upstream +} + +test_expect_success '--track sets up tracking' ' + test_when_finished rm -rf track && + git worktree add --track -b track track master && + test_branch_upstream track . master +' + +# setup remote repository $1 and repository $2 with $1 set up as +# remote. The remote has two branches, master and foo. +setup_remote_repo () { + git init $1 && + ( + cd $1 && + test_commit $1_master && + git checkout -b foo && + test_commit upstream_foo + ) && + git init $2 && + ( + cd $2 && + test_commit $2_master && + git remote add $1 ../$1 && + git config remote.$1.fetch \ + "refs/heads/*:refs/remotes/$1/*" && + git fetch --all + ) +} + +test_expect_success '--no-track avoids setting up tracking' ' + test_when_finished rm -rf repo_upstream repo_local foo && + setup_remote_repo repo_upstream repo_local && + ( + cd repo_local && + git worktree add --no-track -b foo ../foo repo_upstream/foo + ) && + ( + cd foo && + test_must_fail git config "branch.foo.remote" && + test_must_fail git config "branch.foo.merge" && + test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo + ) +' + +test_expect_success '"add" <path> <non-existent-branch> fails' ' + test_must_fail git worktree add foo non-existent +' + +test_expect_success '"add" <path> <branch> dwims' ' + test_when_finished rm -rf repo_upstream repo_dwim foo && + setup_remote_repo repo_upstream repo_dwim && + git init repo_dwim && + ( + cd repo_dwim && + git worktree add ../foo foo + ) && + ( + cd foo && + test_branch_upstream foo repo_upstream foo && + test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo + ) +' + +test_expect_success 'git worktree add does not match remote' ' + test_when_finished rm -rf repo_a repo_b foo && + setup_remote_repo repo_a repo_b && + ( + cd repo_b && + git worktree add ../foo + ) && + ( + cd foo && + test_must_fail git config "branch.foo.remote" && + test_must_fail git config "branch.foo.merge" && + ! test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo + ) +' + +test_expect_success 'git worktree add --guess-remote sets up tracking' ' + test_when_finished rm -rf repo_a repo_b foo && + setup_remote_repo repo_a repo_b && + ( + cd repo_b && + git worktree add --guess-remote ../foo + ) && + ( + cd foo && + test_branch_upstream foo repo_a foo && + test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo + ) +' + +test_expect_success 'git worktree add with worktree.guessRemote sets up tracking' ' + test_when_finished rm -rf repo_a repo_b foo && + setup_remote_repo repo_a repo_b && + ( + cd repo_b && + git config worktree.guessRemote true && + git worktree add ../foo + ) && + ( + cd foo && + test_branch_upstream foo repo_a foo && + test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo + ) +' + +test_expect_success 'git worktree --no-guess-remote option overrides config' ' + test_when_finished rm -rf repo_a repo_b foo && + setup_remote_repo repo_a repo_b && + ( + cd repo_b && + git config worktree.guessRemote true && + git worktree add --no-guess-remote ../foo + ) && + ( + cd foo && + test_must_fail git config "branch.foo.remote" && + test_must_fail git config "branch.foo.merge" && + ! test_cmp_rev refs/remotes/repo_a/foo refs/heads/foo + ) +' + +post_checkout_hook () { + gitdir=${1:-.git} + test_when_finished "rm -f $gitdir/hooks/post-checkout" && + mkdir -p $gitdir/hooks && + write_script $gitdir/hooks/post-checkout <<-\EOF + { + echo $* + git rev-parse --git-dir --show-toplevel + } >hook.actual + EOF +} + +test_expect_success '"add" invokes post-checkout hook (branch)' ' + post_checkout_hook && + { + echo $_z40 $(git rev-parse HEAD) 1 && + echo $(pwd)/.git/worktrees/gumby && + echo $(pwd)/gumby + } >hook.expect && + git worktree add gumby && + test_cmp hook.expect gumby/hook.actual +' + +test_expect_success '"add" invokes post-checkout hook (detached)' ' + post_checkout_hook && + { + echo $_z40 $(git rev-parse HEAD) 1 && + echo $(pwd)/.git/worktrees/grumpy && + echo $(pwd)/grumpy + } >hook.expect && + git worktree add --detach grumpy && + test_cmp hook.expect grumpy/hook.actual +' + +test_expect_success '"add --no-checkout" suppresses post-checkout hook' ' + post_checkout_hook && + rm -f hook.actual && + git worktree add --no-checkout gloopy && + test_path_is_missing gloopy/hook.actual +' + +test_expect_success '"add" in other worktree invokes post-checkout hook' ' + post_checkout_hook && + { + echo $_z40 $(git rev-parse HEAD) 1 && + echo $(pwd)/.git/worktrees/guppy && + echo $(pwd)/guppy + } >hook.expect && + git -C gloopy worktree add --detach ../guppy && + test_cmp hook.expect guppy/hook.actual +' + +test_expect_success '"add" in bare repo invokes post-checkout hook' ' + rm -rf bare && + git clone --bare . bare && + { + echo $_z40 $(git --git-dir=bare rev-parse HEAD) 1 && + echo $(pwd)/bare/worktrees/goozy && + echo $(pwd)/goozy + } >hook.expect && + post_checkout_hook bare && + git -C bare worktree add --detach ../goozy && + test_cmp hook.expect goozy/hook.actual +' + +test_done diff --git a/t/t2026-prune-linked-checkouts.sh b/t/t2026-worktree-prune.sh index 1821a480c5..b7d6d5d45a 100755 --- a/t/t2026-prune-linked-checkouts.sh +++ b/t/t2026-worktree-prune.sh @@ -8,15 +8,15 @@ test_expect_success initialize ' git commit --allow-empty -m init ' -test_expect_success 'prune --worktrees on normal repo' ' - git prune --worktrees && - test_must_fail git prune --worktrees abc +test_expect_success 'worktree prune on normal repo' ' + git worktree prune && + test_must_fail git worktree prune abc ' test_expect_success 'prune files inside $GIT_DIR/worktrees' ' mkdir .git/worktrees && : >.git/worktrees/abc && - git prune --worktrees --verbose >actual && + git worktree prune --verbose >actual && cat >expect <<EOF && Removing worktrees/abc: not a valid directory EOF @@ -31,7 +31,7 @@ test_expect_success 'prune directories without gitdir' ' cat >expect <<EOF && Removing worktrees/def: gitdir file does not exist EOF - git prune --worktrees --verbose >actual && + git worktree prune --verbose >actual && test_i18ncmp expect actual && ! test -d .git/worktrees/def && ! test -d .git/worktrees @@ -42,7 +42,7 @@ test_expect_success SANITY 'prune directories with unreadable gitdir' ' : >.git/worktrees/def/def && : >.git/worktrees/def/gitdir && chmod u-r .git/worktrees/def/gitdir && - git prune --worktrees --verbose >actual && + git worktree prune --verbose >actual && test_i18ngrep "Removing worktrees/def: unable to read gitdir file" actual && ! test -d .git/worktrees/def && ! test -d .git/worktrees @@ -52,7 +52,7 @@ test_expect_success 'prune directories with invalid gitdir' ' mkdir -p .git/worktrees/def/abc && : >.git/worktrees/def/def && : >.git/worktrees/def/gitdir && - git prune --worktrees --verbose >actual && + git worktree prune --verbose >actual && test_i18ngrep "Removing worktrees/def: invalid gitdir file" actual && ! test -d .git/worktrees/def && ! test -d .git/worktrees @@ -62,7 +62,7 @@ test_expect_success 'prune directories with gitdir pointing to nowhere' ' mkdir -p .git/worktrees/def/abc && : >.git/worktrees/def/def && echo "$(pwd)"/nowhere >.git/worktrees/def/gitdir && - git prune --worktrees --verbose >actual && + git worktree prune --verbose >actual && test_i18ngrep "Removing worktrees/def: gitdir file points to non-existent location" actual && ! test -d .git/worktrees/def && ! test -d .git/worktrees @@ -72,24 +72,23 @@ test_expect_success 'not prune locked checkout' ' test_when_finished rm -r .git/worktrees && mkdir -p .git/worktrees/ghi && : >.git/worktrees/ghi/locked && - git prune --worktrees && + git worktree prune && test -d .git/worktrees/ghi ' test_expect_success 'not prune recent checkouts' ' test_when_finished rm -r .git/worktrees && - mkdir zz && - mkdir -p .git/worktrees/jlm && - echo "$(pwd)"/zz >.git/worktrees/jlm/gitdir && - rmdir zz && - git prune --worktrees --verbose --expire=2.days.ago && + git worktree add jlm HEAD && + test -d .git/worktrees/jlm && + rm -rf jlm && + git worktree prune --verbose --expire=2.days.ago && test -d .git/worktrees/jlm ' test_expect_success 'not prune proper checkouts' ' test_when_finished rm -r .git/worktrees && - git checkout "--to=$PWD/nop" --detach master && - git prune --worktrees && + git worktree add --detach "$PWD/nop" master && + git worktree prune && test -d .git/worktrees/nop ' diff --git a/t/t2027-worktree-list.sh b/t/t2027-worktree-list.sh new file mode 100755 index 0000000000..720063bf0d --- /dev/null +++ b/t/t2027-worktree-list.sh @@ -0,0 +1,154 @@ +#!/bin/sh + +test_description='test git worktree list' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit init +' + +test_expect_success 'rev-parse --git-common-dir on main worktree' ' + git rev-parse --git-common-dir >actual && + echo .git >expected && + test_cmp expected actual && + mkdir sub && + git -C sub rev-parse --git-common-dir >actual2 && + echo ../.git >expected2 && + test_cmp expected2 actual2 +' + +test_expect_success 'rev-parse --git-path objects linked worktree' ' + echo "$(git rev-parse --show-toplevel)/.git/objects" >expect && + test_when_finished "rm -rf linked-tree actual expect && git worktree prune" && + git worktree add --detach linked-tree master && + git -C linked-tree rev-parse --git-path objects >actual && + test_cmp expect actual +' + +test_expect_success '"list" all worktrees from main' ' + echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect && + test_when_finished "rm -rf here out actual expect && git worktree prune" && + git worktree add --detach here master && + echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect && + git worktree list >out && + sed "s/ */ /g" <out >actual && + test_cmp expect actual +' + +test_expect_success '"list" all worktrees from linked' ' + echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect && + test_when_finished "rm -rf here out actual expect && git worktree prune" && + git worktree add --detach here master && + echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect && + git -C here worktree list >out && + sed "s/ */ /g" <out >actual && + test_cmp expect actual +' + +test_expect_success '"list" all worktrees --porcelain' ' + echo "worktree $(git rev-parse --show-toplevel)" >expect && + echo "HEAD $(git rev-parse HEAD)" >>expect && + echo "branch $(git symbolic-ref HEAD)" >>expect && + echo >>expect && + test_when_finished "rm -rf here actual expect && git worktree prune" && + git worktree add --detach here master && + echo "worktree $(git -C here rev-parse --show-toplevel)" >>expect && + echo "HEAD $(git rev-parse HEAD)" >>expect && + echo "detached" >>expect && + echo >>expect && + git worktree list --porcelain >actual && + test_cmp expect actual +' + +test_expect_success 'bare repo setup' ' + git init --bare bare1 && + echo "data" >file1 && + git add file1 && + git commit -m"File1: add data" && + git push bare1 master && + git reset --hard HEAD^ +' + +test_expect_success '"list" all worktrees from bare main' ' + test_when_finished "rm -rf there out actual expect && git -C bare1 worktree prune" && + git -C bare1 worktree add --detach ../there master && + echo "$(pwd)/bare1 (bare)" >expect && + echo "$(git -C there rev-parse --show-toplevel) $(git -C there rev-parse --short HEAD) (detached HEAD)" >>expect && + git -C bare1 worktree list >out && + sed "s/ */ /g" <out >actual && + test_cmp expect actual +' + +test_expect_success '"list" all worktrees --porcelain from bare main' ' + test_when_finished "rm -rf there actual expect && git -C bare1 worktree prune" && + git -C bare1 worktree add --detach ../there master && + echo "worktree $(pwd)/bare1" >expect && + echo "bare" >>expect && + echo >>expect && + echo "worktree $(git -C there rev-parse --show-toplevel)" >>expect && + echo "HEAD $(git -C there rev-parse HEAD)" >>expect && + echo "detached" >>expect && + echo >>expect && + git -C bare1 worktree list --porcelain >actual && + test_cmp expect actual +' + +test_expect_success '"list" all worktrees from linked with a bare main' ' + test_when_finished "rm -rf there out actual expect && git -C bare1 worktree prune" && + git -C bare1 worktree add --detach ../there master && + echo "$(pwd)/bare1 (bare)" >expect && + echo "$(git -C there rev-parse --show-toplevel) $(git -C there rev-parse --short HEAD) (detached HEAD)" >>expect && + git -C there worktree list >out && + sed "s/ */ /g" <out >actual && + test_cmp expect actual +' + +test_expect_success 'bare repo cleanup' ' + rm -rf bare1 +' + +test_expect_success 'broken main worktree still at the top' ' + git init broken-main && + ( + cd broken-main && + test_commit new && + git worktree add linked && + cat >expected <<-EOF && + worktree $(pwd) + HEAD $_z40 + + EOF + cd linked && + echo "worktree $(pwd)" >expected && + echo "ref: .broken" >../.git/HEAD && + git worktree list --porcelain >out && + head -n 3 out >actual && + test_cmp ../expected actual && + git worktree list >out && + head -n 1 out >actual.2 && + grep -F "(error)" actual.2 + ) +' + +test_expect_success 'linked worktrees are sorted' ' + mkdir sorted && + git init sorted/main && + ( + cd sorted/main && + test_tick && + test_commit new && + git worktree add ../first && + git worktree add ../second && + git worktree list --porcelain >out && + grep ^worktree out >actual + ) && + cat >expected <<-EOF && + worktree $(pwd)/sorted/main + worktree $(pwd)/sorted/first + worktree $(pwd)/sorted/second + EOF + test_cmp expected sorted/main/actual +' + +test_done diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh new file mode 100755 index 0000000000..5f7d45b7b7 --- /dev/null +++ b/t/t2028-worktree-move.sh @@ -0,0 +1,144 @@ +#!/bin/sh + +test_description='test git worktree move, remove, lock and unlock' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit init && + git worktree add source && + git worktree list --porcelain >out && + grep "^worktree" out >actual && + cat <<-EOF >expected && + worktree $(pwd) + worktree $(pwd)/source + EOF + test_cmp expected actual +' + +test_expect_success 'lock main worktree' ' + test_must_fail git worktree lock . +' + +test_expect_success 'lock linked worktree' ' + git worktree lock --reason hahaha source && + echo hahaha >expected && + test_cmp expected .git/worktrees/source/locked +' + +test_expect_success 'lock linked worktree from another worktree' ' + rm .git/worktrees/source/locked && + git worktree add elsewhere && + git -C elsewhere worktree lock --reason hahaha ../source && + echo hahaha >expected && + test_cmp expected .git/worktrees/source/locked +' + +test_expect_success 'lock worktree twice' ' + test_must_fail git worktree lock source && + echo hahaha >expected && + test_cmp expected .git/worktrees/source/locked +' + +test_expect_success 'lock worktree twice (from the locked worktree)' ' + test_must_fail git -C source worktree lock . && + echo hahaha >expected && + test_cmp expected .git/worktrees/source/locked +' + +test_expect_success 'unlock main worktree' ' + test_must_fail git worktree unlock . +' + +test_expect_success 'unlock linked worktree' ' + git worktree unlock source && + test_path_is_missing .git/worktrees/source/locked +' + +test_expect_success 'unlock worktree twice' ' + test_must_fail git worktree unlock source && + test_path_is_missing .git/worktrees/source/locked +' + +test_expect_success 'move non-worktree' ' + mkdir abc && + test_must_fail git worktree move abc def +' + +test_expect_success 'move locked worktree' ' + git worktree lock source && + test_when_finished "git worktree unlock source" && + test_must_fail git worktree move source destination +' + +test_expect_success 'move worktree' ' + git worktree move source destination && + test_path_is_missing source && + git worktree list --porcelain >out && + grep "^worktree.*/destination$" out && + ! grep "^worktree.*/source$" out && + git -C destination log --format=%s >actual2 && + echo init >expected2 && + test_cmp expected2 actual2 +' + +test_expect_success 'move main worktree' ' + test_must_fail git worktree move . def +' + +test_expect_success 'move worktree to another dir' ' + mkdir some-dir && + git worktree move destination some-dir && + test_when_finished "git worktree move some-dir/destination destination" && + test_path_is_missing destination && + git worktree list --porcelain >out && + grep "^worktree.*/some-dir/destination$" out && + git -C some-dir/destination log --format=%s >actual2 && + echo init >expected2 && + test_cmp expected2 actual2 +' + +test_expect_success 'remove main worktree' ' + test_must_fail git worktree remove . +' + +test_expect_success 'remove locked worktree' ' + git worktree lock destination && + test_when_finished "git worktree unlock destination" && + test_must_fail git worktree remove destination +' + +test_expect_success 'remove worktree with dirty tracked file' ' + echo dirty >>destination/init.t && + test_when_finished "git -C destination checkout init.t" && + test_must_fail git worktree remove destination +' + +test_expect_success 'remove worktree with untracked file' ' + : >destination/untracked && + test_must_fail git worktree remove destination +' + +test_expect_success 'force remove worktree with untracked file' ' + git worktree remove --force destination && + test_path_is_missing destination +' + +test_expect_success 'remove missing worktree' ' + git worktree add to-be-gone && + test -d .git/worktrees/to-be-gone && + mv to-be-gone gone && + git worktree remove to-be-gone && + test_path_is_missing .git/worktrees/to-be-gone +' + +test_expect_success 'NOT remove missing-but-locked worktree' ' + git worktree add gone-but-locked && + git worktree lock gone-but-locked && + test -d .git/worktrees/gone-but-locked && + mv gone-but-locked really-gone-now && + test_must_fail git worktree remove gone-but-locked && + test_path_is_dir .git/worktrees/gone-but-locked +' + +test_done diff --git a/t/t2101-update-index-reupdate.sh b/t/t2101-update-index-reupdate.sh index c8bce8c2e4..685ec45639 100755 --- a/t/t2101-update-index-reupdate.sh +++ b/t/t2101-update-index-reupdate.sh @@ -8,19 +8,20 @@ test_description='git update-index --again test. . ./test-lib.sh -cat > expected <<\EOF -100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0 file1 -100644 9db8893856a8a02eaa73470054b7c1c5a7c82e47 0 file2 -EOF -test_expect_success 'update-index --add' \ - 'echo hello world >file1 && - echo goodbye people >file2 && - git update-index --add file1 file2 && - git ls-files -s >current && - cmp current expected' +test_expect_success 'update-index --add' ' + echo hello world >file1 && + echo goodbye people >file2 && + git update-index --add file1 file2 && + git ls-files -s >current && + cat >expected <<-EOF && + 100644 $(git hash-object file1) 0 file1 + 100644 $(git hash-object file2) 0 file2 + EOF + cmp current expected +' -test_expect_success 'update-index --again' \ - 'rm -f file1 && +test_expect_success 'update-index --again' ' + rm -f file1 && echo hello everybody >file2 && if git update-index --again then @@ -29,25 +30,23 @@ test_expect_success 'update-index --again' \ else echo happy - failed as expected fi && - git ls-files -s >current && - cmp current expected' + git ls-files -s >current && + cmp current expected +' -cat > expected <<\EOF -100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0 file2 -EOF -test_expect_success 'update-index --remove --again' \ - 'git update-index --remove --again && - git ls-files -s >current && - cmp current expected' +test_expect_success 'update-index --remove --again' ' + git update-index --remove --again && + git ls-files -s >current && + cat >expected <<-EOF && + 100644 $(git hash-object file2) 0 file2 + EOF + cmp current expected +' test_expect_success 'first commit' 'git commit -m initial' -cat > expected <<\EOF -100644 53ab446c3f4e42ce9bb728a0ccb283a101be4979 0 dir1/file3 -100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0 file2 -EOF -test_expect_success 'update-index again' \ - 'mkdir -p dir1 && +test_expect_success 'update-index again' ' + mkdir -p dir1 && echo hello world >dir1/file3 && echo goodbye people >file2 && git update-index --add file2 dir1/file3 && @@ -55,30 +54,38 @@ test_expect_success 'update-index again' \ echo happy >dir1/file3 && git update-index --again && git ls-files -s >current && - cmp current expected' + cat >expected <<-EOF && + 100644 $(git hash-object dir1/file3) 0 dir1/file3 + 100644 $(git hash-object file2) 0 file2 + EOF + cmp current expected +' -cat > expected <<\EOF -100644 d7fb3f695f06c759dbf3ab00046e7cc2da22d10f 0 dir1/file3 -100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0 file2 -EOF -test_expect_success 'update-index --update from subdir' \ - 'echo not so happy >file2 && +file2=$(git hash-object file2) +test_expect_success 'update-index --update from subdir' ' + echo not so happy >file2 && (cd dir1 && cat ../file2 >file3 && git update-index --again ) && git ls-files -s >current && - cmp current expected' + cat >expected <<-EOF && + 100644 $(git hash-object dir1/file3) 0 dir1/file3 + 100644 $file2 0 file2 + EOF + test_cmp current expected +' -cat > expected <<\EOF -100644 594fb5bb1759d90998e2bf2a38261ae8e243c760 0 dir1/file3 -100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0 file2 -EOF -test_expect_success 'update-index --update with pathspec' \ - 'echo very happy >file2 && +test_expect_success 'update-index --update with pathspec' ' + echo very happy >file2 && cat file2 >dir1/file3 && git update-index --again dir1/ && git ls-files -s >current && - cmp current expected' + cat >expected <<-EOF && + 100644 $(git hash-object dir1/file3) 0 dir1/file3 + 100644 $file2 0 file2 + EOF + cmp current expected +' test_done diff --git a/t/t2102-update-index-symlinks.sh b/t/t2102-update-index-symlinks.sh index 4d0d0a3515..22f2c730ae 100755 --- a/t/t2102-update-index-symlinks.sh +++ b/t/t2102-update-index-symlinks.sh @@ -23,7 +23,7 @@ git update-index symlink' test_expect_success \ 'the index entry must still be a symbolic link' ' -case "`git ls-files --stage --cached symlink`" in +case "$(git ls-files --stage --cached symlink)" in 120000" "*symlink) echo pass;; *) echo fail; git ls-files --stage --cached symlink; (exit 1);; esac' diff --git a/t/t2104-update-index-skip-worktree.sh b/t/t2104-update-index-skip-worktree.sh index cc830da58d..7e2e7dd4ae 100755 --- a/t/t2104-update-index-skip-worktree.sh +++ b/t/t2104-update-index-skip-worktree.sh @@ -33,7 +33,7 @@ test_expect_success 'setup' ' ' test_expect_success 'index is at version 2' ' - test "$(test-index-version < .git/index)" = 2 + test "$(test-tool index-version < .git/index)" = 2 ' test_expect_success 'update-index --skip-worktree' ' @@ -42,7 +42,7 @@ test_expect_success 'update-index --skip-worktree' ' ' test_expect_success 'index is at version 3 after having some skip-worktree entries' ' - test "$(test-index-version < .git/index)" = 3 + test "$(test-tool index-version < .git/index)" = 3 ' test_expect_success 'ls-files -t' ' @@ -55,7 +55,7 @@ test_expect_success 'update-index --no-skip-worktree' ' ' test_expect_success 'index version is back to 2 when there is no skip-worktree entry' ' - test "$(test-index-version < .git/index)" = 2 + test "$(test-tool index-version < .git/index)" = 2 ' test_done diff --git a/t/t2107-update-index-basic.sh b/t/t2107-update-index-basic.sh index dfe02f4818..1db7e6a1ab 100755 --- a/t/t2107-update-index-basic.sh +++ b/t/t2107-update-index-basic.sh @@ -80,4 +80,17 @@ test_expect_success '.lock files cleaned up' ' ) ' +test_expect_success '--chmod=+x and chmod=-x in the same argument list' ' + >A && + >B && + git add A B && + git update-index --chmod=+x A --chmod=-x B && + cat >expect <<-EOF && + 100755 $EMPTY_BLOB 0 A + 100644 $EMPTY_BLOB 0 B + EOF + git ls-files --stage A B >actual && + test_cmp expect actual +' + test_done diff --git a/t/t2200-add-update.sh b/t/t2200-add-update.sh index e16b15d3e5..314c73c5a7 100755 --- a/t/t2200-add-update.sh +++ b/t/t2200-add-update.sh @@ -84,6 +84,8 @@ test_expect_success 'non-qualified update in subdir updates from the root' ' ( cd dir1 && echo even more >>sub2 && + git --literal-pathspecs add -u && + echo even more >>sub2 && git add -u ) && : >expect && diff --git a/t/t2202-add-addremove.sh b/t/t2202-add-addremove.sh index fc8b59e7f7..6a5a3166b1 100755 --- a/t/t2202-add-addremove.sh +++ b/t/t2202-add-addremove.sh @@ -14,6 +14,7 @@ test_expect_success setup ' echo expect echo ignored ) >.gitignore && + git --literal-pathspecs add --all && >will-remove && git add --all && test_tick && diff --git a/t/t2203-add-intent.sh b/t/t2203-add-intent.sh index 7c641bfaf4..78236dc7d8 100755 --- a/t/t2203-add-intent.sh +++ b/t/t2203-add-intent.sh @@ -25,6 +25,18 @@ test_expect_success 'git status' ' test_cmp expect actual ' +test_expect_success 'git status with porcelain v2' ' + git status --porcelain=v2 | grep -v "^?" >actual && + nam1=d00491fd7e5bb6fa28c517a0bb32b8b506539d4d && + nam2=ce013625030ba8dba906f756967f9e9ca394464a && + cat >expect <<-EOF && + 1 DA N... 100644 000000 100644 $nam1 $_z40 1.t + 1 A. N... 000000 100644 100644 $_z40 $nam2 elif + 1 .A N... 000000 000000 100644 $_z40 $_z40 file + EOF + test_cmp expect actual +' + test_expect_success 'check result of "add -N"' ' git ls-files -s file >actual && empty=$(git hash-object --stdin </dev/null) && @@ -57,8 +69,9 @@ test_expect_success 'i-t-a entry is simply ignored' ' git add -N nitfol && git commit -m second && test $(git ls-tree HEAD -- nitfol | wc -l) = 0 && - test $(git diff --name-only HEAD -- nitfol | wc -l) = 0 && - test $(git diff --name-only -- nitfol | wc -l) = 1 + test $(git diff --name-only HEAD -- nitfol | wc -l) = 1 && + test $(git diff --name-only --ita-invisible-in-index HEAD -- nitfol | wc -l) = 0 && + test $(git diff --name-only --ita-invisible-in-index -- nitfol | wc -l) = 1 ' test_expect_success 'can commit with an unrelated i-t-a entry in index' ' @@ -87,15 +100,127 @@ test_expect_success 'cache-tree invalidates i-t-a paths' ' : >dir/bar && git add -N dir/bar && git diff --cached --name-only >actual && - >expect && + echo dir/bar >expect && test_cmp expect actual && git write-tree >/dev/null && git diff --cached --name-only >actual && - >expect && + echo dir/bar >expect && test_cmp expect actual ' +test_expect_success 'cache-tree does not ignore dir that has i-t-a entries' ' + git init ita-in-dir && + ( + cd ita-in-dir && + mkdir 2 && + for f in 1 2/1 2/2 3 + do + echo "$f" >"$f" + done && + git add 1 2/2 3 && + git add -N 2/1 && + git commit -m committed && + git ls-tree -r HEAD >actual && + grep 2/2 actual + ) +' + +test_expect_success 'cache-tree does skip dir that becomes empty' ' + rm -fr ita-in-dir && + git init ita-in-dir && + ( + cd ita-in-dir && + mkdir -p 1/2/3 && + echo 4 >1/2/3/4 && + git add -N 1/2/3/4 && + git write-tree >actual && + echo $EMPTY_TREE >expected && + test_cmp expected actual + ) +' + +test_expect_success 'commit: ita entries ignored in empty initial commit check' ' + git init empty-initial-commit && + ( + cd empty-initial-commit && + : >one && + git add -N one && + test_must_fail git commit -m nothing-new-here + ) +' + +test_expect_success 'commit: ita entries ignored in empty commit check' ' + git init empty-subsequent-commit && + ( + cd empty-subsequent-commit && + test_commit one && + : >two && + git add -N two && + test_must_fail git commit -m nothing-new-here + ) +' + +test_expect_success 'rename detection finds the right names' ' + git init rename-detection && + ( + cd rename-detection && + echo contents >first && + git add first && + git commit -m first && + mv first third && + git add -N third && + + git status | grep -v "^?" >actual.1 && + test_i18ngrep "renamed: *first -> third" actual.1 && + + git status --porcelain | grep -v "^?" >actual.2 && + cat >expected.2 <<-\EOF && + R first -> third + EOF + test_cmp expected.2 actual.2 && + + hash=12f00e90b6ef79117ce6e650416b8cf517099b78 && + git status --porcelain=v2 | grep -v "^?" >actual.3 && + cat >expected.3 <<-EOF && + 2 .R N... 100644 100644 100644 $hash $hash R100 third first + EOF + test_cmp expected.3 actual.3 + ) +' + +test_expect_success 'double rename detection in status' ' + git init rename-detection-2 && + ( + cd rename-detection-2 && + echo contents >first && + git add first && + git commit -m first && + git mv first second && + mv second third && + git add -N third && + + git status | grep -v "^?" >actual.1 && + test_i18ngrep "renamed: *first -> second" actual.1 && + test_i18ngrep "renamed: *second -> third" actual.1 && + + git status --porcelain | grep -v "^?" >actual.2 && + cat >expected.2 <<-\EOF && + R first -> second + R second -> third + EOF + test_cmp expected.2 actual.2 && + + hash=12f00e90b6ef79117ce6e650416b8cf517099b78 && + git status --porcelain=v2 | grep -v "^?" >actual.3 && + cat >expected.3 <<-EOF && + 2 R. N... 100644 100644 100644 $hash $hash R100 second first + 2 .R N... 100644 100644 100644 $hash $hash R100 third second + EOF + test_cmp expected.3 actual.3 + ) +' + test_done diff --git a/t/t2300-cd-to-toplevel.sh b/t/t2300-cd-to-toplevel.sh index 9965bc5c92..c8de6d8a19 100755 --- a/t/t2300-cd-to-toplevel.sh +++ b/t/t2300-cd-to-toplevel.sh @@ -4,11 +4,20 @@ test_description='cd_to_toplevel' . ./test-lib.sh +EXEC_PATH="$(git --exec-path)" +test_have_prereq !MINGW || +case "$EXEC_PATH" in +[A-Za-z]:/*) + EXEC_PATH="/${EXEC_PATH%%:*}${EXEC_PATH#?:}" + ;; +esac + test_cd_to_toplevel () { test_expect_success $3 "$2" ' ( cd '"'$1'"' && - . "$(git --exec-path)"/git-sh-setup && + PATH="$EXEC_PATH:$PATH" && + . git-sh-setup && cd_to_toplevel && [ "$(pwd -P)" = "$TOPLEVEL" ] ) diff --git a/t/t3000-ls-files-others.sh b/t/t3000-ls-files-others.sh index 88be904c09..c525656b2c 100755 --- a/t/t3000-ls-files-others.sh +++ b/t/t3000-ls-files-others.sh @@ -65,6 +65,13 @@ test_expect_success '--no-empty-directory hides empty directory' ' test_cmp expected3 output ' +test_expect_success 'ls-files --others handles non-submodule .git' ' + mkdir not-a-submodule && + echo foo >not-a-submodule/.git && + git ls-files -o >output && + test_cmp expected1 output +' + test_expect_success SYMLINKS 'ls-files --others with symlinked submodule' ' git init super && git init sub && diff --git a/t/t3007-ls-files-recurse-submodules.sh b/t/t3007-ls-files-recurse-submodules.sh new file mode 100755 index 0000000000..318b5bce7e --- /dev/null +++ b/t/t3007-ls-files-recurse-submodules.sh @@ -0,0 +1,300 @@ +#!/bin/sh + +test_description='Test ls-files recurse-submodules feature + +This test verifies the recurse-submodules feature correctly lists files from +submodules. +' + +. ./test-lib.sh + +test_expect_success 'setup directory structure and submodules' ' + echo a >a && + mkdir b && + echo b >b/b && + git add a b && + git commit -m "add a and b" && + git init submodule && + echo c >submodule/c && + git -C submodule add c && + git -C submodule commit -m "add c" && + git submodule add ./submodule && + git commit -m "added submodule" +' + +test_expect_success 'ls-files correctly outputs files in submodule' ' + cat >expect <<-\EOF && + .gitmodules + a + b/b + submodule/c + EOF + + git ls-files --recurse-submodules >actual && + test_cmp expect actual +' + +test_expect_success 'ls-files correctly outputs files in submodule with -z' ' + lf_to_nul >expect <<-\EOF && + .gitmodules + a + b/b + submodule/c + EOF + + git ls-files --recurse-submodules -z >actual && + test_cmp expect actual +' + +test_expect_success 'ls-files does not output files not added to a repo' ' + cat >expect <<-\EOF && + .gitmodules + a + b/b + submodule/c + EOF + + echo a >not_added && + echo b >b/not_added && + echo c >submodule/not_added && + git ls-files --recurse-submodules >actual && + test_cmp expect actual +' + +test_expect_success 'ls-files recurses more than 1 level' ' + cat >expect <<-\EOF && + .gitmodules + a + b/b + submodule/.gitmodules + submodule/c + submodule/subsub/d + EOF + + git init submodule/subsub && + echo d >submodule/subsub/d && + git -C submodule/subsub add d && + git -C submodule/subsub commit -m "add d" && + git -C submodule submodule add ./subsub && + git -C submodule commit -m "added subsub" && + git submodule absorbgitdirs && + git ls-files --recurse-submodules >actual && + test_cmp expect actual +' + +test_expect_success 'ls-files works with GIT_DIR' ' + cat >expect <<-\EOF && + .gitmodules + c + subsub/d + EOF + + git --git-dir=submodule/.git ls-files --recurse-submodules >actual && + test_cmp expect actual +' + +test_expect_success '--recurse-submodules and pathspecs setup' ' + echo e >submodule/subsub/e.txt && + git -C submodule/subsub add e.txt && + git -C submodule/subsub commit -m "adding e.txt" && + echo f >submodule/f.TXT && + echo g >submodule/g.txt && + git -C submodule add f.TXT g.txt && + git -C submodule commit -m "add f and g" && + echo h >h.txt && + mkdir sib && + echo sib >sib/file && + git add h.txt sib/file && + git commit -m "add h and sib/file" && + git init sub && + echo sub >sub/file && + git -C sub add file && + git -C sub commit -m "add file" && + git submodule add ./sub && + git commit -m "added sub" && + + cat >expect <<-\EOF && + .gitmodules + a + b/b + h.txt + sib/file + sub/file + submodule/.gitmodules + submodule/c + submodule/f.TXT + submodule/g.txt + submodule/subsub/d + submodule/subsub/e.txt + EOF + + git ls-files --recurse-submodules >actual && + test_cmp expect actual && + cat actual && + git ls-files --recurse-submodules "*" >actual && + test_cmp expect actual +' + +test_expect_success 'inactive submodule' ' + test_when_finished "git config --bool submodule.submodule.active true" && + test_when_finished "git -C submodule config --bool submodule.subsub.active true" && + git config --bool submodule.submodule.active "false" && + + cat >expect <<-\EOF && + .gitmodules + a + b/b + h.txt + sib/file + sub/file + submodule + EOF + + git ls-files --recurse-submodules >actual && + test_cmp expect actual && + + git config --bool submodule.submodule.active "true" && + git -C submodule config --bool submodule.subsub.active "false" && + + cat >expect <<-\EOF && + .gitmodules + a + b/b + h.txt + sib/file + sub/file + submodule/.gitmodules + submodule/c + submodule/f.TXT + submodule/g.txt + submodule/subsub + EOF + + git ls-files --recurse-submodules >actual && + test_cmp expect actual +' + +test_expect_success '--recurse-submodules and pathspecs' ' + cat >expect <<-\EOF && + h.txt + submodule/g.txt + submodule/subsub/e.txt + EOF + + git ls-files --recurse-submodules "*.txt" >actual && + test_cmp expect actual +' + +test_expect_success '--recurse-submodules and pathspecs' ' + cat >expect <<-\EOF && + h.txt + submodule/f.TXT + submodule/g.txt + submodule/subsub/e.txt + EOF + + git ls-files --recurse-submodules ":(icase)*.txt" >actual && + test_cmp expect actual +' + +test_expect_success '--recurse-submodules and pathspecs' ' + cat >expect <<-\EOF && + h.txt + submodule/f.TXT + submodule/g.txt + EOF + + git ls-files --recurse-submodules ":(icase)*.txt" ":(exclude)submodule/subsub/*" >actual && + test_cmp expect actual +' + +test_expect_success '--recurse-submodules and pathspecs' ' + cat >expect <<-\EOF && + sub/file + EOF + + git ls-files --recurse-submodules "sub" >actual && + test_cmp expect actual && + git ls-files --recurse-submodules "sub/" >actual && + test_cmp expect actual && + git ls-files --recurse-submodules "sub/file" >actual && + test_cmp expect actual && + git ls-files --recurse-submodules "su*/file" >actual && + test_cmp expect actual && + git ls-files --recurse-submodules "su?/file" >actual && + test_cmp expect actual +' + +test_expect_success '--recurse-submodules and pathspecs' ' + cat >expect <<-\EOF && + sib/file + sub/file + EOF + + git ls-files --recurse-submodules "s??/file" >actual && + test_cmp expect actual && + git ls-files --recurse-submodules "s???file" >actual && + test_cmp expect actual && + git ls-files --recurse-submodules "s*file" >actual && + test_cmp expect actual +' + +test_expect_success '--recurse-submodules and relative paths' ' + # From subdir + cat >expect <<-\EOF && + b + EOF + git -C b ls-files --recurse-submodules >actual && + test_cmp expect actual && + + # Relative path to top + cat >expect <<-\EOF && + ../.gitmodules + ../a + b + ../h.txt + ../sib/file + ../sub/file + ../submodule/.gitmodules + ../submodule/c + ../submodule/f.TXT + ../submodule/g.txt + ../submodule/subsub/d + ../submodule/subsub/e.txt + EOF + git -C b ls-files --recurse-submodules -- .. >actual && + test_cmp expect actual && + + # Relative path to submodule + cat >expect <<-\EOF && + ../submodule/.gitmodules + ../submodule/c + ../submodule/f.TXT + ../submodule/g.txt + ../submodule/subsub/d + ../submodule/subsub/e.txt + EOF + git -C b ls-files --recurse-submodules -- ../submodule >actual && + test_cmp expect actual +' + +test_expect_success '--recurse-submodules does not support --error-unmatch' ' + test_must_fail git ls-files --recurse-submodules --error-unmatch 2>actual && + test_i18ngrep "does not support --error-unmatch" actual +' + +test_incompatible_with_recurse_submodules () { + test_expect_success "--recurse-submodules and $1 are incompatible" " + test_must_fail git ls-files --recurse-submodules $1 2>actual && + test_i18ngrep 'unsupported mode' actual + " +} + +test_incompatible_with_recurse_submodules --deleted +test_incompatible_with_recurse_submodules --modified +test_incompatible_with_recurse_submodules --others +test_incompatible_with_recurse_submodules --stage +test_incompatible_with_recurse_submodules --killed +test_incompatible_with_recurse_submodules --unmerged + +test_done diff --git a/t/t3008-ls-files-lazy-init-name-hash.sh b/t/t3008-ls-files-lazy-init-name-hash.sh new file mode 100755 index 0000000000..08af596ba6 --- /dev/null +++ b/t/t3008-ls-files-lazy-init-name-hash.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +test_description='Test the lazy init name hash with various folder structures' + +. ./test-lib.sh + +if test 1 -eq $($GIT_BUILD_DIR/t/helper/test-tool online-cpus) +then + skip_all='skipping lazy-init tests, single cpu' + test_done +fi + +LAZY_THREAD_COST=2000 + +test_expect_success 'no buffer overflow in lazy_init_name_hash' ' + ( + test_seq $LAZY_THREAD_COST | sed "s/^/a_/" + echo b/b/b + test_seq $LAZY_THREAD_COST | sed "s/^/c_/" + test_seq 50 | sed "s/^/d_/" | tr "\n" "/"; echo d + ) | + sed "s/^/100644 $EMPTY_BLOB /" | + git update-index --index-info && + test-tool lazy-init-name-hash -m +' + +test_done diff --git a/t/t3020-ls-files-error-unmatch.sh b/t/t3020-ls-files-error-unmatch.sh index ca01053bcc..124e73b8e6 100755 --- a/t/t3020-ls-files-error-unmatch.sh +++ b/t/t3020-ls-files-error-unmatch.sh @@ -22,7 +22,7 @@ test_expect_success \ 'test_must_fail git ls-files --error-unmatch foo bar-does-not-match' test_expect_success \ - 'git ls-files --error-unmatch should succeed eith matched paths.' \ + 'git ls-files --error-unmatch should succeed with matched paths.' \ 'git ls-files --error-unmatch foo bar' test_done diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh index 82e18548c3..3563e77b37 100755 --- a/t/t3030-merge-recursive.sh +++ b/t/t3030-merge-recursive.sh @@ -263,7 +263,7 @@ test_expect_success 'setup 8' ' test_ln_s_add e a && test_tick && git commit -m "rename a->e, symlink a->e" && - oln=`printf e | git hash-object --stdin` + oln=$(printf e | git hash-object --stdin) ' test_expect_success 'setup 9' ' @@ -525,20 +525,22 @@ test_expect_success 'merge-recursive w/ empty work tree - ours has rename' ' GIT_INDEX_FILE="$PWD/ours-has-rename-index" && export GIT_INDEX_FILE && mkdir "$GIT_WORK_TREE" && - git read-tree -i -m $c7 && - git update-index --ignore-missing --refresh && - git merge-recursive $c0 -- $c7 $c3 && - git ls-files -s >actual-files - ) 2>actual-err && - >expected-err && + git read-tree -i -m $c7 2>actual-err && + test_must_be_empty actual-err && + git update-index --ignore-missing --refresh 2>actual-err && + test_must_be_empty actual-err && + git merge-recursive $c0 -- $c7 $c3 2>actual-err && + test_must_be_empty actual-err && + git ls-files -s >actual-files 2>actual-err && + test_must_be_empty actual-err + ) && cat >expected-files <<-EOF && 100644 $o3 0 b/c 100644 $o0 0 c 100644 $o0 0 d/e 100644 $o0 0 e EOF - test_cmp expected-files actual-files && - test_cmp expected-err actual-err + test_cmp expected-files actual-files ' test_expect_success 'merge-recursive w/ empty work tree - theirs has rename' ' @@ -548,20 +550,22 @@ test_expect_success 'merge-recursive w/ empty work tree - theirs has rename' ' GIT_INDEX_FILE="$PWD/theirs-has-rename-index" && export GIT_INDEX_FILE && mkdir "$GIT_WORK_TREE" && - git read-tree -i -m $c3 && - git update-index --ignore-missing --refresh && - git merge-recursive $c0 -- $c3 $c7 && - git ls-files -s >actual-files - ) 2>actual-err && - >expected-err && + git read-tree -i -m $c3 2>actual-err && + test_must_be_empty actual-err && + git update-index --ignore-missing --refresh 2>actual-err && + test_must_be_empty actual-err && + git merge-recursive $c0 -- $c3 $c7 2>actual-err && + test_must_be_empty actual-err && + git ls-files -s >actual-files 2>actual-err && + test_must_be_empty actual-err + ) && cat >expected-files <<-EOF && 100644 $o3 0 b/c 100644 $o0 0 c 100644 $o0 0 d/e 100644 $o0 0 e EOF - test_cmp expected-files actual-files && - test_cmp expected-err actual-err + test_cmp expected-files actual-files ' test_expect_success 'merge removes empty directories' ' @@ -575,13 +579,13 @@ test_expect_success 'merge removes empty directories' ' test_must_fail test -d d ' -test_expect_failure 'merge-recursive simple w/submodule' ' +test_expect_success 'merge-recursive simple w/submodule' ' git checkout submod && git merge remove ' -test_expect_failure 'merge-recursive simple w/submodule result' ' +test_expect_success 'merge-recursive simple w/submodule result' ' git ls-files -s >actual && ( @@ -629,5 +633,103 @@ test_expect_failure 'merge-recursive rename vs. rename/symlink' ' test_cmp expected actual ' +test_expect_success 'merging with triple rename across D/F conflict' ' + git reset --hard HEAD && + git checkout -b main && + git rm -rf . && + + echo "just a file" >sub1 && + mkdir -p sub2 && + echo content1 >sub2/file1 && + echo content2 >sub2/file2 && + echo content3 >sub2/file3 && + mkdir simple && + echo base >simple/bar && + git add -A && + test_tick && + git commit -m base && + + git checkout -b other && + echo more >>simple/bar && + test_tick && + git commit -a -m changesimplefile && + + git checkout main && + git rm sub1 && + git mv sub2 sub1 && + test_tick && + git commit -m changefiletodir && + + test_tick && + git merge other +' + +test_expect_success 'merge-recursive remembers the names of all base trees' ' + git reset --hard HEAD && + + # more trees than static slots used by oid_to_hex() + for commit in $c0 $c2 $c4 $c5 $c6 $c7 + do + git rev-parse "$commit^{tree}" + done >trees && + + # ignore the return code -- it only fails because the input is weird + test_must_fail git -c merge.verbosity=5 merge-recursive $(cat trees) -- $c1 $c3 >out && + + # merge-recursive prints in reverse order, but we do not care + sort <trees >expect && + sed -n "s/^virtual //p" out | sort >actual && + test_cmp expect actual +' + +test_expect_success 'merge-recursive internal merge resolves to the sameness' ' + git reset --hard HEAD && + + # We are going to create a history leading to two criss-cross + # branches A and B. The common ancestor at the bottom, O0, + # has two child commits O1 and O2, both of which will be merge + # base between A and B, like so: + # + # O1---A + # / \ / + # O0 . + # \ / \ + # O2---B + # + # The recently added "check to see if the index is different from + # the tree into which something else is getting merged" check must + # NOT kick in when an inner merge between O1 and O2 is made. Both + # O1 and O2 happen to have the same tree as O0 in this test to + # trigger the bug---whether the inner merge is made by merging O2 + # into O1 or O1 into O2, their common ancestor O0 and the branch + # being merged have the same tree. We should not trigger the "is + # the index dirty?" check in this case. + + echo "zero" >file && + git add file && + test_tick && + git commit -m "O0" && + O0=$(git rev-parse HEAD) && + + test_tick && + git commit --allow-empty -m "O1" && + O1=$(git rev-parse HEAD) && + + git reset --hard $O0 && + test_tick && + git commit --allow-empty -m "O2" && + O2=$(git rev-parse HEAD) && + + test_tick && + git merge -s ours $O1 && + B=$(git rev-parse HEAD) && + + git reset --hard $O1 && + test_tick && + git merge -s ours $O2 && + A=$(git rev-parse HEAD) && + + git merge $B +' test_done diff --git a/t/t3032-merge-recursive-options.sh b/t/t3032-merge-recursive-space-options.sh index 4029c9c8c0..b56180ee4a 100755 --- a/t/t3032-merge-recursive-options.sh +++ b/t/t3032-merge-recursive-space-options.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='merge-recursive options +test_description='merge-recursive space options * [master] Clarify ! [remote] Remove cruft diff --git a/t/t3033-merge-toplevel.sh b/t/t3033-merge-toplevel.sh index 46aadc410b..d314599428 100755 --- a/t/t3033-merge-toplevel.sh +++ b/t/t3033-merge-toplevel.sh @@ -19,6 +19,8 @@ test_expect_success setup ' test_commit three && git checkout right && test_commit four && + git checkout --orphan newroot && + test_commit five && git checkout master ' @@ -133,4 +135,18 @@ test_expect_success 'merge FETCH_HEAD octopus non-fast-forward' ' test_cmp expect actual ' +# two-project merge +test_expect_success 'refuse two-project merge by default' ' + t3033_reset && + git reset --hard four && + test_must_fail git merge five +' + +test_expect_success 'two-project merge with --allow-unrelated-histories' ' + t3033_reset && + git reset --hard four && + git merge --allow-unrelated-histories five && + git diff --exit-code five +' + test_done diff --git a/t/t3034-merge-recursive-rename-options.sh b/t/t3034-merge-recursive-rename-options.sh new file mode 100755 index 0000000000..b9c4028496 --- /dev/null +++ b/t/t3034-merge-recursive-rename-options.sh @@ -0,0 +1,312 @@ +#!/bin/sh + +test_description='merge-recursive rename options + +Test rename detection by examining rename/delete conflicts. + +* (HEAD -> rename) rename +| * (master) delete +|/ +* base + +git diff --name-status base master +D 0-old +D 1-old +D 2-old +D 3-old + +git diff --name-status -M01 base rename +R025 0-old 0-new +R050 1-old 1-new +R075 2-old 2-new +R100 3-old 3-new + +Actual similarity indices are parsed from diff output. We rely on the fact that +they are rounded down (see, e.g., Documentation/diff-generate-patch.txt, which +mentions this in a different context). +' + +. ./test-lib.sh + +get_expected_stages () { + git checkout rename -- $1-new && + git ls-files --stage $1-new >expected-stages-undetected-$1 && + sed "s/ 0 / 2 /" <expected-stages-undetected-$1 \ + >expected-stages-detected-$1 && + git read-tree -u --reset HEAD +} + +rename_detected () { + git ls-files --stage $1-old $1-new >stages-actual-$1 && + test_cmp expected-stages-detected-$1 stages-actual-$1 +} + +rename_undetected () { + git ls-files --stage $1-old $1-new >stages-actual-$1 && + test_cmp expected-stages-undetected-$1 stages-actual-$1 +} + +check_common () { + git ls-files --stage >stages-actual && + test_line_count = 4 stages-actual +} + +check_threshold_0 () { + check_common && + rename_detected 0 && + rename_detected 1 && + rename_detected 2 && + rename_detected 3 +} + +check_threshold_1 () { + check_common && + rename_undetected 0 && + rename_detected 1 && + rename_detected 2 && + rename_detected 3 +} + +check_threshold_2 () { + check_common && + rename_undetected 0 && + rename_undetected 1 && + rename_detected 2 && + rename_detected 3 +} + +check_exact_renames () { + check_common && + rename_undetected 0 && + rename_undetected 1 && + rename_undetected 2 && + rename_detected 3 +} + +check_no_renames () { + check_common && + rename_undetected 0 && + rename_undetected 1 && + rename_undetected 2 && + rename_undetected 3 +} + +test_expect_success 'setup repo' ' + cat <<-\EOF >3-old && + 33a + 33b + 33c + 33d + EOF + sed s/33/22/ <3-old >2-old && + sed s/33/11/ <3-old >1-old && + sed s/33/00/ <3-old >0-old && + git add [0-3]-old && + git commit -m base && + git rm [0-3]-old && + git commit -m delete && + git checkout -b rename HEAD^ && + cp 3-old 3-new && + sed 1,1s/./x/ <2-old >2-new && + sed 1,2s/./x/ <1-old >1-new && + sed 1,3s/./x/ <0-old >0-new && + git add [0-3]-new && + git rm [0-3]-old && + git commit -m rename && + get_expected_stages 0 && + get_expected_stages 1 && + get_expected_stages 2 && + get_expected_stages 3 && + check_50="false" && + tail="HEAD^ -- HEAD master" +' + +test_expect_success 'setup thresholds' ' + git diff --name-status -M01 HEAD^ HEAD >diff-output && + test_debug "cat diff-output" && + test_line_count = 4 diff-output && + grep "R[0-9][0-9][0-9] \([0-3]\)-old \1-new" diff-output \ + >grep-output && + test_cmp diff-output grep-output && + th0=$(sed -n "s/R\(...\) 0-old 0-new/\1/p" <diff-output) && + th1=$(sed -n "s/R\(...\) 1-old 1-new/\1/p" <diff-output) && + th2=$(sed -n "s/R\(...\) 2-old 2-new/\1/p" <diff-output) && + th3=$(sed -n "s/R\(...\) 3-old 3-new/\1/p" <diff-output) && + test "$th0" -lt "$th1" && + test "$th1" -lt "$th2" && + test "$th2" -lt "$th3" && + test "$th3" = 100 && + if test 50 -le "$th0" + then + check_50=check_threshold_0 + elif test 50 -le "$th1" + then + check_50=check_threshold_1 + elif test 50 -le "$th2" + then + check_50=check_threshold_2 + fi && + th0="$th0%" && + th1="$th1%" && + th2="$th2%" && + th3="$th3%" +' + +test_expect_success 'assumption for tests: rename detection with diff' ' + git diff --name-status -M$th0 --diff-filter=R HEAD^ HEAD \ + >diff-output-0 && + git diff --name-status -M$th1 --diff-filter=R HEAD^ HEAD \ + >diff-output-1 && + git diff --name-status -M$th2 --diff-filter=R HEAD^ HEAD \ + >diff-output-2 && + git diff --name-status -M100% --diff-filter=R HEAD^ HEAD \ + >diff-output-3 && + test_line_count = 4 diff-output-0 && + test_line_count = 3 diff-output-1 && + test_line_count = 2 diff-output-2 && + test_line_count = 1 diff-output-3 +' + +test_expect_success 'default similarity threshold is 50%' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive $tail && + $check_50 +' + +test_expect_success 'low rename threshold' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --find-renames=$th0 $tail && + check_threshold_0 +' + +test_expect_success 'medium rename threshold' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --find-renames=$th1 $tail && + check_threshold_1 +' + +test_expect_success 'high rename threshold' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --find-renames=$th2 $tail && + check_threshold_2 +' + +test_expect_success 'exact renames only' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --find-renames=100% $tail && + check_exact_renames +' + +test_expect_success 'rename threshold is truncated' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --find-renames=200% $tail && + check_exact_renames +' + +test_expect_success 'disabled rename detection' ' + git read-tree --reset -u HEAD && + git merge-recursive --no-renames $tail && + check_no_renames +' + +test_expect_success 'last wins in --find-renames=<m> --find-renames=<n>' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive \ + --find-renames=$th0 --find-renames=$th2 $tail && + check_threshold_2 +' + +test_expect_success '--find-renames resets threshold' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive \ + --find-renames=$th0 --find-renames $tail && + $check_50 +' + +test_expect_success 'last wins in --no-renames --find-renames' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --no-renames --find-renames $tail && + $check_50 +' + +test_expect_success 'last wins in --find-renames --no-renames' ' + git read-tree --reset -u HEAD && + git merge-recursive --find-renames --no-renames $tail && + check_no_renames +' + +test_expect_success 'assumption for further tests: trivial merge succeeds' ' + git read-tree --reset -u HEAD && + git merge-recursive HEAD -- HEAD HEAD && + git diff --quiet --cached && + git merge-recursive --find-renames=$th0 HEAD -- HEAD HEAD && + git diff --quiet --cached && + git merge-recursive --find-renames=$th2 HEAD -- HEAD HEAD && + git diff --quiet --cached && + git merge-recursive --find-renames=100% HEAD -- HEAD HEAD && + git diff --quiet --cached && + git merge-recursive --no-renames HEAD -- HEAD HEAD && + git diff --quiet --cached +' + +test_expect_success '--find-renames rejects negative argument' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --find-renames=-25 \ + HEAD -- HEAD HEAD && + git diff --quiet --cached +' + +test_expect_success '--find-renames rejects non-numbers' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --find-renames=0xf \ + HEAD -- HEAD HEAD && + git diff --quiet --cached +' + +test_expect_success 'rename-threshold=<n> is a synonym for find-renames=<n>' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --rename-threshold=$th0 $tail && + check_threshold_0 +' + +test_expect_success 'last wins in --no-renames --rename-threshold=<n>' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --no-renames --rename-threshold=$th0 $tail && + check_threshold_0 +' + +test_expect_success 'last wins in --rename-threshold=<n> --no-renames' ' + git read-tree --reset -u HEAD && + git merge-recursive --rename-threshold=$th0 --no-renames $tail && + check_no_renames +' + +test_expect_success '--rename-threshold=<n> rejects negative argument' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --rename-threshold=-25 \ + HEAD -- HEAD HEAD && + git diff --quiet --cached +' + +test_expect_success '--rename-threshold=<n> rejects non-numbers' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --rename-threshold=0xf \ + HEAD -- HEAD HEAD && + git diff --quiet --cached +' + +test_expect_success 'last wins in --rename-threshold=<m> --find-renames=<n>' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive \ + --rename-threshold=$th0 --find-renames=$th2 $tail && + check_threshold_2 +' + +test_expect_success 'last wins in --find-renames=<m> --rename-threshold=<n>' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive \ + --find-renames=$th2 --rename-threshold=$th0 $tail && + check_threshold_0 +' + +test_done diff --git a/t/t3040-subprojects-basic.sh b/t/t3040-subprojects-basic.sh index 0a4ff6d824..b81eb5fd6f 100755 --- a/t/t3040-subprojects-basic.sh +++ b/t/t3040-subprojects-basic.sh @@ -19,7 +19,7 @@ test_expect_success 'setup: create subprojects' ' git update-index --add sub1 && git add sub2 && git commit -q -m "subprojects added" && - git diff-tree --abbrev=5 HEAD^ HEAD |cut -d" " -f-3,5- >current && + GIT_PRINT_SHA1_ELLIPSIS="yes" git diff-tree --abbrev=5 HEAD^ HEAD |cut -d" " -f-3,5- >current && git branch save HEAD && cat >expected <<-\EOF && :000000 160000 00000... A sub1 diff --git a/t/t3070-wildmatch.sh b/t/t3070-wildmatch.sh index ef509df351..dce102130f 100755 --- a/t/t3070-wildmatch.sh +++ b/t/t3070-wildmatch.sh @@ -4,265 +4,431 @@ test_description='wildmatch tests' . ./test-lib.sh -match() { - if [ $1 = 1 ]; then - test_expect_success "wildmatch: match '$3' '$4'" " - test-wildmatch wildmatch '$3' '$4' - " - else - test_expect_success "wildmatch: no match '$3' '$4'" " - ! test-wildmatch wildmatch '$3' '$4' - " - fi +should_create_test_file() { + file=$1 + + case $file in + # `touch .` will succeed but obviously not do what we intend + # here. + ".") + return 1 + ;; + # We cannot create a file with an empty filename. + "") + return 1 + ;; + # The tests that are testing that e.g. foo//bar is matched by + # foo/*/bar can't be tested on filesystems since there's no + # way we're getting a double slash. + *//*) + return 1 + ;; + # When testing the difference between foo/bar and foo/bar/ we + # can't test the latter. + */) + return 1 + ;; + # On Windows, \ in paths is silently converted to /, which + # would result in the "touch" below working, but the test + # itself failing. See 6fd1106aa4 ("t3700: Skip a test with + # backslashes in pathspec", 2009-03-13) for prior art and + # details. + *\\*) + if ! test_have_prereq BSLASHPSPEC + then + return 1 + fi + # NOTE: The ;;& bash extension is not portable, so + # this test needs to be at the end of the pattern + # list. + # + # If we want to add more conditional returns we either + # need a new case statement, or turn this whole thing + # into a series of "if" tests. + ;; + esac + + + # On Windows proper (i.e. not Cygwin) many file names which + # under Cygwin would be emulated don't work. + if test_have_prereq MINGW + then + case $file in + " ") + # Files called " " are forbidden on Windows + return 1 + ;; + *\<*|*\>*|*:*|*\"*|*\|*|*\?*|*\**) + # Files with various special characters aren't + # allowed on Windows. Sourced from + # https://stackoverflow.com/a/31976060 + return 1 + ;; + esac + fi + + return 0 } -imatch() { - if [ $1 = 1 ]; then - test_expect_success "iwildmatch: match '$2' '$3'" " - test-wildmatch iwildmatch '$2' '$3' - " - else - test_expect_success "iwildmatch: no match '$2' '$3'" " - ! test-wildmatch iwildmatch '$2' '$3' - " - fi +match_with_function() { + text=$1 + pattern=$2 + match_expect=$3 + match_function=$4 + + if test "$match_expect" = 1 + then + test_expect_success "$match_function: match '$text' '$pattern'" " + test-tool wildmatch $match_function '$text' '$pattern' + " + elif test "$match_expect" = 0 + then + test_expect_success "$match_function: no match '$text' '$pattern'" " + test_must_fail test-tool wildmatch $match_function '$text' '$pattern' + " + else + test_expect_success "PANIC: Test framework error. Unknown matches value $match_expect" 'false' + fi + +} + +match_with_ls_files() { + text=$1 + pattern=$2 + match_expect=$3 + match_function=$4 + ls_files_args=$5 + + match_stdout_stderr_cmp=" + tr -d '\0' <actual.raw >actual && + >expect.err && + test_cmp expect.err actual.err && + test_cmp expect actual" + + if test "$match_expect" = 'E' + then + if test -e .git/created_test_file + then + test_expect_success EXPENSIVE_ON_WINDOWS "$match_function (via ls-files): match dies on '$pattern' '$text'" " + printf '%s' '$text' >expect && + test_must_fail git$ls_files_args ls-files -z -- '$pattern' + " + else + test_expect_failure EXPENSIVE_ON_WINDOWS "$match_function (via ls-files): match skip '$pattern' '$text'" 'false' + fi + elif test "$match_expect" = 1 + then + if test -e .git/created_test_file + then + test_expect_success EXPENSIVE_ON_WINDOWS "$match_function (via ls-files): match '$pattern' '$text'" " + printf '%s' '$text' >expect && + git$ls_files_args ls-files -z -- '$pattern' >actual.raw 2>actual.err && + $match_stdout_stderr_cmp + " + else + test_expect_failure EXPENSIVE_ON_WINDOWS "$match_function (via ls-files): match skip '$pattern' '$text'" 'false' + fi + elif test "$match_expect" = 0 + then + if test -e .git/created_test_file + then + test_expect_success EXPENSIVE_ON_WINDOWS "$match_function (via ls-files): no match '$pattern' '$text'" " + >expect && + git$ls_files_args ls-files -z -- '$pattern' >actual.raw 2>actual.err && + $match_stdout_stderr_cmp + " + else + test_expect_failure EXPENSIVE_ON_WINDOWS "$match_function (via ls-files): no match skip '$pattern' '$text'" 'false' + fi + else + test_expect_success "PANIC: Test framework error. Unknown matches value $match_expect" 'false' + fi } -pathmatch() { - if [ $1 = 1 ]; then - test_expect_success "pathmatch: match '$2' '$3'" " - test-wildmatch pathmatch '$2' '$3' - " - else - test_expect_success "pathmatch: no match '$2' '$3'" " - ! test-wildmatch pathmatch '$2' '$3' - " - fi +match() { + if test "$#" = 6 + then + # When test-tool wildmatch and git ls-files produce the same + # result. + match_glob=$1 + match_file_glob=$match_glob + match_iglob=$2 + match_file_iglob=$match_iglob + match_pathmatch=$3 + match_file_pathmatch=$match_pathmatch + match_pathmatchi=$4 + match_file_pathmatchi=$match_pathmatchi + text=$5 + pattern=$6 + elif test "$#" = 10 + then + match_glob=$1 + match_iglob=$2 + match_pathmatch=$3 + match_pathmatchi=$4 + match_file_glob=$5 + match_file_iglob=$6 + match_file_pathmatch=$7 + match_file_pathmatchi=$8 + text=$9 + pattern=${10} + fi + + test_expect_success EXPENSIVE_ON_WINDOWS 'cleanup after previous file test' ' + if test -e .git/created_test_file + then + git reset && + git clean -df + fi + ' + + printf '%s' "$text" >.git/expected_test_file + + test_expect_success EXPENSIVE_ON_WINDOWS "setup match file test for $text" ' + file=$(cat .git/expected_test_file) && + if should_create_test_file "$file" + then + dirs=${file%/*} + if test "$file" != "$dirs" + then + mkdir -p -- "$dirs" && + touch -- "./$text" + else + touch -- "./$file" + fi && + git add -A && + printf "%s" "$file" >.git/created_test_file + elif test -e .git/created_test_file + then + rm .git/created_test_file + fi + ' + + # $1: Case sensitive glob match: test-tool wildmatch & ls-files + match_with_function "$text" "$pattern" $match_glob "wildmatch" + match_with_ls_files "$text" "$pattern" $match_file_glob "wildmatch" " --glob-pathspecs" + + # $2: Case insensitive glob match: test-tool wildmatch & ls-files + match_with_function "$text" "$pattern" $match_iglob "iwildmatch" + match_with_ls_files "$text" "$pattern" $match_file_iglob "iwildmatch" " --glob-pathspecs --icase-pathspecs" + + # $3: Case sensitive path match: test-tool wildmatch & ls-files + match_with_function "$text" "$pattern" $match_pathmatch "pathmatch" + match_with_ls_files "$text" "$pattern" $match_file_pathmatch "pathmatch" "" + + # $4: Case insensitive path match: test-tool wildmatch & ls-files + match_with_function "$text" "$pattern" $match_pathmatchi "ipathmatch" + match_with_ls_files "$text" "$pattern" $match_file_pathmatchi "ipathmatch" " --icase-pathspecs" } -# Basic wildmat features -match 1 1 foo foo -match 0 0 foo bar -match 1 1 '' "" -match 1 1 foo '???' -match 0 0 foo '??' -match 1 1 foo '*' -match 1 1 foo 'f*' -match 0 0 foo '*f' -match 1 1 foo '*foo*' -match 1 1 foobar '*ob*a*r*' -match 1 1 aaaaaaabababab '*ab' -match 1 1 'foo*' 'foo\*' -match 0 0 foobar 'foo\*bar' -match 1 1 'f\oo' 'f\\oo' -match 1 1 ball '*[al]?' -match 0 0 ten '[ten]' -match 0 1 ten '**[!te]' -match 0 0 ten '**[!ten]' -match 1 1 ten 't[a-g]n' -match 0 0 ten 't[!a-g]n' -match 1 1 ton 't[!a-g]n' -match 1 1 ton 't[^a-g]n' -match 1 x 'a]b' 'a[]]b' -match 1 x a-b 'a[]-]b' -match 1 x 'a]b' 'a[]-]b' -match 0 x aab 'a[]-]b' -match 1 x aab 'a[]a-]b' -match 1 1 ']' ']' +# Basic wildmatch features +match 1 1 1 1 foo foo +match 0 0 0 0 foo bar +match 1 1 1 1 '' "" +match 1 1 1 1 foo '???' +match 0 0 0 0 foo '??' +match 1 1 1 1 foo '*' +match 1 1 1 1 foo 'f*' +match 0 0 0 0 foo '*f' +match 1 1 1 1 foo '*foo*' +match 1 1 1 1 foobar '*ob*a*r*' +match 1 1 1 1 aaaaaaabababab '*ab' +match 1 1 1 1 'foo*' 'foo\*' +match 0 0 0 0 foobar 'foo\*bar' +match 1 1 1 1 'f\oo' 'f\\oo' +match 1 1 1 1 ball '*[al]?' +match 0 0 0 0 ten '[ten]' +match 0 0 1 1 ten '**[!te]' +match 0 0 0 0 ten '**[!ten]' +match 1 1 1 1 ten 't[a-g]n' +match 0 0 0 0 ten 't[!a-g]n' +match 1 1 1 1 ton 't[!a-g]n' +match 1 1 1 1 ton 't[^a-g]n' +match 1 1 1 1 'a]b' 'a[]]b' +match 1 1 1 1 a-b 'a[]-]b' +match 1 1 1 1 'a]b' 'a[]-]b' +match 0 0 0 0 aab 'a[]-]b' +match 1 1 1 1 aab 'a[]a-]b' +match 1 1 1 1 ']' ']' # Extended slash-matching features -match 0 0 'foo/baz/bar' 'foo*bar' -match 0 0 'foo/baz/bar' 'foo**bar' -match 0 1 'foobazbar' 'foo**bar' -match 1 1 'foo/baz/bar' 'foo/**/bar' -match 1 0 'foo/baz/bar' 'foo/**/**/bar' -match 1 0 'foo/b/a/z/bar' 'foo/**/bar' -match 1 0 'foo/b/a/z/bar' 'foo/**/**/bar' -match 1 0 'foo/bar' 'foo/**/bar' -match 1 0 'foo/bar' 'foo/**/**/bar' -match 0 0 'foo/bar' 'foo?bar' -match 0 0 'foo/bar' 'foo[/]bar' -match 0 0 'foo/bar' 'f[^eiu][^eiu][^eiu][^eiu][^eiu]r' -match 1 1 'foo-bar' 'f[^eiu][^eiu][^eiu][^eiu][^eiu]r' -match 1 0 'foo' '**/foo' -match 1 x 'XXX/foo' '**/foo' -match 1 0 'bar/baz/foo' '**/foo' -match 0 0 'bar/baz/foo' '*/foo' -match 0 0 'foo/bar/baz' '**/bar*' -match 1 0 'deep/foo/bar/baz' '**/bar/*' -match 0 0 'deep/foo/bar/baz/' '**/bar/*' -match 1 0 'deep/foo/bar/baz/' '**/bar/**' -match 0 0 'deep/foo/bar' '**/bar/*' -match 1 0 'deep/foo/bar/' '**/bar/**' -match 0 0 'foo/bar/baz' '**/bar**' -match 1 0 'foo/bar/baz/x' '*/bar/**' -match 0 0 'deep/foo/bar/baz/x' '*/bar/**' -match 1 0 'deep/foo/bar/baz/x' '**/bar/*/*' +match 0 0 1 1 'foo/baz/bar' 'foo*bar' +match 0 0 1 1 'foo/baz/bar' 'foo**bar' +match 0 0 1 1 'foobazbar' 'foo**bar' +match 1 1 1 1 'foo/baz/bar' 'foo/**/bar' +match 1 1 0 0 'foo/baz/bar' 'foo/**/**/bar' +match 1 1 1 1 'foo/b/a/z/bar' 'foo/**/bar' +match 1 1 1 1 'foo/b/a/z/bar' 'foo/**/**/bar' +match 1 1 0 0 'foo/bar' 'foo/**/bar' +match 1 1 0 0 'foo/bar' 'foo/**/**/bar' +match 0 0 1 1 'foo/bar' 'foo?bar' +match 0 0 1 1 'foo/bar' 'foo[/]bar' +match 0 0 1 1 'foo/bar' 'foo[^a-z]bar' +match 0 0 1 1 'foo/bar' 'f[^eiu][^eiu][^eiu][^eiu][^eiu]r' +match 1 1 1 1 'foo-bar' 'f[^eiu][^eiu][^eiu][^eiu][^eiu]r' +match 1 1 0 0 'foo' '**/foo' +match 1 1 1 1 'XXX/foo' '**/foo' +match 1 1 1 1 'bar/baz/foo' '**/foo' +match 0 0 1 1 'bar/baz/foo' '*/foo' +match 0 0 1 1 'foo/bar/baz' '**/bar*' +match 1 1 1 1 'deep/foo/bar/baz' '**/bar/*' +match 0 0 1 1 'deep/foo/bar/baz/' '**/bar/*' +match 1 1 1 1 'deep/foo/bar/baz/' '**/bar/**' +match 0 0 0 0 'deep/foo/bar' '**/bar/*' +match 1 1 1 1 'deep/foo/bar/' '**/bar/**' +match 0 0 1 1 'foo/bar/baz' '**/bar**' +match 1 1 1 1 'foo/bar/baz/x' '*/bar/**' +match 0 0 1 1 'deep/foo/bar/baz/x' '*/bar/**' +match 1 1 1 1 'deep/foo/bar/baz/x' '**/bar/*/*' # Various additional tests -match 0 0 'acrt' 'a[c-c]st' -match 1 1 'acrt' 'a[c-c]rt' -match 0 0 ']' '[!]-]' -match 1 x 'a' '[!]-]' -match 0 0 '' '\' -match 0 x '\' '\' -match 0 x 'XXX/\' '*/\' -match 1 x 'XXX/\' '*/\\' -match 1 1 'foo' 'foo' -match 1 1 '@foo' '@foo' -match 0 0 'foo' '@foo' -match 1 1 '[ab]' '\[ab]' -match 1 1 '[ab]' '[[]ab]' -match 1 x '[ab]' '[[:]ab]' -match 0 x '[ab]' '[[::]ab]' -match 1 x '[ab]' '[[:digit]ab]' -match 1 x '[ab]' '[\[:]ab]' -match 1 1 '?a?b' '\??\?b' -match 1 1 'abc' '\a\b\c' -match 0 0 'foo' '' -match 1 0 'foo/bar/baz/to' '**/t[o]' +match 0 0 0 0 'acrt' 'a[c-c]st' +match 1 1 1 1 'acrt' 'a[c-c]rt' +match 0 0 0 0 ']' '[!]-]' +match 1 1 1 1 'a' '[!]-]' +match 0 0 0 0 '' '\' +match 0 0 0 0 \ + 1 1 1 1 '\' '\' +match 0 0 0 0 'XXX/\' '*/\' +match 1 1 1 1 'XXX/\' '*/\\' +match 1 1 1 1 'foo' 'foo' +match 1 1 1 1 '@foo' '@foo' +match 0 0 0 0 'foo' '@foo' +match 1 1 1 1 '[ab]' '\[ab]' +match 1 1 1 1 '[ab]' '[[]ab]' +match 1 1 1 1 '[ab]' '[[:]ab]' +match 0 0 0 0 '[ab]' '[[::]ab]' +match 1 1 1 1 '[ab]' '[[:digit]ab]' +match 1 1 1 1 '[ab]' '[\[:]ab]' +match 1 1 1 1 '?a?b' '\??\?b' +match 1 1 1 1 'abc' '\a\b\c' +match 0 0 0 0 \ + E E E E 'foo' '' +match 1 1 1 1 'foo/bar/baz/to' '**/t[o]' # Character class tests -match 1 x 'a1B' '[[:alpha:]][[:digit:]][[:upper:]]' -match 0 x 'a' '[[:digit:][:upper:][:space:]]' -match 1 x 'A' '[[:digit:][:upper:][:space:]]' -match 1 x '1' '[[:digit:][:upper:][:space:]]' -match 0 x '1' '[[:digit:][:upper:][:spaci:]]' -match 1 x ' ' '[[:digit:][:upper:][:space:]]' -match 0 x '.' '[[:digit:][:upper:][:space:]]' -match 1 x '.' '[[:digit:][:punct:][:space:]]' -match 1 x '5' '[[:xdigit:]]' -match 1 x 'f' '[[:xdigit:]]' -match 1 x 'D' '[[:xdigit:]]' -match 1 x '_' '[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]' -match 1 x '_' '[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]' -match 1 x '.' '[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]' -match 1 x '5' '[a-c[:digit:]x-z]' -match 1 x 'b' '[a-c[:digit:]x-z]' -match 1 x 'y' '[a-c[:digit:]x-z]' -match 0 x 'q' '[a-c[:digit:]x-z]' - -# Additional tests, including some malformed wildmats -match 1 x ']' '[\\-^]' -match 0 0 '[' '[\\-^]' -match 1 x '-' '[\-_]' -match 1 x ']' '[\]]' -match 0 0 '\]' '[\]]' -match 0 0 '\' '[\]]' -match 0 0 'ab' 'a[]b' -match 0 x 'a[]b' 'a[]b' -match 0 x 'ab[' 'ab[' -match 0 0 'ab' '[!' -match 0 0 'ab' '[-' -match 1 1 '-' '[-]' -match 0 0 '-' '[a-' -match 0 0 '-' '[!a-' -match 1 x '-' '[--A]' -match 1 x '5' '[--A]' -match 1 1 ' ' '[ --]' -match 1 1 '$' '[ --]' -match 1 1 '-' '[ --]' -match 0 0 '0' '[ --]' -match 1 x '-' '[---]' -match 1 x '-' '[------]' -match 0 0 'j' '[a-e-n]' -match 1 x '-' '[a-e-n]' -match 1 x 'a' '[!------]' -match 0 0 '[' '[]-a]' -match 1 x '^' '[]-a]' -match 0 0 '^' '[!]-a]' -match 1 x '[' '[!]-a]' -match 1 1 '^' '[a^bc]' -match 1 x '-b]' '[a-]b]' -match 0 0 '\' '[\]' -match 1 1 '\' '[\\]' -match 0 0 '\' '[!\\]' -match 1 1 'G' '[A-\\]' -match 0 0 'aaabbb' 'b*a' -match 0 0 'aabcaa' '*ba*' -match 1 1 ',' '[,]' -match 1 1 ',' '[\\,]' -match 1 1 '\' '[\\,]' -match 1 1 '-' '[,-.]' -match 0 0 '+' '[,-.]' -match 0 0 '-.]' '[,-.]' -match 1 1 '2' '[\1-\3]' -match 1 1 '3' '[\1-\3]' -match 0 0 '4' '[\1-\3]' -match 1 1 '\' '[[-\]]' -match 1 1 '[' '[[-\]]' -match 1 1 ']' '[[-\]]' -match 0 0 '-' '[[-\]]' +match 1 1 1 1 'a1B' '[[:alpha:]][[:digit:]][[:upper:]]' +match 0 1 0 1 'a' '[[:digit:][:upper:][:space:]]' +match 1 1 1 1 'A' '[[:digit:][:upper:][:space:]]' +match 1 1 1 1 '1' '[[:digit:][:upper:][:space:]]' +match 0 0 0 0 '1' '[[:digit:][:upper:][:spaci:]]' +match 1 1 1 1 ' ' '[[:digit:][:upper:][:space:]]' +match 0 0 0 0 '.' '[[:digit:][:upper:][:space:]]' +match 1 1 1 1 '.' '[[:digit:][:punct:][:space:]]' +match 1 1 1 1 '5' '[[:xdigit:]]' +match 1 1 1 1 'f' '[[:xdigit:]]' +match 1 1 1 1 'D' '[[:xdigit:]]' +match 1 1 1 1 '_' '[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]' +match 1 1 1 1 '.' '[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]' +match 1 1 1 1 '5' '[a-c[:digit:]x-z]' +match 1 1 1 1 'b' '[a-c[:digit:]x-z]' +match 1 1 1 1 'y' '[a-c[:digit:]x-z]' +match 0 0 0 0 'q' '[a-c[:digit:]x-z]' -# Test recursion and the abort code (use "wildtest -i" to see iteration counts) -match 1 1 '-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1' '-*-*-*-*-*-*-12-*-*-*-m-*-*-*' -match 0 0 '-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1' '-*-*-*-*-*-*-12-*-*-*-m-*-*-*' -match 0 0 '-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1' '-*-*-*-*-*-*-12-*-*-*-m-*-*-*' -match 1 1 'XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1' 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*' -match 0 0 'XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1' 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*' -match 1 0 'abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt' '**/*a*b*g*n*t' -match 0 0 'abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz' '**/*a*b*g*n*t' -match 0 x foo '*/*/*' -match 0 x foo/bar '*/*/*' -match 1 x foo/bba/arr '*/*/*' -match 0 x foo/bb/aa/rr '*/*/*' -match 1 x foo/bb/aa/rr '**/**/**' -match 1 x abcXdefXghi '*X*i' -match 0 x ab/cXd/efXg/hi '*X*i' -match 1 x ab/cXd/efXg/hi '*/*X*/*/*i' -match 1 x ab/cXd/efXg/hi '**/*X*/**/*i' +# Additional tests, including some malformed wildmatch patterns +match 1 1 1 1 ']' '[\\-^]' +match 0 0 0 0 '[' '[\\-^]' +match 1 1 1 1 '-' '[\-_]' +match 1 1 1 1 ']' '[\]]' +match 0 0 0 0 '\]' '[\]]' +match 0 0 0 0 '\' '[\]]' +match 0 0 0 0 'ab' 'a[]b' +match 0 0 0 0 \ + 1 1 1 1 'a[]b' 'a[]b' +match 0 0 0 0 \ + 1 1 1 1 'ab[' 'ab[' +match 0 0 0 0 'ab' '[!' +match 0 0 0 0 'ab' '[-' +match 1 1 1 1 '-' '[-]' +match 0 0 0 0 '-' '[a-' +match 0 0 0 0 '-' '[!a-' +match 1 1 1 1 '-' '[--A]' +match 1 1 1 1 '5' '[--A]' +match 1 1 1 1 ' ' '[ --]' +match 1 1 1 1 '$' '[ --]' +match 1 1 1 1 '-' '[ --]' +match 0 0 0 0 '0' '[ --]' +match 1 1 1 1 '-' '[---]' +match 1 1 1 1 '-' '[------]' +match 0 0 0 0 'j' '[a-e-n]' +match 1 1 1 1 '-' '[a-e-n]' +match 1 1 1 1 'a' '[!------]' +match 0 0 0 0 '[' '[]-a]' +match 1 1 1 1 '^' '[]-a]' +match 0 0 0 0 '^' '[!]-a]' +match 1 1 1 1 '[' '[!]-a]' +match 1 1 1 1 '^' '[a^bc]' +match 1 1 1 1 '-b]' '[a-]b]' +match 0 0 0 0 '\' '[\]' +match 1 1 1 1 '\' '[\\]' +match 0 0 0 0 '\' '[!\\]' +match 1 1 1 1 'G' '[A-\\]' +match 0 0 0 0 'aaabbb' 'b*a' +match 0 0 0 0 'aabcaa' '*ba*' +match 1 1 1 1 ',' '[,]' +match 1 1 1 1 ',' '[\\,]' +match 1 1 1 1 '\' '[\\,]' +match 1 1 1 1 '-' '[,-.]' +match 0 0 0 0 '+' '[,-.]' +match 0 0 0 0 '-.]' '[,-.]' +match 1 1 1 1 '2' '[\1-\3]' +match 1 1 1 1 '3' '[\1-\3]' +match 0 0 0 0 '4' '[\1-\3]' +match 1 1 1 1 '\' '[[-\]]' +match 1 1 1 1 '[' '[[-\]]' +match 1 1 1 1 ']' '[[-\]]' +match 0 0 0 0 '-' '[[-\]]' -pathmatch 1 foo foo -pathmatch 0 foo fo -pathmatch 1 foo/bar foo/bar -pathmatch 1 foo/bar 'foo/*' -pathmatch 1 foo/bba/arr 'foo/*' -pathmatch 1 foo/bba/arr 'foo/**' -pathmatch 1 foo/bba/arr 'foo*' -pathmatch 1 foo/bba/arr 'foo**' -pathmatch 1 foo/bba/arr 'foo/*arr' -pathmatch 1 foo/bba/arr 'foo/**arr' -pathmatch 0 foo/bba/arr 'foo/*z' -pathmatch 0 foo/bba/arr 'foo/**z' -pathmatch 1 foo/bar 'foo?bar' -pathmatch 1 foo/bar 'foo[/]bar' -pathmatch 0 foo '*/*/*' -pathmatch 0 foo/bar '*/*/*' -pathmatch 1 foo/bba/arr '*/*/*' -pathmatch 1 foo/bb/aa/rr '*/*/*' -pathmatch 1 abcXdefXghi '*X*i' -pathmatch 1 ab/cXd/efXg/hi '*/*X*/*/*i' -pathmatch 1 ab/cXd/efXg/hi '*Xg*i' +# Test recursion +match 1 1 1 1 '-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1' '-*-*-*-*-*-*-12-*-*-*-m-*-*-*' +match 0 0 0 0 '-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1' '-*-*-*-*-*-*-12-*-*-*-m-*-*-*' +match 0 0 0 0 '-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1' '-*-*-*-*-*-*-12-*-*-*-m-*-*-*' +match 1 1 1 1 'XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1' 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*' +match 0 0 0 0 'XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1' 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*' +match 1 1 1 1 'abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt' '**/*a*b*g*n*t' +match 0 0 0 0 'abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz' '**/*a*b*g*n*t' +match 0 0 0 0 foo '*/*/*' +match 0 0 0 0 foo/bar '*/*/*' +match 1 1 1 1 foo/bba/arr '*/*/*' +match 0 0 1 1 foo/bb/aa/rr '*/*/*' +match 1 1 1 1 foo/bb/aa/rr '**/**/**' +match 1 1 1 1 abcXdefXghi '*X*i' +match 0 0 1 1 ab/cXd/efXg/hi '*X*i' +match 1 1 1 1 ab/cXd/efXg/hi '*/*X*/*/*i' +match 1 1 1 1 ab/cXd/efXg/hi '**/*X*/**/*i' -# Case-sensitivy features -match 0 x 'a' '[A-Z]' -match 1 x 'A' '[A-Z]' -match 0 x 'A' '[a-z]' -match 1 x 'a' '[a-z]' -match 0 x 'a' '[[:upper:]]' -match 1 x 'A' '[[:upper:]]' -match 0 x 'A' '[[:lower:]]' -match 1 x 'a' '[[:lower:]]' -match 0 x 'A' '[B-Za]' -match 1 x 'a' '[B-Za]' -match 0 x 'A' '[B-a]' -match 1 x 'a' '[B-a]' -match 0 x 'z' '[Z-y]' -match 1 x 'Z' '[Z-y]' +# Extra pathmatch tests +match 0 0 0 0 foo fo +match 1 1 1 1 foo/bar foo/bar +match 1 1 1 1 foo/bar 'foo/*' +match 0 0 1 1 foo/bba/arr 'foo/*' +match 1 1 1 1 foo/bba/arr 'foo/**' +match 0 0 1 1 foo/bba/arr 'foo*' +match 0 0 1 1 \ + 1 1 1 1 foo/bba/arr 'foo**' +match 0 0 1 1 foo/bba/arr 'foo/*arr' +match 0 0 1 1 foo/bba/arr 'foo/**arr' +match 0 0 0 0 foo/bba/arr 'foo/*z' +match 0 0 0 0 foo/bba/arr 'foo/**z' +match 0 0 1 1 foo/bar 'foo?bar' +match 0 0 1 1 foo/bar 'foo[/]bar' +match 0 0 1 1 foo/bar 'foo[^a-z]bar' +match 0 0 1 1 ab/cXd/efXg/hi '*Xg*i' -imatch 1 'a' '[A-Z]' -imatch 1 'A' '[A-Z]' -imatch 1 'A' '[a-z]' -imatch 1 'a' '[a-z]' -imatch 1 'a' '[[:upper:]]' -imatch 1 'A' '[[:upper:]]' -imatch 1 'A' '[[:lower:]]' -imatch 1 'a' '[[:lower:]]' -imatch 1 'A' '[B-Za]' -imatch 1 'a' '[B-Za]' -imatch 1 'A' '[B-a]' -imatch 1 'a' '[B-a]' -imatch 1 'z' '[Z-y]' -imatch 1 'Z' '[Z-y]' +# Extra case-sensitivity tests +match 0 1 0 1 'a' '[A-Z]' +match 1 1 1 1 'A' '[A-Z]' +match 0 1 0 1 'A' '[a-z]' +match 1 1 1 1 'a' '[a-z]' +match 0 1 0 1 'a' '[[:upper:]]' +match 1 1 1 1 'A' '[[:upper:]]' +match 0 1 0 1 'A' '[[:lower:]]' +match 1 1 1 1 'a' '[[:lower:]]' +match 0 1 0 1 'A' '[B-Za]' +match 1 1 1 1 'a' '[B-Za]' +match 0 1 0 1 'A' '[B-a]' +match 1 1 1 1 'a' '[B-a]' +match 0 1 0 1 'z' '[Z-y]' +match 1 1 1 1 'Z' '[Z-y]' test_done diff --git a/t/t3100-ls-tree-restrict.sh b/t/t3100-ls-tree-restrict.sh index eb73c06a4e..325114f8fe 100755 --- a/t/t3100-ls-tree-restrict.sh +++ b/t/t3100-ls-tree-restrict.sh @@ -28,7 +28,7 @@ test_expect_success \ echo Mi >path2/baz/b && find path? \( -type f -o -type l \) -print | xargs git update-index --add && - tree=`git write-tree` && + tree=$(git write-tree) && echo $tree' test_output () { diff --git a/t/t3101-ls-tree-dirname.sh b/t/t3101-ls-tree-dirname.sh index 026f9f89d9..327ded4000 100755 --- a/t/t3101-ls-tree-dirname.sh +++ b/t/t3101-ls-tree-dirname.sh @@ -16,7 +16,7 @@ This test runs git ls-tree with the following in a tree. path3/1.txt - a file in a directory path3/2.txt - a file in a directory -Test the handling of mulitple directories which have matching file +Test the handling of multiple directories which have matching file entries. Also test odd filename and missing entries handling. ' . ./test-lib.sh @@ -35,7 +35,7 @@ test_expect_success 'setup' ' echo 222 >path3/2.txt && find *.txt path* \( -type f -o -type l \) -print | xargs git update-index --add && - tree=`git write-tree` && + tree=$(git write-tree) && echo $tree ' diff --git a/t/t3102-ls-tree-wildcards.sh b/t/t3102-ls-tree-wildcards.sh index 4d4b02e760..e804377f1c 100755 --- a/t/t3102-ls-tree-wildcards.sh +++ b/t/t3102-ls-tree-wildcards.sh @@ -12,16 +12,16 @@ test_expect_success 'setup' ' ' test_expect_success 'ls-tree a[a] matches literally' ' - cat >expect <<-\EOF && - 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a[a]/three + cat >expect <<-EOF && + 100644 blob $EMPTY_BLOB a[a]/three EOF git ls-tree -r HEAD "a[a]" >actual && test_cmp expect actual ' test_expect_success 'ls-tree outside prefix' ' - cat >expect <<-\EOF && - 100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 ../a[a]/three + cat >expect <<-EOF && + 100644 blob $EMPTY_BLOB ../a[a]/three EOF ( cd aa && git ls-tree -r HEAD "../a[a]"; ) >actual && test_cmp expect actual diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index ddea49808d..c0ef946811 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -6,6 +6,7 @@ test_description='git branch assorted tests' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh test_expect_success 'prepare a trivial repository' ' echo Hello >A && @@ -59,7 +60,7 @@ test_expect_success 'git branch -l d/e/f should create a branch and a log' ' test_expect_success 'git branch -d d/e/f should delete a branch and a log' ' git branch -d d/e/f && test_path_is_missing .git/refs/heads/d/e/f && - test_path_is_missing .git/logs/refs/heads/d/e/f + test_must_fail git reflog exists refs/heads/d/e/f ' test_expect_success 'git branch j/k should work after branch j has been deleted' ' @@ -79,16 +80,52 @@ test_expect_success 'git branch -m dumps usage' ' test_i18ngrep "branch name required" err ' +test_expect_success 'git branch -m m broken_symref should work' ' + test_when_finished "git branch -D broken_symref" && + git branch -l m && + git symbolic-ref refs/heads/broken_symref refs/heads/i_am_broken && + git branch -m m broken_symref && + git reflog exists refs/heads/broken_symref && + test_must_fail git reflog exists refs/heads/i_am_broken +' + test_expect_success 'git branch -m m m/m should work' ' git branch -l m && git branch -m m m/m && - test_path_is_file .git/logs/refs/heads/m/m + git reflog exists refs/heads/m/m ' test_expect_success 'git branch -m n/n n should work' ' git branch -l n/n && git branch -m n/n n && - test_path_is_file .git/logs/refs/heads/n + git reflog exists refs/heads/n +' + +# The topmost entry in reflog for branch bbb is about branch creation. +# Hence, we compare bbb@{1} (instead of bbb@{0}) with aaa@{0}. + +test_expect_success 'git branch -m bbb should rename checked out branch' ' + test_when_finished git branch -D bbb && + test_when_finished git checkout master && + git checkout -b aaa && + git commit --allow-empty -m "a new commit" && + git rev-parse aaa@{0} >expect && + git branch -m bbb && + git rev-parse bbb@{1} >actual && + test_cmp expect actual && + git symbolic-ref HEAD >actual && + echo refs/heads/bbb >expect && + test_cmp expect actual +' + +test_expect_success 'renaming checked out branch works with d/f conflict' ' + test_when_finished "git branch -D foo/bar || git branch -D foo" && + test_when_finished git checkout master && + git checkout -b foo && + git branch -m foo/bar && + git symbolic-ref HEAD >actual && + echo refs/heads/foo/bar >expect && + test_cmp expect actual ' test_expect_success 'git branch -m o/o o should fail when o/p exists' ' @@ -126,7 +163,57 @@ test_expect_success 'git branch -M foo bar should fail when bar is checked out' test_expect_success 'git branch -M baz bam should succeed when baz is checked out' ' git checkout -b baz && git branch bam && - git branch -M baz bam + git branch -M baz bam && + test $(git rev-parse --abbrev-ref HEAD) = bam +' + +test_expect_success 'git branch -M baz bam should add entries to .git/logs/HEAD' ' + msg="Branch: renamed refs/heads/baz to refs/heads/bam" && + grep " 0\{40\}.*$msg$" .git/logs/HEAD && + grep "^0\{40\}.*$msg$" .git/logs/HEAD +' + +test_expect_success 'git branch -M should leave orphaned HEAD alone' ' + git init orphan && + ( + cd orphan && + test_commit initial && + git checkout --orphan lonely && + grep lonely .git/HEAD && + test_path_is_missing .git/refs/head/lonely && + git branch -M master mistress && + grep lonely .git/HEAD + ) +' + +test_expect_success 'resulting reflog can be shown by log -g' ' + oid=$(git rev-parse HEAD) && + cat >expect <<-EOF && + HEAD@{0} $oid $msg + HEAD@{2} $oid checkout: moving from foo to baz + EOF + git log -g --format="%gd %H %gs" -2 HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'git branch -M baz bam should succeed when baz is checked out as linked working tree' ' + git checkout master && + git worktree add -b baz bazdir && + git worktree add -f bazdir2 baz && + git branch -M baz bam && + test $(git -C bazdir rev-parse --abbrev-ref HEAD) = bam && + test $(git -C bazdir2 rev-parse --abbrev-ref HEAD) = bam +' + +test_expect_success 'git branch -M baz bam should succeed within a worktree in which baz is checked out' ' + git checkout -b baz && + git worktree add -f bazdir3 baz && + ( + cd bazdir3 && + git branch -M baz bam && + test $(git rev-parse --abbrev-ref HEAD) = bam + ) && + test $(git rev-parse --abbrev-ref HEAD) = bam ' test_expect_success 'git branch -M master should work when master is checked out' ' @@ -177,6 +264,31 @@ test_expect_success 'git branch --list -d t should fail' ' test_path_is_missing .git/refs/heads/t ' +test_expect_success 'git branch --list -v with --abbrev' ' + test_when_finished "git branch -D t" && + git branch t && + git branch -v --list t >actual.default && + git branch -v --list --abbrev t >actual.abbrev && + test_cmp actual.default actual.abbrev && + + git branch -v --list --no-abbrev t >actual.noabbrev && + git branch -v --list --abbrev=0 t >actual.0abbrev && + test_cmp actual.noabbrev actual.0abbrev && + + git branch -v --list --abbrev=36 t >actual.36abbrev && + # how many hexdigits are used? + read name objdefault rest <actual.abbrev && + read name obj36 rest <actual.36abbrev && + objfull=$(git rev-parse --verify t) && + + # are we really getting abbreviations? + test "$obj36" != "$objdefault" && + expr "$obj36" : "$objdefault" >/dev/null && + test "$objfull" != "$obj36" && + expr "$objfull" : "$obj36" >/dev/null + +' + test_expect_success 'git branch --column' ' COLUMNS=81 git branch --column=column >actual && cat >expected <<\EOF && @@ -267,17 +379,273 @@ git config branch.s/s.dummy Hello test_expect_success 'git branch -m s/s s should work when s/t is deleted' ' git branch -l s/s && - test_path_is_file .git/logs/refs/heads/s/s && + git reflog exists refs/heads/s/s && git branch -l s/t && - test_path_is_file .git/logs/refs/heads/s/t && + git reflog exists refs/heads/s/t && git branch -d s/t && git branch -m s/s s && - test_path_is_file .git/logs/refs/heads/s + git reflog exists refs/heads/s ' test_expect_success 'config information was renamed, too' ' test $(git config branch.s.dummy) = Hello && - test_must_fail git config branch.s/s/dummy + test_must_fail git config branch.s/s.dummy +' + +test_expect_success 'git branch -m correctly renames multiple config sections' ' + test_when_finished "git checkout master" && + git checkout -b source master && + + # Assert that a config file with multiple config sections has + # those sections preserved... + cat >expect <<-\EOF && + branch.dest.key1=value1 + some.gar.b=age + branch.dest.key2=value2 + EOF + cat >config.branch <<\EOF && +;; Note the lack of -\EOF above & mixed indenting here. This is +;; intentional, we are also testing that the formatting of copied +;; sections is preserved. + +;; Comment for source. Tabs +[branch "source"] + ;; Comment for the source value + key1 = value1 +;; Comment for some.gar. Spaces +[some "gar"] + ;; Comment for the some.gar value + b = age +;; Comment for source, again. Mixed tabs/spaces. +[branch "source"] + ;; Comment for the source value, again + key2 = value2 +EOF + cat config.branch >>.git/config && + git branch -m source dest && + git config -f .git/config -l | grep -F -e source -e dest -e some.gar >actual && + test_cmp expect actual && + + # ...and that the comments for those sections are also + # preserved. + cat config.branch | sed "s/\"source\"/\"dest\"/" >expect && + sed -n -e "/Note the lack/,\$p" .git/config >actual && + test_cmp expect actual +' + +test_expect_success 'git branch -c dumps usage' ' + test_expect_code 128 git branch -c 2>err && + test_i18ngrep "branch name required" err +' + +test_expect_success 'git branch --copy dumps usage' ' + test_expect_code 128 git branch --copy 2>err && + test_i18ngrep "branch name required" err +' + +test_expect_success 'git branch -c d e should work' ' + git branch -l d && + git reflog exists refs/heads/d && + git config branch.d.dummy Hello && + git branch -c d e && + git reflog exists refs/heads/d && + git reflog exists refs/heads/e && + echo Hello >expect && + git config branch.e.dummy >actual && + test_cmp expect actual && + echo Hello >expect && + git config branch.d.dummy >actual && + test_cmp expect actual +' + +test_expect_success 'git branch --copy is a synonym for -c' ' + git branch -l copy && + git reflog exists refs/heads/copy && + git config branch.copy.dummy Hello && + git branch --copy copy copy-to && + git reflog exists refs/heads/copy && + git reflog exists refs/heads/copy-to && + echo Hello >expect && + git config branch.copy.dummy >actual && + test_cmp expect actual && + echo Hello >expect && + git config branch.copy-to.dummy >actual && + test_cmp expect actual +' + +test_expect_success 'git branch -c ee ef should copy ee to create branch ef' ' + git checkout -b ee && + git reflog exists refs/heads/ee && + git config branch.ee.dummy Hello && + git branch -c ee ef && + git reflog exists refs/heads/ee && + git reflog exists refs/heads/ef && + test $(git config branch.ee.dummy) = Hello && + test $(git config branch.ef.dummy) = Hello && + test $(git rev-parse --abbrev-ref HEAD) = ee +' + +test_expect_success 'git branch -c f/f g/g should work' ' + git branch -l f/f && + git reflog exists refs/heads/f/f && + git config branch.f/f.dummy Hello && + git branch -c f/f g/g && + git reflog exists refs/heads/f/f && + git reflog exists refs/heads/g/g && + test $(git config branch.f/f.dummy) = Hello && + test $(git config branch.g/g.dummy) = Hello +' + +test_expect_success 'git branch -c m2 m2 should work' ' + git branch -l m2 && + git reflog exists refs/heads/m2 && + git config branch.m2.dummy Hello && + git branch -c m2 m2 && + git reflog exists refs/heads/m2 && + test $(git config branch.m2.dummy) = Hello +' + +test_expect_success 'git branch -c zz zz/zz should fail' ' + git branch -l zz && + git reflog exists refs/heads/zz && + test_must_fail git branch -c zz zz/zz +' + +test_expect_success 'git branch -c b/b b should fail' ' + git branch -l b/b && + test_must_fail git branch -c b/b b +' + +test_expect_success 'git branch -C o/q o/p should work when o/p exists' ' + git branch -l o/q && + git reflog exists refs/heads/o/q && + git reflog exists refs/heads/o/p && + git branch -C o/q o/p +' + +test_expect_success 'git branch -c -f o/q o/p should work when o/p exists' ' + git reflog exists refs/heads/o/q && + git reflog exists refs/heads/o/p && + git branch -c -f o/q o/p +' + +test_expect_success 'git branch -c qq rr/qq should fail when rr exists' ' + git branch qq && + git branch rr && + test_must_fail git branch -c qq rr/qq +' + +test_expect_success 'git branch -C b1 b2 should fail when b2 is checked out' ' + git branch b1 && + git checkout -b b2 && + test_must_fail git branch -C b1 b2 +' + +test_expect_success 'git branch -C c1 c2 should succeed when c1 is checked out' ' + git checkout -b c1 && + git branch c2 && + git branch -C c1 c2 && + test $(git rev-parse --abbrev-ref HEAD) = c1 +' + +test_expect_success 'git branch -C c1 c2 should never touch HEAD' ' + msg="Branch: copied refs/heads/c1 to refs/heads/c2" && + ! grep "$msg$" .git/logs/HEAD +' + +test_expect_success 'git branch -C master should work when master is checked out' ' + git checkout master && + git branch -C master +' + +test_expect_success 'git branch -C master master should work when master is checked out' ' + git checkout master && + git branch -C master master +' + +test_expect_success 'git branch -C master5 master5 should work when master is checked out' ' + git checkout master && + git branch master5 && + git branch -C master5 master5 +' + +test_expect_success 'git branch -C ab cd should overwrite existing config for cd' ' + git branch -l cd && + git reflog exists refs/heads/cd && + git config branch.cd.dummy CD && + git branch -l ab && + git reflog exists refs/heads/ab && + git config branch.ab.dummy AB && + git branch -C ab cd && + git reflog exists refs/heads/ab && + git reflog exists refs/heads/cd && + test $(git config branch.ab.dummy) = AB && + test $(git config branch.cd.dummy) = AB +' + +test_expect_success 'git branch -c correctly copies multiple config sections' ' + FOO=1 && + export FOO && + test_when_finished "git checkout master" && + git checkout -b source2 master && + + # Assert that a config file with multiple config sections has + # those sections preserved... + cat >expect <<-\EOF && + branch.source2.key1=value1 + branch.dest2.key1=value1 + more.gar.b=age + branch.source2.key2=value2 + branch.dest2.key2=value2 + EOF + cat >config.branch <<\EOF && +;; Note the lack of -\EOF above & mixed indenting here. This is +;; intentional, we are also testing that the formatting of copied +;; sections is preserved. + +;; Comment for source2. Tabs +[branch "source2"] + ;; Comment for the source2 value + key1 = value1 +;; Comment for more.gar. Spaces +[more "gar"] + ;; Comment for the more.gar value + b = age +;; Comment for source2, again. Mixed tabs/spaces. +[branch "source2"] + ;; Comment for the source2 value, again + key2 = value2 +EOF + cat config.branch >>.git/config && + git branch -c source2 dest2 && + git config -f .git/config -l | grep -F -e source2 -e dest2 -e more.gar >actual && + test_cmp expect actual && + + # ...and that the comments and formatting for those sections + # is also preserved. + cat >expect <<\EOF && +;; Comment for source2. Tabs +[branch "source2"] + ;; Comment for the source2 value + key1 = value1 +;; Comment for more.gar. Spaces +[branch "dest2"] + ;; Comment for the source2 value + key1 = value1 +;; Comment for more.gar. Spaces +[more "gar"] + ;; Comment for the more.gar value + b = age +;; Comment for source2, again. Mixed tabs/spaces. +[branch "source2"] + ;; Comment for the source2 value, again + key2 = value2 +[branch "dest2"] + ;; Comment for the source2 value, again + key2 = value2 +EOF + sed -n -e "/Comment for source2/,\$p" .git/config >actual && + test_cmp expect actual ' test_expect_success 'deleting a symref' ' @@ -403,6 +771,12 @@ test_expect_success 'test deleting branch without config' ' test_i18ncmp expect actual ' +test_expect_success 'deleting currently checked out branch fails' ' + git worktree add -b my7 my7 && + test_must_fail git -C my7 branch -d my7 && + test_must_fail git branch -d my7 +' + test_expect_success 'test --track without .fetch entries' ' git branch --track my8 && test "$(git config branch.my8.remote)" && @@ -446,6 +820,13 @@ test_expect_success '--set-upstream-to fails on a non-ref' ' test_must_fail git branch --set-upstream-to HEAD^{} ' +test_expect_success '--set-upstream-to fails on locked config' ' + test_when_finished "rm -f .git/config.lock" && + >.git/config.lock && + git branch locked && + test_must_fail git branch --set-upstream-to locked +' + test_expect_success 'use --set-upstream-to modify HEAD' ' test_config branch.master.remote foo && test_config branch.master.merge foo && @@ -458,6 +839,7 @@ test_expect_success 'use --set-upstream-to modify HEAD' ' test_expect_success 'use --set-upstream-to modify a particular branch' ' git branch my13 && git branch --set-upstream-to master my13 && + test_when_finished "git branch --unset-upstream my13" && test "$(git config branch.my13.remote)" = "." && test "$(git config branch.my13.merge)" = "refs/heads/master" ' @@ -466,6 +848,13 @@ test_expect_success '--unset-upstream should fail if given a non-existent branch test_must_fail git branch --unset-upstream i-dont-exist ' +test_expect_success '--unset-upstream should fail if config is locked' ' + test_when_finished "rm -f .git/config.lock" && + git branch --set-upstream-to locked && + >.git/config.lock && + test_must_fail git branch --unset-upstream +' + test_expect_success 'test --unset-upstream on HEAD' ' git branch my14 && test_config branch.master.remote foo && @@ -496,38 +885,8 @@ test_expect_success 'test --unset-upstream on a particular branch' ' test_must_fail git config branch.my14.merge ' -test_expect_success '--set-upstream shows message when creating a new branch that exists as remote-tracking' ' - git update-ref refs/remotes/origin/master HEAD && - git branch --set-upstream origin/master 2>actual && - test_when_finished git update-ref -d refs/remotes/origin/master && - test_when_finished git branch -d origin/master && - cat >expected <<EOF && -The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to - -If you wanted to make '"'master'"' track '"'origin/master'"', do this: - - git branch -d origin/master - git branch --set-upstream-to origin/master -EOF - test_cmp expected actual -' - -test_expect_success '--set-upstream with two args only shows the deprecation message' ' - git branch --set-upstream master my13 2>actual && - test_when_finished git branch --unset-upstream master && - cat >expected <<EOF && -The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to -EOF - test_cmp expected actual -' - -test_expect_success '--set-upstream with one arg only shows the deprecation message if the branch existed' ' - git branch --set-upstream my13 2>actual && - test_when_finished git branch --unset-upstream my13 && - cat >expected <<EOF && -The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to -EOF - test_cmp expected actual +test_expect_success '--set-upstream fails' ' + test_must_fail git branch --set-upstream origin/master ' test_expect_success '--set-upstream-to notices an error to set branch as own upstream' ' @@ -579,7 +938,7 @@ test_expect_success 'avoid ambiguous track' ' git config remote.ambi1.fetch refs/heads/lalala:refs/heads/master && git config remote.ambi2.url lilili && git config remote.ambi2.fetch refs/heads/lilili:refs/heads/master && - git branch all1 master && + test_must_fail git branch all1 master && test -z "$(git config branch.all1.merge)" ' @@ -852,19 +1211,6 @@ test_expect_success 'attempt to delete a branch merged to its base' ' test_must_fail git branch -d my10 ' -test_expect_success 'use set-upstream on the current branch' ' - git checkout master && - git --bare init myupstream.git && - git push myupstream.git master:refs/heads/frotz && - git remote add origin myupstream.git && - git fetch && - git branch --set-upstream master origin/frotz && - - test "z$(git config branch.master.remote)" = "zorigin" && - test "z$(git config branch.master.merge)" = "zrefs/heads/frotz" - -' - test_expect_success 'use --edit-description' ' write_script editor <<-\EOF && echo "New contents" >"$1" @@ -897,6 +1243,33 @@ test_expect_success '--merged catches invalid object names' ' test_must_fail git branch --merged 0000000000000000000000000000000000000000 ' +test_expect_success '--merged is incompatible with --no-merged' ' + test_must_fail git branch --merged HEAD --no-merged HEAD +' + +test_expect_success '--list during rebase' ' + test_when_finished "reset_rebase" && + git checkout master && + FAKE_LINES="1 edit 2" && + export FAKE_LINES && + set_fake_editor && + git rebase -i HEAD~2 && + git branch --list >actual && + test_i18ngrep "rebasing master" actual +' + +test_expect_success '--list during rebase from detached HEAD' ' + test_when_finished "reset_rebase && git checkout master" && + git checkout master^0 && + oid=$(git rev-parse --short HEAD) && + FAKE_LINES="1 edit 2" && + export FAKE_LINES && + set_fake_editor && + git rebase -i HEAD~2 && + git branch --list >actual && + test_i18ngrep "rebasing detached HEAD $oid" actual +' + test_expect_success 'tracking with unexpected .fetch refspec' ' rm -rf a b c d && git init a && diff --git a/t/t3201-branch-contains.sh b/t/t3201-branch-contains.sh index 912a6635a8..0ef1b6fdcc 100755 --- a/t/t3201-branch-contains.sh +++ b/t/t3201-branch-contains.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='branch --contains <commit>, --merged, and --no-merged' +test_description='branch --contains <commit>, --no-contains <commit> --merged, and --no-merged' . ./test-lib.sh @@ -45,6 +45,22 @@ test_expect_success 'branch --contains master' ' ' +test_expect_success 'branch --no-contains=master' ' + + git branch --no-contains=master >actual && + >expect && + test_cmp expect actual + +' + +test_expect_success 'branch --no-contains master' ' + + git branch --no-contains master >actual && + >expect && + test_cmp expect actual + +' + test_expect_success 'branch --contains=side' ' git branch --contains=side >actual && @@ -55,6 +71,16 @@ test_expect_success 'branch --contains=side' ' ' +test_expect_success 'branch --no-contains=side' ' + + git branch --no-contains=side >actual && + { + echo " master" + } >expect && + test_cmp expect actual + +' + test_expect_success 'branch --contains with pattern implies --list' ' git branch --contains=master master >actual && @@ -65,6 +91,14 @@ test_expect_success 'branch --contains with pattern implies --list' ' ' +test_expect_success 'branch --no-contains with pattern implies --list' ' + + git branch --no-contains=master master >actual && + >expect && + test_cmp expect actual + +' + test_expect_success 'side: branch --merged' ' git branch --merged >actual && @@ -126,8 +160,20 @@ test_expect_success 'branch --no-merged with pattern implies --list' ' test_expect_success 'implicit --list conflicts with modification options' ' test_must_fail git branch --contains=master -d && - test_must_fail git branch --contains=master -m foo + test_must_fail git branch --contains=master -m foo && + test_must_fail git branch --no-contains=master -d && + test_must_fail git branch --no-contains=master -m foo + +' +test_expect_success 'Assert that --contains only works on commits, not trees & blobs' ' + test_must_fail git branch --contains master^{tree} && + blob=$(git hash-object -w --stdin <<-\EOF + Some blob + EOF + ) && + test_must_fail git branch --contains $blob && + test_must_fail git branch --no-contains $blob ' # We want to set up a case where the walk for the tracking info @@ -156,7 +202,18 @@ test_expect_success 'branch --merged with --verbose' ' * topic 2c939f4 [ahead 1] foo zzz c77a0a9 second on master EOF + test_i18ncmp expect actual +' + +test_expect_success 'branch --contains combined with --no-contains' ' + git branch --contains zzz --no-contains topic >actual && + cat >expect <<-\EOF && + master + side + zzz + EOF test_cmp expect actual + ' test_done diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index f51d0f3cad..ee6787614c 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -2,6 +2,7 @@ test_description='git branch display tests' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-terminal.sh test_expect_success 'make commits' ' echo content >file && @@ -89,6 +90,11 @@ test_expect_success 'git branch --list -v pattern shows branch summaries' ' awk "{print \$NF}" <tmp >actual && test_cmp expect actual ' +test_expect_success 'git branch --ignore-case --list -v pattern shows branch summaries' ' + git branch --list --ignore-case -v BRANCH* >tmp && + awk "{print \$NF}" <tmp >actual && + test_cmp expect actual +' test_expect_success 'git branch -v pattern does not show branch summaries' ' test_must_fail git branch -v branch* @@ -106,6 +112,19 @@ EOF test_i18ncmp expect actual ' +test_expect_success 'git branch shows detached HEAD properly after checkout --detach' ' + git checkout master && + cat >expect <<EOF && +* (HEAD detached at $(git rev-parse --short HEAD^0)) + branch-one + branch-two + master +EOF + git checkout --detach && + git branch >actual && + test_i18ncmp expect actual +' + test_expect_success 'git branch shows detached HEAD properly after moving' ' cat >expect <<EOF && * (HEAD detached from $(git rev-parse --short HEAD)) @@ -143,4 +162,106 @@ EOF test_i18ncmp expect actual ' +test_expect_success 'git branch `--sort` option' ' + cat >expect <<-\EOF && + * (HEAD detached from fromtag) + branch-two + branch-one + master + EOF + git branch --sort=objectsize >actual && + test_i18ncmp expect actual +' + +test_expect_success 'git branch --points-at option' ' + cat >expect <<-\EOF && + branch-one + master + EOF + git branch --points-at=branch-one >actual && + test_cmp expect actual +' + +test_expect_success 'ambiguous branch/tag not marked' ' + git tag ambiguous && + git branch ambiguous && + echo " ambiguous" >expect && + git branch --list ambiguous >actual && + test_cmp expect actual +' + +test_expect_success 'local-branch symrefs shortened properly' ' + git symbolic-ref refs/heads/ref-to-branch refs/heads/branch-one && + git symbolic-ref refs/heads/ref-to-remote refs/remotes/origin/branch-one && + cat >expect <<-\EOF && + ref-to-branch -> branch-one + ref-to-remote -> origin/branch-one + EOF + git branch >actual.raw && + grep ref-to <actual.raw >actual && + test_cmp expect actual +' + +test_expect_success 'sort branches, ignore case' ' + ( + git init sort-icase && + cd sort-icase && + test_commit initial && + git branch branch-one && + git branch BRANCH-two && + git branch --list | awk "{print \$NF}" >actual && + cat >expected <<-\EOF && + BRANCH-two + branch-one + master + EOF + test_cmp expected actual && + git branch --list -i | awk "{print \$NF}" >actual && + cat >expected <<-\EOF && + branch-one + BRANCH-two + master + EOF + test_cmp expected actual + ) +' + +test_expect_success 'git branch --format option' ' + cat >expect <<-\EOF && + Refname is (HEAD detached from fromtag) + Refname is refs/heads/ambiguous + Refname is refs/heads/branch-one + Refname is refs/heads/branch-two + Refname is refs/heads/master + Refname is refs/heads/ref-to-branch + Refname is refs/heads/ref-to-remote + EOF + git branch --format="Refname is %(refname)" >actual && + test_i18ncmp expect actual +' + +test_expect_success "set up color tests" ' + echo "<RED>master<RESET>" >expect.color && + echo "master" >expect.bare && + color_args="--format=%(color:red)%(refname:short) --list master" +' + +test_expect_success '%(color) omitted without tty' ' + TERM=vt100 git branch $color_args >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expect.bare actual +' + +test_expect_success TTY '%(color) present with tty' ' + test_terminal git branch $color_args >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expect.color actual +' + +test_expect_success '--color overrides auto-color' ' + git branch --color $color_args >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expect.color actual +' + test_done diff --git a/t/t3204-branch-name-interpretation.sh b/t/t3204-branch-name-interpretation.sh new file mode 100755 index 0000000000..698d9cc4f3 --- /dev/null +++ b/t/t3204-branch-name-interpretation.sh @@ -0,0 +1,133 @@ +#!/bin/sh + +test_description='interpreting exotic branch name arguments + +Branch name arguments are usually names which are taken to be inside of +refs/heads/, but we interpret some magic syntax like @{-1}, @{upstream}, etc. +This script aims to check the behavior of those corner cases. +' +. ./test-lib.sh + +expect_branch() { + git log -1 --format=%s "$1" >actual && + echo "$2" >expect && + test_cmp expect actual +} + +expect_deleted() { + test_must_fail git rev-parse --verify "$1" +} + +test_expect_success 'set up repo' ' + test_commit one && + test_commit two && + git remote add origin foo.git +' + +test_expect_success 'update branch via @{-1}' ' + git branch previous one && + + git checkout previous && + git checkout master && + + git branch -f @{-1} two && + expect_branch previous two +' + +test_expect_success 'update branch via local @{upstream}' ' + git branch local one && + git branch --set-upstream-to=local && + + git branch -f @{upstream} two && + expect_branch local two +' + +test_expect_success 'disallow updating branch via remote @{upstream}' ' + git update-ref refs/remotes/origin/remote one && + git branch --set-upstream-to=origin/remote && + + test_must_fail git branch -f @{upstream} two +' + +test_expect_success 'create branch with pseudo-qualified name' ' + git branch refs/heads/qualified two && + expect_branch refs/heads/refs/heads/qualified two +' + +test_expect_success 'delete branch via @{-1}' ' + git branch previous-del && + + git checkout previous-del && + git checkout master && + + git branch -D @{-1} && + expect_deleted previous-del +' + +test_expect_success 'delete branch via local @{upstream}' ' + git branch local-del && + git branch --set-upstream-to=local-del && + + git branch -D @{upstream} && + expect_deleted local-del +' + +test_expect_success 'delete branch via remote @{upstream}' ' + git update-ref refs/remotes/origin/remote-del two && + git branch --set-upstream-to=origin/remote-del && + + git branch -r -D @{upstream} && + expect_deleted origin/remote-del +' + +# Note that we create two oddly named local branches here. We want to make +# sure that we do not accidentally delete either of them, even if +# shorten_unambiguous_ref() tweaks the name to avoid ambiguity. +test_expect_success 'delete @{upstream} expansion matches -r option' ' + git update-ref refs/remotes/origin/remote-del two && + git branch --set-upstream-to=origin/remote-del && + git update-ref refs/heads/origin/remote-del two && + git update-ref refs/heads/remotes/origin/remote-del two && + + test_must_fail git branch -D @{upstream} && + expect_branch refs/heads/origin/remote-del two && + expect_branch refs/heads/remotes/origin/remote-del two +' + +test_expect_success 'disallow deleting remote branch via @{-1}' ' + git update-ref refs/remotes/origin/previous one && + + git checkout -b origin/previous two && + git checkout master && + + test_must_fail git branch -r -D @{-1} && + expect_branch refs/remotes/origin/previous one && + expect_branch refs/heads/origin/previous two +' + +# The thing we are testing here is that "@" is the real branch refs/heads/@, +# and not refs/heads/HEAD. These tests should not imply that refs/heads/@ is a +# sane thing, but it _is_ technically allowed for now. If we disallow it, these +# can be switched to test_must_fail. +test_expect_success 'create branch named "@"' ' + git branch -f @ one && + expect_branch refs/heads/@ one +' + +test_expect_success 'delete branch named "@"' ' + git update-ref refs/heads/@ two && + git branch -D @ && + expect_deleted refs/heads/@ +' + +test_expect_success 'checkout does not treat remote @{upstream} as a branch' ' + git update-ref refs/remotes/origin/checkout one && + git branch --set-upstream-to=origin/checkout && + git update-ref refs/heads/origin/checkout two && + git update-ref refs/heads/remotes/origin/checkout two && + + git checkout @{upstream} && + expect_branch HEAD one +' + +test_done diff --git a/t/t3205-branch-color.sh b/t/t3205-branch-color.sh new file mode 100755 index 0000000000..4f1e16bb44 --- /dev/null +++ b/t/t3205-branch-color.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +test_description='basic branch output coloring' +. ./test-lib.sh + +test_expect_success 'set up some sample branches' ' + test_commit foo && + git update-ref refs/remotes/origin/master HEAD && + git update-ref refs/heads/other HEAD +' + +# choose non-default colors to make sure config +# is taking effect +test_expect_success 'set up some color config' ' + git config color.branch.local blue && + git config color.branch.remote yellow && + git config color.branch.current cyan +' + +test_expect_success 'regular output shows colors' ' + cat >expect <<-\EOF && + * <CYAN>master<RESET> + <BLUE>other<RESET> + <YELLOW>remotes/origin/master<RESET> + EOF + git branch --color -a >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expect actual +' + +test_expect_success 'verbose output shows colors' ' + oid=$(git rev-parse --short HEAD) && + cat >expect <<-EOF && + * <CYAN>master <RESET> $oid foo + <BLUE>other <RESET> $oid foo + <YELLOW>remotes/origin/master<RESET> $oid foo + EOF + git branch --color -v -a >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh index 8aae98d482..afa27ffe2d 100755 --- a/t/t3210-pack-refs.sh +++ b/t/t3210-pack-refs.sh @@ -27,7 +27,7 @@ SHA1= test_expect_success \ 'see if git show-ref works as expected' \ 'git branch a && - SHA1=`cat .git/refs/heads/a` && + SHA1=$(cat .git/refs/heads/a) && echo "$SHA1 refs/heads/a" >expect && git show-ref a >result && test_cmp expect result' @@ -160,6 +160,13 @@ test_expect_success 'pack ref directly below refs/' ' test_path_is_missing .git/refs/top ' +test_expect_success 'do not pack ref in refs/bisect' ' + git update-ref refs/bisect/local HEAD && + git pack-refs --all --prune && + ! grep refs/bisect/local .git/packed-refs >/dev/null && + test_path_is_file .git/refs/bisect/local +' + test_expect_success 'disable reflogs' ' git config core.logallrefupdates false && rm -rf .git/logs @@ -169,7 +176,7 @@ test_expect_success 'create packed foo/bar/baz branch' ' git branch foo/bar/baz && git pack-refs --all --prune && test_path_is_missing .git/refs/heads/foo/bar/baz && - test_path_is_missing .git/logs/refs/heads/foo/bar/baz + test_must_fail git reflog exists refs/heads/foo/bar/baz ' test_expect_success 'notice d/f conflict with existing directory' ' @@ -187,6 +194,33 @@ test_expect_success 'notice d/f conflict with existing ref' ' test_must_fail git branch foo/bar/baz/lots/of/extra/components ' +test_expect_success 'reject packed-refs with unterminated line' ' + cp .git/packed-refs .git/packed-refs.bak && + test_when_finished "mv .git/packed-refs.bak .git/packed-refs" && + printf "%s" "$HEAD refs/zzzzz" >>.git/packed-refs && + echo "fatal: unterminated line in .git/packed-refs: $HEAD refs/zzzzz" >expected_err && + test_must_fail git for-each-ref >out 2>err && + test_cmp expected_err err +' + +test_expect_success 'reject packed-refs containing junk' ' + cp .git/packed-refs .git/packed-refs.bak && + test_when_finished "mv .git/packed-refs.bak .git/packed-refs" && + printf "%s\n" "bogus content" >>.git/packed-refs && + echo "fatal: unexpected line in .git/packed-refs: bogus content" >expected_err && + test_must_fail git for-each-ref >out 2>err && + test_cmp expected_err err +' + +test_expect_success 'reject packed-refs with a short SHA-1' ' + cp .git/packed-refs .git/packed-refs.bak && + test_when_finished "mv .git/packed-refs.bak .git/packed-refs" && + printf "%.7s %s\n" $HEAD refs/zzzzz >>.git/packed-refs && + printf "fatal: unexpected line in .git/packed-refs: %.7s %s\n" $HEAD refs/zzzzz >expected_err && + test_must_fail git for-each-ref >out 2>err && + test_cmp expected_err err +' + test_expect_success 'timeout if packed-refs.lock exists' ' LOCK=.git/packed-refs.lock && >"$LOCK" && @@ -204,4 +238,19 @@ test_expect_success 'retry acquiring packed-refs.lock' ' git -c core.packedrefstimeout=3000 pack-refs --all --prune ' +test_expect_success SYMLINKS 'pack symlinked packed-refs' ' + # First make sure that symlinking works when reading: + git update-ref refs/heads/loosy refs/heads/master && + git for-each-ref >all-refs-before && + mv .git/packed-refs .git/my-deviant-packed-refs && + ln -s my-deviant-packed-refs .git/packed-refs && + git for-each-ref >all-refs-linked && + test_cmp all-refs-before all-refs-linked && + git pack-refs --all --prune && + git for-each-ref >all-refs-packed && + test_cmp all-refs-before all-refs-packed && + test -h .git/packed-refs && + test "$(readlink .git/packed-refs)" = "my-deviant-packed-refs" +' + test_done diff --git a/t/t3300-funny-names.sh b/t/t3300-funny-names.sh index 9a146f1335..04de03cad0 100755 --- a/t/t3300-funny-names.sh +++ b/t/t3300-funny-names.sh @@ -13,6 +13,7 @@ tree, index, and tree objects. HT=' ' +test_have_prereq MINGW || echo 2>/dev/null > "Name with an${HT}HT" if ! test -f "Name with an${HT}HT" then diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index 8cffd35fb0..2d200fdf36 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -83,6 +83,16 @@ test_expect_success 'edit existing notes' ' test_must_fail git notes show HEAD^ ' +test_expect_success 'show notes from treeish' ' + test "b3" = "$(git notes --ref commits^{tree} show)" && + test "b4" = "$(git notes --ref commits@{1} show)" +' + +test_expect_success 'cannot edit notes from non-ref' ' + test_must_fail git notes --ref commits^{tree} edit && + test_must_fail git notes --ref commits@{1} edit +' + test_expect_success 'cannot "git notes add -m" where notes already exists' ' test_must_fail git notes add -m "b2" && test_path_is_missing .git/NOTES_EDITMSG && @@ -1122,6 +1132,12 @@ test_expect_success 'git notes copy diagnoses too many or too few parameters' ' test_must_fail git notes copy one two three ' +test_expect_success 'git notes get-ref expands refs/heads/master to refs/notes/refs/heads/master' ' + test_unconfig core.notesRef && + sane_unset GIT_NOTES_REF && + test "$(git notes --ref=refs/heads/master get-ref)" = "refs/notes/refs/heads/master" +' + test_expect_success 'git notes get-ref (no overrides)' ' test_unconfig core.notesRef && sane_unset GIT_NOTES_REF && diff --git a/t/t3306-notes-prune.sh b/t/t3306-notes-prune.sh index 86bf909ee3..61748088eb 100755 --- a/t/t3306-notes-prune.sh +++ b/t/t3306-notes-prune.sh @@ -22,7 +22,7 @@ test_expect_success 'setup: create a few commits with notes' ' git commit -m 3rd && COMMIT_FILE=.git/objects/5e/e1c35e83ea47cd3cc4f8cbee0568915fbbbd29 && test -f $COMMIT_FILE && - test-chmtime =+0 $COMMIT_FILE && + test-tool chmtime =+0 $COMMIT_FILE && git notes add -m "Note #3" ' diff --git a/t/t3308-notes-merge.sh b/t/t3308-notes-merge.sh index 24d82b49bb..ab946a5153 100755 --- a/t/t3308-notes-merge.sh +++ b/t/t3308-notes-merge.sh @@ -18,7 +18,9 @@ test_expect_success setup ' git notes add -m "Notes on 1st commit" 1st && git notes add -m "Notes on 2nd commit" 2nd && git notes add -m "Notes on 3rd commit" 3rd && - git notes add -m "Notes on 4th commit" 4th + git notes add -m "Notes on 4th commit" 4th && + # Copy notes to remote-notes + git fetch . refs/notes/*:refs/remote-notes/origin/* ' commit_sha1=$(git rev-parse 1st^{commit}) @@ -66,7 +68,9 @@ test_expect_success 'verify initial notes (x)' ' ' cp expect_notes_x expect_notes_y +cp expect_notes_x expect_notes_v cp expect_log_x expect_log_y +cp expect_log_x expect_log_v test_expect_success 'fail to merge empty notes ref into empty notes ref (z => y)' ' test_must_fail git -c "core.notesRef=refs/notes/y" notes merge z @@ -75,7 +79,7 @@ test_expect_success 'fail to merge empty notes ref into empty notes ref (z => y) test_expect_success 'fail to merge into various non-notes refs' ' test_must_fail git -c "core.notesRef=refs/notes" notes merge x && test_must_fail git -c "core.notesRef=refs/notes/" notes merge x && - mkdir -p .git/refs/notes/dir && + git update-ref refs/notes/dir/foo HEAD && test_must_fail git -c "core.notesRef=refs/notes/dir" notes merge x && test_must_fail git -c "core.notesRef=refs/notes/dir/" notes merge x && test_must_fail git -c "core.notesRef=refs/heads/master" notes merge x && @@ -84,16 +88,12 @@ test_expect_success 'fail to merge into various non-notes refs' ' test_must_fail git -c "core.notesRef=refs/notes/foo^{bar" notes merge x ' -test_expect_success 'fail to merge various non-note-trees' ' - git config core.notesRef refs/notes/y && - test_must_fail git notes merge refs/notes && - test_must_fail git notes merge refs/notes/ && - test_must_fail git notes merge refs/notes/dir && - test_must_fail git notes merge refs/notes/dir/ && - test_must_fail git notes merge refs/heads/master && - test_must_fail git notes merge x: && - test_must_fail git notes merge x:foo && - test_must_fail git notes merge foo^{bar +test_expect_success 'merge non-notes ref into empty notes ref (remote-notes/origin/x => v)' ' + git config core.notesRef refs/notes/v && + git notes merge refs/remote-notes/origin/x && + verify_notes v && + # refs/remote-notes/origin/x and v should point to the same notes commit + test "$(git rev-parse refs/remote-notes/origin/x)" = "$(git rev-parse refs/notes/v)" ' test_expect_success 'merge notes into empty notes ref (x => y)' ' diff --git a/t/t3309-notes-merge-auto-resolve.sh b/t/t3309-notes-merge-auto-resolve.sh index 461fd84755..14c2adf970 100755 --- a/t/t3309-notes-merge-auto-resolve.sh +++ b/t/t3309-notes-merge-auto-resolve.sh @@ -298,6 +298,13 @@ test_expect_success 'merge z into y with invalid strategy => Fail/No changes' ' verify_notes y y ' +test_expect_success 'merge z into y with invalid configuration option => Fail/No changes' ' + git config core.notesRef refs/notes/y && + test_must_fail git -c notes.mergeStrategy="foo" notes merge z && + # Verify no changes (y) + verify_notes y y +' + cat <<EOF | sort >expect_notes_ours 68b8630d25516028bed862719855b3d6768d7833 $commit_sha15 5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14 @@ -365,6 +372,28 @@ test_expect_success 'reset to pre-merge state (y)' ' verify_notes y y ' +test_expect_success 'merge z into y with "ours" configuration option => Non-conflicting 3-way merge' ' + git -c notes.mergeStrategy="ours" notes merge z && + verify_notes y ours +' + +test_expect_success 'reset to pre-merge state (y)' ' + git update-ref refs/notes/y refs/notes/y^1 && + # Verify pre-merge state + verify_notes y y +' + +test_expect_success 'merge z into y with "ours" per-ref configuration option => Non-conflicting 3-way merge' ' + git -c notes.y.mergeStrategy="ours" notes merge z && + verify_notes y ours +' + +test_expect_success 'reset to pre-merge state (y)' ' + git update-ref refs/notes/y refs/notes/y^1 && + # Verify pre-merge state + verify_notes y y +' + cat <<EOF | sort >expect_notes_theirs 9b4b2c61f0615412da3c10f98ff85b57c04ec765 $commit_sha15 5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14 @@ -432,6 +461,17 @@ test_expect_success 'reset to pre-merge state (y)' ' verify_notes y y ' +test_expect_success 'merge z into y with "theirs" strategy overriding configuration option "ours" => Non-conflicting 3-way merge' ' + git -c notes.mergeStrategy="ours" notes merge --strategy=theirs z && + verify_notes y theirs +' + +test_expect_success 'reset to pre-merge state (y)' ' + git update-ref refs/notes/y refs/notes/y^1 && + # Verify pre-merge state + verify_notes y y +' + cat <<EOF | sort >expect_notes_union 7c4e546efd0fe939f876beb262ece02797880b54 $commit_sha15 5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14 @@ -505,6 +545,34 @@ test_expect_success 'reset to pre-merge state (y)' ' verify_notes y y ' +test_expect_success 'merge z into y with "union" strategy overriding per-ref configuration => Non-conflicting 3-way merge' ' + git -c notes.y.mergeStrategy="theirs" notes merge --strategy=union z && + verify_notes y union +' + +test_expect_success 'reset to pre-merge state (y)' ' + git update-ref refs/notes/y refs/notes/y^1 && + # Verify pre-merge state + verify_notes y y +' + +test_expect_success 'merge z into y with "union" per-ref overriding general configuration => Non-conflicting 3-way merge' ' + git -c notes.y.mergeStrategy="union" -c notes.mergeStrategy="theirs" notes merge z && + verify_notes y union +' + +test_expect_success 'reset to pre-merge state (y)' ' + git update-ref refs/notes/y refs/notes/y^1 && + # Verify pre-merge state + verify_notes y y +' + +test_expect_success 'merge z into y with "manual" per-ref only checks specific ref configuration => Conflicting 3-way merge' ' + test_must_fail git -c notes.z.mergeStrategy="union" notes merge z && + git notes merge --abort && + verify_notes y y +' + cat <<EOF | sort >expect_notes_union2 d682107b8bf7a7aea1e537a8d5cb6a12b60135f1 $commit_sha15 5de7ea7ad4f47e7ff91989fb82234634730f75df $commit_sha14 @@ -644,4 +712,15 @@ test_expect_success 'merge y into z with "cat_sort_uniq" strategy => Non-conflic verify_notes z cat_sort_uniq ' +test_expect_success 'reset to pre-merge state (z)' ' + git update-ref refs/notes/z refs/notes/z^1 && + # Verify pre-merge state + verify_notes z z +' + +test_expect_success 'merge y into z with "cat_sort_uniq" strategy configuration option => Non-conflicting 3-way merge' ' + git -c notes.mergeStrategy="cat_sort_uniq" notes merge y && + verify_notes z cat_sort_uniq +' + test_done diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh index 195bb97f85..9c1bf6eb3d 100755 --- a/t/t3310-notes-merge-manual-resolve.sh +++ b/t/t3310-notes-merge-manual-resolve.sh @@ -176,9 +176,9 @@ git rev-parse refs/notes/z > pre_merge_z test_expect_success 'merge z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' ' git update-ref refs/notes/m refs/notes/y && git config core.notesRef refs/notes/m && - test_must_fail git notes merge z >output && + test_must_fail git notes merge z >output 2>&1 && # Output should point to where to resolve conflicts - grep -q "\\.git/NOTES_MERGE_WORKTREE" output && + test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output && # Inspect merge conflicts ls .git/NOTES_MERGE_WORKTREE >output_conflicts && test_cmp expect_conflicts output_conflicts && @@ -225,7 +225,7 @@ test_expect_success 'cannot do merge w/conflicts when previous merge is unfinish test -d .git/NOTES_MERGE_WORKTREE && test_must_fail git notes merge z >output 2>&1 && # Output should indicate what is wrong - grep -q "\\.git/NOTES_MERGE_\\* exists" output + test_i18ngrep -q "\\.git/NOTES_MERGE_\\* exists" output ' # Setup non-conflicting merge between x and new notes ref w @@ -314,6 +314,18 @@ y and z notes on 1st commit EOF +test_expect_success 'do not allow mixing --commit and --abort' ' + test_must_fail git notes merge --commit --abort +' + +test_expect_success 'do not allow mixing --commit and --strategy' ' + test_must_fail git notes merge --commit --strategy theirs +' + +test_expect_success 'do not allow mixing --abort and --strategy' ' + test_must_fail git notes merge --abort --strategy theirs +' + test_expect_success 'finalize conflicting merge (z => m)' ' # Resolve conflicts and finalize merge cat >.git/NOTES_MERGE_WORKTREE/$commit_sha1 <<EOF && @@ -367,9 +379,9 @@ git rev-parse refs/notes/z > pre_merge_z test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' ' git update-ref refs/notes/m refs/notes/y && git config core.notesRef refs/notes/m && - test_must_fail git notes merge z >output && + test_must_fail git notes merge z >output 2>&1 && # Output should point to where to resolve conflicts - grep -q "\\.git/NOTES_MERGE_WORKTREE" output && + test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output && # Inspect merge conflicts ls .git/NOTES_MERGE_WORKTREE >output_conflicts && test_cmp expect_conflicts output_conflicts && @@ -401,9 +413,9 @@ git rev-parse refs/notes/y > pre_merge_y git rev-parse refs/notes/z > pre_merge_z test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' ' - test_must_fail git notes merge z >output && + test_must_fail git notes merge z >output 2>&1 && # Output should point to where to resolve conflicts - grep -q "\\.git/NOTES_MERGE_WORKTREE" output && + test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output && # Inspect merge conflicts ls .git/NOTES_MERGE_WORKTREE >output_conflicts && test_cmp expect_conflicts output_conflicts && @@ -482,9 +494,9 @@ cp expect_log_y expect_log_m test_expect_success 'redo merge of z into m (== y) with default ("manual") resolver => Conflicting 3-way merge' ' git update-ref refs/notes/m refs/notes/y && - test_must_fail git notes merge z >output && + test_must_fail git notes merge z >output 2>&1 && # Output should point to where to resolve conflicts - grep -q "\\.git/NOTES_MERGE_WORKTREE" output && + test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output && # Inspect merge conflicts ls .git/NOTES_MERGE_WORKTREE >output_conflicts && test_cmp expect_conflicts output_conflicts && diff --git a/t/t3320-notes-merge-worktrees.sh b/t/t3320-notes-merge-worktrees.sh new file mode 100755 index 0000000000..10bfc8b947 --- /dev/null +++ b/t/t3320-notes-merge-worktrees.sh @@ -0,0 +1,72 @@ +#!/bin/sh +# +# Copyright (c) 2015 Twitter, Inc +# + +test_description='Test merging of notes trees in multiple worktrees' + +. ./test-lib.sh + +test_expect_success 'setup commit' ' + test_commit tantrum +' + +commit_tantrum=$(git rev-parse tantrum^{commit}) + +test_expect_success 'setup notes ref (x)' ' + git config core.notesRef refs/notes/x && + git notes add -m "x notes on tantrum" tantrum +' + +test_expect_success 'setup local branch (y)' ' + git update-ref refs/notes/y refs/notes/x && + git config core.notesRef refs/notes/y && + git notes remove tantrum +' + +test_expect_success 'setup remote branch (z)' ' + git update-ref refs/notes/z refs/notes/x && + git config core.notesRef refs/notes/z && + git notes add -f -m "conflicting notes on tantrum" tantrum +' + +test_expect_success 'modify notes ref ourselves (x)' ' + git config core.notesRef refs/notes/x && + git notes add -f -m "more conflicting notes on tantrum" tantrum +' + +test_expect_success 'create some new worktrees' ' + git worktree add -b newbranch worktree master && + git worktree add -b newbranch2 worktree2 master +' + +test_expect_success 'merge z into y fails and sets NOTES_MERGE_REF' ' + git config core.notesRef refs/notes/y && + test_must_fail git notes merge z && + echo "ref: refs/notes/y" >expect && + test_cmp .git/NOTES_MERGE_REF expect +' + +test_expect_success 'merge z into y while mid-merge in another workdir fails' ' + ( + cd worktree && + git config core.notesRef refs/notes/y && + test_must_fail git notes merge z 2>err && + test_i18ngrep "a notes merge into refs/notes/y is already in-progress at" err + ) && + test_path_is_missing .git/worktrees/worktree/NOTES_MERGE_REF +' + +test_expect_success 'merge z into x while mid-merge on y succeeds' ' + ( + cd worktree2 && + git config core.notesRef refs/notes/x && + test_must_fail git notes merge z >out 2>&1 && + test_i18ngrep "Automatic notes merge failed" out && + grep -v "A notes merge into refs/notes/x is already in-progress in" out + ) && + echo "ref: refs/notes/x" >expect && + test_cmp .git/worktrees/worktree2/NOTES_MERGE_REF expect +' + +test_done diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 47b5682662..72d9564747 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -136,8 +136,8 @@ test_expect_success 'setup: recover' ' test_expect_success 'Show verbose error when HEAD could not be detached' ' >B && test_must_fail git rebase topic 2>output.err >output.out && - grep "The following untracked working tree files would be overwritten by checkout:" output.err && - grep B output.err + test_i18ngrep "The following untracked working tree files would be overwritten by checkout:" output.err && + test_i18ngrep B output.err ' rm -f B @@ -255,4 +255,60 @@ test_expect_success 'rebase commit with an ancient timestamp' ' grep "author .* 34567 +0600$" actual ' +test_expect_success 'rebase with "From " line in commit message' ' + git checkout -b preserve-from master~1 && + cat >From_.msg <<EOF && +Somebody embedded an mbox in a commit message + +This is from so-and-so: + +From a@b Mon Sep 17 00:00:00 2001 +From: John Doe <nobody@example.com> +Date: Sat, 11 Nov 2017 00:00:00 +0000 +Subject: not this message + +something +EOF + >From_ && + git add From_ && + git commit -F From_.msg && + git rebase master && + git log -1 --pretty=format:%B >out && + test_cmp From_.msg out +' + +test_expect_success 'rebase--am.sh and --show-current-patch' ' + test_create_repo conflict-apply && + ( + cd conflict-apply && + test_commit init && + echo one >>init.t && + git commit -a -m one && + echo two >>init.t && + git commit -a -m two && + git tag two && + test_must_fail git rebase --onto init HEAD^ && + GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && + grep "show.*$(git rev-parse two)" stderr + ) +' + +test_expect_success 'rebase--merge.sh and --show-current-patch' ' + test_create_repo conflict-merge && + ( + cd conflict-merge && + test_commit init && + echo one >>init.t && + git commit -a -m one && + echo two >>init.t && + git commit -a -m two && + git tag two && + test_must_fail git rebase --merge --onto init HEAD^ && + git rebase --show-current-patch >actual.patch && + GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && + grep "show.*REBASE_HEAD" stderr && + test "$(git rev-parse REBASE_HEAD)" = "$(git rev-parse two)" + ) +' + test_done diff --git a/t/t3402-rebase-merge.sh b/t/t3402-rebase-merge.sh index 8f64505e4f..488945e007 100755 --- a/t/t3402-rebase-merge.sh +++ b/t/t3402-rebase-merge.sh @@ -85,6 +85,15 @@ test_expect_success 'rebase -Xtheirs' ' ! grep 11 original ' +test_expect_success 'rebase -Xtheirs from orphan' ' + git checkout --orphan orphan-conflicting master~2 && + echo "AB $T" >> original && + git commit -morphan-conflicting original && + git rebase -Xtheirs master && + grep AB original && + ! grep 11 original +' + test_expect_success 'merge and rebase should match' ' git diff-tree -r test-rebase test-merge >difference && if test -s difference diff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh index 3968020e64..1f5122b632 100755 --- a/t/t3403-rebase-skip.sh +++ b/t/t3403-rebase-skip.sh @@ -67,7 +67,7 @@ test_expect_success 'rebase --skip with --merge' ' ' test_expect_success 'merge and reference trees equal' ' - test -z "`git diff-tree skip-merge skip-reference`" + test -z "$(git diff-tree skip-merge skip-reference)" ' test_expect_success 'moved back to branch correctly' ' diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index ac429a0bbb..756de26c19 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -60,9 +60,9 @@ test_expect_success 'setup' ' test_commit P fileP ' -# "exec" commands are ran with the user shell by default, but this may +# "exec" commands are run with the user shell by default, but this may # be non-POSIX. For example, if SHELL=zsh then ">file" doesn't work -# to create a file. Unseting SHELL avoids such non-portable behavior +# to create a file. Unsetting SHELL avoids such non-portable behavior # in tests. It must be exported for it to take effect where needed. SHELL= export SHELL @@ -108,6 +108,17 @@ test_expect_success 'rebase -i with the exec command runs from tree root' ' rm -fr subdir ' +test_expect_success 'rebase -i with exec allows git commands in subdirs' ' + test_when_finished "rm -rf subdir" && + test_when_finished "git rebase --abort ||:" && + git checkout master && + mkdir subdir && (cd subdir && + set_fake_editor && + FAKE_LINES="1 exec_cd_subdir_&&_git_rev-parse_--is-inside-work-tree" \ + git rebase -i HEAD^ + ) +' + test_expect_success 'rebase -i with the exec command checks tree cleanness' ' git checkout master && set_fake_editor && @@ -169,6 +180,13 @@ test_expect_success 'reflog for the branch shows state before rebase' ' test $(git rev-parse branch1@{1}) = $(git rev-parse original-branch1) ' +test_expect_success 'reflog for the branch shows correct finish message' ' + printf "rebase -i (finish): refs/heads/branch1 onto %s\n" \ + "$(git rev-parse branch2)" >expected && + git log -g --pretty=%gs -1 refs/heads/branch1 >actual && + test_cmp expected actual +' + test_expect_success 'exchange two commits' ' set_fake_editor && FAKE_LINES="2 1" git rebase -i HEAD~2 && @@ -207,6 +225,14 @@ test_expect_success 'stop on conflicting pick' ' test 0 = $(grep -c "^[^#]" < .git/rebase-merge/git-rebase-todo) ' +test_expect_success 'show conflicted patch' ' + GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && + grep "show.*REBASE_HEAD" stderr && + # the original stopped-sha1 is abbreviated + stopped_sha1="$(git rev-parse $(cat ".git/rebase-merge/stopped-sha"))" && + test "$(git rev-parse REBASE_HEAD)" = "$stopped_sha1" +' + test_expect_success 'abort' ' git rebase --abort && test $(git rev-parse new-branch1) = $(git rev-parse HEAD) && @@ -219,9 +245,9 @@ test_expect_success 'abort with error when new base cannot be checked out' ' git commit -m "remove file in base" && set_fake_editor && test_must_fail git rebase -i master > output 2>&1 && - grep "The following untracked working tree files would be overwritten by checkout:" \ + test_i18ngrep "The following untracked working tree files would be overwritten by checkout:" \ output && - grep "file1" output && + test_i18ngrep "file1" output && test_path_is_missing .git/rebase-merge && git reset --hard HEAD^ ' @@ -237,6 +263,22 @@ test_expect_success 'retain authorship' ' git show HEAD | grep "^Author: Twerp Snog" ' +test_expect_success 'retain authorship w/ conflicts' ' + git reset --hard twerp && + test_commit a conflict a conflict-a && + git reset --hard twerp && + GIT_AUTHOR_NAME=AttributeMe \ + test_commit b conflict b conflict-b && + set_fake_editor && + test_must_fail git rebase -i conflict-a && + echo resolved >conflict && + git add conflict && + git rebase --continue && + test $(git rev-parse conflict-a^0) = $(git rev-parse HEAD^) && + git show >out && + grep AttributeMe out +' + test_expect_success 'squash' ' git reset --hard twerp && echo B > file7 && @@ -350,7 +392,7 @@ test_expect_success 'verbose flag is heeded, even after --continue' ' grep "^ file1 | 2 +-$" output ' -test_expect_success 'multi-squash only fires up editor once' ' +test_expect_success C_LOCALE_OUTPUT 'multi-squash only fires up editor once' ' base=$(git rev-parse HEAD~4) && set_fake_editor && FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 squash 2 squash 3 squash 4" \ @@ -360,7 +402,7 @@ test_expect_success 'multi-squash only fires up editor once' ' test 1 = $(git show | grep ONCE | wc -l) ' -test_expect_success 'multi-fixup does not fire up editor' ' +test_expect_success C_LOCALE_OUTPUT 'multi-fixup does not fire up editor' ' git checkout -b multi-fixup E && base=$(git rev-parse HEAD~4) && set_fake_editor && @@ -410,7 +452,7 @@ D ONCE EOF -test_expect_success 'squash and fixup generate correct log messages' ' +test_expect_success C_LOCALE_OUTPUT 'squash and fixup generate correct log messages' ' git checkout -b squash-fixup E && base=$(git rev-parse HEAD~4) && set_fake_editor && @@ -419,11 +461,15 @@ test_expect_success 'squash and fixup generate correct log messages' ' git rebase -i $base && git cat-file commit HEAD | sed -e 1,/^\$/d > actual-squash-fixup && test_cmp expect-squash-fixup actual-squash-fixup && + git cat-file commit HEAD@{2} | + grep "^# This is a combination of 3 commits\." && + git cat-file commit HEAD@{3} | + grep "^# This is a combination of 2 commits\." && git checkout to-be-rebased && git branch -D squash-fixup ' -test_expect_success 'squash ignores comments' ' +test_expect_success C_LOCALE_OUTPUT 'squash ignores comments' ' git checkout -b skip-comments E && base=$(git rev-parse HEAD~4) && set_fake_editor && @@ -436,7 +482,7 @@ test_expect_success 'squash ignores comments' ' git branch -D skip-comments ' -test_expect_success 'squash ignores blank lines' ' +test_expect_success C_LOCALE_OUTPUT 'squash ignores blank lines' ' git checkout -b skip-blank-lines E && base=$(git rev-parse HEAD~4) && set_fake_editor && @@ -540,7 +586,7 @@ test_expect_success 'clean error after failed "exec"' ' echo "edited again" > file7 && git add file7 && test_must_fail git rebase --continue 2>error && - grep "You have staged changes in your working tree." error + test_i18ngrep "you have staged changes in your working tree" error ' test_expect_success 'rebase a detached HEAD' ' @@ -555,10 +601,9 @@ test_expect_success 'rebase a detached HEAD' ' test_expect_success 'rebase a commit violating pre-commit' ' mkdir -p .git/hooks && - PRE_COMMIT=.git/hooks/pre-commit && - echo "#!/bin/sh" > $PRE_COMMIT && - echo "test -z \"\$(git diff --cached --check)\"" >> $PRE_COMMIT && - chmod a+x $PRE_COMMIT && + write_script .git/hooks/pre-commit <<-\EOF && + test -z "$(git diff --cached --check)" + EOF echo "monde! " >> file1 && test_tick && test_must_fail git commit -m doesnt-verify file1 && @@ -666,13 +711,13 @@ test_expect_success 'rebase -i continue with unstaged submodule' ' test_expect_success 'avoid unnecessary reset' ' git checkout master && git reset --hard && - test-chmtime =123456789 file3 && + test-tool chmtime =123456789 file3 && git update-index --refresh && HEAD=$(git rev-parse HEAD) && set_fake_editor && git rebase -i HEAD~4 && test $HEAD = $(git rev-parse HEAD) && - MTIME=$(test-chmtime -v +0 file3 | sed 's/[^0-9].*$//') && + MTIME=$(test-tool chmtime -v +0 file3 | sed 's/[^0-9].*$//') && test 123456789 = $MTIME ' @@ -771,7 +816,6 @@ test_expect_success 'rebase-i history with funny messages' ' test_cmp expect actual ' - test_expect_success 'prepare for rebase -i --exec' ' git checkout master && git checkout -b execute && @@ -780,7 +824,6 @@ test_expect_success 'prepare for rebase -i --exec' ' test_commit three_exec main.txt three_exec ' - test_expect_success 'running "git rebase -i --exec git show HEAD"' ' set_fake_editor && git rebase -i --exec "git show HEAD" HEAD~2 >actual && @@ -793,7 +836,6 @@ test_expect_success 'running "git rebase -i --exec git show HEAD"' ' test_cmp expected actual ' - test_expect_success 'running "git rebase --exec git show HEAD -i"' ' git reset --hard execute && set_fake_editor && @@ -807,7 +849,6 @@ test_expect_success 'running "git rebase --exec git show HEAD -i"' ' test_cmp expected actual ' - test_expect_success 'running "git rebase -ix git show HEAD"' ' git reset --hard execute && set_fake_editor && @@ -835,7 +876,6 @@ test_expect_success 'rebase -ix with several <CMD>' ' test_cmp expected actual ' - test_expect_success 'rebase -ix with several instances of --exec' ' git reset --hard execute && set_fake_editor && @@ -850,8 +890,7 @@ test_expect_success 'rebase -ix with several instances of --exec' ' test_cmp expected actual ' - -test_expect_success 'rebase -ix with --autosquash' ' +test_expect_success C_LOCALE_OUTPUT 'rebase -ix with --autosquash' ' git reset --hard execute && git checkout -b autosquash && echo second >second.txt && @@ -876,23 +915,20 @@ test_expect_success 'rebase -ix with --autosquash' ' test_cmp expected actual ' - -test_expect_success 'rebase --exec without -i shows error message' ' +test_expect_success 'rebase --exec works without -i ' ' git reset --hard execute && - set_fake_editor && - test_must_fail git rebase --exec "git show HEAD" HEAD~2 2>actual && - echo "The --exec option must be used with the --interactive option" >expected && - test_i18ncmp expected actual + rm -rf exec_output && + EDITOR="echo >invoked_editor" git rebase --exec "echo a line >>exec_output" HEAD~2 2>actual && + test_i18ngrep "Successfully rebased and updated" actual && + test_line_count = 2 exec_output && + test_path_is_missing invoked_editor ' - test_expect_success 'rebase -i --exec without <CMD>' ' git reset --hard execute && set_fake_editor && - test_must_fail git rebase -i --exec 2>tmp && - sed -e "1d" tmp >actual && - test_must_fail git rebase -h >expected && - test_cmp expected actual && + test_must_fail git rebase -i --exec 2>actual && + test_i18ngrep "requires a value" actual && git checkout master ' @@ -935,7 +971,7 @@ test_expect_success 'rebase -i --root fixup root commit' ' test 0 = $(git cat-file commit HEAD | grep -c ^parent\ ) ' -test_expect_success 'rebase --edit-todo does not works on non-interactive rebase' ' +test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-interactive rebase' ' git reset --hard && git checkout conflict-branch && set_fake_editor && @@ -961,13 +997,13 @@ test_expect_success 'rebase -i produces readable reflog' ' set_fake_editor && git rebase -i --onto I F branch-reflog-test && cat >expect <<-\EOF && - rebase -i (start): checkout I - rebase -i (pick): G - rebase -i (pick): H rebase -i (finish): returning to refs/heads/branch-reflog-test + rebase -i (pick): H + rebase -i (pick): G + rebase -i (start): checkout I EOF - tail -n 4 .git/logs/HEAD | - sed -e "s/.* //" >actual && + git reflog -n4 HEAD | + sed "s/[^:]*: //" >actual && test_cmp expect actual ' @@ -984,6 +1020,17 @@ test_expect_success 'rebase -i respects core.commentchar' ' test B = $(git cat-file commit HEAD^ | sed -ne \$p) ' +test_expect_success 'rebase -i respects core.commentchar=auto' ' + test_config core.commentchar auto && + write_script copy-edit-script.sh <<-\EOF && + cp "$1" edit-script + EOF + test_set_editor "$(pwd)/copy-edit-script.sh" && + test_when_finished "git rebase --abort || :" && + git rebase -i HEAD^ && + test -z "$(grep -ve "^#" -e "^\$" -e "^pick" edit-script)" +' + test_expect_success 'rebase -i, with <onto> and <upstream> specified as :/quuxery' ' test_when_finished "git branch -D torebase" && git checkout -b torebase branch1 && @@ -1006,6 +1053,22 @@ test_expect_success 'rebase -i with --strategy and -X' ' test $(cat file1) = Z ' +test_expect_success 'interrupted rebase -i with --strategy and -X' ' + git checkout -b conflict-merge-use-theirs-interrupted conflict-branch && + git reset --hard HEAD^ && + >breakpoint && + git add breakpoint && + git commit -m "breakpoint for interactive mode" && + echo five >conflict && + echo Z >file1 && + git commit -a -m "one file conflict" && + set_fake_editor && + FAKE_LINES="edit 1 2" git rebase -i --strategy=recursive -Xours conflict-branch && + git rebase --continue && + test $(git show conflict-branch:conflict) = $(cat conflict) && + test $(cat file1) = Z +' + test_expect_success 'rebase -i error on commits with \ in message' ' current_head=$(git rev-parse HEAD) && test_when_finished "git rebase --abort; git reset --hard $current_head; rm -f error" && @@ -1052,7 +1115,7 @@ test_expect_success 'todo count' ' EOF test_set_editor "$(pwd)/dump-raw.sh" && git rebase -i HEAD~4 >actual && - grep "^# Rebase ..* onto ..* ([0-9]" actual + test_i18ngrep "^# Rebase ..* onto ..* ([0-9]" actual ' test_expect_success 'rebase -i commits that overwrite untracked files (pick)' ' @@ -1102,4 +1165,201 @@ test_expect_success 'rebase -i commits that overwrite untracked files (no ff)' ' test $(git cat-file commit HEAD | sed -ne \$p) = I ' +test_expect_success 'rebase --continue removes CHERRY_PICK_HEAD' ' + git checkout -b commit-to-skip && + for double in X 3 1 + do + test_seq 5 | sed "s/$double/&&/" >seq && + git add seq && + test_tick && + git commit -m seq-$double + done && + git tag seq-onto && + git reset --hard HEAD~2 && + git cherry-pick seq-onto && + set_fake_editor && + test_must_fail env FAKE_LINES= git rebase -i seq-onto && + test -d .git/rebase-merge && + git rebase --continue && + git diff --exit-code seq-onto && + test ! -d .git/rebase-merge && + test ! -f .git/CHERRY_PICK_HEAD +' + +rebase_setup_and_clean () { + test_when_finished " + git checkout master && + test_might_fail git branch -D $1 && + test_might_fail git rebase --abort + " && + git checkout -b $1 master +} + +test_expect_success 'drop' ' + rebase_setup_and_clean drop-test && + set_fake_editor && + FAKE_LINES="1 drop 2 3 drop 4 5" git rebase -i --root && + test E = $(git cat-file commit HEAD | sed -ne \$p) && + test C = $(git cat-file commit HEAD^ | sed -ne \$p) && + test A = $(git cat-file commit HEAD^^ | sed -ne \$p) +' + +cat >expect <<EOF +Successfully rebased and updated refs/heads/missing-commit. +EOF + +test_expect_success 'rebase -i respects rebase.missingCommitsCheck = ignore' ' + test_config rebase.missingCommitsCheck ignore && + rebase_setup_and_clean missing-commit && + set_fake_editor && + FAKE_LINES="1 2 3 4" \ + git rebase -i --root 2>actual && + test D = $(git cat-file commit HEAD | sed -ne \$p) && + test_i18ncmp expect actual +' + +cat >expect <<EOF +Warning: some commits may have been dropped accidentally. +Dropped commits (newer to older): + - $(git rev-list --pretty=oneline --abbrev-commit -1 master) +To avoid this message, use "drop" to explicitly remove a commit. + +Use 'git config rebase.missingCommitsCheck' to change the level of warnings. +The possible behaviours are: ignore, warn, error. + +Successfully rebased and updated refs/heads/missing-commit. +EOF + +test_expect_success 'rebase -i respects rebase.missingCommitsCheck = warn' ' + test_config rebase.missingCommitsCheck warn && + rebase_setup_and_clean missing-commit && + set_fake_editor && + FAKE_LINES="1 2 3 4" \ + git rebase -i --root 2>actual && + test_i18ncmp expect actual && + test D = $(git cat-file commit HEAD | sed -ne \$p) +' + +cat >expect <<EOF +Warning: some commits may have been dropped accidentally. +Dropped commits (newer to older): + - $(git rev-list --pretty=oneline --abbrev-commit -1 master) + - $(git rev-list --pretty=oneline --abbrev-commit -1 master~2) +To avoid this message, use "drop" to explicitly remove a commit. + +Use 'git config rebase.missingCommitsCheck' to change the level of warnings. +The possible behaviours are: ignore, warn, error. + +You can fix this with 'git rebase --edit-todo' and then run 'git rebase --continue'. +Or you can abort the rebase with 'git rebase --abort'. +EOF + +test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' ' + test_config rebase.missingCommitsCheck error && + rebase_setup_and_clean missing-commit && + set_fake_editor && + test_must_fail env FAKE_LINES="1 2 4" \ + git rebase -i --root 2>actual && + test_i18ncmp expect actual && + cp .git/rebase-merge/git-rebase-todo.backup \ + .git/rebase-merge/git-rebase-todo && + FAKE_LINES="1 2 drop 3 4 drop 5" \ + git rebase --edit-todo && + git rebase --continue && + test D = $(git cat-file commit HEAD | sed -ne \$p) && + test B = $(git cat-file commit HEAD^ | sed -ne \$p) +' + +test_expect_success 'respects rebase.abbreviateCommands with fixup, squash and exec' ' + rebase_setup_and_clean abbrevcmd && + test_commit "first" file1.txt "first line" first && + test_commit "second" file1.txt "another line" second && + test_commit "fixup! first" file2.txt "first line again" first_fixup && + test_commit "squash! second" file1.txt "another line here" second_squash && + cat >expected <<-EOF && + p $(git rev-list --abbrev-commit -1 first) first + f $(git rev-list --abbrev-commit -1 first_fixup) fixup! first + x git show HEAD + p $(git rev-list --abbrev-commit -1 second) second + s $(git rev-list --abbrev-commit -1 second_squash) squash! second + x git show HEAD + EOF + git checkout abbrevcmd && + set_cat_todo_editor && + test_config rebase.abbreviateCommands true && + test_must_fail git rebase -i --exec "git show HEAD" \ + --autosquash master >actual && + test_cmp expected actual +' + +test_expect_success 'static check of bad command' ' + rebase_setup_and_clean bad-cmd && + set_fake_editor && + test_must_fail env FAKE_LINES="1 2 3 bad 4 5" \ + git rebase -i --root 2>actual && + test_i18ngrep "badcmd $(git rev-list --oneline -1 master~1)" actual && + test_i18ngrep "You can fix this with .git rebase --edit-todo.." actual && + FAKE_LINES="1 2 3 drop 4 5" git rebase --edit-todo && + git rebase --continue && + test E = $(git cat-file commit HEAD | sed -ne \$p) && + test C = $(git cat-file commit HEAD^ | sed -ne \$p) +' + +test_expect_success 'tabs and spaces are accepted in the todolist' ' + rebase_setup_and_clean indented-comment && + write_script add-indent.sh <<-\EOF && + ( + # Turn single spaces into space/tab mix + sed "1s/ / /g; 2s/ / /g; 3s/ / /g" "$1" + printf "\n\t# comment\n #more\n\t # comment\n" + ) >"$1.new" + mv "$1.new" "$1" + EOF + test_set_editor "$(pwd)/add-indent.sh" && + git rebase -i HEAD^^^ && + test E = $(git cat-file commit HEAD | sed -ne \$p) +' + +test_expect_success 'static check of bad SHA-1' ' + rebase_setup_and_clean bad-sha && + set_fake_editor && + test_must_fail env FAKE_LINES="1 2 edit fakesha 3 4 5 #" \ + git rebase -i --root 2>actual && + test_i18ngrep "edit XXXXXXX False commit" actual && + test_i18ngrep "You can fix this with .git rebase --edit-todo.." actual && + FAKE_LINES="1 2 4 5 6" git rebase --edit-todo && + git rebase --continue && + test E = $(git cat-file commit HEAD | sed -ne \$p) +' + +test_expect_success 'editor saves as CR/LF' ' + git checkout -b with-crlf && + write_script add-crs.sh <<-\EOF && + sed -e "s/\$/Q/" <"$1" | tr Q "\\015" >"$1".new && + mv -f "$1".new "$1" + EOF + ( + test_set_editor "$(pwd)/add-crs.sh" && + git rebase -i HEAD^ + ) +' + +SQ="'" +test_expect_success 'rebase -i --gpg-sign=<key-id>' ' + test_when_finished "test_might_fail git rebase --abort" && + set_fake_editor && + FAKE_LINES="edit 1" git rebase -i --gpg-sign="\"S I Gner\"" HEAD^ \ + >out 2>err && + test_i18ngrep "$SQ-S\"S I Gner\"$SQ" err +' + +test_expect_success 'rebase -i --gpg-sign=<key-id> overrides commit.gpgSign' ' + test_when_finished "test_might_fail git rebase --abort" && + test_config commit.gpgsign true && + set_fake_editor && + FAKE_LINES="edit 1" git rebase -i --gpg-sign="\"S I Gner\"" HEAD^ \ + >out 2>err && + test_i18ngrep "$SQ-S\"S I Gner\"$SQ" err +' + test_done diff --git a/t/t3405-rebase-malformed.sh b/t/t3405-rebase-malformed.sh index ff8c360cd5..cb7c6de84a 100755 --- a/t/t3405-rebase-malformed.sh +++ b/t/t3405-rebase-malformed.sh @@ -3,6 +3,7 @@ test_description='rebase should handle arbitrary git message' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh cat >F <<\EOF This is an example of a commit log message @@ -25,6 +26,7 @@ test_expect_success setup ' test_tick && git commit -m "Initial commit" && git branch diff-in-message && + git branch empty-message-merge && git checkout -b multi-line-subject && cat F >file2 && @@ -45,6 +47,11 @@ test_expect_success setup ' git cat-file commit HEAD | sed -e "1,/^\$/d" >G0 && + git checkout empty-message-merge && + echo file3 >file3 && + git add file3 && + git commit --allow-empty-message -m "" && + git checkout master && echo One >file1 && @@ -69,4 +76,20 @@ test_expect_success 'rebase commit with diff in message' ' test_cmp G G0 ' +test_expect_success 'rebase -m commit with empty message' ' + test_must_fail git rebase -m master empty-message-merge && + git rebase --abort && + git rebase -m --allow-empty-message master empty-message-merge +' + +test_expect_success 'rebase -i commit with empty message' ' + git checkout diff-in-message && + set_fake_editor && + test_must_fail env FAKE_COMMIT_MESSAGE=" " FAKE_LINES="reword 1" \ + git rebase -i HEAD^ && + git rebase --abort && + FAKE_COMMIT_MESSAGE=" " FAKE_LINES="reword 1" \ + git rebase -i --allow-empty-message HEAD^ +' + test_done diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh index a6a6c40a98..910f218284 100755 --- a/t/t3407-rebase-abort.sh +++ b/t/t3407-rebase-abort.sh @@ -99,4 +99,28 @@ testrebase() { testrebase "" .git/rebase-apply testrebase " --merge" .git/rebase-merge +test_expect_success 'rebase --quit' ' + cd "$work_dir" && + # Clean up the state from the previous one + git reset --hard pre-rebase && + test_must_fail git rebase master && + test_path_is_dir .git/rebase-apply && + head_before=$(git rev-parse HEAD) && + git rebase --quit && + test $(git rev-parse HEAD) = $head_before && + test ! -d .git/rebase-apply +' + +test_expect_success 'rebase --merge --quit' ' + cd "$work_dir" && + # Clean up the state from the previous one + git reset --hard pre-rebase && + test_must_fail git rebase --merge master && + test_path_is_dir .git/rebase-merge && + head_before=$(git rev-parse HEAD) && + git rebase --quit && + test $(git rev-parse HEAD) = $head_before && + test ! -d .git/rebase-merge +' + test_done diff --git a/t/t3408-rebase-multi-line.sh b/t/t3408-rebase-multi-line.sh index 6b84e6042a..e7292f5b9b 100755 --- a/t/t3408-rebase-multi-line.sh +++ b/t/t3408-rebase-multi-line.sh @@ -24,8 +24,23 @@ But otherwise with a sane description." && >elif && git add elif && test_tick && - git commit -m second + git commit -m second && + git checkout -b side2 && + >afile && + git add afile && + test_tick && + git commit -m third && + echo hello >afile && + test_tick && + git commit -a -m fourth && + git checkout -b side-merge && + git reset --hard HEAD^^ && + git merge --no-ff -m "A merge commit log message that has a long +summary that spills over multiple lines. + +But otherwise with a sane description." side2 && + git branch side-merge-original ' test_expect_success rebase ' @@ -37,5 +52,14 @@ test_expect_success rebase ' test_cmp expect actual ' +test_expect_success rebasep ' + + git checkout side-merge && + git rebase -p side && + git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && + git cat-file commit side-merge-original | sed -e "1,/^\$/d" >expect && + test_cmp expect actual + +' test_done diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh index 0b52105728..73a39f2923 100755 --- a/t/t3412-rebase-root.sh +++ b/t/t3412-rebase-root.sh @@ -133,7 +133,7 @@ test_expect_success 'set up second root and merge' ' rm A B C && test_commit 6 D && git checkout other && - git merge third + git merge --allow-unrelated-histories third ' cat > expect-third <<'EOF' diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh index 41370ab998..e364c12622 100755 --- a/t/t3415-rebase-autosquash.sh +++ b/t/t3415-rebase-autosquash.sh @@ -234,20 +234,100 @@ test_auto_fixup_fixup () { fi } -test_expect_success 'fixup! fixup!' ' +test_expect_success C_LOCALE_OUTPUT 'fixup! fixup!' ' test_auto_fixup_fixup fixup fixup ' -test_expect_success 'fixup! squash!' ' +test_expect_success C_LOCALE_OUTPUT 'fixup! squash!' ' test_auto_fixup_fixup fixup squash ' -test_expect_success 'squash! squash!' ' +test_expect_success C_LOCALE_OUTPUT 'squash! squash!' ' test_auto_fixup_fixup squash squash ' -test_expect_success 'squash! fixup!' ' +test_expect_success C_LOCALE_OUTPUT 'squash! fixup!' ' test_auto_fixup_fixup squash fixup ' +test_expect_success C_LOCALE_OUTPUT 'autosquash with custom inst format' ' + git reset --hard base && + git config --add rebase.instructionFormat "[%an @ %ar] %s" && + echo 2 >file1 && + git add -u && + test_tick && + git commit -m "squash! $(git rev-parse --short HEAD^)" && + echo 1 >file1 && + git add -u && + test_tick && + git commit -m "squash! $(git log -n 1 --format=%s HEAD~2)" && + git tag final-squash-instFmt && + test_tick && + git rebase --autosquash -i HEAD~4 && + git log --oneline >actual && + test_line_count = 3 actual && + git diff --exit-code final-squash-instFmt && + test 1 = "$(git cat-file blob HEAD^:file1)" && + test 2 = $(git cat-file commit HEAD^ | grep squash | wc -l) +' + +test_expect_success 'autosquash with empty custom instructionFormat' ' + git reset --hard base && + test_commit empty-instructionFormat-test && + ( + set_cat_todo_editor && + test_must_fail git -c rebase.instructionFormat= \ + rebase --autosquash --force -i HEAD^ >actual && + git log -1 --format="pick %h %s" >expect && + test_cmp expect actual + ) +' + +set_backup_editor () { + write_script backup-editor.sh <<-\EOF + cp "$1" .git/backup-"$(basename "$1")" + EOF + test_set_editor "$PWD/backup-editor.sh" +} + +test_expect_success 'autosquash with multiple empty patches' ' + test_tick && + git commit --allow-empty -m "empty" && + test_tick && + git commit --allow-empty -m "empty2" && + test_tick && + >fixup && + git add fixup && + git commit --fixup HEAD^^ && + ( + set_backup_editor && + GIT_USE_REBASE_HELPER=false \ + git rebase -i --force-rebase --autosquash HEAD~4 && + grep empty2 .git/backup-git-rebase-todo + ) +' + +test_expect_success 'extra spaces after fixup!' ' + base=$(git rev-parse HEAD) && + test_commit to-fixup && + git commit --allow-empty -m "fixup! to-fixup" && + git rebase -i --autosquash --keep-empty HEAD~2 && + parent=$(git rev-parse HEAD^) && + test $base = $parent +' + +test_expect_success 'wrapped original subject' ' + if test -d .git/rebase-merge; then git rebase --abort; fi && + base=$(git rev-parse HEAD) && + echo "wrapped subject" >wrapped && + git add wrapped && + test_tick && + git commit --allow-empty -m "$(printf "To\nfixup")" && + test_tick && + git commit --allow-empty -m "fixup! To fixup" && + git rebase -i --autosquash --keep-empty HEAD~2 && + parent=$(git rev-parse HEAD^) && + test $base = $parent +' + test_done diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh index 2680375628..9214d0bb51 100755 --- a/t/t3418-rebase-continue.sh +++ b/t/t3418-rebase-continue.sh @@ -24,7 +24,7 @@ test_expect_success 'interactive rebase --continue works with touched file' ' git checkout master && FAKE_LINES="edit 1" git rebase -i HEAD^ && - test-chmtime =-60 F1 && + test-tool chmtime =-60 F1 && git rebase --continue ' @@ -36,7 +36,7 @@ test_expect_success 'non-interactive rebase --continue works with touched file' test_must_fail git rebase --onto master master topic && echo "Resolved" >F2 && git add F2 && - test-chmtime =-60 F1 && + test-tool chmtime =-60 F1 && git rebase --continue ' @@ -74,25 +74,89 @@ test_expect_success 'rebase --continue remembers merge strategy and options' ' test -f funny.was.run ' -test_expect_success 'rebase --continue remembers --rerere-autoupdate' ' +test_expect_success 'rebase passes merge strategy options correctly' ' + rm -fr .git/rebase-* && + git reset --hard commit-new-file-F3-on-topic-branch && + test_commit theirs-to-merge && + git reset --hard HEAD^ && + test_commit some-commit && + test_tick && + git merge --no-ff theirs-to-merge && + FAKE_LINES="1 edit 2 3" git rebase -i -f -p -m \ + -s recursive --strategy-option=theirs HEAD~2 && + test_commit force-change && + git rebase --continue +' + +test_expect_success 'setup rerere database' ' rm -fr .git/rebase-* && git reset --hard commit-new-file-F3-on-topic-branch && git checkout master && test_commit "commit-new-file-F3" F3 3 && - git config rerere.enabled true && + test_config rerere.enabled true && test_must_fail git rebase -m master topic && echo "Resolved" >F2 && + cp F2 expected-F2 && git add F2 && test_must_fail git rebase --continue && echo "Resolved" >F3 && + cp F3 expected-F3 && git add F3 && git rebase --continue && - git reset --hard topic@{1} && - test_must_fail git rebase -m --rerere-autoupdate master && - test "$(cat F2)" = "Resolved" && - test_must_fail git rebase --continue && - test "$(cat F3)" = "Resolved" && - git rebase --continue + git reset --hard topic@{1} ' +prepare () { + rm -fr .git/rebase-* && + git reset --hard commit-new-file-F3-on-topic-branch && + git checkout master && + test_config rerere.enabled true +} + +test_rerere_autoupdate () { + action=$1 && + test_expect_success "rebase $action --continue remembers --rerere-autoupdate" ' + prepare && + test_must_fail git rebase $action --rerere-autoupdate master topic && + test_cmp expected-F2 F2 && + git diff-files --quiet && + test_must_fail git rebase --continue && + test_cmp expected-F3 F3 && + git diff-files --quiet && + git rebase --continue + ' + + test_expect_success "rebase $action --continue honors rerere.autoUpdate" ' + prepare && + test_config rerere.autoupdate true && + test_must_fail git rebase $action master topic && + test_cmp expected-F2 F2 && + git diff-files --quiet && + test_must_fail git rebase --continue && + test_cmp expected-F3 F3 && + git diff-files --quiet && + git rebase --continue + ' + + test_expect_success "rebase $action --continue remembers --no-rerere-autoupdate" ' + prepare && + test_config rerere.autoupdate true && + test_must_fail git rebase $action --no-rerere-autoupdate master topic && + test_cmp expected-F2 F2 && + test_must_fail git diff-files --quiet && + git add F2 && + test_must_fail git rebase --continue && + test_cmp expected-F3 F3 && + test_must_fail git diff-files --quiet && + git add F3 && + git rebase --continue + ' +} + +test_rerere_autoupdate +test_rerere_autoupdate -m +GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR +test_rerere_autoupdate -i +test_rerere_autoupdate --preserve-merges + test_done diff --git a/t/t3419-rebase-patch-id.sh b/t/t3419-rebase-patch-id.sh index 217dd79b2e..49f548cdb9 100755 --- a/t/t3419-rebase-patch-id.sh +++ b/t/t3419-rebase-patch-id.sh @@ -73,17 +73,17 @@ do_tests () { run git format-patch --stdout --ignore-if-in-upstream master " - test_expect_success $pr 'detect upstream patch' " + test_expect_success $pr 'detect upstream patch' ' git checkout -q master && scramble file && git add file && - git commit -q -m 'change big file again' && + git commit -q -m "change big file again" && git checkout -q other^{} && git rebase master && - test_must_fail test -n \"\$(git rev-list master...HEAD~)\" - " + test_must_fail test -n "$(git rev-list master...HEAD~)" + ' - test_expect_success $pr 'do not drop patch' " + test_expect_success $pr 'do not drop patch' ' git branch -f squashed master && git checkout -q -f squashed && git reset -q --soft HEAD~2 && @@ -91,7 +91,7 @@ do_tests () { git checkout -q other^{} && test_must_fail git rebase squashed && rm -rf .git/rebase-apply - " + ' } do_tests 500 diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh index d783f03d3f..e243700660 100755 --- a/t/t3420-rebase-autostash.sh +++ b/t/t3420-rebase-autostash.sh @@ -33,22 +33,154 @@ test_expect_success setup ' git commit -m "related commit" ' -testrebase() { +create_expected_success_am () { + cat >expected <<-EOF + $(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual) + HEAD is now at $(git rev-parse --short feature-branch) third commit + First, rewinding head to replay your work on top of it... + Applying: second commit + Applying: third commit + Applied autostash. + EOF +} + +create_expected_success_interactive () { + q_to_cr >expected <<-EOF + $(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual) + HEAD is now at $(git rev-parse --short feature-branch) third commit + Rebasing (1/2)QRebasing (2/2)QApplied autostash. + Successfully rebased and updated refs/heads/rebased-feature-branch. + EOF +} + +create_expected_success_merge () { + cat >expected <<-EOF + $(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual) + HEAD is now at $(git rev-parse --short feature-branch) third commit + First, rewinding head to replay your work on top of it... + Merging unrelated-onto-branch with HEAD~1 + Merging: + $(git rev-parse --short unrelated-onto-branch) unrelated commit + $(git rev-parse --short feature-branch^) second commit + found 1 common ancestor: + $(git rev-parse --short feature-branch~2) initial commit + [detached HEAD $(git rev-parse --short rebased-feature-branch~1)] second commit + Author: A U Thor <author@example.com> + Date: Thu Apr 7 15:14:13 2005 -0700 + 2 files changed, 2 insertions(+) + create mode 100644 file1 + create mode 100644 file2 + Committed: 0001 second commit + Merging unrelated-onto-branch with HEAD~0 + Merging: + $(git rev-parse --short rebased-feature-branch~1) second commit + $(git rev-parse --short feature-branch) third commit + found 1 common ancestor: + $(git rev-parse --short feature-branch~1) second commit + [detached HEAD $(git rev-parse --short rebased-feature-branch)] third commit + Author: A U Thor <author@example.com> + Date: Thu Apr 7 15:15:13 2005 -0700 + 1 file changed, 1 insertion(+) + create mode 100644 file3 + Committed: 0002 third commit + All done. + Applied autostash. + EOF +} + +create_expected_failure_am () { + cat >expected <<-EOF + $(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual) + HEAD is now at $(git rev-parse --short feature-branch) third commit + First, rewinding head to replay your work on top of it... + Applying: second commit + Applying: third commit + Applying autostash resulted in conflicts. + Your changes are safe in the stash. + You can run "git stash pop" or "git stash drop" at any time. + EOF +} + +create_expected_failure_interactive () { + q_to_cr >expected <<-EOF + $(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual) + HEAD is now at $(git rev-parse --short feature-branch) third commit + Rebasing (1/2)QRebasing (2/2)QApplying autostash resulted in conflicts. + Your changes are safe in the stash. + You can run "git stash pop" or "git stash drop" at any time. + Successfully rebased and updated refs/heads/rebased-feature-branch. + EOF +} + +create_expected_failure_merge () { + cat >expected <<-EOF + $(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual) + HEAD is now at $(git rev-parse --short feature-branch) third commit + First, rewinding head to replay your work on top of it... + Merging unrelated-onto-branch with HEAD~1 + Merging: + $(git rev-parse --short unrelated-onto-branch) unrelated commit + $(git rev-parse --short feature-branch^) second commit + found 1 common ancestor: + $(git rev-parse --short feature-branch~2) initial commit + [detached HEAD $(git rev-parse --short rebased-feature-branch~1)] second commit + Author: A U Thor <author@example.com> + Date: Thu Apr 7 15:14:13 2005 -0700 + 2 files changed, 2 insertions(+) + create mode 100644 file1 + create mode 100644 file2 + Committed: 0001 second commit + Merging unrelated-onto-branch with HEAD~0 + Merging: + $(git rev-parse --short rebased-feature-branch~1) second commit + $(git rev-parse --short feature-branch) third commit + found 1 common ancestor: + $(git rev-parse --short feature-branch~1) second commit + [detached HEAD $(git rev-parse --short rebased-feature-branch)] third commit + Author: A U Thor <author@example.com> + Date: Thu Apr 7 15:15:13 2005 -0700 + 1 file changed, 1 insertion(+) + create mode 100644 file3 + Committed: 0002 third commit + All done. + Applying autostash resulted in conflicts. + Your changes are safe in the stash. + You can run "git stash pop" or "git stash drop" at any time. + EOF +} + +testrebase () { type=$1 dotest=$2 - test_expect_success "rebase$type: dirty worktree, non-conflicting rebase" ' + test_expect_success "rebase$type: dirty worktree, --no-autostash" ' test_config rebase.autostash true && git reset --hard && git checkout -b rebased-feature-branch feature-branch && test_when_finished git branch -D rebased-feature-branch && + test_when_finished git checkout feature-branch && echo dirty >>file3 && - git rebase$type unrelated-onto-branch && + test_must_fail git rebase$type --no-autostash unrelated-onto-branch + ' + + test_expect_success "rebase$type: dirty worktree, non-conflicting rebase" ' + test_config rebase.autostash true && + git reset --hard && + git checkout -b rebased-feature-branch feature-branch && + echo dirty >>file3 && + git rebase$type unrelated-onto-branch >actual 2>&1 && grep unrelated file4 && grep dirty file3 && git checkout feature-branch ' + test_expect_success "rebase$type --autostash: check output" ' + test_when_finished git branch -D rebased-feature-branch && + suffix=${type#\ --} && suffix=${suffix:-am} && + create_expected_success_$suffix && + test_i18ncmp expected actual + ' + test_expect_success "rebase$type: dirty index, non-conflicting rebase" ' test_config rebase.autostash true && git reset --hard && @@ -127,10 +259,9 @@ testrebase() { test_config rebase.autostash true && git reset --hard && git checkout -b rebased-feature-branch feature-branch && - test_when_finished git branch -D rebased-feature-branch && echo dirty >file4 && git add file4 && - git rebase$type unrelated-onto-branch && + git rebase$type unrelated-onto-branch >actual 2>&1 && test_path_is_missing $dotest && git reset --hard && grep unrelated file4 && @@ -139,6 +270,13 @@ testrebase() { git stash pop && grep dirty file4 ' + + test_expect_success "rebase$type: check output with conflicting stash" ' + test_when_finished git branch -D rebased-feature-branch && + suffix=${type#\ --} && suffix=${suffix:-am} && + create_expected_failure_$suffix && + test_i18ncmp expected actual + ' } test_expect_success "rebase: fast-forward rebase" ' @@ -169,7 +307,7 @@ testrebase " --interactive" .git/rebase-merge test_expect_success 'abort rebase -i with --autostash' ' test_when_finished "git reset --hard" && - echo uncommited-content >file0 && + echo uncommitted-content >file0 && ( write_script abort-editor.sh <<-\EOF && echo >"$1" @@ -178,7 +316,38 @@ test_expect_success 'abort rebase -i with --autostash' ' test_must_fail git rebase -i --autostash HEAD^ && rm -f abort-editor.sh ) && - echo uncommited-content >expected && + echo uncommitted-content >expected && + test_cmp expected file0 +' + +test_expect_success 'restore autostash on editor failure' ' + test_when_finished "git reset --hard" && + echo uncommitted-content >file0 && + ( + test_set_editor "false" && + test_must_fail git rebase -i --autostash HEAD^ + ) && + echo uncommitted-content >expected && + test_cmp expected file0 +' + +test_expect_success 'autostash is saved on editor failure with conflict' ' + test_when_finished "git reset --hard" && + echo uncommitted-content >file0 && + ( + write_script abort-editor.sh <<-\EOF && + echo conflicting-content >file0 + exit 1 + EOF + test_set_editor "$(pwd)/abort-editor.sh" && + test_must_fail git rebase -i --autostash HEAD^ && + rm -f abort-editor.sh + ) && + echo conflicting-content >expected && + test_cmp expected file0 && + git checkout file0 && + git stash pop && + echo uncommitted-content >expected && test_cmp expected file0 ' diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh index 9c55cba198..b078f93046 100755 --- a/t/t3421-rebase-topology-linear.sh +++ b/t/t3421-rebase-topology-linear.sh @@ -199,7 +199,7 @@ test_run_rebase () { " } test_run_rebase success '' -test_run_rebase failure -m +test_run_rebase success -m test_run_rebase success -i test_run_rebase failure -p @@ -214,8 +214,8 @@ test_run_rebase () { " } test_run_rebase success '' -test_run_rebase failure -m -test_run_rebase failure -i +test_run_rebase success -m +test_run_rebase success -i test_run_rebase failure -p # m @@ -253,7 +253,7 @@ test_run_rebase () { " } test_run_rebase success '' -test_run_rebase failure -m +test_run_rebase success -m test_run_rebase success -i test_run_rebase success -p @@ -268,7 +268,7 @@ test_run_rebase () { " } test_run_rebase success '' -test_run_rebase failure -m +test_run_rebase success -m test_run_rebase success -i test_run_rebase failure -p diff --git a/t/t3426-rebase-submodule.sh b/t/t3426-rebase-submodule.sh index d5b896d445..a2bba04ba9 100755 --- a/t/t3426-rebase-submodule.sh +++ b/t/t3426-rebase-submodule.sh @@ -38,9 +38,23 @@ git_rebase_interactive () { git rebase -i "$1" } -KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1 -# The real reason "replace directory with submodule" fails is because a -# directory "sub1" exists, but we reuse the suppression added for merge here test_submodule_switch "git_rebase_interactive" +test_expect_success 'rebase interactive ignores modified submodules' ' + test_when_finished "rm -rf super sub" && + git init sub && + git -C sub commit --allow-empty -m "Initial commit" && + git init super && + git -C super submodule add ../sub && + git -C super config submodule.sub.ignore dirty && + >super/foo && + git -C super add foo && + git -C super commit -m "Initial commit" && + test_commit -C super a && + test_commit -C super b && + test_commit -C super/sub c && + set_fake_editor && + git -C super rebase -i HEAD^^ +' + test_done diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh new file mode 100755 index 0000000000..3780877e4e --- /dev/null +++ b/t/t3427-rebase-subtree.sh @@ -0,0 +1,119 @@ +#!/bin/sh + +test_description='git rebase tests for -Xsubtree + +This test runs git rebase and tests the subtree strategy. +' +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh + +commit_message() { + git log --pretty=format:%s -1 "$1" +} + +test_expect_success 'setup' ' + test_commit README && + mkdir files && + ( + cd files && + git init && + test_commit master1 && + test_commit master2 && + test_commit master3 + ) && + git fetch files master && + git branch files-master FETCH_HEAD && + git read-tree --prefix=files_subtree files-master && + git checkout -- files_subtree && + tree=$(git write-tree) && + head=$(git rev-parse HEAD) && + rev=$(git rev-parse --verify files-master^0) && + commit=$(git commit-tree -p $head -p $rev -m "Add subproject master" $tree) && + git update-ref HEAD $commit && + ( + cd files_subtree && + test_commit master4 + ) && + test_commit files_subtree/master5 +' + +# FAILURE: Does not preserve master4. +test_expect_failure 'Rebase -Xsubtree --preserve-merges --onto commit 4' ' + reset_rebase && + git checkout -b rebase-preserve-merges-4 master && + git filter-branch --prune-empty -f --subdirectory-filter files_subtree && + git commit -m "Empty commit" --allow-empty && + git rebase -Xsubtree=files_subtree --preserve-merges --onto files-master master && + verbose test "$(commit_message HEAD~)" = "files_subtree/master4" +' + +# FAILURE: Does not preserve master5. +test_expect_failure 'Rebase -Xsubtree --preserve-merges --onto commit 5' ' + reset_rebase && + git checkout -b rebase-preserve-merges-5 master && + git filter-branch --prune-empty -f --subdirectory-filter files_subtree && + git commit -m "Empty commit" --allow-empty && + git rebase -Xsubtree=files_subtree --preserve-merges --onto files-master master && + verbose test "$(commit_message HEAD)" = "files_subtree/master5" +' + +# FAILURE: Does not preserve master4. +test_expect_failure 'Rebase -Xsubtree --keep-empty --preserve-merges --onto commit 4' ' + reset_rebase && + git checkout -b rebase-keep-empty-4 master && + git filter-branch --prune-empty -f --subdirectory-filter files_subtree && + git commit -m "Empty commit" --allow-empty && + git rebase -Xsubtree=files_subtree --keep-empty --preserve-merges --onto files-master master && + verbose test "$(commit_message HEAD~2)" = "files_subtree/master4" +' + +# FAILURE: Does not preserve master5. +test_expect_failure 'Rebase -Xsubtree --keep-empty --preserve-merges --onto commit 5' ' + reset_rebase && + git checkout -b rebase-keep-empty-5 master && + git filter-branch --prune-empty -f --subdirectory-filter files_subtree && + git commit -m "Empty commit" --allow-empty && + git rebase -Xsubtree=files_subtree --keep-empty --preserve-merges --onto files-master master && + verbose test "$(commit_message HEAD~)" = "files_subtree/master5" +' + +# FAILURE: Does not preserve Empty. +test_expect_failure 'Rebase -Xsubtree --keep-empty --preserve-merges --onto empty commit' ' + reset_rebase && + git checkout -b rebase-keep-empty-empty master && + git filter-branch --prune-empty -f --subdirectory-filter files_subtree && + git commit -m "Empty commit" --allow-empty && + git rebase -Xsubtree=files_subtree --keep-empty --preserve-merges --onto files-master master && + verbose test "$(commit_message HEAD)" = "Empty commit" +' + +# FAILURE: fatal: Could not parse object +test_expect_failure 'Rebase -Xsubtree --onto commit 4' ' + reset_rebase && + git checkout -b rebase-onto-4 master && + git filter-branch --prune-empty -f --subdirectory-filter files_subtree && + git commit -m "Empty commit" --allow-empty && + git rebase -Xsubtree=files_subtree --onto files-master master && + verbose test "$(commit_message HEAD~2)" = "files_subtree/master4" +' + +# FAILURE: fatal: Could not parse object +test_expect_failure 'Rebase -Xsubtree --onto commit 5' ' + reset_rebase && + git checkout -b rebase-onto-5 master && + git filter-branch --prune-empty -f --subdirectory-filter files_subtree && + git commit -m "Empty commit" --allow-empty && + git rebase -Xsubtree=files_subtree --onto files-master master && + verbose test "$(commit_message HEAD~)" = "files_subtree/master5" +' +# FAILURE: fatal: Could not parse object +test_expect_failure 'Rebase -Xsubtree --onto empty commit' ' + reset_rebase && + git checkout -b rebase-onto-empty master && + git filter-branch --prune-empty -f --subdirectory-filter files_subtree && + git commit -m "Empty commit" --allow-empty && + git rebase -Xsubtree=files_subtree --onto files-master master && + verbose test "$(commit_message HEAD)" = "Empty commit" +' + +test_done diff --git a/t/t3428-rebase-signoff.sh b/t/t3428-rebase-signoff.sh new file mode 100755 index 0000000000..f6993b7e14 --- /dev/null +++ b/t/t3428-rebase-signoff.sh @@ -0,0 +1,84 @@ +#!/bin/sh + +test_description='git rebase --signoff + +This test runs git rebase --signoff and make sure that it works. +' + +. ./test-lib.sh + +# A simple file to commit +cat >file <<EOF +a +EOF + +# Expected commit message for initial commit after rebase --signoff +cat >expected-initial-signed <<EOF +Initial empty commit + +Signed-off-by: $(git var GIT_COMMITTER_IDENT | sed -e "s/>.*/>/") +EOF + +# Expected commit message after rebase --signoff +cat >expected-signed <<EOF +first + +Signed-off-by: $(git var GIT_COMMITTER_IDENT | sed -e "s/>.*/>/") +EOF + +# Expected commit message after rebase without --signoff (or with --no-signoff) +cat >expected-unsigned <<EOF +first +EOF + + +# We configure an alias to do the rebase --signoff so that +# on the next subtest we can show that --no-signoff overrides the alias +test_expect_success 'rebase --signoff adds a sign-off line' ' + git commit --allow-empty -m "Initial empty commit" && + git add file && git commit -m first && + git config alias.rbs "rebase --signoff" && + git rbs HEAD^ && + git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && + test_cmp expected-signed actual +' + +test_expect_success 'rebase --no-signoff does not add a sign-off line' ' + git commit --amend -m "first" && + git rbs --no-signoff HEAD^ && + git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && + test_cmp expected-unsigned actual +' + +test_expect_success 'rebase --exec --signoff adds a sign-off line' ' + test_when_finished "rm exec" && + git commit --amend -m "first" && + git rebase --exec "touch exec" --signoff HEAD^ && + test_path_is_file exec && + git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && + test_cmp expected-signed actual +' + +test_expect_success 'rebase --root --signoff adds a sign-off line' ' + git commit --amend -m "first" && + git rebase --root --keep-empty --signoff && + git cat-file commit HEAD^ | sed -e "1,/^\$/d" >actual && + test_cmp expected-initial-signed actual && + git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && + test_cmp expected-signed actual +' + +test_expect_success 'rebase -i --signoff fails' ' + git commit --amend -m "first" && + git rebase -i --signoff HEAD^ && + git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && + test_cmp expected-signed actual +' + +test_expect_success 'rebase -m --signoff fails' ' + git commit --amend -m "first" && + git rebase -m --signoff HEAD^ && + git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && + test_cmp expected-signed actual +' +test_done diff --git a/t/t3429-rebase-edit-todo.sh b/t/t3429-rebase-edit-todo.sh new file mode 100755 index 0000000000..b9292dfc2a --- /dev/null +++ b/t/t3429-rebase-edit-todo.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +test_description='rebase should reread the todo file if an exec modifies it' + +. ./test-lib.sh + +test_expect_success 'rebase exec modifies rebase-todo' ' + test_commit initial && + todo=.git/rebase-merge/git-rebase-todo && + git rebase HEAD -x "echo exec touch F >>$todo" && + test -e F +' + +test_done diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh index 51f3bbb8af..ccbc118514 100755 --- a/t/t3501-revert-cherry-pick.sh +++ b/t/t3501-revert-cherry-pick.sh @@ -86,7 +86,7 @@ test_expect_success 'cherry-pick on stat-dirty working tree' ' ( cd copy && git checkout initial && - test-chmtime +40 oops && + test-tool chmtime +40 oops && git cherry-pick added ) ' @@ -96,7 +96,7 @@ test_expect_success 'revert forbidden on dirty working tree' ' echo content >extra_file && git add extra_file && test_must_fail git revert HEAD 2>errors && - test_i18ngrep "Your local changes would be overwritten by " errors + test_i18ngrep "your local changes would be overwritten by " errors ' @@ -141,4 +141,19 @@ test_expect_success 'cherry-pick "-" works with arguments' ' test_cmp expect actual ' +test_expect_failure 'cherry-pick works with dirty renamed file' ' + test_commit to-rename && + git checkout -b unrelated && + test_commit unrelated && + git checkout @{-1} && + git mv to-rename.t renamed && + test_tick && + git commit -m renamed && + echo modified >renamed && + test_must_fail git cherry-pick refs/heads/unrelated >out && + test_i18ngrep "Refusing to lose dirty file at renamed" out && + test $(git rev-parse :0:renamed) = $(git rev-parse HEAD^:to-rename.t) && + grep -q "^modified$" renamed +' + test_done diff --git a/t/t3502-cherry-pick-merge.sh b/t/t3502-cherry-pick-merge.sh index e37547f41a..b1602718f8 100755 --- a/t/t3502-cherry-pick-merge.sh +++ b/t/t3502-cherry-pick-merge.sh @@ -31,6 +31,15 @@ test_expect_success setup ' ' +test_expect_success 'cherry-pick -m complains of bogus numbers' ' + # expect 129 here to distinguish between cases where + # there was nothing to cherry-pick + test_expect_code 129 git cherry-pick -m && + test_expect_code 129 git cherry-pick -m foo b && + test_expect_code 129 git cherry-pick -m -1 b && + test_expect_code 129 git cherry-pick -m 0 b +' + test_expect_success 'cherry-pick a non-merge with -m should fail' ' git reset --hard && diff --git a/t/t3504-cherry-pick-rerere.sh b/t/t3504-cherry-pick-rerere.sh index e6a64816ef..a267b2d144 100755 --- a/t/t3504-cherry-pick-rerere.sh +++ b/t/t3504-cherry-pick-rerere.sh @@ -5,14 +5,13 @@ test_description='cherry-pick should rerere for conflicts' . ./test-lib.sh test_expect_success setup ' - echo foo >foo && - git add foo && test_tick && git commit -q -m 1 && - echo foo-master >foo && - git add foo && test_tick && git commit -q -m 2 && - - git checkout -b dev HEAD^ && - echo foo-dev >foo && - git add foo && test_tick && git commit -q -m 3 && + test_commit foo && + test_commit foo-master foo && + test_commit bar-master bar && + + git checkout -b dev foo && + test_commit foo-dev foo && + test_commit bar-dev bar && git config rerere.enabled true ' @@ -21,23 +20,80 @@ test_expect_success 'conflicting merge' ' ' test_expect_success 'fixup' ' - echo foo-dev >foo && - git add foo && test_tick && git commit -q -m 4 && - git reset --hard HEAD^ && - echo foo-dev >expect + echo foo-resolved >foo && + echo bar-resolved >bar && + git commit -am resolved && + cp foo foo-expect && + cp bar bar-expect && + git reset --hard HEAD^ ' -test_expect_success 'cherry-pick conflict' ' - test_must_fail git cherry-pick master && - test_cmp expect foo +test_expect_success 'cherry-pick conflict with --rerere-autoupdate' ' + test_must_fail git cherry-pick --rerere-autoupdate foo..bar-master && + test_cmp foo-expect foo && + git diff-files --quiet && + test_must_fail git cherry-pick --continue && + test_cmp bar-expect bar && + git diff-files --quiet && + git cherry-pick --continue && + git reset --hard bar-dev +' + +test_expect_success 'cherry-pick conflict repsects rerere.autoUpdate' ' + test_config rerere.autoUpdate true && + test_must_fail git cherry-pick foo..bar-master && + test_cmp foo-expect foo && + git diff-files --quiet && + test_must_fail git cherry-pick --continue && + test_cmp bar-expect bar && + git diff-files --quiet && + git cherry-pick --continue && + git reset --hard bar-dev +' + +test_expect_success 'cherry-pick conflict with --no-rerere-autoupdate' ' + test_config rerere.autoUpdate true && + test_must_fail git cherry-pick --no-rerere-autoupdate foo..bar-master && + test_cmp foo-expect foo && + test_must_fail git diff-files --quiet && + git add foo && + test_must_fail git cherry-pick --continue && + test_cmp bar-expect bar && + test_must_fail git diff-files --quiet && + git add bar && + git cherry-pick --continue && + git reset --hard bar-dev +' + +test_expect_success 'cherry-pick --continue rejects --rerere-autoupdate' ' + test_must_fail git cherry-pick --rerere-autoupdate foo..bar-master && + test_cmp foo-expect foo && + git diff-files --quiet && + test_must_fail git cherry-pick --continue --rerere-autoupdate >actual 2>&1 && + echo "fatal: cherry-pick: --rerere-autoupdate cannot be used with --continue" >expect && + test_i18ncmp expect actual && + test_must_fail git cherry-pick --continue --no-rerere-autoupdate >actual 2>&1 && + echo "fatal: cherry-pick: --no-rerere-autoupdate cannot be used with --continue" >expect && + test_i18ncmp expect actual && + git cherry-pick --abort ' -test_expect_success 'reconfigure' ' - git config rerere.enabled false && - git reset --hard +test_expect_success 'cherry-pick --rerere-autoupdate more than once' ' + test_must_fail git cherry-pick --rerere-autoupdate --rerere-autoupdate foo..bar-master && + test_cmp foo-expect foo && + git diff-files --quiet && + git cherry-pick --abort && + test_must_fail git cherry-pick --rerere-autoupdate --no-rerere-autoupdate --rerere-autoupdate foo..bar-master && + test_cmp foo-expect foo && + git diff-files --quiet && + git cherry-pick --abort && + test_must_fail git cherry-pick --rerere-autoupdate --no-rerere-autoupdate foo..bar-master && + test_must_fail git diff-files --quiet && + git cherry-pick --abort ' test_expect_success 'cherry-pick conflict without rerere' ' + test_config rerere.enabled false && test_must_fail git cherry-pick master && test_must_fail test_cmp expect foo ' diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh index 7b7a89dbd5..9f93445f1e 100755 --- a/t/t3510-cherry-pick-sequence.sh +++ b/t/t3510-cherry-pick-sequence.sh @@ -147,6 +147,16 @@ test_expect_success '--abort to cancel single cherry-pick' ' git diff-index --exit-code HEAD ' +test_expect_success '--abort does not unsafely change HEAD' ' + pristine_detach initial && + test_must_fail git cherry-pick picked anotherpick && + git reset --hard base && + test_must_fail git cherry-pick picked anotherpick && + git cherry-pick --abort 2>actual && + test_i18ngrep "You seem to have moved HEAD" actual && + test_cmp_rev base HEAD +' + test_expect_success 'cherry-pick --abort to cancel multiple revert' ' pristine_detach anotherpick && test_expect_code 1 git revert base..picked && @@ -237,9 +247,9 @@ test_expect_success '--abort after last commit in sequence' ' test_expect_success 'cherry-pick does not implicitly stomp an existing operation' ' pristine_detach initial && test_expect_code 1 git cherry-pick base..anotherpick && - test-chmtime -v +0 .git/sequencer >expect && + test-tool chmtime -v +0 .git/sequencer >expect && test_expect_code 128 git cherry-pick unrelatedpick && - test-chmtime -v +0 .git/sequencer >actual && + test-tool chmtime -v +0 .git/sequencer >actual && test_cmp expect actual ' @@ -375,7 +385,7 @@ test_expect_success '--continue respects opts' ' git cat-file commit HEAD~1 >picked_msg && git cat-file commit HEAD~2 >unrelatedpick_msg && git cat-file commit HEAD~3 >initial_msg && - test_must_fail grep "cherry picked from" initial_msg && + ! grep "cherry picked from" initial_msg && grep "cherry picked from" unrelatedpick_msg && grep "cherry picked from" picked_msg && grep "cherry picked from" anotherpick_msg @@ -416,9 +426,9 @@ test_expect_failure '--signoff is automatically propagated to resolved conflict' git cat-file commit HEAD~1 >picked_msg && git cat-file commit HEAD~2 >unrelatedpick_msg && git cat-file commit HEAD~3 >initial_msg && - test_must_fail grep "Signed-off-by:" initial_msg && + ! grep "Signed-off-by:" initial_msg && grep "Signed-off-by:" unrelatedpick_msg && - test_must_fail grep "Signed-off-by:" picked_msg && + ! grep "Signed-off-by:" picked_msg && grep "Signed-off-by:" anotherpick_msg ' diff --git a/t/t3511-cherry-pick-x.sh b/t/t3511-cherry-pick-x.sh index b7dff09d06..9888bf34b9 100755 --- a/t/t3511-cherry-pick-x.sh +++ b/t/t3511-cherry-pick-x.sh @@ -25,9 +25,8 @@ Signed-off-by: B.U. Thor <buthor@example.com>" mesg_broken_footer="$mesg_no_footer -The signed-off-by string should begin with the words Signed-off-by followed -by a colon and space, and then the signers name and email address. e.g. -Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" +This is not recognized as a footer because Myfooter is not a recognized token. +Myfooter: A.U. Thor <author@example.com>" mesg_with_footer_sob="$mesg_with_footer Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" @@ -77,7 +76,7 @@ test_expect_success setup ' test_expect_success 'cherry-pick -x inserts blank line after one line subject' ' pristine_detach initial && - sha1=`git rev-parse mesg-one-line^0` && + sha1=$(git rev-parse mesg-one-line^0) && git cherry-pick -x mesg-one-line && cat <<-EOF >expect && $mesg_one_line @@ -112,9 +111,20 @@ test_expect_success 'cherry-pick -s inserts blank line after non-conforming foot test_cmp expect actual ' +test_expect_success 'cherry-pick -s recognizes trailer config' ' + pristine_detach initial && + git -c "trailer.Myfooter.ifexists=add" cherry-pick -s mesg-broken-footer && + cat <<-EOF >expect && + $mesg_broken_footer + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + test_expect_success 'cherry-pick -x inserts blank line when conforming footer not found' ' pristine_detach initial && - sha1=`git rev-parse mesg-no-footer^0` && + sha1=$(git rev-parse mesg-no-footer^0) && git cherry-pick -x mesg-no-footer && cat <<-EOF >expect && $mesg_no_footer @@ -139,7 +149,7 @@ test_expect_success 'cherry-pick -s inserts blank line when conforming footer no test_expect_success 'cherry-pick -x -s inserts blank line when conforming footer not found' ' pristine_detach initial && - sha1=`git rev-parse mesg-no-footer^0` && + sha1=$(git rev-parse mesg-no-footer^0) && git cherry-pick -x -s mesg-no-footer && cat <<-EOF >expect && $mesg_no_footer @@ -164,7 +174,7 @@ test_expect_success 'cherry-pick -s adds sob when last sob doesnt match committe test_expect_success 'cherry-pick -x -s adds sob when last sob doesnt match committer' ' pristine_detach initial && - sha1=`git rev-parse mesg-with-footer^0` && + sha1=$(git rev-parse mesg-with-footer^0) && git cherry-pick -x -s mesg-with-footer && cat <<-EOF >expect && $mesg_with_footer @@ -187,7 +197,7 @@ test_expect_success 'cherry-pick -s refrains from adding duplicate trailing sob' test_expect_success 'cherry-pick -x -s adds sob even when trailing sob exists for committer' ' pristine_detach initial && - sha1=`git rev-parse mesg-with-footer-sob^0` && + sha1=$(git rev-parse mesg-with-footer-sob^0) && git cherry-pick -x -s mesg-with-footer-sob && cat <<-EOF >expect && $mesg_with_footer_sob @@ -198,9 +208,53 @@ test_expect_success 'cherry-pick -x -s adds sob even when trailing sob exists fo test_cmp expect actual ' +test_expect_success 'cherry-pick -x handles commits with no NL at end of message' ' + pristine_detach initial && + printf "title\n\nSigned-off-by: A <a@example.com>" >msg && + sha1=$(git commit-tree -p initial mesg-with-footer^{tree} <msg) && + git cherry-pick -x $sha1 && + git log -1 --pretty=format:%B >actual && + + printf "\n(cherry picked from commit %s)\n" $sha1 >>msg && + test_cmp msg actual +' + +test_expect_success 'cherry-pick -x handles commits with no footer and no NL at end of message' ' + pristine_detach initial && + printf "title\n\nnot a footer" >msg && + sha1=$(git commit-tree -p initial mesg-with-footer^{tree} <msg) && + git cherry-pick -x $sha1 && + git log -1 --pretty=format:%B >actual && + + printf "\n\n(cherry picked from commit %s)\n" $sha1 >>msg && + test_cmp msg actual +' + +test_expect_success 'cherry-pick -s handles commits with no NL at end of message' ' + pristine_detach initial && + printf "title\n\nSigned-off-by: A <a@example.com>" >msg && + sha1=$(git commit-tree -p initial mesg-with-footer^{tree} <msg) && + git cherry-pick -s $sha1 && + git log -1 --pretty=format:%B >actual && + + printf "\nSigned-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\n" >>msg && + test_cmp msg actual +' + +test_expect_success 'cherry-pick -s handles commits with no footer and no NL at end of message' ' + pristine_detach initial && + printf "title\n\nnot a footer" >msg && + sha1=$(git commit-tree -p initial mesg-with-footer^{tree} <msg) && + git cherry-pick -s $sha1 && + git log -1 --pretty=format:%B >actual && + + printf "\n\nSigned-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>\n" >>msg && + test_cmp msg actual +' + test_expect_success 'cherry-pick -x treats "(cherry picked from..." line as part of footer' ' pristine_detach initial && - sha1=`git rev-parse mesg-with-cherry-footer^0` && + sha1=$(git rev-parse mesg-with-cherry-footer^0) && git cherry-pick -x mesg-with-cherry-footer && cat <<-EOF >expect && $mesg_with_cherry_footer @@ -223,7 +277,7 @@ test_expect_success 'cherry-pick -s treats "(cherry picked from..." line as part test_expect_success 'cherry-pick -x -s treats "(cherry picked from..." line as part of footer' ' pristine_detach initial && - sha1=`git rev-parse mesg-with-cherry-footer^0` && + sha1=$(git rev-parse mesg-with-cherry-footer^0) && git cherry-pick -x -s mesg-with-cherry-footer && cat <<-EOF >expect && $mesg_with_cherry_footer diff --git a/t/t3512-cherry-pick-submodule.sh b/t/t3512-cherry-pick-submodule.sh index 6863b7bb6f..bd78287841 100755 --- a/t/t3512-cherry-pick-submodule.sh +++ b/t/t3512-cherry-pick-submodule.sh @@ -5,9 +5,44 @@ test_description='cherry-pick can handle submodules' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh -KNOWN_FAILURE_CHERRY_PICK_SEES_EMPTY_COMMIT=1 KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1 KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1 test_submodule_switch "git cherry-pick" +test_expect_success 'unrelated submodule/file conflict is ignored' ' + test_create_repo sub && + + touch sub/file && + git -C sub add file && + git -C sub commit -m "add a file in a submodule" && + + test_create_repo a_repo && + ( + cd a_repo && + >a_file && + git add a_file && + git commit -m "add a file" && + + git branch test && + git checkout test && + + mkdir sub && + >sub/content && + git add sub/content && + git commit -m "add a regular folder with name sub" && + + echo "123" >a_file && + git add a_file && + git commit -m "modify a file" && + + git checkout master && + + git submodule add ../sub sub && + git submodule update sub && + git commit -m "add a submodule info folder with name sub" && + + git cherry-pick test + ) +' + test_done diff --git a/t/t3513-revert-submodule.sh b/t/t3513-revert-submodule.sh index a1c4e0216f..5e39fcdb66 100755 --- a/t/t3513-revert-submodule.sh +++ b/t/t3513-revert-submodule.sh @@ -14,18 +14,17 @@ test_description='revert can handle submodules' git_revert () { git status -su >expect && ls -1pR * >>expect && - tar czf "$TRASH_DIRECTORY/tmp.tgz" * && + tar cf "$TRASH_DIRECTORY/tmp.tar" * && git checkout "$1" && git revert HEAD && rm -rf * && - tar xzf "$TRASH_DIRECTORY/tmp.tgz" && + tar xf "$TRASH_DIRECTORY/tmp.tar" && git status -su >actual && ls -1pR * >>actual && test_cmp expect actual && git revert HEAD } -KNOWN_FAILURE_CHERRY_PICK_SEES_EMPTY_COMMIT=1 KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1 test_submodule_switch "git_revert" diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index 9d90d2c935..b8fbdefcdc 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -14,7 +14,7 @@ test_expect_success \ git add -- foo bar baz 'space embedded' -q && git commit -m 'add normal files'" -if touch -- 'tab embedded' 'newline +if test_have_prereq !MINGW && touch -- 'tab embedded' 'newline embedded' 2>/dev/null then test_set_prereq FUNNYNAMES @@ -97,9 +97,9 @@ test_expect_success FUNNYNAMES \ embedded'" test_expect_success SANITY 'Test that "git rm -f" fails if its rm fails' ' + test_when_finished "chmod 775 ." && chmod a-w . && - test_must_fail git rm -f baz && - chmod 775 . + test_must_fail git rm -f baz ' test_expect_success \ @@ -111,21 +111,21 @@ test_expect_success 'Remove nonexistent file with --ignore-unmatch' ' ' test_expect_success '"rm" command printed' ' - echo frotz > test-file && + echo frotz >test-file && git add test-file && git commit -m "add file for rm test" && - git rm test-file > rm-output && - test `grep "^rm " rm-output | wc -l` = 1 && + git rm test-file >rm-output && + test $(grep "^rm " rm-output | wc -l) = 1 && rm -f test-file rm-output && git commit -m "remove file from rm test" ' test_expect_success '"rm" command suppressed with --quiet' ' - echo frotz > test-file && + echo frotz >test-file && git add test-file && git commit -m "add file for rm --quiet test" && - git rm --quiet test-file > rm-output && - test `wc -l < rm-output` = 0 && + git rm --quiet test-file >rm-output && + test_must_be_empty rm-output && rm -f test-file rm-output && git commit -m "remove file from rm --quiet test" ' @@ -221,7 +221,7 @@ test_expect_success 'Call "rm" from outside the work tree' ' mkdir repo && (cd repo && git init && - echo something > somefile && + echo something >somefile && git add somefile && git commit -m "add a file" && (cd .. && @@ -232,7 +232,7 @@ test_expect_success 'Call "rm" from outside the work tree' ' test_expect_success 'refresh index before checking if it is up-to-date' ' git reset --hard && - test-chmtime -86400 frotz/nitfol && + test-tool chmtime -86400 frotz/nitfol && git rm frotz/nitfol && test ! -f frotz/nitfol @@ -268,6 +268,14 @@ cat >expect.modified <<EOF M submod EOF +cat >expect.modified_inside <<EOF + m submod +EOF + +cat >expect.modified_untracked <<EOF + ? submod +EOF + cat >expect.cached <<EOF D submod EOF @@ -287,7 +295,7 @@ test_expect_success 'rm removes empty submodules from work tree' ' git commit -m "add submodule" && git rm submod && test ! -e submod && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect actual && test_must_fail git config -f .gitmodules submodule.sub.url && test_must_fail git config -f .gitmodules submodule.sub.path @@ -298,7 +306,7 @@ test_expect_success 'rm removes removed submodule from index and .gitmodules' ' git submodule update && rm -rf submod && git rm submod && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect actual && test_must_fail git config -f .gitmodules submodule.sub.url && test_must_fail git config -f .gitmodules submodule.sub.path @@ -309,7 +317,7 @@ test_expect_success 'rm removes work tree of unmodified submodules' ' git submodule update && git rm submod && test ! -d submod && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect actual && test_must_fail git config -f .gitmodules submodule.sub.url && test_must_fail git config -f .gitmodules submodule.sub.path @@ -320,7 +328,7 @@ test_expect_success 'rm removes a submodule with a trailing /' ' git submodule update && git rm submod/ && test ! -d submod && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect actual ' @@ -335,17 +343,15 @@ test_expect_success 'rm succeeds when given a directory with a trailing /' ' test_expect_success 'rm of a populated submodule with different HEAD fails unless forced' ' git reset --hard && git submodule update && - (cd submod && - git checkout HEAD^ - ) && + git -C submod checkout HEAD^ && test_must_fail git rm submod && test -d submod && test -f submod/.git && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect.modified actual && git rm -f submod && test ! -d submod && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect actual && test_must_fail git config -f .gitmodules submodule.sub.url && test_must_fail git config -f .gitmodules submodule.sub.path @@ -418,34 +424,30 @@ test_expect_success 'rm issues a warning when section is not found in .gitmodule test_expect_success 'rm of a populated submodule with modifications fails unless forced' ' git reset --hard && git submodule update && - (cd submod && - echo X >empty - ) && + echo X >submod/empty && test_must_fail git rm submod && test -d submod && test -f submod/.git && - git status -s -uno --ignore-submodules=none > actual && - test_cmp expect.modified actual && + git status -s -uno --ignore-submodules=none >actual && + test_cmp expect.modified_inside actual && git rm -f submod && test ! -d submod && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect actual ' test_expect_success 'rm of a populated submodule with untracked files fails unless forced' ' git reset --hard && git submodule update && - (cd submod && - echo X >untracked - ) && + echo X >submod/untracked && test_must_fail git rm submod && test -d submod && test -f submod/.git && - git status -s -uno --ignore-submodules=none > actual && - test_cmp expect.modified actual && + git status -s -uno --ignore-submodules=none >actual && + test_cmp expect.modified_untracked actual && git rm -f submod && test ! -d submod && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect actual ' @@ -461,16 +463,12 @@ test_expect_success 'setup submodule conflict' ' git add nitfol && git commit -m "added nitfol 2" && git checkout -b conflict1 master && - (cd submod && - git fetch && - git checkout branch1 - ) && + git -C submod fetch && + git -C submod checkout branch1 && git add submod && git commit -m "submod 1" && git checkout -b conflict2 master && - (cd submod && - git checkout branch2 - ) && + git -C submod checkout branch2 && git add submod && git commit -m "submod 2" ' @@ -486,7 +484,7 @@ test_expect_success 'rm removes work tree of unmodified conflicted submodule' ' test_must_fail git merge conflict2 && git rm submod && test ! -d submod && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect actual ' @@ -494,18 +492,16 @@ test_expect_success 'rm of a conflicted populated submodule with different HEAD git checkout conflict1 && git reset --hard && git submodule update && - (cd submod && - git checkout HEAD^ - ) && + git -C submod checkout HEAD^ && test_must_fail git merge conflict2 && test_must_fail git rm submod && test -d submod && test -f submod/.git && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect.conflict actual && git rm -f submod && test ! -d submod && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect actual && test_must_fail git config -f .gitmodules submodule.sub.url && test_must_fail git config -f .gitmodules submodule.sub.path @@ -515,18 +511,16 @@ test_expect_success 'rm of a conflicted populated submodule with modifications f git checkout conflict1 && git reset --hard && git submodule update && - (cd submod && - echo X >empty - ) && + echo X >submod/empty && test_must_fail git merge conflict2 && test_must_fail git rm submod && test -d submod && test -f submod/.git && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect.conflict actual && git rm -f submod && test ! -d submod && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect actual && test_must_fail git config -f .gitmodules submodule.sub.url && test_must_fail git config -f .gitmodules submodule.sub.path @@ -536,18 +530,16 @@ test_expect_success 'rm of a conflicted populated submodule with untracked files git checkout conflict1 && git reset --hard && git submodule update && - (cd submod && - echo X >untracked - ) && + echo X >submod/untracked && test_must_fail git merge conflict2 && test_must_fail git rm submod && test -d submod && test -f submod/.git && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect.conflict actual && git rm -f submod && test ! -d submod && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect actual ' @@ -564,12 +556,12 @@ test_expect_success 'rm of a conflicted populated submodule with a .git director test_must_fail git rm submod && test -d submod && test -d submod/.git && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect.conflict actual && test_must_fail git rm -f submod && test -d submod && test -d submod/.git && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect.conflict actual && git merge --abort && rm -rf submod @@ -581,30 +573,26 @@ test_expect_success 'rm of a conflicted unpopulated submodule succeeds' ' test_must_fail git merge conflict2 && git rm submod && test ! -d submod && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect actual ' -test_expect_success 'rm of a populated submodule with a .git directory fails even when forced' ' +test_expect_success 'rm of a populated submodule with a .git directory migrates git dir' ' git checkout -f master && git reset --hard && git submodule update && (cd submod && rm .git && cp -R ../.git/modules/sub .git && - GIT_WORK_TREE=. git config --unset core.worktree + GIT_WORK_TREE=. git config --unset core.worktree && + rm -r ../.git/modules/sub ) && - test_must_fail git rm submod && - test -d submod && - test -d submod/.git && - git status -s -uno --ignore-submodules=none > actual && - ! test -s actual && - test_must_fail git rm -f submod && - test -d submod && - test -d submod/.git && - git status -s -uno --ignore-submodules=none > actual && - ! test -s actual && - rm -rf submod + git rm submod 2>output.err && + ! test -d submod && + ! test -d submod/.git && + git status -s -uno --ignore-submodules=none >actual && + test -s actual && + test_i18ngrep Migrating output.err ' cat >expect.deepmodified <<EOF @@ -629,94 +617,83 @@ test_expect_success 'setup subsubmodule' ' test_expect_success 'rm recursively removes work tree of unmodified submodules' ' git rm submod && test ! -d submod && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect actual ' test_expect_success 'rm of a populated nested submodule with different nested HEAD fails unless forced' ' git reset --hard && git submodule update --recursive && - (cd submod/subsubmod && - git checkout HEAD^ - ) && + git -C submod/subsubmod checkout HEAD^ && test_must_fail git rm submod && test -d submod && test -f submod/.git && - git status -s -uno --ignore-submodules=none > actual && - test_cmp expect.modified actual && + git status -s -uno --ignore-submodules=none >actual && + test_cmp expect.modified_inside actual && git rm -f submod && test ! -d submod && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect actual ' test_expect_success 'rm of a populated nested submodule with nested modifications fails unless forced' ' git reset --hard && git submodule update --recursive && - (cd submod/subsubmod && - echo X >empty - ) && + echo X >submod/subsubmod/empty && test_must_fail git rm submod && test -d submod && test -f submod/.git && - git status -s -uno --ignore-submodules=none > actual && - test_cmp expect.modified actual && + git status -s -uno --ignore-submodules=none >actual && + test_cmp expect.modified_inside actual && git rm -f submod && test ! -d submod && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect actual ' test_expect_success 'rm of a populated nested submodule with nested untracked files fails unless forced' ' git reset --hard && git submodule update --recursive && - (cd submod/subsubmod && - echo X >untracked - ) && + echo X >submod/subsubmod/untracked && test_must_fail git rm submod && test -d submod && test -f submod/.git && - git status -s -uno --ignore-submodules=none > actual && - test_cmp expect.modified actual && + git status -s -uno --ignore-submodules=none >actual && + test_cmp expect.modified_untracked actual && git rm -f submod && test ! -d submod && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && test_cmp expect actual ' -test_expect_success 'rm of a populated nested submodule with a nested .git directory fails even when forced' ' +test_expect_success "rm absorbs submodule's nested .git directory" ' git reset --hard && git submodule update --recursive && (cd submod/subsubmod && rm .git && - cp -R ../../.git/modules/sub/modules/sub .git && + mv ../../.git/modules/sub/modules/sub .git && GIT_WORK_TREE=. git config --unset core.worktree ) && - test_must_fail git rm submod && - test -d submod && - test -d submod/subsubmod/.git && - git status -s -uno --ignore-submodules=none > actual && - ! test -s actual && - test_must_fail git rm -f submod && - test -d submod && - test -d submod/subsubmod/.git && - git status -s -uno --ignore-submodules=none > actual && - ! test -s actual && - rm -rf submod + git rm submod 2>output.err && + ! test -d submod && + ! test -d submod/subsubmod/.git && + git status -s -uno --ignore-submodules=none >actual && + test -s actual && + test_i18ngrep Migrating output.err ' test_expect_success 'checking out a commit after submodule removal needs manual updates' ' - git commit -m "submodule removal" submod && + git commit -m "submodule removal" submod .gitmodules && git checkout HEAD^ && git submodule update && - git checkout -q HEAD^ 2>actual && + git checkout -q HEAD^ && git checkout -q master 2>actual && - test_i18ngrep "^warning: unable to rmdir submod:" actual && + test_i18ngrep "^warning: unable to rmdir '\''submod'\'':" actual && git status -s submod >actual && echo "?? submod/" >expected && test_cmp expected actual && rm -rf submod && - git status -s -uno --ignore-submodules=none > actual && + git status -s -uno --ignore-submodules=none >actual && ! test -s actual ' @@ -881,4 +858,8 @@ test_expect_success 'rm files with two different errors' ' test_i18ncmp expect actual ' +test_expect_success 'rm empty string should fail' ' + test_must_fail git rm -rf "" +' + test_done diff --git a/t/t3700-add.sh b/t/t3700-add.sh index f7ff1f555d..07af05d7ae 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -7,6 +7,20 @@ test_description='Test of git add, including the -- option.' . ./test-lib.sh +# Test the file mode "$1" of the file "$2" in the index. +test_mode_in_index () { + case "$(git ls-files -s "$2")" in + "$1 "*" $2") + echo pass + ;; + *) + echo fail + git ls-files -s "$2" + return 1 + ;; + esac +} + test_expect_success \ 'Test of git add' \ 'touch foo && git add foo' @@ -25,18 +39,12 @@ test_expect_success \ echo foo >xfoo1 && chmod 755 xfoo1 && git add xfoo1 && - case "`git ls-files --stage xfoo1`" in - 100644" "*xfoo1) echo pass;; - *) echo fail; git ls-files --stage xfoo1; (exit 1);; - esac' + test_mode_in_index 100644 xfoo1' test_expect_success 'git add: filemode=0 should not get confused by symlink' ' rm -f xfoo1 && test_ln_s_add foo xfoo1 && - case "`git ls-files --stage xfoo1`" in - 120000" "*xfoo1) echo pass;; - *) echo fail; git ls-files --stage xfoo1; (exit 1);; - esac + test_mode_in_index 120000 xfoo1 ' test_expect_success \ @@ -45,28 +53,19 @@ test_expect_success \ echo foo >xfoo2 && chmod 755 xfoo2 && git update-index --add xfoo2 && - case "`git ls-files --stage xfoo2`" in - 100644" "*xfoo2) echo pass;; - *) echo fail; git ls-files --stage xfoo2; (exit 1);; - esac' + test_mode_in_index 100644 xfoo2' test_expect_success 'git add: filemode=0 should not get confused by symlink' ' rm -f xfoo2 && test_ln_s_add foo xfoo2 && - case "`git ls-files --stage xfoo2`" in - 120000" "*xfoo2) echo pass;; - *) echo fail; git ls-files --stage xfoo2; (exit 1);; - esac + test_mode_in_index 120000 xfoo2 ' test_expect_success \ 'git update-index --add: Test that executable bit is not used...' \ 'git config core.filemode 0 && test_ln_s_add xfoo2 xfoo3 && # runs git update-index --add - case "`git ls-files --stage xfoo3`" in - 120000" "*xfoo3) echo pass;; - *) echo fail; git ls-files --stage xfoo3; (exit 1);; - esac' + test_mode_in_index 120000 xfoo3' test_expect_success '.gitignore test setup' ' echo "*.ig" >.gitignore && @@ -173,14 +172,14 @@ test_expect_success 'git add with filemode=0, symlinks=0 prefers stage 2 over st test_expect_success 'git add --refresh' ' >foo && git add foo && git commit -a -m "commit all" && - test -z "`git diff-index HEAD -- foo`" && + test -z "$(git diff-index HEAD -- foo)" && git read-tree HEAD && - case "`git diff-index HEAD -- foo`" in + case "$(git diff-index HEAD -- foo)" in :100644" "*"M foo") echo pass;; *) echo fail; (exit 1);; esac && git add --refresh -- foo && - test -z "`git diff-index HEAD -- foo`" + test -z "$(git diff-index HEAD -- foo)" ' test_expect_success 'git add --refresh with pathspec' ' @@ -188,7 +187,7 @@ test_expect_success 'git add --refresh with pathspec' ' echo >foo && echo >bar && echo >baz && git add foo bar baz && H=$(git rev-parse :foo) && git rm -f foo && echo "100644 $H 3 foo" | git update-index --index-info && - test-chmtime -60 bar baz && + test-tool chmtime -60 bar baz && >expect && git add --refresh bar >actual && test_cmp expect actual && @@ -332,4 +331,76 @@ test_expect_success 'git add --dry-run --ignore-missing of non-existing file out test_i18ncmp expect.err actual.err ' +test_expect_success 'git add empty string should fail' ' + test_must_fail git add "" +' + +test_expect_success 'git add --chmod=[+-]x stages correctly' ' + rm -f foo1 && + echo foo >foo1 && + git add --chmod=+x foo1 && + test_mode_in_index 100755 foo1 && + git add --chmod=-x foo1 && + test_mode_in_index 100644 foo1 +' + +test_expect_success POSIXPERM,SYMLINKS 'git add --chmod=+x with symlinks' ' + git config core.filemode 1 && + git config core.symlinks 1 && + rm -f foo2 && + echo foo >foo2 && + git add --chmod=+x foo2 && + test_mode_in_index 100755 foo2 +' + +test_expect_success 'git add --chmod=[+-]x changes index with already added file' ' + rm -f foo3 xfoo3 && + git reset --hard && + echo foo >foo3 && + git add foo3 && + git add --chmod=+x foo3 && + test_mode_in_index 100755 foo3 && + echo foo >xfoo3 && + chmod 755 xfoo3 && + git add xfoo3 && + git add --chmod=-x xfoo3 && + test_mode_in_index 100644 xfoo3 +' + +test_expect_success POSIXPERM 'git add --chmod=[+-]x does not change the working tree' ' + echo foo >foo4 && + git add foo4 && + git add --chmod=+x foo4 && + ! test -x foo4 +' + +test_expect_success 'no file status change if no pathspec is given' ' + >foo5 && + >foo6 && + git add foo5 foo6 && + git add --chmod=+x && + test_mode_in_index 100644 foo5 && + test_mode_in_index 100644 foo6 +' + +test_expect_success 'no file status change if no pathspec is given in subdir' ' + mkdir -p sub && + ( + cd sub && + >sub-foo1 && + >sub-foo2 && + git add . && + git add --chmod=+x && + test_mode_in_index 100644 sub-foo1 && + test_mode_in_index 100644 sub-foo2 + ) +' + +test_expect_success 'all statuses changed in folder if . is given' ' + git add --chmod=+x . && + test $(git ls-files --stage | grep ^100644 | wc -l) -eq 0 && + git add --chmod=-x . && + test $(git ls-files --stage | grep ^100755 | wc -l) -eq 0 +' + test_done diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index deae948c76..b170fb02b8 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -2,6 +2,7 @@ test_description='add -i basic tests' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-terminal.sh if ! test_have_prereq PERL then @@ -9,6 +10,19 @@ then test_done fi +diff_cmp () { + for x + do + sed -e '/^index/s/[0-9a-f]*[1-9a-f][0-9a-f]*\.\./1234567../' \ + -e '/^index/s/\.\.[0-9a-f]*[1-9a-f][0-9a-f]*/..9abcdef/' \ + -e '/^index/s/ 00*\.\./ 0000000../' \ + -e '/^index/s/\.\.00*$/..0000000/' \ + -e '/^index/s/\.\.00* /..0000000 /' \ + "$x" >"$x.filtered" + done + test_cmp "$1.filtered" "$2.filtered" +} + test_expect_success 'setup (initial)' ' echo content >file && git add file && @@ -21,20 +35,20 @@ test_expect_success 'status works (initial)' ' ' test_expect_success 'setup expected' ' -cat >expected <<EOF -new file mode 100644 -index 0000000..d95f3ad ---- /dev/null -+++ b/file -@@ -0,0 +1 @@ -+content -EOF + cat >expected <<-\EOF + new file mode 100644 + index 0000000..d95f3ad + --- /dev/null + +++ b/file + @@ -0,0 +1 @@ + +content + EOF ' test_expect_success 'diff works (initial)' ' (echo d; echo 1) | git add -i >output && sed -ne "/new file/,/content/p" <output >diff && - test_cmp expected diff + diff_cmp expected diff ' test_expect_success 'revert works (initial)' ' git add file && @@ -58,20 +72,20 @@ test_expect_success 'status works (commit)' ' ' test_expect_success 'setup expected' ' -cat >expected <<EOF -index 180b47c..b6f2c08 100644 ---- a/file -+++ b/file -@@ -1 +1,2 @@ - baseline -+content -EOF + cat >expected <<-\EOF + index 180b47c..b6f2c08 100644 + --- a/file + +++ b/file + @@ -1 +1,2 @@ + baseline + +content + EOF ' test_expect_success 'diff works (commit)' ' (echo d; echo 1) | git add -i >output && sed -ne "/^index/,/content/p" <output >diff && - test_cmp expected diff + diff_cmp expected diff ' test_expect_success 'revert works (commit)' ' git add file && @@ -82,39 +96,32 @@ test_expect_success 'revert works (commit)' ' test_expect_success 'setup expected' ' -cat >expected <<EOF -EOF -' - -test_expect_success 'setup fake editor' ' - >fake_editor.sh && - chmod a+x fake_editor.sh && - test_set_editor "$(pwd)/fake_editor.sh" + cat >expected <<-\EOF + EOF ' test_expect_success 'dummy edit works' ' + test_set_editor : && (echo e; echo a) | git add -p && git diff > diff && - test_cmp expected diff + diff_cmp expected diff ' test_expect_success 'setup patch' ' -cat >patch <<EOF -@@ -1,1 +1,4 @@ - this -+patch --does not - apply -EOF + cat >patch <<-\EOF + @@ -1,1 +1,4 @@ + this + +patch + -does not + apply + EOF ' test_expect_success 'setup fake editor' ' - echo "#!$SHELL_PATH" >fake_editor.sh && - cat >>fake_editor.sh <<\EOF && -mv -f "$1" oldpatch && -mv -f patch "$1" -EOF - chmod a+x fake_editor.sh && + write_script "fake_editor.sh" <<-\EOF && + mv -f "$1" oldpatch && + mv -f patch "$1" + EOF test_set_editor "$(pwd)/fake_editor.sh" ' @@ -125,10 +132,10 @@ test_expect_success 'bad edit rejected' ' ' test_expect_success 'setup patch' ' -cat >patch <<EOF -this patch -is garbage -EOF + cat >patch <<-\EOF + this patch + is garbage + EOF ' test_expect_success 'garbage edit rejected' ' @@ -138,34 +145,34 @@ test_expect_success 'garbage edit rejected' ' ' test_expect_success 'setup patch' ' -cat >patch <<EOF -@@ -1,0 +1,0 @@ - baseline -+content -+newcontent -+lines -EOF + cat >patch <<-\EOF + @@ -1,0 +1,0 @@ + baseline + +content + +newcontent + +lines + EOF ' test_expect_success 'setup expected' ' -cat >expected <<EOF -diff --git a/file b/file -index b5dd6c9..f910ae9 100644 ---- a/file -+++ b/file -@@ -1,4 +1,4 @@ - baseline - content --newcontent -+more - lines -EOF + cat >expected <<-\EOF + diff --git a/file b/file + index b5dd6c9..f910ae9 100644 + --- a/file + +++ b/file + @@ -1,4 +1,4 @@ + baseline + content + -newcontent + +more + lines + EOF ' test_expect_success 'real edit works' ' (echo e; echo n; echo d) | git add -p && git diff >output && - test_cmp expected output + diff_cmp expected output ' test_expect_success 'skip files similarly as commit -a' ' @@ -177,7 +184,7 @@ test_expect_success 'skip files similarly as commit -a' ' git reset && git commit -am commit && git diff >expected && - test_cmp expected output && + diff_cmp expected output && git reset --hard HEAD^ ' rm -f .gitignore @@ -221,52 +228,67 @@ test_expect_success 'setup again' ' # Write the patch file with a new line at the top and bottom test_expect_success 'setup patch' ' -cat >patch <<EOF -index 180b47c..b6f2c08 100644 ---- a/file -+++ b/file -@@ -1,2 +1,4 @@ -+firstline - baseline - content -+lastline -EOF -' - -# Expected output, similar to the patch but w/ diff at the top + cat >patch <<-\EOF + index 180b47c..b6f2c08 100644 + --- a/file + +++ b/file + @@ -1,2 +1,4 @@ + +firstline + baseline + content + +lastline + \ No newline at end of file + EOF +' + +# Expected output, diff is similar to the patch but w/ diff at the top test_expect_success 'setup expected' ' -cat >expected <<EOF -diff --git a/file b/file -index b6f2c08..61b9053 100755 ---- a/file -+++ b/file -@@ -1,2 +1,4 @@ -+firstline - baseline - content -+lastline -EOF + echo diff --git a/file b/file >expected && + cat patch |sed "/^index/s/ 100644/ 100755/" >>expected && + cat >expected-output <<-\EOF + --- a/file + +++ b/file + @@ -1,2 +1,4 @@ + +firstline + baseline + content + +lastline + \ No newline at end of file + @@ -1,2 +1,3 @@ + +firstline + baseline + content + @@ -1,2 +2,3 @@ + baseline + content + +lastline + \ No newline at end of file + EOF ' # Test splitting the first patch, then adding both -test_expect_success 'add first line works' ' +test_expect_success C_LOCALE_OUTPUT 'add first line works' ' git commit -am "clear local changes" && git apply patch && - (echo s; echo y; echo y) | git add -p file && - git diff --cached > diff && - test_cmp expected diff + printf "%s\n" s y y | git add -p file 2>error | + sed -n -e "s/^Stage this hunk[^@]*\(@@ .*\)/\1/" \ + -e "/^[-+@ \\\\]"/p >output && + test_must_be_empty error && + git diff --cached >diff && + diff_cmp expected diff && + test_cmp expected-output output ' test_expect_success 'setup expected' ' -cat >expected <<EOF -diff --git a/non-empty b/non-empty -deleted file mode 100644 -index d95f3ad..0000000 ---- a/non-empty -+++ /dev/null -@@ -1 +0,0 @@ --content -EOF + cat >expected <<-\EOF + diff --git a/non-empty b/non-empty + deleted file mode 100644 + index d95f3ad..0000000 + --- a/non-empty + +++ /dev/null + @@ -1 +0,0 @@ + -content + EOF ' test_expect_success 'deleting a non-empty file' ' @@ -277,15 +299,15 @@ test_expect_success 'deleting a non-empty file' ' rm non-empty && echo y | git add -p non-empty && git diff --cached >diff && - test_cmp expected diff + diff_cmp expected diff ' test_expect_success 'setup expected' ' -cat >expected <<EOF -diff --git a/empty b/empty -deleted file mode 100644 -index e69de29..0000000 -EOF + cat >expected <<-\EOF + diff --git a/empty b/empty + deleted file mode 100644 + index e69de29..0000000 + EOF ' test_expect_success 'deleting an empty file' ' @@ -296,23 +318,17 @@ test_expect_success 'deleting an empty file' ' rm empty && echo y | git add -p empty && git diff --cached >diff && - test_cmp expected diff + diff_cmp expected diff ' test_expect_success 'split hunk setup' ' git reset --hard && - for i in 10 20 30 40 50 60 - do - echo $i - done >test && + test_write_lines 10 20 30 40 50 60 >test && git add test && test_tick && git commit -m test && - for i in 10 15 20 21 22 23 24 30 40 50 60 - do - echo $i - done >test + test_write_lines 10 15 20 21 22 23 24 30 40 50 60 >test ' test_expect_success 'split hunk "add -p (edit)"' ' @@ -333,17 +349,7 @@ test_expect_success 'split hunk "add -p (edit)"' ' ' test_expect_failure 'split hunk "add -p (no, yes, edit)"' ' - cat >test <<-\EOF && - 5 - 10 - 20 - 21 - 30 - 31 - 40 - 50 - 60 - EOF + test_write_lines 5 10 20 21 30 31 40 50 60 >test && git reset && # test sequence is s(plit), n(o), y(es), e(dit) # q n q q is there to make sure we exit at the end. @@ -377,7 +383,217 @@ test_expect_success 'patch mode ignores unmerged entries' ' +changed EOF git diff --cached >diff && - test_cmp expected diff + diff_cmp expected diff +' + +test_expect_success TTY 'diffs can be colorized' ' + git reset --hard && + + echo content >test && + printf y | test_terminal git add -p >output 2>&1 && + + # We do not want to depend on the exact coloring scheme + # git uses for diffs, so just check that we saw some kind of color. + grep "$(printf "\\033")" output +' + +test_expect_success TTY 'diffFilter filters diff' ' + git reset --hard && + + echo content >test && + test_config interactive.diffFilter "sed s/^/foo:/" && + printf y | test_terminal git add -p >output 2>&1 && + + # avoid depending on the exact coloring or content of the prompts, + # and just make sure we saw our diff prefixed + grep foo:.*content output +' + +test_expect_success TTY 'detect bogus diffFilter output' ' + git reset --hard && + + echo content >test && + test_config interactive.diffFilter "echo too-short" && + printf y | test_must_fail test_terminal git add -p +' + +test_expect_success 'patch-mode via -i prompts for files' ' + git reset --hard && + + echo one >file && + echo two >test && + git add -i <<-\EOF && + patch + test + + y + quit + EOF + + echo test >expect && + git diff --cached --name-only >actual && + diff_cmp expect actual +' + +test_expect_success 'add -p handles globs' ' + git reset --hard && + + mkdir -p subdir && + echo base >one.c && + echo base >subdir/two.c && + git add "*.c" && + git commit -m base && + + echo change >one.c && + echo change >subdir/two.c && + git add -p "*.c" <<-\EOF && + y + y + EOF + + cat >expect <<-\EOF && + one.c + subdir/two.c + EOF + git diff --cached --name-only >actual && + test_cmp expect actual +' + +test_expect_success 'add -p handles relative paths' ' + git reset --hard && + + echo base >relpath.c && + git add "*.c" && + git commit -m relpath && + + echo change >relpath.c && + mkdir -p subdir && + git -C subdir add -p .. 2>error <<-\EOF && + y + EOF + + test_must_be_empty error && + + cat >expect <<-\EOF && + relpath.c + EOF + git diff --cached --name-only >actual && + test_cmp expect actual +' + +test_expect_success 'add -p does not expand argument lists' ' + git reset --hard && + + echo content >not-changed && + git add not-changed && + git commit -m "add not-changed file" && + + echo change >file && + GIT_TRACE=$(pwd)/trace.out git add -p . <<-\EOF && + y + EOF + + # we know that "file" must be mentioned since we actually + # update it, but we want to be sure that our "." pathspec + # was not expanded into the argument list of any command. + # So look only for "not-changed". + ! grep not-changed trace.out +' + +test_expect_success 'hunk-editing handles custom comment char' ' + git reset --hard && + echo change >>file && + test_config core.commentChar "\$" && + echo e | GIT_EDITOR=true git add -p && + git diff --exit-code +' + +test_expect_success 'add -p works even with color.ui=always' ' + git reset --hard && + echo change >>file && + test_config color.ui always && + echo y | git add -p && + echo file >expect && + git diff --cached --name-only >actual && + test_cmp expect actual +' + +test_expect_success 'setup different kinds of dirty submodules' ' + test_create_repo for-submodules && + ( + cd for-submodules && + test_commit initial && + test_create_repo dirty-head && + ( + cd dirty-head && + test_commit initial + ) && + cp -R dirty-head dirty-otherwise && + cp -R dirty-head dirty-both-ways && + git add dirty-head && + git add dirty-otherwise dirty-both-ways && + git commit -m initial && + + cd dirty-head && + test_commit updated && + cd ../dirty-both-ways && + test_commit updated && + echo dirty >>initial && + : >untracked && + cd ../dirty-otherwise && + echo dirty >>initial && + : >untracked + ) && + git -C for-submodules diff-files --name-only >actual && + cat >expected <<-\EOF && + dirty-both-ways + dirty-head + dirty-otherwise + EOF + test_cmp expected actual && + git -C for-submodules diff-files --name-only --ignore-submodules=dirty >actual && + cat >expected <<-\EOF && + dirty-both-ways + dirty-head + EOF + test_cmp expected actual +' + +test_expect_success 'status ignores dirty submodules (except HEAD)' ' + git -C for-submodules add -i </dev/null >output && + grep dirty-head output && + grep dirty-both-ways output && + ! grep dirty-otherwise output +' + +test_expect_success 'set up pathological context' ' + git reset --hard && + test_write_lines a a a a a a a a a a a >a && + git add a && + git commit -m a && + test_write_lines c b a a a a a a a b a a a a >a && + test_write_lines a a a a a a a b a a a a >expected-1 && + test_write_lines b a a a a a a a b a a a a >expected-2 && + # check editing can cope with missing header and deleted context lines + # as well as changes to other lines + test_write_lines +b " a" >patch +' + +test_expect_success 'add -p works with pathological context lines' ' + git reset && + printf "%s\n" n y | + git add -p && + git cat-file blob :a >actual && + test_cmp expected-1 actual +' + +test_expect_success 'add -p patch editing works with pathological context lines' ' + git reset && + # n q q below is in case edit fails + printf "%s\n" e y n q q | + git add -p && + git cat-file blob :a >actual && + test_cmp expected-2 actual ' test_done diff --git a/t/t3703-add-magic-pathspec.sh b/t/t3703-add-magic-pathspec.sh index 5115de7036..3ef525a559 100755 --- a/t/t3703-add-magic-pathspec.sh +++ b/t/t3703-add-magic-pathspec.sh @@ -38,7 +38,7 @@ cat >expected <<EOF add 'sub/foo' EOF -if mkdir ":" 2>/dev/null +if test_have_prereq !MINGW && mkdir ":" 2>/dev/null then test_set_prereq COLON_DIR fi diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh index 4bf1dbe9c9..b92ff95977 100755 --- a/t/t3900-i18n-commit.sh +++ b/t/t3900-i18n-commit.sh @@ -40,40 +40,40 @@ test_expect_success 'UTF-16 refused because of NULs' ' ' test_expect_success 'UTF-8 invalid characters refused' ' - test_when_finished "rm -f $HOME/stderr $HOME/invalid" && + test_when_finished "rm -f \"\$HOME/stderr\" \"\$HOME/invalid\"" && echo "UTF-8 characters" >F && printf "Commit message\n\nInvalid surrogate:\355\240\200\n" \ >"$HOME/invalid" && git commit -a -F "$HOME/invalid" 2>"$HOME"/stderr && - grep "did not conform" "$HOME"/stderr + test_i18ngrep "did not conform" "$HOME"/stderr ' test_expect_success 'UTF-8 overlong sequences rejected' ' - test_when_finished "rm -f $HOME/stderr $HOME/invalid" && + test_when_finished "rm -f \"\$HOME/stderr\" \"\$HOME/invalid\"" && rm -f "$HOME/stderr" "$HOME/invalid" && echo "UTF-8 overlong" >F && printf "\340\202\251ommit message\n\nThis is not a space:\300\240\n" \ >"$HOME/invalid" && git commit -a -F "$HOME/invalid" 2>"$HOME"/stderr && - grep "did not conform" "$HOME"/stderr + test_i18ngrep "did not conform" "$HOME"/stderr ' test_expect_success 'UTF-8 non-characters refused' ' - test_when_finished "rm -f $HOME/stderr $HOME/invalid" && + test_when_finished "rm -f \"\$HOME/stderr\" \"\$HOME/invalid\"" && echo "UTF-8 non-character 1" >F && printf "Commit message\n\nNon-character:\364\217\277\276\n" \ >"$HOME/invalid" && git commit -a -F "$HOME/invalid" 2>"$HOME"/stderr && - grep "did not conform" "$HOME"/stderr + test_i18ngrep "did not conform" "$HOME"/stderr ' test_expect_success 'UTF-8 non-characters refused' ' - test_when_finished "rm -f $HOME/stderr $HOME/invalid" && + test_when_finished "rm -f \"\$HOME/stderr\" \"\$HOME/invalid\"" && echo "UTF-8 non-character 2." >F && printf "Commit message\n\nNon-character:\357\267\220\n" \ >"$HOME/invalid" && git commit -a -F "$HOME/invalid" 2>"$HOME"/stderr && - grep "did not conform" "$HOME"/stderr + test_i18ngrep "did not conform" "$HOME"/stderr ' for H in ISO8859-1 eucJP ISO-2022-JP diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh index 75cf3ff9bd..923eb01f0e 100755 --- a/t/t3901-i18n-patch.sh +++ b/t/t3901-i18n-patch.sh @@ -31,7 +31,7 @@ test_expect_success setup ' # use UTF-8 in author and committer name to match the # i18n.commitencoding settings - . "$TEST_DIRECTORY"/t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901/utf8.txt && test_tick && echo "$GIT_AUTHOR_NAME" >mine && @@ -55,7 +55,7 @@ test_expect_success setup ' # the second one on the side branch is ISO-8859-1 git config i18n.commitencoding ISO8859-1 && # use author and committer name in ISO-8859-1 to match it. - . "$TEST_DIRECTORY"/t3901-8859-1.txt + . "$TEST_DIRECTORY"/t3901/8859-1.txt fi && test_tick && echo Yet another >theirs && @@ -100,7 +100,7 @@ test_expect_success 'rebase (U/U)' ' # The result will be committed by GIT_COMMITTER_NAME -- # we want UTF-8 encoded name. - . "$TEST_DIRECTORY"/t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901/utf8.txt && git checkout -b test && git rebase master && @@ -110,7 +110,7 @@ test_expect_success 'rebase (U/U)' ' test_expect_success 'rebase (U/L)' ' git config i18n.commitencoding UTF-8 && git config i18n.logoutputencoding ISO8859-1 && - . "$TEST_DIRECTORY"/t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901/utf8.txt && git reset --hard side && git rebase master && @@ -122,7 +122,7 @@ test_expect_success !MINGW 'rebase (L/L)' ' # In this test we want ISO-8859-1 encoded commits as the result git config i18n.commitencoding ISO8859-1 && git config i18n.logoutputencoding ISO8859-1 && - . "$TEST_DIRECTORY"/t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && git reset --hard side && git rebase master && @@ -135,7 +135,7 @@ test_expect_success !MINGW 'rebase (L/U)' ' # to get ISO-8859-1 results. git config i18n.commitencoding ISO8859-1 && git config i18n.logoutputencoding UTF-8 && - . "$TEST_DIRECTORY"/t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && git reset --hard side && git rebase master && @@ -148,7 +148,7 @@ test_expect_success 'cherry-pick(U/U)' ' git config i18n.commitencoding UTF-8 && git config i18n.logoutputencoding UTF-8 && - . "$TEST_DIRECTORY"/t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901/utf8.txt && git reset --hard master && git cherry-pick side^ && @@ -163,7 +163,7 @@ test_expect_success !MINGW 'cherry-pick(L/L)' ' git config i18n.commitencoding ISO8859-1 && git config i18n.logoutputencoding ISO8859-1 && - . "$TEST_DIRECTORY"/t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && git reset --hard master && git cherry-pick side^ && @@ -178,7 +178,7 @@ test_expect_success 'cherry-pick(U/L)' ' git config i18n.commitencoding UTF-8 && git config i18n.logoutputencoding ISO8859-1 && - . "$TEST_DIRECTORY"/t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901/utf8.txt && git reset --hard master && git cherry-pick side^ && @@ -194,7 +194,7 @@ test_expect_success !MINGW 'cherry-pick(L/U)' ' git config i18n.commitencoding ISO8859-1 && git config i18n.logoutputencoding UTF-8 && - . "$TEST_DIRECTORY"/t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && git reset --hard master && git cherry-pick side^ && @@ -207,7 +207,7 @@ test_expect_success !MINGW 'cherry-pick(L/U)' ' test_expect_success 'rebase --merge (U/U)' ' git config i18n.commitencoding UTF-8 && git config i18n.logoutputencoding UTF-8 && - . "$TEST_DIRECTORY"/t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901/utf8.txt && git reset --hard side && git rebase --merge master && @@ -218,7 +218,7 @@ test_expect_success 'rebase --merge (U/U)' ' test_expect_success 'rebase --merge (U/L)' ' git config i18n.commitencoding UTF-8 && git config i18n.logoutputencoding ISO8859-1 && - . "$TEST_DIRECTORY"/t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901/utf8.txt && git reset --hard side && git rebase --merge master && @@ -230,7 +230,7 @@ test_expect_success 'rebase --merge (L/L)' ' # In this test we want ISO-8859-1 encoded commits as the result git config i18n.commitencoding ISO8859-1 && git config i18n.logoutputencoding ISO8859-1 && - . "$TEST_DIRECTORY"/t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && git reset --hard side && git rebase --merge master && @@ -243,7 +243,7 @@ test_expect_success 'rebase --merge (L/U)' ' # to get ISO-8859-1 results. git config i18n.commitencoding ISO8859-1 && git config i18n.logoutputencoding UTF-8 && - . "$TEST_DIRECTORY"/t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && git reset --hard side && git rebase --merge master && @@ -251,4 +251,66 @@ test_expect_success 'rebase --merge (L/U)' ' check_encoding 2 8859 ' +test_expect_success 'am (U/U)' ' + # Apply UTF-8 patches with UTF-8 commitencoding + git config i18n.commitencoding UTF-8 && + . "$TEST_DIRECTORY"/t3901/utf8.txt && + + git reset --hard master && + git am out-u1 out-u2 && + + check_encoding 2 +' + +test_expect_success !MINGW 'am (L/L)' ' + # Apply ISO-8859-1 patches with ISO-8859-1 commitencoding + git config i18n.commitencoding ISO8859-1 && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && + + git reset --hard master && + git am out-l1 out-l2 && + + check_encoding 2 8859 +' + +test_expect_success 'am (U/L)' ' + # Apply ISO-8859-1 patches with UTF-8 commitencoding + git config i18n.commitencoding UTF-8 && + . "$TEST_DIRECTORY"/t3901/utf8.txt && + git reset --hard master && + + # am specifies --utf8 by default. + git am out-l1 out-l2 && + + check_encoding 2 +' + +test_expect_success 'am --no-utf8 (U/L)' ' + # Apply ISO-8859-1 patches with UTF-8 commitencoding + git config i18n.commitencoding UTF-8 && + . "$TEST_DIRECTORY"/t3901/utf8.txt && + + git reset --hard master && + git am --no-utf8 out-l1 out-l2 2>err && + + # commit-tree will warn that the commit message does not contain valid UTF-8 + # as mailinfo did not convert it + test_i18ngrep "did not conform" err && + + check_encoding 2 +' + +test_expect_success !MINGW 'am (L/U)' ' + # Apply UTF-8 patches with ISO-8859-1 commitencoding + git config i18n.commitencoding ISO8859-1 && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && + + git reset --hard master && + # mailinfo will re-code the commit message to the charset specified by + # i18n.commitencoding + git am out-u1 out-u2 && + + check_encoding 2 8859 +' + test_done diff --git a/t/t3901-8859-1.txt b/t/t3901/8859-1.txt index 38c21a6a7f..38c21a6a7f 100755 --- a/t/t3901-8859-1.txt +++ b/t/t3901/8859-1.txt diff --git a/t/t3901-utf8.txt b/t/t3901/utf8.txt index 5f5205cd02..5f5205cd02 100755 --- a/t/t3901-utf8.txt +++ b/t/t3901/utf8.txt diff --git a/t/t3902-quoted.sh b/t/t3902-quoted.sh index 892f567844..f528008c36 100755 --- a/t/t3902-quoted.sh +++ b/t/t3902-quoted.sh @@ -12,6 +12,7 @@ GN='ç´”' HT=' ' DQ='"' +test_have_prereq MINGW || echo foo 2>/dev/null > "Name and an${HT}HT" if ! test -f "Name and an${HT}HT" then diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 7396ca9911..aefde7b172 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -45,13 +45,6 @@ test_expect_success 'applying bogus stash does nothing' ' test_cmp expect file ' -test_expect_success 'apply requires a clean index' ' - test_when_finished "git reset --hard" && - echo changed >other-file && - git add other-file && - test_must_fail git stash apply -' - test_expect_success 'apply does not need clean working directory' ' echo 4 >other-file && git stash apply && @@ -138,6 +131,26 @@ test_expect_success 'drop middle stash' ' test 1 = $(git show HEAD:file) ' +test_expect_success 'drop middle stash by index' ' + git reset --hard && + echo 8 >file && + git stash && + echo 9 >file && + git stash && + git stash drop 1 && + test 2 = $(git stash list | wc -l) && + git stash apply && + test 9 = $(cat file) && + test 1 = $(git show :file) && + test 1 = $(git show HEAD:file) && + git reset --hard && + git stash drop && + git stash apply && + test 3 = $(cat file) && + test 1 = $(git show :file) && + test 1 = $(git show HEAD:file) +' + test_expect_success 'stash pop' ' git reset --hard && git stash pop && @@ -261,9 +274,7 @@ test_expect_success 'stash --invalid-option' ' git add file2 && test_must_fail git stash --invalid-option && test_must_fail git stash save --invalid-option && - test bar5,bar6 = $(cat file),$(cat file2) && - git stash -- -message-starting-with-dash && - test bar,bar2 = $(cat file),$(cat file2) + test bar5,bar6 = $(cat file),$(cat file2) ' test_expect_success 'stash an added file' ' @@ -433,6 +444,14 @@ test_expect_failure 'stash file to directory' ' test foo = "$(cat file/file)" ' +test_expect_success 'stash create - no changes' ' + git stash clear && + test_when_finished "git reset --hard HEAD" && + git reset --hard && + git stash create >actual && + test_must_be_empty actual +' + test_expect_success 'stash branch - no stashes on stack, stash-like argument' ' git stash clear && test_when_finished "git reset --hard HEAD" && @@ -611,6 +630,21 @@ test_expect_success 'invalid ref of the form stash@{n}, n >= N' ' git stash drop ' +test_expect_success 'invalid ref of the form "n", n >= N' ' + git stash clear && + test_must_fail git stash drop 0 && + echo bar5 >file && + echo bar6 >file2 && + git add file2 && + git stash && + test_must_fail git stash drop 1 && + test_must_fail git stash pop 1 && + test_must_fail git stash apply 1 && + test_must_fail git stash show 1 && + test_must_fail git stash branch tmp 1 && + git stash drop +' + test_expect_success 'stash branch should not drop the stash if the branch exists' ' git stash clear && echo foo >file && @@ -622,6 +656,20 @@ test_expect_success 'stash branch should not drop the stash if the branch exists git rev-parse stash@{0} -- ' +test_expect_success 'stash branch should not drop the stash if the apply fails' ' + git stash clear && + git reset HEAD~1 --hard && + echo foo >file && + git add file && + git commit -m initial && + echo bar >file && + git stash && + echo baz >file && + test_when_finished "git checkout master" && + test_must_fail git stash branch new_branch stash@{0} && + git rev-parse stash@{0} -- +' + test_expect_success 'stash apply shows status same as git status (relative to current directory)' ' git stash clear && echo 1 >subdir/subfile1 && @@ -637,7 +685,7 @@ test_expect_success 'stash apply shows status same as git status (relative to cu sane_unset GIT_MERGE_VERBOSITY && git stash apply ) | - sed -e 1,2d >actual && # drop "Saved..." and "HEAD is now..." + sed -e 1d >actual && # drop "Saved..." test_i18ncmp expect actual ' @@ -679,7 +727,7 @@ test_expect_success 'store updates stash ref and reflog' ' ! grep quux bazzy && git stash store -m quuxery $STASH_ID && test $(cat .git/refs/stash) = $STASH_ID && - grep $STASH_ID .git/logs/refs/stash && + git reflog --format=%H stash| grep $STASH_ID && git stash pop && grep quux bazzy ' @@ -738,4 +786,314 @@ test_expect_success 'stash list --cc shows combined diff' ' test_cmp expect actual ' +test_expect_success 'stash is not confused by partial renames' ' + mv file renamed && + git add renamed && + git stash && + git stash apply && + test_path_is_file renamed && + test_path_is_missing file +' + +test_expect_success 'push -m shows right message' ' + >foo && + git add foo && + git stash push -m "test message" && + echo "stash@{0}: On master: test message" >expect && + git stash list -1 >actual && + test_cmp expect actual +' + +test_expect_success 'push -m also works without space' ' + >foo && + git add foo && + git stash push -m"unspaced test message" && + echo "stash@{0}: On master: unspaced test message" >expect && + git stash list -1 >actual && + test_cmp expect actual +' + +test_expect_success 'store -m foo shows right message' ' + git stash clear && + git reset --hard && + echo quux >bazzy && + git add bazzy && + STASH_ID=$(git stash create) && + git stash store -m "store m" $STASH_ID && + echo "stash@{0}: store m" >expect && + git stash list -1 >actual && + test_cmp expect actual +' + +test_expect_success 'store -mfoo shows right message' ' + git stash clear && + git reset --hard && + echo quux >bazzy && + git add bazzy && + STASH_ID=$(git stash create) && + git stash store -m"store mfoo" $STASH_ID && + echo "stash@{0}: store mfoo" >expect && + git stash list -1 >actual && + test_cmp expect actual +' + +test_expect_success 'store --message=foo shows right message' ' + git stash clear && + git reset --hard && + echo quux >bazzy && + git add bazzy && + STASH_ID=$(git stash create) && + git stash store --message="store message=foo" $STASH_ID && + echo "stash@{0}: store message=foo" >expect && + git stash list -1 >actual && + test_cmp expect actual +' + +test_expect_success 'store --message foo shows right message' ' + git stash clear && + git reset --hard && + echo quux >bazzy && + git add bazzy && + STASH_ID=$(git stash create) && + git stash store --message "store message foo" $STASH_ID && + echo "stash@{0}: store message foo" >expect && + git stash list -1 >actual && + test_cmp expect actual +' + +test_expect_success 'push -mfoo uses right message' ' + >foo && + git add foo && + git stash push -m"test mfoo" && + echo "stash@{0}: On master: test mfoo" >expect && + git stash list -1 >actual && + test_cmp expect actual +' + +test_expect_success 'push --message foo is synonym for -mfoo' ' + >foo && + git add foo && + git stash push --message "test message foo" && + echo "stash@{0}: On master: test message foo" >expect && + git stash list -1 >actual && + test_cmp expect actual +' + +test_expect_success 'push --message=foo is synonym for -mfoo' ' + >foo && + git add foo && + git stash push --message="test message=foo" && + echo "stash@{0}: On master: test message=foo" >expect && + git stash list -1 >actual && + test_cmp expect actual +' + +test_expect_success 'push -m shows right message' ' + >foo && + git add foo && + git stash push -m "test m foo" && + echo "stash@{0}: On master: test m foo" >expect && + git stash list -1 >actual && + test_cmp expect actual +' + +test_expect_success 'create stores correct message' ' + >foo && + git add foo && + STASH_ID=$(git stash create "create test message") && + echo "On master: create test message" >expect && + git show --pretty=%s -s ${STASH_ID} >actual && + test_cmp expect actual +' + +test_expect_success 'create with multiple arguments for the message' ' + >foo && + git add foo && + STASH_ID=$(git stash create test untracked) && + echo "On master: test untracked" >expect && + git show --pretty=%s -s ${STASH_ID} >actual && + test_cmp expect actual +' + +test_expect_success 'create in a detached state' ' + test_when_finished "git checkout master" && + git checkout HEAD~1 && + >foo && + git add foo && + STASH_ID=$(git stash create) && + HEAD_ID=$(git rev-parse --short HEAD) && + echo "WIP on (no branch): ${HEAD_ID} initial" >expect && + git show --pretty=%s -s ${STASH_ID} >actual && + test_cmp expect actual +' + +test_expect_success 'stash -- <pathspec> stashes and restores the file' ' + >foo && + >bar && + git add foo bar && + git stash push -- foo && + test_path_is_file bar && + test_path_is_missing foo && + git stash pop && + test_path_is_file foo && + test_path_is_file bar +' + +test_expect_success 'stash -- <pathspec> stashes in subdirectory' ' + mkdir sub && + >foo && + >bar && + git add foo bar && + ( + cd sub && + git stash push -- ../foo + ) && + test_path_is_file bar && + test_path_is_missing foo && + git stash pop && + test_path_is_file foo && + test_path_is_file bar +' + +test_expect_success 'stash with multiple pathspec arguments' ' + >foo && + >bar && + >extra && + git add foo bar extra && + git stash push -- foo bar && + test_path_is_missing bar && + test_path_is_missing foo && + test_path_is_file extra && + git stash pop && + test_path_is_file foo && + test_path_is_file bar && + test_path_is_file extra +' + +test_expect_success 'stash with file including $IFS character' ' + >"foo bar" && + >foo && + >bar && + git add foo* && + git stash push -- "foo b*" && + test_path_is_missing "foo bar" && + test_path_is_file foo && + test_path_is_file bar && + git stash pop && + test_path_is_file "foo bar" && + test_path_is_file foo && + test_path_is_file bar +' + +test_expect_success 'stash with pathspec matching multiple paths' ' + echo original >file && + echo original >other-file && + git commit -m "two" file other-file && + echo modified >file && + echo modified >other-file && + git stash push -- "*file" && + echo original >expect && + test_cmp expect file && + test_cmp expect other-file && + git stash pop && + echo modified >expect && + test_cmp expect file && + test_cmp expect other-file +' + +test_expect_success 'stash push -p with pathspec shows no changes only once' ' + >foo && + git add foo && + git commit -m "tmp" && + git stash push -p foo >actual && + echo "No local changes to save" >expect && + git reset --hard HEAD~ && + test_i18ncmp expect actual +' + +test_expect_success 'stash push with pathspec shows no changes when there are none' ' + >foo && + git add foo && + git commit -m "tmp" && + git stash push foo >actual && + echo "No local changes to save" >expect && + git reset --hard HEAD~ && + test_i18ncmp expect actual +' + +test_expect_success 'stash push with pathspec not in the repository errors out' ' + >untracked && + test_must_fail git stash push untracked && + test_path_is_file untracked +' + +test_expect_success 'untracked files are left in place when -u is not given' ' + >file && + git add file && + >untracked && + git stash push file && + test_path_is_file untracked +' + +test_expect_success 'stash without verb with pathspec' ' + >"foo bar" && + >foo && + >bar && + git add foo* && + git stash -- "foo b*" && + test_path_is_missing "foo bar" && + test_path_is_file foo && + test_path_is_file bar && + git stash pop && + test_path_is_file "foo bar" && + test_path_is_file foo && + test_path_is_file bar +' + +test_expect_success 'stash -k -- <pathspec> leaves unstaged files intact' ' + git reset && + >foo && + >bar && + git add foo bar && + git commit -m "test" && + echo "foo" >foo && + echo "bar" >bar && + git stash -k -- foo && + test "",bar = $(cat foo),$(cat bar) && + git stash pop && + test foo,bar = $(cat foo),$(cat bar) +' + +test_expect_success 'stash -- <subdir> leaves untracked files in subdir intact' ' + git reset && + >subdir/untracked && + >subdir/tracked1 && + >subdir/tracked2 && + git add subdir/tracked* && + git stash -- subdir/ && + test_path_is_missing subdir/tracked1 && + test_path_is_missing subdir/tracked2 && + test_path_is_file subdir/untracked && + git stash pop && + test_path_is_file subdir/tracked1 && + test_path_is_file subdir/tracked2 && + test_path_is_file subdir/untracked +' + +test_expect_success 'stash -- <subdir> works with binary files' ' + git reset && + >subdir/untracked && + >subdir/tracked && + cp "$TEST_DIRECTORY"/test-binary-1.png subdir/tracked-binary && + git add subdir/tracked* && + git stash -- subdir/ && + test_path_is_missing subdir/tracked && + test_path_is_missing subdir/tracked-binary && + test_path_is_file subdir/untracked && + git stash pop && + test_path_is_file subdir/tracked && + test_path_is_file subdir/tracked-binary && + test_path_is_file subdir/untracked +' + test_done diff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh index 38e730090f..83744f8c93 100755 --- a/t/t3904-stash-patch.sh +++ b/t/t3904-stash-patch.sh @@ -77,6 +77,14 @@ test_expect_success 'git stash --no-keep-index -p' ' verify_state dir/foo work index ' +test_expect_success 'stash -p --no-keep-index -- <pathspec> does not unstage other files' ' + set_state HEAD HEADfile_work HEADfile_index && + set_state dir/foo work index && + echo y | git stash push -p --no-keep-index -- HEAD && + verify_state HEAD committed committed && + verify_state dir/foo work index +' + test_expect_success 'none of this moved HEAD' ' verify_saved_head ' diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh index f372fc8ca8..3ea5b9bb3f 100755 --- a/t/t3905-stash-include-untracked.sh +++ b/t/t3905-stash-include-untracked.sh @@ -185,4 +185,99 @@ test_expect_success 'stash save --all is stash poppable' ' test -s .gitignore ' +test_expect_success 'stash push --include-untracked with pathspec' ' + >foo && + >bar && + git stash push --include-untracked -- foo && + test_path_is_file bar && + test_path_is_missing foo && + git stash pop && + test_path_is_file bar && + test_path_is_file foo +' + +test_expect_success 'stash push with $IFS character' ' + >"foo bar" && + >foo && + >bar && + git add foo* && + git stash push --include-untracked -- "foo b*" && + test_path_is_missing "foo bar" && + test_path_is_file foo && + test_path_is_file bar && + git stash pop && + test_path_is_file "foo bar" && + test_path_is_file foo && + test_path_is_file bar +' + +cat > .gitignore <<EOF +ignored +ignored.d/* +EOF + +test_expect_success 'stash previously ignored file' ' + git reset HEAD && + git add .gitignore && + git commit -m "Add .gitignore" && + >ignored.d/foo && + echo "!ignored.d/foo" >> .gitignore && + git stash save --include-untracked && + test_path_is_missing ignored.d/foo && + git stash pop && + test_path_is_file ignored.d/foo +' + +test_expect_success 'stash -u -- <untracked> doesnt print error' ' + >untracked && + git stash push -u -- untracked 2>actual && + test_path_is_missing untracked && + test_line_count = 0 actual +' + +test_expect_success 'stash -u -- <untracked> leaves rest of working tree in place' ' + >tracked && + git add tracked && + >untracked && + git stash push -u -- untracked && + test_path_is_missing untracked && + test_path_is_file tracked +' + +test_expect_success 'stash -u -- <tracked> <untracked> clears changes in both' ' + >tracked && + git add tracked && + >untracked && + git stash push -u -- tracked untracked && + test_path_is_missing tracked && + test_path_is_missing untracked +' + +test_expect_success 'stash --all -- <ignored> stashes ignored file' ' + >ignored.d/bar && + git stash push --all -- ignored.d/bar && + test_path_is_missing ignored.d/bar +' + +test_expect_success 'stash --all -- <tracked> <ignored> clears changes in both' ' + >tracked && + git add tracked && + >ignored.d/bar && + git stash push --all -- tracked ignored.d/bar && + test_path_is_missing tracked && + test_path_is_missing ignored.d/bar +' + +test_expect_success 'stash -u -- <ignored> leaves ignored file alone' ' + >ignored.d/bar && + git stash push -u -- ignored.d/bar && + test_path_is_file ignored.d/bar +' + +test_expect_success 'stash -u -- <non-existant> shows no changes when there are none' ' + git stash push -u -- non-existant >actual && + echo "No local changes to save" >expect && + test_i18ncmp expect actual +' + test_done diff --git a/t/t3910-mac-os-precompose.sh b/t/t3910-mac-os-precompose.sh index 831935665e..26dd5b7f78 100755 --- a/t/t3910-mac-os-precompose.sh +++ b/t/t3910-mac-os-precompose.sh @@ -49,12 +49,54 @@ test_expect_success "setup" ' test_expect_success "setup case mac" ' git checkout -b mac_os ' +# This will test nfd2nfc in git diff +test_expect_success "git diff f.Adiar" ' + touch f.$Adiarnfc && + git add f.$Adiarnfc && + echo f.Adiarnfc >f.$Adiarnfc && + git diff f.$Adiarnfd >expect && + git diff f.$Adiarnfc >actual && + test_cmp expect actual && + git reset HEAD f.Adiarnfc && + rm f.$Adiarnfc expect actual +' +# This will test nfd2nfc in git diff-files +test_expect_success "git diff-files f.Adiar" ' + touch f.$Adiarnfc && + git add f.$Adiarnfc && + echo f.Adiarnfc >f.$Adiarnfc && + git diff-files f.$Adiarnfd >expect && + git diff-files f.$Adiarnfc >actual && + test_cmp expect actual && + git reset HEAD f.Adiarnfc && + rm f.$Adiarnfc expect actual +' +# This will test nfd2nfc in git diff-index +test_expect_success "git diff-index f.Adiar" ' + touch f.$Adiarnfc && + git add f.$Adiarnfc && + echo f.Adiarnfc >f.$Adiarnfc && + git diff-index HEAD f.$Adiarnfd >expect && + git diff-index HEAD f.$Adiarnfc >actual && + test_cmp expect actual && + git reset HEAD f.Adiarnfc && + rm f.$Adiarnfc expect actual +' # This will test nfd2nfc in readdir() test_expect_success "add file Adiarnfc" ' echo f.Adiarnfc >f.$Adiarnfc && git add f.$Adiarnfc && git commit -m "add f.$Adiarnfc" ' +# This will test nfd2nfc in git diff-tree +test_expect_success "git diff-tree f.Adiar" ' + echo f.Adiarnfc >>f.$Adiarnfc && + git diff-tree HEAD f.$Adiarnfd >expect && + git diff-tree HEAD f.$Adiarnfc >actual && + test_cmp expect actual && + git checkout f.$Adiarnfc && + rm expect actual +' # This will test nfd2nfc in git stage() test_expect_success "stage file d.Adiarnfd/f.Adiarnfd" ' mkdir d.$Adiarnfd && diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh index 2f327b7495..a07816d560 100755 --- a/t/t4001-diff-rename.sh +++ b/t/t4001-diff-rename.sh @@ -9,21 +9,84 @@ test_description='Test rename detection in diff engine. . ./test-lib.sh . "$TEST_DIRECTORY"/diff-lib.sh -echo >path0 'Line 1 -Line 2 -Line 3 -Line 4 -Line 5 -Line 6 -Line 7 -Line 8 -Line 9 -Line 10 -line 11 -Line 12 -Line 13 -Line 14 -Line 15 +test_expect_success 'setup' ' + cat >path0 <<-\EOF && + Line 1 + Line 2 + Line 3 + Line 4 + Line 5 + Line 6 + Line 7 + Line 8 + Line 9 + Line 10 + line 11 + Line 12 + Line 13 + Line 14 + Line 15 + EOF + cat >expected <<-\EOF && + diff --git a/path0 b/path1 + rename from path0 + rename to path1 + --- a/path0 + +++ b/path1 + @@ -8,7 +8,7 @@ Line 7 + Line 8 + Line 9 + Line 10 + -line 11 + +Line 11 + Line 12 + Line 13 + Line 14 + EOF + cat >no-rename <<-\EOF + diff --git a/path0 b/path0 + deleted file mode 100644 + index fdbec44..0000000 + --- a/path0 + +++ /dev/null + @@ -1,15 +0,0 @@ + -Line 1 + -Line 2 + -Line 3 + -Line 4 + -Line 5 + -Line 6 + -Line 7 + -Line 8 + -Line 9 + -Line 10 + -line 11 + -Line 12 + -Line 13 + -Line 14 + -Line 15 + diff --git a/path1 b/path1 + new file mode 100644 + index 0000000..752c50e + --- /dev/null + +++ b/path1 + @@ -0,0 +1,15 @@ + +Line 1 + +Line 2 + +Line 3 + +Line 4 + +Line 5 + +Line 6 + +Line 7 + +Line 8 + +Line 9 + +Line 10 + +Line 11 + +Line 12 + +Line 13 + +Line 14 + +Line 15 + EOF ' test_expect_success \ @@ -43,27 +106,27 @@ test_expect_success \ test_expect_success \ 'git diff-index -p -M after rename and editing.' \ 'git diff-index -p -M $tree >current' -cat >expected <<\EOF -diff --git a/path0 b/path1 -rename from path0 -rename to path1 ---- a/path0 -+++ b/path1 -@@ -8,7 +8,7 @@ Line 7 - Line 8 - Line 9 - Line 10 --line 11 -+Line 11 - Line 12 - Line 13 - Line 14 -EOF + test_expect_success \ 'validate the output.' \ 'compare_diff_patch current expected' +test_expect_success 'test diff.renames=true' ' + git -c diff.renames=true diff --cached $tree >current && + compare_diff_patch current expected +' + +test_expect_success 'test diff.renames=false' ' + git -c diff.renames=false diff --cached $tree >current && + compare_diff_patch current no-rename +' + +test_expect_success 'test diff.renames unset' ' + git diff --cached $tree >current && + compare_diff_patch current expected +' + test_expect_success 'favour same basenames over different ones' ' cp path1 another-path && git add another-path && @@ -71,11 +134,27 @@ test_expect_success 'favour same basenames over different ones' ' git rm path1 && mkdir subdir && git mv another-path subdir/path1 && - git status | test_i18ngrep "renamed: .*path1 -> subdir/path1"' + git status >out && + test_i18ngrep "renamed: .*path1 -> subdir/path1" out +' test_expect_success 'favour same basenames even with minor differences' ' git show HEAD:path1 | sed "s/15/16/" > subdir/path1 && - git status | test_i18ngrep "renamed: .*path1 -> subdir/path1"' + git status >out && + test_i18ngrep "renamed: .*path1 -> subdir/path1" out +' + +test_expect_success 'two files with same basename and same content' ' + git reset --hard && + mkdir -p dir/A dir/B && + cp path1 dir/A/file && + cp path1 dir/B/file && + git add dir && + git commit -m 2 && + git mv dir other-dir && + git status >out && + test_i18ngrep "renamed: .*dir/A/file -> other-dir/A/file" out +' test_expect_success 'setup for many rename source candidates' ' git reset --hard && @@ -156,4 +235,19 @@ test_expect_success 'rename pretty print common prefix and suffix overlap' ' test_i18ngrep " d/f/{ => f}/e " output ' +test_expect_success 'diff-tree -l0 defaults to a big rename limit, not zero' ' + test_write_lines line1 line2 line3 >myfile && + git add myfile && + git commit -m x && + + test_write_lines line1 line2 line4 >myotherfile && + git rm myfile && + git add myotherfile && + git commit -m x && + + git diff-tree -M -l0 HEAD HEAD^ >actual && + # Verify that a rename from myotherfile to myfile was detected + grep "myotherfile.*myfile" actual +' + test_done diff --git a/t/t4005-diff-rename-2.sh b/t/t4005-diff-rename-2.sh index 135addbfbd..f542d2929d 100755 --- a/t/t4005-diff-rename-2.sh +++ b/t/t4005-diff-rename-2.sh @@ -3,84 +3,75 @@ # Copyright (c) 2005 Junio C Hamano # -test_description='Same rename detection as t4003 but testing diff-raw. +test_description='Same rename detection as t4003 but testing diff-raw.' -' . ./test-lib.sh . "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash -test_expect_success \ - 'prepare reference tree' \ - 'cat "$TEST_DIRECTORY"/diff-lib/COPYING >COPYING && - echo frotz >rezrov && - git update-index --add COPYING rezrov && - tree=$(git write-tree) && - echo $tree' - -test_expect_success \ - 'prepare work tree' \ - 'sed -e 's/HOWEVER/However/' <COPYING >COPYING.1 && - sed -e 's/GPL/G.P.L/g' <COPYING >COPYING.2 && - rm -f COPYING && - git update-index --add --remove COPYING COPYING.?' +test_expect_success 'setup reference tree' ' + cat "$TEST_DIRECTORY"/diff-lib/COPYING >COPYING && + echo frotz >rezrov && + git update-index --add COPYING rezrov && + tree=$(git write-tree) && + echo $tree && + sed -e 's/HOWEVER/However/' <COPYING >COPYING.1 && + sed -e 's/GPL/G.P.L/g' <COPYING >COPYING.2 && + origoid=$(git hash-object COPYING) && + oid1=$(git hash-object COPYING.1) && + oid2=$(git hash-object COPYING.2) +' +################################################################ # tree has COPYING and rezrov. work tree has COPYING.1 and COPYING.2, # both are slightly edited, and unchanged rezrov. We say COPYING.1 # and COPYING.2 are based on COPYING, and do not say anything about # rezrov. -git diff-index -C $tree >current - -cat >expected <<\EOF -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1 -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 R1234 COPYING COPYING.2 -EOF +test_expect_success 'validate output from rename/copy detection (#1)' ' + rm -f COPYING && + git update-index --add --remove COPYING COPYING.? && -test_expect_success \ - 'validate output from rename/copy detection (#1)' \ - 'compare_diff_raw current expected' + cat <<-EOF >expected && + :100644 100644 $origoid $oid1 C1234 COPYING COPYING.1 + :100644 100644 $origoid $oid2 R1234 COPYING COPYING.2 + EOF + git diff-index -C $tree >current && + compare_diff_raw expected current +' ################################################################ - -test_expect_success \ - 'prepare work tree again' \ - 'mv COPYING.2 COPYING && - git update-index --add --remove COPYING COPYING.1 COPYING.2' - # tree has COPYING and rezrov. work tree has COPYING and COPYING.1, # both are slightly edited, and unchanged rezrov. We say COPYING.1 # is based on COPYING and COPYING is still there, and do not say anything # about rezrov. -git diff-index -C $tree >current -cat >expected <<\EOF -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 M COPYING -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1 -EOF +test_expect_success 'validate output from rename/copy detection (#2)' ' + mv COPYING.2 COPYING && + git update-index --add --remove COPYING COPYING.1 COPYING.2 && -test_expect_success \ - 'validate output from rename/copy detection (#2)' \ - 'compare_diff_raw current expected' + cat <<-EOF >expected && + :100644 100644 $origoid $oid2 M COPYING + :100644 100644 $origoid $oid1 C1234 COPYING COPYING.1 + EOF + git diff-index -C $tree >current && + compare_diff_raw current expected +' ################################################################ - # tree has COPYING and rezrov. work tree has the same COPYING and # copy-edited COPYING.1, and unchanged rezrov. We should not say # anything about rezrov or COPYING, since the revised again diff-raw # nows how to say Copy. -test_expect_success \ - 'prepare work tree once again' \ - 'cat "$TEST_DIRECTORY"/diff-lib/COPYING >COPYING && - git update-index --add --remove COPYING COPYING.1' - -git diff-index -C --find-copies-harder $tree >current -cat >expected <<\EOF -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1 -EOF +test_expect_success 'validate output from rename/copy detection (#3)' ' + cat "$TEST_DIRECTORY"/diff-lib/COPYING >COPYING && + git update-index --add --remove COPYING COPYING.1 && -test_expect_success \ - 'validate output from rename/copy detection (#3)' \ - 'compare_diff_raw current expected' + cat <<-EOF >expected && + :100644 100644 $origoid $oid1 C1234 COPYING COPYING.1 + EOF + git diff-index -C --find-copies-harder $tree >current && + compare_diff_raw current expected +' test_done diff --git a/t/t4010-diff-pathspec.sh b/t/t4010-diff-pathspec.sh index 43c488b545..35b35a81c8 100755 --- a/t/t4010-diff-pathspec.sh +++ b/t/t4010-diff-pathspec.sh @@ -78,8 +78,6 @@ test_expect_success 'diff-tree pathspec' ' test_cmp expected current ' -EMPTY_TREE=4b825dc642cb6eb9a060e54bf8d69288fbee4904 - test_expect_success 'diff-tree with wildcard shows dir also matches' ' git diff-tree --name-only $EMPTY_TREE $tree -- "f*" >result && echo file0 >expected && diff --git a/t/t4011-diff-symlink.sh b/t/t4011-diff-symlink.sh index 7452fce0da..cf0f3a1ee7 100755 --- a/t/t4011-diff-symlink.sh +++ b/t/t4011-diff-symlink.sh @@ -73,7 +73,7 @@ test_expect_success 'diff identical, but newly created symlink and file' ' >expected && rm -f frotz nitfol && echo xyzzy >nitfol && - test-chmtime +10 nitfol && + test-tool chmtime +10 nitfol && if test_have_prereq SYMLINKS then ln -s xyzzy frotz @@ -139,13 +139,11 @@ test_expect_success SYMLINKS 'setup symlinks with attributes' ' test_expect_success SYMLINKS 'symlinks do not respect userdiff config by path' ' cat >expect <<-\EOF && diff --git a/file.bin b/file.bin - new file mode 100644 - index 0000000..d95f3ad - Binary files /dev/null and b/file.bin differ + index e69de29..d95f3ad 100644 + Binary files a/file.bin and b/file.bin differ diff --git a/link.bin b/link.bin - new file mode 120000 - index 0000000..dce41ec - --- /dev/null + index e69de29..dce41ec 120000 + --- a/link.bin +++ b/link.bin @@ -0,0 +1 @@ +file.bin diff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh index 643d729157..0a8af76aab 100755 --- a/t/t4012-diff-binary.sh +++ b/t/t4012-diff-binary.sh @@ -68,7 +68,7 @@ test_expect_success C_LOCALE_OUTPUT 'apply detecting corrupt patch correctly' ' sed -e "s/-CIT/xCIT/" <output >broken && test_must_fail git apply --stat --summary broken 2>detected && detected=$(cat detected) && - detected=$(expr "$detected" : "fatal.*at line \\([0-9]*\\)\$") && + detected=$(expr "$detected" : "error.*at line \\([0-9]*\\)\$") && detected=$(sed -ne "${detected}p" broken) && test "$detected" = xCIT ' @@ -77,7 +77,7 @@ test_expect_success C_LOCALE_OUTPUT 'apply detecting corrupt patch correctly' ' git diff --binary | sed -e "s/-CIT/xCIT/" >broken && test_must_fail git apply --stat --summary broken 2>detected && detected=$(cat detected) && - detected=$(expr "$detected" : "fatal.*at line \\([0-9]*\\)\$") && + detected=$(expr "$detected" : "error.*at line \\([0-9]*\\)\$") && detected=$(sed -ne "${detected}p" broken) && test "$detected" = xCIT ' diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 6ec6072118..f8d853595b 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -76,7 +76,7 @@ test_expect_success setup ' mkdir dir3 && cp dir/sub dir3/sub && - test-chmtime +1 dir3/sub && + test-tool chmtime +1 dir3/sub && git config log.showroot false && git commit --amend && @@ -90,6 +90,16 @@ test_expect_success setup ' git commit -m "Rearranged lines in dir/sub" && git checkout master && + GIT_AUTHOR_DATE="2006-06-26 00:06:00 +0000" && + GIT_COMMITTER_DATE="2006-06-26 00:06:00 +0000" && + export GIT_AUTHOR_DATE GIT_COMMITTER_DATE && + git checkout -b mode initial && + git update-index --chmod=+x file0 && + git commit -m "update mode" && + git checkout -f master && + + git config diff.renames false && + git show-branch ' @@ -108,20 +118,37 @@ test_expect_success setup ' EOF V=$(git version | sed -e 's/^git version //' -e 's/\./\\./g') -while read cmd +while read magic cmd do - case "$cmd" in - '' | '#'*) continue ;; + case "$magic" in + '' | '#'*) + continue ;; + :*) + magic=${magic#:} + label="$magic-$cmd" + case "$magic" in + noellipses) ;; + *) + die "bug in t4103: unknown magic $magic" ;; + esac ;; + *) + cmd="$magic $cmd" magic= + label="$cmd" ;; esac - test=$(echo "$cmd" | sed -e 's|[/ ][/ ]*|_|g') + test=$(echo "$label" | sed -e 's|[/ ][/ ]*|_|g') pfx=$(printf "%04d" $test_count) expect="$TEST_DIRECTORY/t4013/diff.$test" actual="$pfx-diff.$test" - test_expect_success "git $cmd" ' + test_expect_success "git $cmd # magic is ${magic:-"(not used)"}" ' { - echo "\$ git $cmd" - git $cmd | + echo "$ git $cmd" + case "$magic" in + "") + GIT_PRINT_SHA1_ELLIPSIS=yes git $cmd ;; + noellipses) + git $cmd ;; + esac | sed -e "s/^\\(-*\\)$V\\(-*\\)\$/\\1g-i-t--v-e-r-s-i-o-n\2/" \ -e "s/^\\(.*mixed; boundary=\"-*\\)$V\\(-*\\)\"\$/\\1g-i-t--v-e-r-s-i-o-n\2\"/" echo "\$" @@ -148,9 +175,12 @@ diff-tree -r --abbrev initial diff-tree -r --abbrev=4 initial diff-tree --root initial diff-tree --root --abbrev initial +:noellipses diff-tree --root --abbrev initial diff-tree --root -r initial diff-tree --root -r --abbrev initial +:noellipses diff-tree --root -r --abbrev initial diff-tree --root -r --abbrev=4 initial +:noellipses diff-tree --root -r --abbrev=4 initial diff-tree -p initial diff-tree --root -p initial diff-tree --patch-with-stat initial @@ -190,11 +220,16 @@ diff-tree --pretty side diff-tree --pretty -p side diff-tree --pretty --patch-with-stat side +diff-tree initial mode +diff-tree --stat initial mode +diff-tree --summary initial mode + diff-tree master diff-tree -p master diff-tree -p -m master diff-tree -c master diff-tree -c --abbrev master +:noellipses diff-tree -c --abbrev master diff-tree --cc master # stat only should show the diffstat with the first parent diff-tree -c --stat master @@ -241,8 +276,10 @@ rev-list --parents HEAD rev-list --children HEAD whatchanged master +:noellipses whatchanged master whatchanged -p master whatchanged --root master +:noellipses whatchanged --root master whatchanged --root -p master whatchanged --patch-with-stat master whatchanged --root --patch-with-stat master @@ -252,6 +289,7 @@ whatchanged --root -c --patch-with-stat --summary master # improved by Timo's patch whatchanged --root --cc --patch-with-stat --summary master whatchanged -SF master +:noellipses whatchanged -SF master whatchanged -SF -p master log --patch-with-stat master -- dir/ @@ -270,6 +308,7 @@ show --stat side show --stat --summary side show --patch-with-stat side show --patch-with-raw side +:noellipses show --patch-with-raw side show --patch-with-stat --summary side format-patch --stdout initial..side @@ -297,16 +336,36 @@ diff -r --stat initial..side diff initial..side diff --patch-with-stat initial..side diff --patch-with-raw initial..side +:noellipses diff --patch-with-raw initial..side diff --patch-with-stat -r initial..side diff --patch-with-raw -r initial..side +:noellipses diff --patch-with-raw -r initial..side diff --name-status dir2 dir diff --no-index --name-status dir2 dir diff --no-index --name-status -- dir2 dir diff --no-index dir dir3 diff master master^ side +# Can't use spaces... +diff --line-prefix=abc master master^ side diff --dirstat master~1 master~2 diff --dirstat initial rearrange diff --dirstat-by-file initial rearrange +# No-index --abbrev and --no-abbrev +diff --raw initial +:noellipses diff --raw initial +diff --raw --abbrev=4 initial +:noellipses diff --raw --abbrev=4 initial +diff --raw --no-abbrev initial +diff --no-index --raw dir2 dir +:noellipses diff --no-index --raw dir2 dir +diff --no-index --raw --abbrev=4 dir2 dir +:noellipses diff --no-index --raw --abbrev=4 dir2 dir +diff --no-index --raw --no-abbrev dir2 dir + +diff-tree --pretty --root --stat --compact-summary initial +diff-tree --pretty -R --root --stat --compact-summary initial +diff-tree --stat --compact-summary initial mode +diff-tree -R --stat --compact-summary initial mode EOF test_expect_success 'log -S requires an argument' ' @@ -323,6 +382,10 @@ test_expect_success 'diff --cached -- file on unborn branch' ' git diff --cached -- file0 >result && test_cmp "$TEST_DIRECTORY/t4013/diff.diff_--cached_--_file0" result ' +test_expect_success 'diff --line-prefix with spaces' ' + git diff --line-prefix="| | | " --cached -- file0 >result && + test_cmp "$TEST_DIRECTORY/t4013/diff.diff_--line-prefix_--cached_--_file0" result +' test_expect_success 'diff-tree --stdin with log formatting' ' cat >expect <<-\EOF && diff --git a/t/t4013/diff.diff-tree_--pretty_--root_--stat_--compact-summary_initial b/t/t4013/diff.diff-tree_--pretty_--root_--stat_--compact-summary_initial new file mode 100644 index 0000000000..d6451ff7cc --- /dev/null +++ b/t/t4013/diff.diff-tree_--pretty_--root_--stat_--compact-summary_initial @@ -0,0 +1,12 @@ +$ git diff-tree --pretty --root --stat --compact-summary initial +commit 444ac553ac7612cc88969031b02b3767fb8a353a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:00:00 2006 +0000 + + Initial + + dir/sub (new) | 2 ++ + file0 (new) | 3 +++ + file2 (new) | 3 +++ + 3 files changed, 8 insertions(+) +$ diff --git a/t/t4013/diff.diff-tree_--pretty_-R_--root_--stat_--compact-summary_initial b/t/t4013/diff.diff-tree_--pretty_-R_--root_--stat_--compact-summary_initial new file mode 100644 index 0000000000..1989e55cd0 --- /dev/null +++ b/t/t4013/diff.diff-tree_--pretty_-R_--root_--stat_--compact-summary_initial @@ -0,0 +1,12 @@ +$ git diff-tree --pretty -R --root --stat --compact-summary initial +commit 444ac553ac7612cc88969031b02b3767fb8a353a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:00:00 2006 +0000 + + Initial + + dir/sub (gone) | 2 -- + file0 (gone) | 3 --- + file2 (gone) | 3 --- + 3 files changed, 8 deletions(-) +$ diff --git a/t/t4013/diff.diff-tree_--stat_--compact-summary_initial_mode b/t/t4013/diff.diff-tree_--stat_--compact-summary_initial_mode new file mode 100644 index 0000000000..9c7c8f63af --- /dev/null +++ b/t/t4013/diff.diff-tree_--stat_--compact-summary_initial_mode @@ -0,0 +1,4 @@ +$ git diff-tree --stat --compact-summary initial mode + file0 (mode +x) | 0 + 1 file changed, 0 insertions(+), 0 deletions(-) +$ diff --git a/t/t4013/diff.diff-tree_--stat_initial_mode b/t/t4013/diff.diff-tree_--stat_initial_mode new file mode 100644 index 0000000000..0e5943c2c6 --- /dev/null +++ b/t/t4013/diff.diff-tree_--stat_initial_mode @@ -0,0 +1,4 @@ +$ git diff-tree --stat initial mode + file0 | 0 + 1 file changed, 0 insertions(+), 0 deletions(-) +$ diff --git a/t/t4013/diff.diff-tree_--summary_initial_mode b/t/t4013/diff.diff-tree_--summary_initial_mode new file mode 100644 index 0000000000..25846b6af8 --- /dev/null +++ b/t/t4013/diff.diff-tree_--summary_initial_mode @@ -0,0 +1,3 @@ +$ git diff-tree --summary initial mode + mode change 100644 => 100755 file0 +$ diff --git a/t/t4013/diff.diff-tree_-R_--stat_--compact-summary_initial_mode b/t/t4013/diff.diff-tree_-R_--stat_--compact-summary_initial_mode new file mode 100644 index 0000000000..e38f3d3bfb --- /dev/null +++ b/t/t4013/diff.diff-tree_-R_--stat_--compact-summary_initial_mode @@ -0,0 +1,4 @@ +$ git diff-tree -R --stat --compact-summary initial mode + file0 (mode -x) | 0 + 1 file changed, 0 insertions(+), 0 deletions(-) +$ diff --git a/t/t4013/diff.diff-tree_initial_mode b/t/t4013/diff.diff-tree_initial_mode new file mode 100644 index 0000000000..c47c09423e --- /dev/null +++ b/t/t4013/diff.diff-tree_initial_mode @@ -0,0 +1,3 @@ +$ git diff-tree initial mode +:100644 100755 01e79c32a8c99c557f0757da7cb6d65b3414466d 01e79c32a8c99c557f0757da7cb6d65b3414466d M file0 +$ diff --git a/t/t4013/diff.diff_--line-prefix=abc_master_master^_side b/t/t4013/diff.diff_--line-prefix=abc_master_master^_side new file mode 100644 index 0000000000..99f91e7f0e --- /dev/null +++ b/t/t4013/diff.diff_--line-prefix=abc_master_master^_side @@ -0,0 +1,29 @@ +$ git diff --line-prefix=abc master master^ side +abcdiff --cc dir/sub +abcindex cead32e,7289e35..992913c +abc--- a/dir/sub +abc+++ b/dir/sub +abc@@@ -1,6 -1,4 +1,8 @@@ +abc A +abc B +abc +C +abc +D +abc +E +abc +F +abc+ 1 +abc+ 2 +abcdiff --cc file0 +abcindex b414108,f4615da..10a8a9f +abc--- a/file0 +abc+++ b/file0 +abc@@@ -1,6 -1,6 +1,9 @@@ +abc 1 +abc 2 +abc 3 +abc +4 +abc +5 +abc +6 +abc+ A +abc+ B +abc+ C +$ diff --git a/t/t4013/diff.diff_--line-prefix_--cached_--_file0 b/t/t4013/diff.diff_--line-prefix_--cached_--_file0 new file mode 100644 index 0000000000..f41ba4d36a --- /dev/null +++ b/t/t4013/diff.diff_--line-prefix_--cached_--_file0 @@ -0,0 +1,15 @@ +| | | diff --git a/file0 b/file0 +| | | new file mode 100644 +| | | index 0000000..10a8a9f +| | | --- /dev/null +| | | +++ b/file0 +| | | @@ -0,0 +1,9 @@ +| | | +1 +| | | +2 +| | | +3 +| | | +4 +| | | +5 +| | | +6 +| | | +A +| | | +B +| | | +C diff --git a/t/t4013/diff.diff_--no-index_--raw_--abbrev=4_dir2_dir b/t/t4013/diff.diff_--no-index_--raw_--abbrev=4_dir2_dir new file mode 100644 index 0000000000..a71b38a833 --- /dev/null +++ b/t/t4013/diff.diff_--no-index_--raw_--abbrev=4_dir2_dir @@ -0,0 +1,3 @@ +$ git diff --no-index --raw --abbrev=4 dir2 dir +:000000 100644 0000... 0000... A dir/sub +$ diff --git a/t/t4013/diff.diff_--no-index_--raw_--no-abbrev_dir2_dir b/t/t4013/diff.diff_--no-index_--raw_--no-abbrev_dir2_dir new file mode 100644 index 0000000000..e0f00977c8 --- /dev/null +++ b/t/t4013/diff.diff_--no-index_--raw_--no-abbrev_dir2_dir @@ -0,0 +1,3 @@ +$ git diff --no-index --raw --no-abbrev dir2 dir +:000000 100644 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 A dir/sub +$ diff --git a/t/t4013/diff.diff_--no-index_--raw_dir2_dir b/t/t4013/diff.diff_--no-index_--raw_dir2_dir new file mode 100644 index 0000000000..3cb4ee7a9a --- /dev/null +++ b/t/t4013/diff.diff_--no-index_--raw_dir2_dir @@ -0,0 +1,3 @@ +$ git diff --no-index --raw dir2 dir +:000000 100644 0000000... 0000000... A dir/sub +$ diff --git a/t/t4013/diff.diff_--raw_--abbrev=4_initial b/t/t4013/diff.diff_--raw_--abbrev=4_initial new file mode 100644 index 0000000000..c3641db31d --- /dev/null +++ b/t/t4013/diff.diff_--raw_--abbrev=4_initial @@ -0,0 +1,6 @@ +$ git diff --raw --abbrev=4 initial +:100644 100644 35d2... 9929... M dir/sub +:100644 100644 01e7... 10a8... M file0 +:000000 100644 0000... b1e6... A file1 +:100644 000000 01e7... 0000... D file2 +$ diff --git a/t/t4013/diff.diff_--raw_--no-abbrev_initial b/t/t4013/diff.diff_--raw_--no-abbrev_initial new file mode 100644 index 0000000000..c87a1258e3 --- /dev/null +++ b/t/t4013/diff.diff_--raw_--no-abbrev_initial @@ -0,0 +1,6 @@ +$ git diff --raw --no-abbrev initial +:100644 100644 35d242ba79ae89ac695e26b3d4c27a8e6f028f9e 992913c5aa0a5476d10c49ed0f21fc0c6d1aedf3 M dir/sub +:100644 100644 01e79c32a8c99c557f0757da7cb6d65b3414466d 10a8a9f3657f91a156b9f0184ed79a20adef9f7f M file0 +:000000 100644 0000000000000000000000000000000000000000 b1e67221afe8461efd244b487afca22d46b95eb8 A file1 +:100644 000000 01e79c32a8c99c557f0757da7cb6d65b3414466d 0000000000000000000000000000000000000000 D file2 +$ diff --git a/t/t4013/diff.diff_--raw_initial b/t/t4013/diff.diff_--raw_initial new file mode 100644 index 0000000000..a3e978040d --- /dev/null +++ b/t/t4013/diff.diff_--raw_initial @@ -0,0 +1,6 @@ +$ git diff --raw initial +:100644 100644 35d242b... 992913c... M dir/sub +:100644 100644 01e79c3... 10a8a9f... M file0 +:000000 100644 0000000... b1e6722... A file1 +:100644 000000 01e79c3... 0000000... D file2 +$ diff --git a/t/t4013/diff.log_--decorate=full_--all b/t/t4013/diff.log_--decorate=full_--all index b345b2ebfa..2afe91f116 100644 --- a/t/t4013/diff.log_--decorate=full_--all +++ b/t/t4013/diff.log_--decorate=full_--all @@ -1,4 +1,10 @@ $ git log --decorate=full --all +commit b7e0bc69303b488b47deca799a7d723971dfa6cd (refs/heads/mode) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:06:00 2006 +0000 + + update mode + commit cd4e72fd96faed3f0ba949dc42967430374e2290 (refs/heads/rearrange) Author: A U Thor <author@example.com> Date: Mon Jun 26 00:06:00 2006 +0000 diff --git a/t/t4013/diff.log_--decorate_--all b/t/t4013/diff.log_--decorate_--all index 3aa16a9e42..d0f308ab2b 100644 --- a/t/t4013/diff.log_--decorate_--all +++ b/t/t4013/diff.log_--decorate_--all @@ -1,4 +1,10 @@ $ git log --decorate --all +commit b7e0bc69303b488b47deca799a7d723971dfa6cd (mode) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:06:00 2006 +0000 + + update mode + commit cd4e72fd96faed3f0ba949dc42967430374e2290 (rearrange) Author: A U Thor <author@example.com> Date: Mon Jun 26 00:06:00 2006 +0000 diff --git a/t/t4013/diff.noellipses-diff-tree_--root_--abbrev_initial b/t/t4013/diff.noellipses-diff-tree_--root_--abbrev_initial new file mode 100644 index 0000000000..4bdad4072e --- /dev/null +++ b/t/t4013/diff.noellipses-diff-tree_--root_--abbrev_initial @@ -0,0 +1,6 @@ +$ git diff-tree --root --abbrev initial +444ac553ac7612cc88969031b02b3767fb8a353a +:000000 040000 0000000 da7a33f A dir +:000000 100644 0000000 01e79c3 A file0 +:000000 100644 0000000 01e79c3 A file2 +$ diff --git a/t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev=4_initial b/t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev=4_initial new file mode 100644 index 0000000000..26fbfeb177 --- /dev/null +++ b/t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev=4_initial @@ -0,0 +1,6 @@ +$ git diff-tree --root -r --abbrev=4 initial +444ac553ac7612cc88969031b02b3767fb8a353a +:000000 100644 0000 35d2 A dir/sub +:000000 100644 0000 01e7 A file0 +:000000 100644 0000 01e7 A file2 +$ diff --git a/t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev_initial b/t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev_initial new file mode 100644 index 0000000000..2ac8561191 --- /dev/null +++ b/t/t4013/diff.noellipses-diff-tree_--root_-r_--abbrev_initial @@ -0,0 +1,6 @@ +$ git diff-tree --root -r --abbrev initial +444ac553ac7612cc88969031b02b3767fb8a353a +:000000 100644 0000000 35d242b A dir/sub +:000000 100644 0000000 01e79c3 A file0 +:000000 100644 0000000 01e79c3 A file2 +$ diff --git a/t/t4013/diff.noellipses-diff-tree_-c_--abbrev_master b/t/t4013/diff.noellipses-diff-tree_-c_--abbrev_master new file mode 100644 index 0000000000..bb80f013b3 --- /dev/null +++ b/t/t4013/diff.noellipses-diff-tree_-c_--abbrev_master @@ -0,0 +1,5 @@ +$ git diff-tree -c --abbrev master +59d314ad6f356dd08601a4cd5e530381da3e3c64 +::100644 100644 100644 cead32e 7289e35 992913c MM dir/sub +::100644 100644 100644 b414108 f4615da 10a8a9f MM file0 +$ diff --git a/t/t4013/diff.noellipses-diff_--no-index_--raw_--abbrev=4_dir2_dir b/t/t4013/diff.noellipses-diff_--no-index_--raw_--abbrev=4_dir2_dir new file mode 100644 index 0000000000..41b7baf0a5 --- /dev/null +++ b/t/t4013/diff.noellipses-diff_--no-index_--raw_--abbrev=4_dir2_dir @@ -0,0 +1,3 @@ +$ git diff --no-index --raw --abbrev=4 dir2 dir +:000000 100644 0000 0000 A dir/sub +$ diff --git a/t/t4013/diff.noellipses-diff_--no-index_--raw_dir2_dir b/t/t4013/diff.noellipses-diff_--no-index_--raw_dir2_dir new file mode 100644 index 0000000000..0cf3a3efea --- /dev/null +++ b/t/t4013/diff.noellipses-diff_--no-index_--raw_dir2_dir @@ -0,0 +1,3 @@ +$ git diff --no-index --raw dir2 dir +:000000 100644 0000000 0000000 A dir/sub +$ diff --git a/t/t4013/diff.noellipses-diff_--patch-with-raw_-r_initial..side b/t/t4013/diff.noellipses-diff_--patch-with-raw_-r_initial..side new file mode 100644 index 0000000000..8d1f1e3721 --- /dev/null +++ b/t/t4013/diff.noellipses-diff_--patch-with-raw_-r_initial..side @@ -0,0 +1,36 @@ +$ git diff --patch-with-raw -r initial..side +:100644 100644 35d242b 7289e35 M dir/sub +:100644 100644 01e79c3 f4615da M file0 +:000000 100644 0000000 7289e35 A file3 + +diff --git a/dir/sub b/dir/sub +index 35d242b..7289e35 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++1 ++2 +diff --git a/file0 b/file0 +index 01e79c3..f4615da 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++A ++B ++C +diff --git a/file3 b/file3 +new file mode 100644 +index 0000000..7289e35 +--- /dev/null ++++ b/file3 +@@ -0,0 +1,4 @@ ++A ++B ++1 ++2 +$ diff --git a/t/t4013/diff.noellipses-diff_--patch-with-raw_initial..side b/t/t4013/diff.noellipses-diff_--patch-with-raw_initial..side new file mode 100644 index 0000000000..50d8aee4f7 --- /dev/null +++ b/t/t4013/diff.noellipses-diff_--patch-with-raw_initial..side @@ -0,0 +1,36 @@ +$ git diff --patch-with-raw initial..side +:100644 100644 35d242b 7289e35 M dir/sub +:100644 100644 01e79c3 f4615da M file0 +:000000 100644 0000000 7289e35 A file3 + +diff --git a/dir/sub b/dir/sub +index 35d242b..7289e35 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++1 ++2 +diff --git a/file0 b/file0 +index 01e79c3..f4615da 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++A ++B ++C +diff --git a/file3 b/file3 +new file mode 100644 +index 0000000..7289e35 +--- /dev/null ++++ b/file3 +@@ -0,0 +1,4 @@ ++A ++B ++1 ++2 +$ diff --git a/t/t4013/diff.noellipses-diff_--raw_--abbrev=4_initial b/t/t4013/diff.noellipses-diff_--raw_--abbrev=4_initial new file mode 100644 index 0000000000..8ae44d6c83 --- /dev/null +++ b/t/t4013/diff.noellipses-diff_--raw_--abbrev=4_initial @@ -0,0 +1,6 @@ +$ git diff --raw --abbrev=4 initial +:100644 100644 35d2 9929 M dir/sub +:100644 100644 01e7 10a8 M file0 +:000000 100644 0000 b1e6 A file1 +:100644 000000 01e7 0000 D file2 +$ diff --git a/t/t4013/diff.noellipses-diff_--raw_initial b/t/t4013/diff.noellipses-diff_--raw_initial new file mode 100644 index 0000000000..0175bfb281 --- /dev/null +++ b/t/t4013/diff.noellipses-diff_--raw_initial @@ -0,0 +1,6 @@ +$ git diff --raw initial +:100644 100644 35d242b 992913c M dir/sub +:100644 100644 01e79c3 10a8a9f M file0 +:000000 100644 0000000 b1e6722 A file1 +:100644 000000 01e79c3 0000000 D file2 +$ diff --git a/t/t4013/diff.noellipses-show_--patch-with-raw_side b/t/t4013/diff.noellipses-show_--patch-with-raw_side new file mode 100644 index 0000000000..32fed3d576 --- /dev/null +++ b/t/t4013/diff.noellipses-show_--patch-with-raw_side @@ -0,0 +1,42 @@ +$ git show --patch-with-raw side +commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:03:00 2006 +0000 + + Side + +:100644 100644 35d242b 7289e35 M dir/sub +:100644 100644 01e79c3 f4615da M file0 +:000000 100644 0000000 7289e35 A file3 + +diff --git a/dir/sub b/dir/sub +index 35d242b..7289e35 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++1 ++2 +diff --git a/file0 b/file0 +index 01e79c3..f4615da 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++A ++B ++C +diff --git a/file3 b/file3 +new file mode 100644 +index 0000000..7289e35 +--- /dev/null ++++ b/file3 +@@ -0,0 +1,4 @@ ++A ++B ++1 ++2 +$ diff --git a/t/t4013/diff.noellipses-whatchanged_--root_master b/t/t4013/diff.noellipses-whatchanged_--root_master new file mode 100644 index 0000000000..c2cfd4e729 --- /dev/null +++ b/t/t4013/diff.noellipses-whatchanged_--root_master @@ -0,0 +1,42 @@ +$ git whatchanged --root master +commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:03:00 2006 +0000 + + Side + +:100644 100644 35d242b 7289e35 M dir/sub +:100644 100644 01e79c3 f4615da M file0 +:000000 100644 0000000 7289e35 A file3 + +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third + +:100644 100644 8422d40 cead32e M dir/sub +:000000 100644 0000000 b1e6722 A file1 + +commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:01:00 2006 +0000 + + Second + + This is the second commit. + +:100644 100644 35d242b 8422d40 M dir/sub +:100644 100644 01e79c3 b414108 M file0 +:100644 000000 01e79c3 0000000 D file2 + +commit 444ac553ac7612cc88969031b02b3767fb8a353a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:00:00 2006 +0000 + + Initial + +:000000 100644 0000000 35d242b A dir/sub +:000000 100644 0000000 01e79c3 A file0 +:000000 100644 0000000 01e79c3 A file2 +$ diff --git a/t/t4013/diff.noellipses-whatchanged_-SF_master b/t/t4013/diff.noellipses-whatchanged_-SF_master new file mode 100644 index 0000000000..b36ce5886e --- /dev/null +++ b/t/t4013/diff.noellipses-whatchanged_-SF_master @@ -0,0 +1,9 @@ +$ git whatchanged -SF master +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third + +:100644 100644 8422d40 cead32e M dir/sub +$ diff --git a/t/t4013/diff.noellipses-whatchanged_master b/t/t4013/diff.noellipses-whatchanged_master new file mode 100644 index 0000000000..55e500f2ed --- /dev/null +++ b/t/t4013/diff.noellipses-whatchanged_master @@ -0,0 +1,32 @@ +$ git whatchanged master +commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:03:00 2006 +0000 + + Side + +:100644 100644 35d242b 7289e35 M dir/sub +:100644 100644 01e79c3 f4615da M file0 +:000000 100644 0000000 7289e35 A file3 + +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third + +:100644 100644 8422d40 cead32e M dir/sub +:000000 100644 0000000 b1e6722 A file1 + +commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:01:00 2006 +0000 + + Second + + This is the second commit. + +:100644 100644 35d242b 8422d40 M dir/sub +:100644 100644 01e79c3 b414108 M file0 +:100644 000000 01e79c3 0000000 D file2 +$ diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index c39e50028f..482112ca33 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -57,6 +57,14 @@ test_expect_success "format-patch --ignore-if-in-upstream" ' ' +test_expect_success "format-patch --ignore-if-in-upstream handles tags" ' + git tag -a v1 -m tag side && + git tag -a v2 -m tag master && + git format-patch --stdout --ignore-if-in-upstream v2..v1 >patch1 && + cnt=$(grep "^From " patch1 | wc -l) && + test $cnt = 2 +' + test_expect_success "format-patch doesn't consider merge commits" ' git checkout -b slave master && @@ -221,6 +229,46 @@ check_patch () { grep -e "^Subject:" "$1" } +test_expect_success 'format.from=false' ' + + git -c format.from=false format-patch --stdout master..side | + sed -e "/^\$/q" >patch && + check_patch patch && + ! grep "^From: C O Mitter <committer@example.com>\$" patch +' + +test_expect_success 'format.from=true' ' + + git -c format.from=true format-patch --stdout master..side | + sed -e "/^\$/q" >patch && + check_patch patch && + grep "^From: C O Mitter <committer@example.com>\$" patch +' + +test_expect_success 'format.from with address' ' + + git -c format.from="F R Om <from@example.com>" format-patch --stdout master..side | + sed -e "/^\$/q" >patch && + check_patch patch && + grep "^From: F R Om <from@example.com>\$" patch +' + +test_expect_success '--no-from overrides format.from' ' + + git -c format.from="F R Om <from@example.com>" format-patch --no-from --stdout master..side | + sed -e "/^\$/q" >patch && + check_patch patch && + ! grep "^From: F R Om <from@example.com>\$" patch +' + +test_expect_success '--from overrides format.from' ' + + git -c format.from="F R Om <from@example.com>" format-patch --from --stdout master..side | + sed -e "/^\$/q" >patch && + check_patch patch && + ! grep "^From: F R Om <from@example.com>\$" patch +' + test_expect_success '--no-to overrides config.to' ' git config --replace-all format.to \ @@ -541,7 +589,7 @@ test_expect_success 'cover-letter inherits diff options' ' git mv file foo && git commit -m foo && - git format-patch --cover-letter -1 && + git format-patch --no-renames --cover-letter -1 && check_patch 0000-cover-letter.patch && ! grep "file => foo .* 0 *\$" 0000-cover-letter.patch && git format-patch --cover-letter -1 -M && @@ -695,7 +743,7 @@ test_expect_success 'options no longer allowed for format-patch' ' test_expect_success 'format-patch --numstat should produce a patch' ' git format-patch --numstat --stdout master..side > output && - test 6 = $(grep "^diff --git a/" output | wc -l)' + test 5 = $(grep "^diff --git a/" output | wc -l)' test_expect_success 'format-patch -- <path>' ' git format-patch master..side -- file 2>error && @@ -706,9 +754,22 @@ test_expect_success 'format-patch --ignore-if-in-upstream HEAD' ' git format-patch --ignore-if-in-upstream HEAD ' +git_version="$(git --version | sed "s/.* //")" + +signature() { + printf "%s\n%s\n\n" "-- " "${1:-$git_version}" +} + +test_expect_success 'format-patch default signature' ' + git format-patch --stdout -1 | tail -n 3 >output && + signature >expect && + test_cmp expect output +' + test_expect_success 'format-patch --signature' ' - git format-patch --stdout --signature="my sig" -1 >output && - grep "my sig" output + git format-patch --stdout --signature="my sig" -1 | tail -n 3 >output && + signature "my sig" >expect && + test_cmp expect output ' test_expect_success 'format-patch with format.signature config' ' @@ -1025,6 +1086,15 @@ test_expect_success 'empty subject prefix does not have extra space' ' test_cmp expect actual ' +test_expect_success '--rfc' ' + cat >expect <<-\EOF && + Subject: [RFC PATCH 1/1] header with . in it + EOF + git format-patch -n -1 --stdout --rfc >patch && + grep ^Subject: patch >actual && + test_cmp expect actual +' + test_expect_success '--from=ident notices bogus ident' ' test_must_fail git format-patch -1 --stdout --from=foo >patch ' @@ -1064,7 +1134,7 @@ test_expect_success '--from omits redundant in-body header' ' ' test_expect_success 'in-body headers trigger content encoding' ' - GIT_AUTHOR_NAME="éxötìc" test_commit exotic && + test_env GIT_AUTHOR_NAME="éxötìc" test_commit exotic && test_when_finished "git reset --hard HEAD^" && git format-patch -1 --stdout --from >patch && cat >expect <<-\EOF && @@ -1224,8 +1294,7 @@ EOF 4:Subject: [PATCH] subject 8: 10:Signed-off-by: example happens to be wrapped here. -11: -12:Signed-off-by: C O Mitter <committer@example.com> +11:Signed-off-by: C O Mitter <committer@example.com> EOF test_cmp expected actual ' @@ -1298,7 +1367,7 @@ EOF test_cmp expected actual ' -test_expect_success 'signoff: detect garbage in non-conforming footer' ' +test_expect_success 'signoff: tolerate garbage in conforming footer' ' append_signoff <<\EOF >actual && subject @@ -1313,8 +1382,36 @@ EOF 8: 10: 13:Signed-off-by: C O Mitter <committer@example.com> -14: -15:Signed-off-by: C O Mitter <committer@example.com> +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: respect trailer config' ' + append_signoff <<\EOF >actual && +subject + +Myfooter: x +Some Trash +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +11: +12:Signed-off-by: C O Mitter <committer@example.com> +EOF + test_cmp expected actual && + + test_config trailer.Myfooter.ifexists add && + append_signoff <<\EOF >actual && +subject + +Myfooter: x +Some Trash +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +11:Signed-off-by: C O Mitter <committer@example.com> EOF test_cmp expected actual ' @@ -1423,4 +1520,187 @@ test_expect_success 'cover letter auto user override' ' test_line_count = 2 list ' +test_expect_success 'format-patch --zero-commit' ' + git format-patch --zero-commit --stdout v2..v1 >patch2 && + grep "^From " patch2 | sort | uniq >actual && + echo "From $_z40 Mon Sep 17 00:00:00 2001" >expect && + test_cmp expect actual +' + +test_expect_success 'From line has expected format' ' + git format-patch --stdout v2..v1 >patch2 && + grep "^From " patch2 >from && + grep "^From $_x40 Mon Sep 17 00:00:00 2001$" patch2 >filtered && + test_cmp from filtered +' + +test_expect_success 'format-patch format.outputDirectory option' ' + test_config format.outputDirectory patches && + rm -fr patches && + git format-patch master..side && + test $(git rev-list master..side | wc -l) -eq $(ls patches | wc -l) +' + +test_expect_success 'format-patch -o overrides format.outputDirectory' ' + test_config format.outputDirectory patches && + rm -fr patches patchset && + git format-patch master..side -o patchset && + test_path_is_missing patches && + test_path_is_dir patchset +' + +test_expect_success 'format-patch --base' ' + git checkout side && + git format-patch --stdout --base=HEAD~3 -1 | tail -n 7 >actual && + echo >expected && + echo "base-commit: $(git rev-parse HEAD~3)" >>expected && + echo "prerequisite-patch-id: $(git show --patch HEAD~2 | git patch-id --stable | awk "{print \$1}")" >>expected && + echo "prerequisite-patch-id: $(git show --patch HEAD~1 | git patch-id --stable | awk "{print \$1}")" >>expected && + signature >> expected && + test_cmp expected actual +' + +test_expect_success 'format-patch --base errors out when base commit is in revision list' ' + test_must_fail git format-patch --base=HEAD -2 && + test_must_fail git format-patch --base=HEAD~1 -2 && + git format-patch --stdout --base=HEAD~2 -2 >patch && + grep "^base-commit:" patch >actual && + echo "base-commit: $(git rev-parse HEAD~2)" >expected && + test_cmp expected actual +' + +test_expect_success 'format-patch --base errors out when base commit is not ancestor of revision list' ' + # For history as below: + # + # ---Q---P---Z---Y---*---X + # \ / + # ------------W + # + # If "format-patch Z..X" is given, P and Z can not be specified as the base commit + git checkout -b topic1 master && + git rev-parse HEAD >commit-id-base && + test_commit P && + git rev-parse HEAD >commit-id-P && + test_commit Z && + git rev-parse HEAD >commit-id-Z && + test_commit Y && + git checkout -b topic2 master && + test_commit W && + git merge topic1 && + test_commit X && + test_must_fail git format-patch --base=$(cat commit-id-P) -3 && + test_must_fail git format-patch --base=$(cat commit-id-Z) -3 && + git format-patch --stdout --base=$(cat commit-id-base) -3 >patch && + grep "^base-commit:" patch >actual && + echo "base-commit: $(cat commit-id-base)" >expected && + test_cmp expected actual +' + +test_expect_success 'format-patch --base=auto' ' + git checkout -b upstream master && + git checkout -b local upstream && + git branch --set-upstream-to=upstream && + test_commit N1 && + test_commit N2 && + git format-patch --stdout --base=auto -2 >patch && + grep "^base-commit:" patch >actual && + echo "base-commit: $(git rev-parse upstream)" >expected && + test_cmp expected actual +' + +test_expect_success 'format-patch errors out when history involves criss-cross' ' + # setup criss-cross history + # + # B---M1---D + # / \ / + # A X + # \ / \ + # C---M2---E + # + git checkout master && + test_commit A && + git checkout -b xb master && + test_commit B && + git checkout -b xc master && + test_commit C && + git checkout -b xbc xb -- && + git merge xc && + git checkout -b xcb xc -- && + git branch --set-upstream-to=xbc && + git merge xb && + git checkout xbc && + test_commit D && + git checkout xcb && + test_commit E && + test_must_fail git format-patch --base=auto -1 +' + +test_expect_success 'format-patch format.useAutoBaseoption' ' + test_when_finished "git config --unset format.useAutoBase" && + git checkout local && + git config format.useAutoBase true && + git format-patch --stdout -1 >patch && + grep "^base-commit:" patch >actual && + echo "base-commit: $(git rev-parse upstream)" >expected && + test_cmp expected actual +' + +test_expect_success 'format-patch --base overrides format.useAutoBase' ' + test_when_finished "git config --unset format.useAutoBase" && + git config format.useAutoBase true && + git format-patch --stdout --base=HEAD~1 -1 >patch && + grep "^base-commit:" patch >actual && + echo "base-commit: $(git rev-parse HEAD~1)" >expected && + test_cmp expected actual +' + +test_expect_success 'format-patch --base with --attach' ' + git format-patch --attach=mimemime --stdout --base=HEAD~ -1 >patch && + sed -n -e "/^base-commit:/s/.*/1/p" -e "/^---*mimemime--$/s/.*/2/p" \ + patch >actual && + test_write_lines 1 2 >expect && + test_cmp expect actual +' + +test_expect_success 'format-patch --pretty=mboxrd' ' + sp=" " && + cat >msg <<-INPUT_END && + mboxrd should escape the body + + From could trip up a loose mbox parser + >From extra escape for reversibility + >>From extra escape for reversibility 2 + from lower case not escaped + Fromm bad speling not escaped + From with leading space not escaped + + F + From + From$sp + From $sp + From $sp + INPUT_END + + cat >expect <<-INPUT_END && + >From could trip up a loose mbox parser + >>From extra escape for reversibility + >>>From extra escape for reversibility 2 + from lower case not escaped + Fromm bad speling not escaped + From with leading space not escaped + + F + From + From + From + From + INPUT_END + + C=$(git commit-tree HEAD^^{tree} -p HEAD <msg) && + git format-patch --pretty=mboxrd --stdout -1 $C~1..$C >patch && + git grep -h --no-index -A11 \ + "^>From could trip up a loose mbox parser" patch >actual && + test_cmp expect actual +' + test_done diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 2434157aa7..17df491a3a 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -106,6 +106,8 @@ test_expect_success 'another test, without options' ' git diff -w -b --ignore-space-at-eol >out && test_cmp expect out && + git diff -w --ignore-cr-at-eol >out && + test_cmp expect out && tr "Q_" "\015 " <<-\EOF >expect && diff --git a/x b/x @@ -128,6 +130,9 @@ test_expect_success 'another test, without options' ' git diff -b --ignore-space-at-eol >out && test_cmp expect out && + git diff -b --ignore-cr-at-eol >out && + test_cmp expect out && + tr "Q_" "\015 " <<-\EOF >expect && diff --git a/x b/x index d99af23..22d9f73 100644 @@ -145,6 +150,29 @@ test_expect_success 'another test, without options' ' CR at end EOF git diff --ignore-space-at-eol >out && + test_cmp expect out && + + git diff --ignore-space-at-eol --ignore-cr-at-eol >out && + test_cmp expect out && + + tr "Q_" "\015 " <<-\EOF >expect && + diff --git a/x b/x + index_d99af23..22d9f73 100644 + --- a/x + +++ b/x + @@ -1,6 +1,6 @@ + -whitespace at beginning + -whitespace change + -whitespace in the middle + -whitespace at end + +_ whitespace at beginning + +whitespace_ _change + +white space in the middle + +whitespace at end__ + unchanged line + CR at end + EOF + git diff --ignore-cr-at-eol >out && test_cmp expect out ' @@ -155,7 +183,7 @@ test_expect_success 'ignore-blank-lines: only new lines' ' " >x && git diff --ignore-blank-lines >out && >expect && - test_cmp out expect + test_cmp expect out ' test_expect_success 'ignore-blank-lines: only new lines with space' ' @@ -165,7 +193,7 @@ test_expect_success 'ignore-blank-lines: only new lines with space' ' " >x && git diff -w --ignore-blank-lines >out && >expect && - test_cmp out expect + test_cmp expect out ' test_expect_success 'ignore-blank-lines: after change' ' @@ -608,6 +636,23 @@ test_expect_success 'check with space before tab in indent (diff-tree)' ' test_must_fail git diff-tree --check HEAD^ HEAD ' +test_expect_success 'check with ignored trailing whitespace attr (diff-tree)' ' + test_when_finished "git reset --hard HEAD^" && + + # create a whitespace error that should be ignored + echo "* -whitespace" >.gitattributes && + git add .gitattributes && + echo "foo(); " >x && + git add x && + git commit -m "add trailing space" && + + # with a worktree diff-tree ignores the whitespace error + git diff-tree --root --check HEAD && + + # without a worktree diff-tree still ignores the whitespace error + git -C .git diff-tree --root --check HEAD +' + test_expect_success 'check trailing whitespace (trailing-space: off)' ' git config core.whitespace "-trailing-space" && echo "foo (); " >x && @@ -802,7 +847,6 @@ test_expect_success 'combined diff with autocrlf conversion' ' # Start testing the colored format for whitespace checks test_expect_success 'setup diff colors' ' - git config color.diff always && git config color.diff.plain normal && git config color.diff.meta bold && git config color.diff.frag cyan && @@ -821,7 +865,7 @@ test_expect_success 'diff that introduces a line with only tabs' ' echo "test" >x && git commit -m "initial" x && echo "{NTN}" | tr "NT" "\n\t" >>x && - git -c color.diff=always diff | test_decode_color >current && + git diff --color | test_decode_color >current && cat >expected <<-\EOF && <BOLD>diff --git a/x b/x<RESET> @@ -851,7 +895,7 @@ test_expect_success 'diff that introduces and removes ws breakages' ' echo "2. and a new line " } >x && - git -c color.diff=always diff | + git diff --color | test_decode_color >current && cat >expected <<-\EOF && @@ -869,7 +913,8 @@ test_expect_success 'diff that introduces and removes ws breakages' ' test_cmp expected current ' -test_expect_success 'the same with --ws-error-highlight' ' +test_expect_success 'ws-error-highlight test setup' ' + git reset --hard && { echo "0. blank-at-eol " && @@ -882,10 +927,7 @@ test_expect_success 'the same with --ws-error-highlight' ' echo "2. and a new line " } >x && - git -c color.diff=always diff --ws-error-highlight=default,old | - test_decode_color >current && - - cat >expected <<-\EOF && + cat >expect.default-old <<-\EOF && <BOLD>diff --git a/x b/x<RESET> <BOLD>index d0233a2..700886e 100644<RESET> <BOLD>--- a/x<RESET> @@ -897,12 +939,7 @@ test_expect_success 'the same with --ws-error-highlight' ' <GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET> EOF - test_cmp expected current && - - git -c color.diff=always diff --ws-error-highlight=all | - test_decode_color >current && - - cat >expected <<-\EOF && + cat >expect.all <<-\EOF && <BOLD>diff --git a/x b/x<RESET> <BOLD>index d0233a2..700886e 100644<RESET> <BOLD>--- a/x<RESET> @@ -914,12 +951,7 @@ test_expect_success 'the same with --ws-error-highlight' ' <GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET> EOF - test_cmp expected current && - - git -c color.diff=always diff --ws-error-highlight=none | - test_decode_color >current && - - cat >expected <<-\EOF && + cat >expect.none <<-\EOF <BOLD>diff --git a/x b/x<RESET> <BOLD>index d0233a2..700886e 100644<RESET> <BOLD>--- a/x<RESET> @@ -931,7 +963,766 @@ test_expect_success 'the same with --ws-error-highlight' ' <GREEN>+2. and a new line <RESET> EOF - test_cmp expected current +' + +test_expect_success 'test --ws-error-highlight option' ' + + git diff --color --ws-error-highlight=default,old | + test_decode_color >current && + test_cmp expect.default-old current && + + git diff --color --ws-error-highlight=all | + test_decode_color >current && + test_cmp expect.all current && + + git diff --color --ws-error-highlight=none | + test_decode_color >current && + test_cmp expect.none current + +' + +test_expect_success 'test diff.wsErrorHighlight config' ' + + git -c diff.wsErrorHighlight=default,old diff --color | + test_decode_color >current && + test_cmp expect.default-old current && + + git -c diff.wsErrorHighlight=all diff --color | + test_decode_color >current && + test_cmp expect.all current && + + git -c diff.wsErrorHighlight=none diff --color | + test_decode_color >current && + test_cmp expect.none current + +' + +test_expect_success 'option overrides diff.wsErrorHighlight' ' + + git -c diff.wsErrorHighlight=none \ + diff --color --ws-error-highlight=default,old | + test_decode_color >current && + test_cmp expect.default-old current && + + git -c diff.wsErrorHighlight=default \ + diff --color --ws-error-highlight=all | + test_decode_color >current && + test_cmp expect.all current && + + git -c diff.wsErrorHighlight=all \ + diff --color --ws-error-highlight=none | + test_decode_color >current && + test_cmp expect.none current + +' + +test_expect_success 'detect moved code, complete file' ' + git reset --hard && + cat <<-\EOF >test.c && + #include<stdio.h> + main() + { + printf("Hello World"); + } + EOF + git add test.c && + git commit -m "add main function" && + git mv test.c main.c && + test_config color.diff.oldMoved "normal red" && + test_config color.diff.newMoved "normal green" && + git diff HEAD --color-moved=zebra --color --no-renames | test_decode_color >actual && + cat >expected <<-\EOF && + <BOLD>diff --git a/main.c b/main.c<RESET> + <BOLD>new file mode 100644<RESET> + <BOLD>index 0000000..a986c57<RESET> + <BOLD>--- /dev/null<RESET> + <BOLD>+++ b/main.c<RESET> + <CYAN>@@ -0,0 +1,5 @@<RESET> + <BGREEN>+<RESET><BGREEN>#include<stdio.h><RESET> + <BGREEN>+<RESET><BGREEN>main()<RESET> + <BGREEN>+<RESET><BGREEN>{<RESET> + <BGREEN>+<RESET><BGREEN>printf("Hello World");<RESET> + <BGREEN>+<RESET><BGREEN>}<RESET> + <BOLD>diff --git a/test.c b/test.c<RESET> + <BOLD>deleted file mode 100644<RESET> + <BOLD>index a986c57..0000000<RESET> + <BOLD>--- a/test.c<RESET> + <BOLD>+++ /dev/null<RESET> + <CYAN>@@ -1,5 +0,0 @@<RESET> + <BRED>-#include<stdio.h><RESET> + <BRED>-main()<RESET> + <BRED>-{<RESET> + <BRED>-printf("Hello World");<RESET> + <BRED>-}<RESET> + EOF + + test_cmp expected actual +' + +test_expect_success 'detect malicious moved code, inside file' ' + test_config color.diff.oldMoved "normal red" && + test_config color.diff.newMoved "normal green" && + test_config color.diff.oldMovedAlternative "blue" && + test_config color.diff.newMovedAlternative "yellow" && + git reset --hard && + cat <<-\EOF >main.c && + #include<stdio.h> + int stuff() + { + printf("Hello "); + printf("World\n"); + } + + int secure_foo(struct user *u) + { + if (!u->is_allowed_foo) + return; + foo(u); + } + + int main() + { + foo(); + } + EOF + cat <<-\EOF >test.c && + #include<stdio.h> + int bar() + { + printf("Hello World, but different\n"); + } + + int another_function() + { + bar(); + } + EOF + git add main.c test.c && + git commit -m "add main and test file" && + cat <<-\EOF >main.c && + #include<stdio.h> + int stuff() + { + printf("Hello "); + printf("World\n"); + } + + int main() + { + foo(); + } + EOF + cat <<-\EOF >test.c && + #include<stdio.h> + int bar() + { + printf("Hello World, but different\n"); + } + + int secure_foo(struct user *u) + { + foo(u); + if (!u->is_allowed_foo) + return; + } + + int another_function() + { + bar(); + } + EOF + git diff HEAD --no-renames --color-moved=zebra --color | test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/main.c b/main.c<RESET> + <BOLD>index 27a619c..7cf9336 100644<RESET> + <BOLD>--- a/main.c<RESET> + <BOLD>+++ b/main.c<RESET> + <CYAN>@@ -5,13 +5,6 @@<RESET> <RESET>printf("Hello ");<RESET> + printf("World\n");<RESET> + }<RESET> + <RESET> + <BRED>-int secure_foo(struct user *u)<RESET> + <BRED>-{<RESET> + <BLUE>-if (!u->is_allowed_foo)<RESET> + <BLUE>-return;<RESET> + <RED>-foo(u);<RESET> + <RED>-}<RESET> + <RED>-<RESET> + int main()<RESET> + {<RESET> + foo();<RESET> + <BOLD>diff --git a/test.c b/test.c<RESET> + <BOLD>index 1dc1d85..2bedec9 100644<RESET> + <BOLD>--- a/test.c<RESET> + <BOLD>+++ b/test.c<RESET> + <CYAN>@@ -4,6 +4,13 @@<RESET> <RESET>int bar()<RESET> + printf("Hello World, but different\n");<RESET> + }<RESET> + <RESET> + <BGREEN>+<RESET><BGREEN>int secure_foo(struct user *u)<RESET> + <BGREEN>+<RESET><BGREEN>{<RESET> + <GREEN>+<RESET><GREEN>foo(u);<RESET> + <BGREEN>+<RESET><BGREEN>if (!u->is_allowed_foo)<RESET> + <BGREEN>+<RESET><BGREEN>return;<RESET> + <GREEN>+<RESET><GREEN>}<RESET> + <GREEN>+<RESET> + int another_function()<RESET> + {<RESET> + bar();<RESET> + EOF + + test_cmp expected actual +' + +test_expect_success 'plain moved code, inside file' ' + test_config color.diff.oldMoved "normal red" && + test_config color.diff.newMoved "normal green" && + test_config color.diff.oldMovedAlternative "blue" && + test_config color.diff.newMovedAlternative "yellow" && + # needs previous test as setup + git diff HEAD --no-renames --color-moved=plain --color | test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/main.c b/main.c<RESET> + <BOLD>index 27a619c..7cf9336 100644<RESET> + <BOLD>--- a/main.c<RESET> + <BOLD>+++ b/main.c<RESET> + <CYAN>@@ -5,13 +5,6 @@<RESET> <RESET>printf("Hello ");<RESET> + printf("World\n");<RESET> + }<RESET> + <RESET> + <BRED>-int secure_foo(struct user *u)<RESET> + <BRED>-{<RESET> + <BRED>-if (!u->is_allowed_foo)<RESET> + <BRED>-return;<RESET> + <BRED>-foo(u);<RESET> + <BRED>-}<RESET> + <BRED>-<RESET> + int main()<RESET> + {<RESET> + foo();<RESET> + <BOLD>diff --git a/test.c b/test.c<RESET> + <BOLD>index 1dc1d85..2bedec9 100644<RESET> + <BOLD>--- a/test.c<RESET> + <BOLD>+++ b/test.c<RESET> + <CYAN>@@ -4,6 +4,13 @@<RESET> <RESET>int bar()<RESET> + printf("Hello World, but different\n");<RESET> + }<RESET> + <RESET> + <BGREEN>+<RESET><BGREEN>int secure_foo(struct user *u)<RESET> + <BGREEN>+<RESET><BGREEN>{<RESET> + <BGREEN>+<RESET><BGREEN>foo(u);<RESET> + <BGREEN>+<RESET><BGREEN>if (!u->is_allowed_foo)<RESET> + <BGREEN>+<RESET><BGREEN>return;<RESET> + <BGREEN>+<RESET><BGREEN>}<RESET> + <BGREEN>+<RESET> + int another_function()<RESET> + {<RESET> + bar();<RESET> + EOF + + test_cmp expected actual +' + +test_expect_success 'detect permutations inside moved code -- dimmed_zebra' ' + git reset --hard && + cat <<-\EOF >lines.txt && + long line 1 + long line 2 + long line 3 + line 4 + line 5 + line 6 + line 7 + line 8 + line 9 + line 10 + line 11 + line 12 + line 13 + long line 14 + long line 15 + long line 16 + EOF + git add lines.txt && + git commit -m "add poetry" && + cat <<-\EOF >lines.txt && + line 4 + line 5 + line 6 + line 7 + line 8 + line 9 + long line 1 + long line 2 + long line 3 + long line 14 + long line 15 + long line 16 + line 10 + line 11 + line 12 + line 13 + EOF + test_config color.diff.oldMoved "magenta" && + test_config color.diff.newMoved "cyan" && + test_config color.diff.oldMovedAlternative "blue" && + test_config color.diff.newMovedAlternative "yellow" && + test_config color.diff.oldMovedDimmed "normal magenta" && + test_config color.diff.newMovedDimmed "normal cyan" && + test_config color.diff.oldMovedAlternativeDimmed "normal blue" && + test_config color.diff.newMovedAlternativeDimmed "normal yellow" && + git diff HEAD --no-renames --color-moved=dimmed_zebra --color | + grep -v "index" | + test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/lines.txt b/lines.txt<RESET> + <BOLD>--- a/lines.txt<RESET> + <BOLD>+++ b/lines.txt<RESET> + <CYAN>@@ -1,16 +1,16 @@<RESET> + <BMAGENTA>-long line 1<RESET> + <BMAGENTA>-long line 2<RESET> + <BMAGENTA>-long line 3<RESET> + line 4<RESET> + line 5<RESET> + line 6<RESET> + line 7<RESET> + line 8<RESET> + line 9<RESET> + <BCYAN>+<RESET><BCYAN>long line 1<RESET> + <BCYAN>+<RESET><BCYAN>long line 2<RESET> + <CYAN>+<RESET><CYAN>long line 3<RESET> + <YELLOW>+<RESET><YELLOW>long line 14<RESET> + <BYELLOW>+<RESET><BYELLOW>long line 15<RESET> + <BYELLOW>+<RESET><BYELLOW>long line 16<RESET> + line 10<RESET> + line 11<RESET> + line 12<RESET> + line 13<RESET> + <BMAGENTA>-long line 14<RESET> + <BMAGENTA>-long line 15<RESET> + <BMAGENTA>-long line 16<RESET> + EOF + test_cmp expected actual +' + +test_expect_success 'cmd option assumes configured colored-moved' ' + test_config color.diff.oldMoved "magenta" && + test_config color.diff.newMoved "cyan" && + test_config color.diff.oldMovedAlternative "blue" && + test_config color.diff.newMovedAlternative "yellow" && + test_config color.diff.oldMovedDimmed "normal magenta" && + test_config color.diff.newMovedDimmed "normal cyan" && + test_config color.diff.oldMovedAlternativeDimmed "normal blue" && + test_config color.diff.newMovedAlternativeDimmed "normal yellow" && + test_config diff.colorMoved zebra && + git diff HEAD --no-renames --color-moved --color | + grep -v "index" | + test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/lines.txt b/lines.txt<RESET> + <BOLD>--- a/lines.txt<RESET> + <BOLD>+++ b/lines.txt<RESET> + <CYAN>@@ -1,16 +1,16 @@<RESET> + <MAGENTA>-long line 1<RESET> + <MAGENTA>-long line 2<RESET> + <MAGENTA>-long line 3<RESET> + line 4<RESET> + line 5<RESET> + line 6<RESET> + line 7<RESET> + line 8<RESET> + line 9<RESET> + <CYAN>+<RESET><CYAN>long line 1<RESET> + <CYAN>+<RESET><CYAN>long line 2<RESET> + <CYAN>+<RESET><CYAN>long line 3<RESET> + <YELLOW>+<RESET><YELLOW>long line 14<RESET> + <YELLOW>+<RESET><YELLOW>long line 15<RESET> + <YELLOW>+<RESET><YELLOW>long line 16<RESET> + line 10<RESET> + line 11<RESET> + line 12<RESET> + line 13<RESET> + <MAGENTA>-long line 14<RESET> + <MAGENTA>-long line 15<RESET> + <MAGENTA>-long line 16<RESET> + EOF + test_cmp expected actual +' + +test_expect_success 'no effect from --color-moved with --word-diff' ' + cat <<-\EOF >text.txt && + Lorem Ipsum is simply dummy text of the printing and typesetting industry. + EOF + git add text.txt && + git commit -a -m "clean state" && + cat <<-\EOF >text.txt && + simply Lorem Ipsum dummy is text of the typesetting and printing industry. + EOF + git diff --color-moved --word-diff >actual && + git diff --word-diff >expect && + test_cmp expect actual +' + +test_expect_success 'set up whitespace tests' ' + git reset --hard && + # Note that these lines have no leading or trailing whitespace. + cat <<-\EOF >lines.txt && + line 1 + line 2 + line 3 + line 4 + line 5 + long line 6 + long line 7 + long line 8 + long line 9 + EOF + git add lines.txt && + git commit -m "add poetry" && + git config color.diff.oldMoved "magenta" && + git config color.diff.newMoved "cyan" +' + +test_expect_success 'move detection ignoring whitespace ' ' + q_to_tab <<-\EOF >lines.txt && + Qlong line 6 + Qlong line 7 + Qlong line 8 + Qchanged long line 9 + line 1 + line 2 + line 3 + line 4 + line 5 + EOF + git diff HEAD --no-renames --color-moved --color | + grep -v "index" | + test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/lines.txt b/lines.txt<RESET> + <BOLD>--- a/lines.txt<RESET> + <BOLD>+++ b/lines.txt<RESET> + <CYAN>@@ -1,9 +1,9 @@<RESET> + <GREEN>+<RESET> <GREEN>long line 6<RESET> + <GREEN>+<RESET> <GREEN>long line 7<RESET> + <GREEN>+<RESET> <GREEN>long line 8<RESET> + <GREEN>+<RESET> <GREEN>changed long line 9<RESET> + line 1<RESET> + line 2<RESET> + line 3<RESET> + line 4<RESET> + line 5<RESET> + <RED>-long line 6<RESET> + <RED>-long line 7<RESET> + <RED>-long line 8<RESET> + <RED>-long line 9<RESET> + EOF + test_cmp expected actual && + + git diff HEAD --no-renames -w --color-moved --color | + grep -v "index" | + test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/lines.txt b/lines.txt<RESET> + <BOLD>--- a/lines.txt<RESET> + <BOLD>+++ b/lines.txt<RESET> + <CYAN>@@ -1,9 +1,9 @@<RESET> + <CYAN>+<RESET> <CYAN>long line 6<RESET> + <CYAN>+<RESET> <CYAN>long line 7<RESET> + <CYAN>+<RESET> <CYAN>long line 8<RESET> + <GREEN>+<RESET> <GREEN>changed long line 9<RESET> + line 1<RESET> + line 2<RESET> + line 3<RESET> + line 4<RESET> + line 5<RESET> + <MAGENTA>-long line 6<RESET> + <MAGENTA>-long line 7<RESET> + <MAGENTA>-long line 8<RESET> + <RED>-long line 9<RESET> + EOF + test_cmp expected actual +' + +test_expect_success 'move detection ignoring whitespace changes' ' + git reset --hard && + # Lines 6-8 have a space change, but 9 is new whitespace + q_to_tab <<-\EOF >lines.txt && + longQline 6 + longQline 7 + longQline 8 + long liQne 9 + line 1 + line 2 + line 3 + line 4 + line 5 + EOF + + git diff HEAD --no-renames --color-moved --color | + grep -v "index" | + test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/lines.txt b/lines.txt<RESET> + <BOLD>--- a/lines.txt<RESET> + <BOLD>+++ b/lines.txt<RESET> + <CYAN>@@ -1,9 +1,9 @@<RESET> + <GREEN>+<RESET><GREEN>long line 6<RESET> + <GREEN>+<RESET><GREEN>long line 7<RESET> + <GREEN>+<RESET><GREEN>long line 8<RESET> + <GREEN>+<RESET><GREEN>long li ne 9<RESET> + line 1<RESET> + line 2<RESET> + line 3<RESET> + line 4<RESET> + line 5<RESET> + <RED>-long line 6<RESET> + <RED>-long line 7<RESET> + <RED>-long line 8<RESET> + <RED>-long line 9<RESET> + EOF + test_cmp expected actual && + + git diff HEAD --no-renames -b --color-moved --color | + grep -v "index" | + test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/lines.txt b/lines.txt<RESET> + <BOLD>--- a/lines.txt<RESET> + <BOLD>+++ b/lines.txt<RESET> + <CYAN>@@ -1,9 +1,9 @@<RESET> + <CYAN>+<RESET><CYAN>long line 6<RESET> + <CYAN>+<RESET><CYAN>long line 7<RESET> + <CYAN>+<RESET><CYAN>long line 8<RESET> + <GREEN>+<RESET><GREEN>long li ne 9<RESET> + line 1<RESET> + line 2<RESET> + line 3<RESET> + line 4<RESET> + line 5<RESET> + <MAGENTA>-long line 6<RESET> + <MAGENTA>-long line 7<RESET> + <MAGENTA>-long line 8<RESET> + <RED>-long line 9<RESET> + EOF + test_cmp expected actual +' + +test_expect_success 'move detection ignoring whitespace at eol' ' + git reset --hard && + # Lines 6-9 have new eol whitespace, but 9 also has it in the middle + q_to_tab <<-\EOF >lines.txt && + long line 6Q + long line 7Q + long line 8Q + longQline 9Q + line 1 + line 2 + line 3 + line 4 + line 5 + EOF + + # avoid cluttering the output with complaints about our eol whitespace + test_config core.whitespace -blank-at-eol && + + git diff HEAD --no-renames --color-moved --color | + grep -v "index" | + test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/lines.txt b/lines.txt<RESET> + <BOLD>--- a/lines.txt<RESET> + <BOLD>+++ b/lines.txt<RESET> + <CYAN>@@ -1,9 +1,9 @@<RESET> + <GREEN>+<RESET><GREEN>long line 6 <RESET> + <GREEN>+<RESET><GREEN>long line 7 <RESET> + <GREEN>+<RESET><GREEN>long line 8 <RESET> + <GREEN>+<RESET><GREEN>long line 9 <RESET> + line 1<RESET> + line 2<RESET> + line 3<RESET> + line 4<RESET> + line 5<RESET> + <RED>-long line 6<RESET> + <RED>-long line 7<RESET> + <RED>-long line 8<RESET> + <RED>-long line 9<RESET> + EOF + test_cmp expected actual && + + git diff HEAD --no-renames --ignore-space-at-eol --color-moved --color | + grep -v "index" | + test_decode_color >actual && + cat <<-\EOF >expected && + <BOLD>diff --git a/lines.txt b/lines.txt<RESET> + <BOLD>--- a/lines.txt<RESET> + <BOLD>+++ b/lines.txt<RESET> + <CYAN>@@ -1,9 +1,9 @@<RESET> + <CYAN>+<RESET><CYAN>long line 6 <RESET> + <CYAN>+<RESET><CYAN>long line 7 <RESET> + <CYAN>+<RESET><CYAN>long line 8 <RESET> + <GREEN>+<RESET><GREEN>long line 9 <RESET> + line 1<RESET> + line 2<RESET> + line 3<RESET> + line 4<RESET> + line 5<RESET> + <MAGENTA>-long line 6<RESET> + <MAGENTA>-long line 7<RESET> + <MAGENTA>-long line 8<RESET> + <RED>-long line 9<RESET> + EOF + test_cmp expected actual +' + +test_expect_success 'clean up whitespace-test colors' ' + git config --unset color.diff.oldMoved && + git config --unset color.diff.newMoved +' + +test_expect_success '--color-moved block at end of diff output respects MIN_ALNUM_COUNT' ' + git reset --hard && + >bar && + cat <<-\EOF >foo && + irrelevant_line + line1 + EOF + git add foo bar && + git commit -m x && + + cat <<-\EOF >bar && + line1 + EOF + cat <<-\EOF >foo && + irrelevant_line + EOF + + git diff HEAD --color-moved=zebra --color --no-renames | + grep -v "index" | + test_decode_color >actual && + cat >expected <<-\EOF && + <BOLD>diff --git a/bar b/bar<RESET> + <BOLD>--- a/bar<RESET> + <BOLD>+++ b/bar<RESET> + <CYAN>@@ -0,0 +1 @@<RESET> + <GREEN>+<RESET><GREEN>line1<RESET> + <BOLD>diff --git a/foo b/foo<RESET> + <BOLD>--- a/foo<RESET> + <BOLD>+++ b/foo<RESET> + <CYAN>@@ -1,2 +1 @@<RESET> + irrelevant_line<RESET> + <RED>-line1<RESET> + EOF + + test_cmp expected actual +' + +test_expect_success '--color-moved respects MIN_ALNUM_COUNT' ' + git reset --hard && + cat <<-\EOF >foo && + nineteen chars 456789 + irrelevant_line + twenty chars 234567890 + EOF + >bar && + git add foo bar && + git commit -m x && + + cat <<-\EOF >foo && + irrelevant_line + EOF + cat <<-\EOF >bar && + twenty chars 234567890 + nineteen chars 456789 + EOF + + git diff HEAD --color-moved=zebra --color --no-renames | + grep -v "index" | + test_decode_color >actual && + cat >expected <<-\EOF && + <BOLD>diff --git a/bar b/bar<RESET> + <BOLD>--- a/bar<RESET> + <BOLD>+++ b/bar<RESET> + <CYAN>@@ -0,0 +1,2 @@<RESET> + <BOLD;CYAN>+<RESET><BOLD;CYAN>twenty chars 234567890<RESET> + <GREEN>+<RESET><GREEN>nineteen chars 456789<RESET> + <BOLD>diff --git a/foo b/foo<RESET> + <BOLD>--- a/foo<RESET> + <BOLD>+++ b/foo<RESET> + <CYAN>@@ -1,3 +1 @@<RESET> + <RED>-nineteen chars 456789<RESET> + irrelevant_line<RESET> + <BOLD;MAGENTA>-twenty chars 234567890<RESET> + EOF + + test_cmp expected actual +' + +test_expect_success '--color-moved treats adjacent blocks as separate for MIN_ALNUM_COUNT' ' + git reset --hard && + cat <<-\EOF >foo && + 7charsA + irrelevant_line + 7charsB + 7charsC + EOF + >bar && + git add foo bar && + git commit -m x && + + cat <<-\EOF >foo && + irrelevant_line + EOF + cat <<-\EOF >bar && + 7charsB + 7charsC + 7charsA + EOF + + git diff HEAD --color-moved=zebra --color --no-renames | grep -v "index" | test_decode_color >actual && + cat >expected <<-\EOF && + <BOLD>diff --git a/bar b/bar<RESET> + <BOLD>--- a/bar<RESET> + <BOLD>+++ b/bar<RESET> + <CYAN>@@ -0,0 +1,3 @@<RESET> + <GREEN>+<RESET><GREEN>7charsB<RESET> + <GREEN>+<RESET><GREEN>7charsC<RESET> + <GREEN>+<RESET><GREEN>7charsA<RESET> + <BOLD>diff --git a/foo b/foo<RESET> + <BOLD>--- a/foo<RESET> + <BOLD>+++ b/foo<RESET> + <CYAN>@@ -1,4 +1 @@<RESET> + <RED>-7charsA<RESET> + irrelevant_line<RESET> + <RED>-7charsB<RESET> + <RED>-7charsC<RESET> + EOF + + test_cmp expected actual +' + +test_expect_success 'move detection with submodules' ' + test_create_repo bananas && + echo ripe >bananas/recipe && + git -C bananas add recipe && + test_commit fruit && + test_commit -C bananas recipe && + git submodule add ./bananas && + git add bananas && + git commit -a -m "bananas are like a heavy library?" && + echo foul >bananas/recipe && + echo ripe >fruit.t && + + git diff --submodule=diff --color-moved --color >actual && + + # no move detection as the moved line is across repository boundaries. + test_decode_color <actual >decoded_actual && + ! grep BGREEN decoded_actual && + ! grep BRED decoded_actual && + + # nor did we mess with it another way + git diff --submodule=diff --color | test_decode_color >expect && + test_cmp expect decoded_actual ' test_done diff --git a/t/t4016-diff-quote.sh b/t/t4016-diff-quote.sh index cd543ecc54..9c48e5c2c9 100755 --- a/t/t4016-diff-quote.sh +++ b/t/t4016-diff-quote.sh @@ -13,6 +13,7 @@ P1='pathname with HT' P2='pathname with SP' P3='pathname with LF' +test_have_prereq !MINGW && echo 2>/dev/null >"$P1" && test -f "$P1" && rm -f "$P1" || { skip_all='Your filesystem does not allow tabs in filenames' test_done diff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh index 1dbaa3864a..22f9f88f0a 100755 --- a/t/t4018-diff-funcname.sh +++ b/t/t4018-diff-funcname.sh @@ -30,7 +30,10 @@ diffpatterns=" bibtex cpp csharp + css fortran + fountain + golang html java matlab diff --git a/t/t4018/css-brace-in-col-1 b/t/t4018/css-brace-in-col-1 new file mode 100644 index 0000000000..7831577506 --- /dev/null +++ b/t/t4018/css-brace-in-col-1 @@ -0,0 +1,5 @@ +RIGHT label.control-label +{ + margin-top: 10px!important; + border : 10px ChangeMe #C6C6C6; +} diff --git a/t/t4018/css-colon-eol b/t/t4018/css-colon-eol new file mode 100644 index 0000000000..5a30553d29 --- /dev/null +++ b/t/t4018/css-colon-eol @@ -0,0 +1,4 @@ +RIGHT h1 { +color: +ChangeMe; +} diff --git a/t/t4018/css-colon-selector b/t/t4018/css-colon-selector new file mode 100644 index 0000000000..c6d71fb42d --- /dev/null +++ b/t/t4018/css-colon-selector @@ -0,0 +1,5 @@ +RIGHT a:hover { + margin-top: + 10px!important; + border : 10px ChangeMe #C6C6C6; +} diff --git a/t/t4018/css-common b/t/t4018/css-common new file mode 100644 index 0000000000..84ed754b33 --- /dev/null +++ b/t/t4018/css-common @@ -0,0 +1,4 @@ +RIGHT label.control-label { + margin-top: 10px!important; + border : 10px ChangeMe #C6C6C6; +} diff --git a/t/t4018/css-long-selector-list b/t/t4018/css-long-selector-list new file mode 100644 index 0000000000..7ccd25d9ed --- /dev/null +++ b/t/t4018/css-long-selector-list @@ -0,0 +1,6 @@ +p.header, +label.control-label, +div ul#RIGHT { + margin-top: 10px!important; + border : 10px ChangeMe #C6C6C6; +} diff --git a/t/t4018/css-prop-sans-indent b/t/t4018/css-prop-sans-indent new file mode 100644 index 0000000000..a9e3c86b3c --- /dev/null +++ b/t/t4018/css-prop-sans-indent @@ -0,0 +1,5 @@ +RIGHT, label.control-label { +margin-top: 10px!important; +padding: 0; +border : 10px ChangeMe #C6C6C6; +} diff --git a/t/t4018/css-short-selector-list b/t/t4018/css-short-selector-list new file mode 100644 index 0000000000..6a0bdee336 --- /dev/null +++ b/t/t4018/css-short-selector-list @@ -0,0 +1,4 @@ +label.control, div ul#RIGHT { + margin-top: 10px!important; + border : 10px ChangeMe #C6C6C6; +} diff --git a/t/t4018/css-trailing-space b/t/t4018/css-trailing-space new file mode 100644 index 0000000000..32b5606c70 --- /dev/null +++ b/t/t4018/css-trailing-space @@ -0,0 +1,5 @@ +RIGHT label.control-label { + margin:10px; + padding:10px; + border : 10px ChangeMe #C6C6C6; +} diff --git a/t/t4018/fountain-scene b/t/t4018/fountain-scene new file mode 100644 index 0000000000..6b3257d680 --- /dev/null +++ b/t/t4018/fountain-scene @@ -0,0 +1,4 @@ +EXT. STREET RIGHT OUTSIDE - DAY + +CHARACTER +You didn't say the magic phrase, "ChangeMe". diff --git a/t/t4018/golang-complex-function b/t/t4018/golang-complex-function new file mode 100644 index 0000000000..e057dcefed --- /dev/null +++ b/t/t4018/golang-complex-function @@ -0,0 +1,8 @@ +type Test struct { + a Type +} + +func (t *Test) RIGHT(a Type) (Type, error) { + t.a = a + return ChangeMe, nil +} diff --git a/t/t4018/golang-func b/t/t4018/golang-func new file mode 100644 index 0000000000..8e9c9ac7c3 --- /dev/null +++ b/t/t4018/golang-func @@ -0,0 +1,4 @@ +func RIGHT() { + a := 5 + b := ChangeMe +} diff --git a/t/t4018/golang-interface b/t/t4018/golang-interface new file mode 100644 index 0000000000..553bedec96 --- /dev/null +++ b/t/t4018/golang-interface @@ -0,0 +1,4 @@ +type RIGHT interface { + a() Type + b() ChangeMe +} diff --git a/t/t4018/golang-long-func b/t/t4018/golang-long-func new file mode 100644 index 0000000000..ac3a77b5c4 --- /dev/null +++ b/t/t4018/golang-long-func @@ -0,0 +1,5 @@ +func RIGHT(aVeryVeryVeryLongVariableName AVeryVeryVeryLongType, + anotherLongVariableName AnotherLongType) { + a := 5 + b := ChangeMe +} diff --git a/t/t4018/golang-struct b/t/t4018/golang-struct new file mode 100644 index 0000000000..5deda77fee --- /dev/null +++ b/t/t4018/golang-struct @@ -0,0 +1,4 @@ +type RIGHT struct { + a Type + b ChangeMe +} diff --git a/t/t4021-format-patch-numbered.sh b/t/t4021-format-patch-numbered.sh index 886494b58f..9be65fd444 100755 --- a/t/t4021-format-patch-numbered.sh +++ b/t/t4021-format-patch-numbered.sh @@ -36,6 +36,11 @@ test_no_numbered() { test_num_no_numbered $1 2 } +test_single_cover_letter_numbered() { + grep "^Subject: \[PATCH 0/1\]" $1 && + grep "^Subject: \[PATCH 1/1\]" $1 +} + test_single_numbered() { grep "^Subject: \[PATCH 1/1\]" $1 } @@ -121,4 +126,16 @@ test_expect_success '--start-number && --numbered' ' grep "^Subject: \[PATCH 3/3\]" patch8 ' +test_expect_success 'single patch with cover-letter defaults to numbers' ' + git format-patch --cover-letter --stdout HEAD~1 >patch9.single && + test_single_cover_letter_numbered patch9.single +' + +test_expect_success 'Use --no-numbered and --cover-letter single patch' ' + git format-patch --no-numbered --stdout --cover-letter HEAD~1 >patch10 && + test_no_numbered patch10 +' + + + test_done diff --git a/t/t4026-color.sh b/t/t4026-color.sh index 2b32c4fbe6..671e951ee5 100755 --- a/t/t4026-color.sh +++ b/t/t4026-color.sh @@ -6,10 +6,11 @@ test_description='Test diff/status color escape codes' . ./test-lib.sh +ESC=$(printf '\033') color() { actual=$(git config --get-color no.such.slot "$1") && - test "$actual" = "$2" + test "$actual" = "${2:+$ESC}$2" } invalid_color() @@ -21,6 +22,10 @@ test_expect_success 'reset' ' color "reset" "[m" ' +test_expect_success 'empty color is empty' ' + color "" "" +' + test_expect_success 'attribute before color name' ' color "bold red" "[1;31m" ' @@ -50,14 +55,19 @@ test_expect_success 'attr negation' ' color "nobold nodim noul noblink noreverse" "[22;24;25;27m" ' +test_expect_success '"no-" variant of negation' ' + color "no-bold no-blink" "[22;25m" +' + test_expect_success 'long color specification' ' color "254 255 bold dim ul blink reverse" "[1;2;4;5;7;38;5;254;48;5;255m" ' test_expect_success 'absurdly long color specification' ' color \ - "#ffffff #ffffff bold nobold dim nodim ul noul blink noblink reverse noreverse" \ - "[1;2;4;5;7;22;24;25;27;38;2;255;255;255;48;2;255;255;255m" + "#ffffff #ffffff bold nobold dim nodim italic noitalic + ul noul blink noblink reverse noreverse strike nostrike" \ + "[1;2;3;4;5;7;9;22;23;24;25;27;29;38;2;255;255;255;48;2;255;255;255m" ' test_expect_success '0-7 are aliases for basic ANSI color names' ' diff --git a/t/t4027-diff-submodule.sh b/t/t4027-diff-submodule.sh index 518bf9524e..2ffd11a142 100755 --- a/t/t4027-diff-submodule.sh +++ b/t/t4027-diff-submodule.sh @@ -113,35 +113,6 @@ test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match)' ! test -s actual4 ' -test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match) [.git/config]' ' - git config diff.ignoreSubmodules all && - git diff HEAD >actual && - ! test -s actual && - git config submodule.subname.ignore none && - git config submodule.subname.path sub && - git diff HEAD >actual && - sed -e "1,/^@@/d" actual >actual.body && - expect_from_to >expect.body $subprev $subprev-dirty && - test_cmp expect.body actual.body && - git config submodule.subname.ignore all && - git diff HEAD >actual2 && - ! test -s actual2 && - git config submodule.subname.ignore untracked && - git diff HEAD >actual3 && - sed -e "1,/^@@/d" actual3 >actual3.body && - expect_from_to >expect.body $subprev $subprev-dirty && - test_cmp expect.body actual3.body && - git config submodule.subname.ignore dirty && - git diff HEAD >actual4 && - ! test -s actual4 && - git diff HEAD --ignore-submodules=none >actual && - sed -e "1,/^@@/d" actual >actual.body && - expect_from_to >expect.body $subprev $subprev-dirty && - test_cmp expect.body actual.body && - git config --remove-section submodule.subname && - git config --unset diff.ignoreSubmodules -' - test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match) [.gitmodules]' ' git config diff.ignoreSubmodules dirty && git diff HEAD >actual && @@ -208,24 +179,6 @@ test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match)' ! test -s actual4 ' -test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match) [.git/config]' ' - git config submodule.subname.ignore all && - git config submodule.subname.path sub && - git diff HEAD >actual2 && - ! test -s actual2 && - git config submodule.subname.ignore untracked && - git diff HEAD >actual3 && - ! test -s actual3 && - git config submodule.subname.ignore dirty && - git diff HEAD >actual4 && - ! test -s actual4 && - git diff --ignore-submodules=none HEAD >actual && - sed -e "1,/^@@/d" actual >actual.body && - expect_from_to >expect.body $subprev $subprev-dirty && - test_cmp expect.body actual.body && - git config --remove-section submodule.subname -' - test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match) [.gitmodules]' ' git config --add -f .gitmodules submodule.subname.ignore all && git config --add -f .gitmodules submodule.subname.path sub && @@ -261,26 +214,6 @@ test_expect_success 'git diff between submodule commits' ' ! test -s actual ' -test_expect_success 'git diff between submodule commits [.git/config]' ' - git diff HEAD^..HEAD >actual && - sed -e "1,/^@@/d" actual >actual.body && - expect_from_to >expect.body $subtip $subprev && - test_cmp expect.body actual.body && - git config submodule.subname.ignore dirty && - git config submodule.subname.path sub && - git diff HEAD^..HEAD >actual && - sed -e "1,/^@@/d" actual >actual.body && - expect_from_to >expect.body $subtip $subprev && - test_cmp expect.body actual.body && - git config submodule.subname.ignore all && - git diff HEAD^..HEAD >actual && - ! test -s actual && - git diff --ignore-submodules=dirty HEAD^..HEAD >actual && - sed -e "1,/^@@/d" actual >actual.body && - expect_from_to >expect.body $subtip $subprev && - git config --remove-section submodule.subname -' - test_expect_success 'git diff between submodule commits [.gitmodules]' ' git diff HEAD^..HEAD >actual && sed -e "1,/^@@/d" actual >actual.body && diff --git a/t/t4032-diff-inter-hunk-context.sh b/t/t4032-diff-inter-hunk-context.sh index e4e3e28fc7..bada0cbd32 100755 --- a/t/t4032-diff-inter-hunk-context.sh +++ b/t/t4032-diff-inter-hunk-context.sh @@ -16,11 +16,15 @@ f() { } t() { + use_config= + git config --unset diff.interHunkContext + case $# in 4) hunks=$4; cmd="diff -U$3";; 5) hunks=$5; cmd="diff -U$3 --inter-hunk-context=$4";; + 6) hunks=$5; cmd="diff -U$3"; git config diff.interHunkContext $4; use_config="(diff.interHunkContext=$4) ";; esac - label="$cmd, $1 common $2" + label="$use_config$cmd, $1 common $2" file=f$1 expected=expected.$file.$3.$hunks @@ -89,4 +93,25 @@ t 9 lines 3 2 t 9 lines 3 2 2 t 9 lines 3 3 1 +# use diff.interHunkContext? +t 1 line 0 0 2 config +t 1 line 0 1 1 config +t 1 line 0 2 1 config +t 9 lines 3 3 1 config +t 2 lines 0 0 2 config +t 2 lines 0 1 2 config +t 2 lines 0 2 1 config +t 3 lines 1 0 2 config +t 3 lines 1 1 1 config +t 3 lines 1 2 1 config +t 9 lines 3 2 2 config +t 9 lines 3 3 1 config + +test_expect_success 'diff.interHunkContext invalid' ' + git config diff.interHunkContext asdf && + test_must_fail git diff && + git config diff.interHunkContext -1 && + test_must_fail git diff +' + test_done diff --git a/t/t4033-diff-patience.sh b/t/t4033-diff-patience.sh index 3c9932edf3..113304dc59 100755 --- a/t/t4033-diff-patience.sh +++ b/t/t4033-diff-patience.sh @@ -5,6 +5,14 @@ test_description='patience diff algorithm' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-diff-alternative.sh +test_expect_success '--ignore-space-at-eol with a single appended character' ' + printf "a\nb\nc\n" >pre && + printf "a\nbX\nc\n" >post && + test_must_fail git diff --no-index \ + --patience --ignore-space-at-eol pre post >diff && + grep "^+.*X" diff +' + test_diff_frobnitz "patience" test_diff_unique "patience" diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh index f2f55fc51c..912df91226 100755 --- a/t/t4034-diff-words.sh +++ b/t/t4034-diff-words.sh @@ -302,6 +302,7 @@ test_language_driver ada test_language_driver bibtex test_language_driver cpp test_language_driver csharp +test_language_driver css test_language_driver fortran test_language_driver html test_language_driver java diff --git a/t/t4034/css/expect b/t/t4034/css/expect new file mode 100644 index 0000000000..ed10393bda --- /dev/null +++ b/t/t4034/css/expect @@ -0,0 +1,16 @@ +<BOLD>diff --git a/pre b/post<RESET> +<BOLD>index b8ae0bb..fe500b7 100644<RESET> +<BOLD>--- a/pre<RESET> +<BOLD>+++ b/post<RESET> +<CYAN>@@ -1,10 +1,10 @@<RESET> +.<RED>class-form<RESET><GREEN>other-form<RESET> label.control-label { + margin-top: <RED>10<RESET><GREEN>15<RESET>px!important; + border : 10px <RED>dashed<RESET><GREEN>dotted<RESET> #C6C6C6; +}<RESET> +<RED>#CCCCCC<RESET><GREEN>#CCCCCB<RESET> +10em<RESET> +<RED>padding-bottom<RESET><GREEN>margin-left<RESET> +150<RED>px<RESET><GREEN>em<RESET> +10px +<RED>!important<RESET> +<RED>div<RESET><GREEN>li<RESET>.class#id diff --git a/t/t4034/css/post b/t/t4034/css/post new file mode 100644 index 0000000000..fe500b7a4f --- /dev/null +++ b/t/t4034/css/post @@ -0,0 +1,10 @@ +.other-form label.control-label { + margin-top: 15px!important; + border : 10px dotted #C6C6C6; +} +#CCCCCB +10em +margin-left +150em +10px +li.class#id diff --git a/t/t4034/css/pre b/t/t4034/css/pre new file mode 100644 index 0000000000..b8ae0bb48f --- /dev/null +++ b/t/t4034/css/pre @@ -0,0 +1,10 @@ +.class-form label.control-label { + margin-top: 10px!important; + border : 10px dashed #C6C6C6; +} +#CCCCCC +10em +padding-bottom +150px +10px!important +div.class#id diff --git a/t/t4035-diff-quiet.sh b/t/t4035-diff-quiet.sh index 461f4bb583..0352bf81a9 100755 --- a/t/t4035-diff-quiet.sh +++ b/t/t4035-diff-quiet.sh @@ -147,9 +147,18 @@ test_expect_success 'git diff --ignore-all-space, both files outside repo' ' ' test_expect_success 'git diff --quiet ignores stat-change only entries' ' - test-chmtime +10 a && + test-tool chmtime +10 a && echo modified >>b && test_expect_code 1 git diff --quiet ' +test_expect_success 'git diff --quiet on a path that need conversion' ' + echo "crlf.txt text=auto" >.gitattributes && + printf "Hello\r\nWorld\r\n" >crlf.txt && + git add .gitattributes crlf.txt && + + printf "Hello\r\nWorld\n" >crlf.txt && + git diff --quiet crlf.txt +' + test_done diff --git a/t/t4038-diff-combined.sh b/t/t4038-diff-combined.sh index 0b4f7dfdc6..e2824d3437 100755 --- a/t/t4038-diff-combined.sh +++ b/t/t4038-diff-combined.sh @@ -354,7 +354,7 @@ test_expect_failure 'combine diff coalesce three parents' ' ' # Test for a bug reported at -# http://thread.gmane.org/gmane.comp.version-control.git/224410 +# https://public-inbox.org/git/20130515143508.GO25742@login.drsnuggles.stderr.nl/ # where a delete lines were missing from combined diff output when they # occurred exactly before the context lines of a later change. test_expect_success 'combine diff missing delete bug' ' diff --git a/t/t4041-diff-submodule-option.sh b/t/t4041-diff-submodule-option.sh index 2d9731b52d..058ee0829d 100755 --- a/t/t4041-diff-submodule-option.sh +++ b/t/t4041-diff-submodule-option.sh @@ -430,9 +430,11 @@ test_expect_success 'deleted submodule' ' test_cmp expected actual ' -test_create_repo sm2 && -head7=$(add_file sm2 foo8 foo9) && -git add sm2 +test_expect_success 'create second submodule' ' + test_create_repo sm2 && + head7=$(add_file sm2 foo8 foo9) && + git add sm2 +' test_expect_success 'multiple submodules' ' git diff-index -p --submodule=log HEAD >actual && diff --git a/t/t4045-diff-relative.sh b/t/t4045-diff-relative.sh index 3950f5034d..6471a68701 100755 --- a/t/t4045-diff-relative.sh +++ b/t/t4045-diff-relative.sh @@ -12,62 +12,76 @@ test_expect_success 'setup' ' git commit -m one ' -check_diff() { -expect=$1; shift -cat >expected <<EOF -diff --git a/$expect b/$expect -new file mode 100644 -index 0000000..25c05ef ---- /dev/null -+++ b/$expect -@@ -0,0 +1 @@ -+other content -EOF -test_expect_success "-p $*" " - git diff -p $* HEAD^ >actual && - test_cmp expected actual -" +check_diff () { + dir=$1 + shift + expect=$1 + shift + cat >expected <<-EOF + diff --git a/$expect b/$expect + new file mode 100644 + index 0000000..25c05ef + --- /dev/null + +++ b/$expect + @@ -0,0 +1 @@ + +other content + EOF + test_expect_success "-p $*" " + git -C '$dir' diff -p $* HEAD^ >actual && + test_cmp expected actual + " } -check_numstat() { -expect=$1; shift -cat >expected <<EOF -1 0 $expect -EOF -test_expect_success "--numstat $*" " - echo '1 0 $expect' >expected && - git diff --numstat $* HEAD^ >actual && - test_cmp expected actual -" +check_numstat () { + dir=$1 + shift + expect=$1 + shift + cat >expected <<-EOF + 1 0 $expect + EOF + test_expect_success "--numstat $*" " + echo '1 0 $expect' >expected && + git -C '$dir' diff --numstat $* HEAD^ >actual && + test_cmp expected actual + " } -check_stat() { -expect=$1; shift -cat >expected <<EOF - $expect | 1 + - 1 file changed, 1 insertion(+) -EOF -test_expect_success "--stat $*" " - git diff --stat $* HEAD^ >actual && - test_i18ncmp expected actual -" +check_stat () { + dir=$1 + shift + expect=$1 + shift + cat >expected <<-EOF + $expect | 1 + + 1 file changed, 1 insertion(+) + EOF + test_expect_success "--stat $*" " + git -C '$dir' diff --stat $* HEAD^ >actual && + test_i18ncmp expected actual + " } -check_raw() { -expect=$1; shift -cat >expected <<EOF -:000000 100644 0000000000000000000000000000000000000000 25c05ef3639d2d270e7fe765a67668f098092bc5 A $expect -EOF -test_expect_success "--raw $*" " - git diff --no-abbrev --raw $* HEAD^ >actual && - test_cmp expected actual -" +check_raw () { + dir=$1 + shift + expect=$1 + shift + cat >expected <<-EOF + :000000 100644 0000000000000000000000000000000000000000 25c05ef3639d2d270e7fe765a67668f098092bc5 A $expect + EOF + test_expect_success "--raw $*" " + git -C '$dir' diff --no-abbrev --raw $* HEAD^ >actual && + test_cmp expected actual + " } -for type in diff numstat stat raw; do - check_$type file2 --relative=subdir/ - check_$type file2 --relative=subdir - check_$type dir/file2 --relative=sub +for type in diff numstat stat raw +do + check_$type . file2 --relative=subdir/ + check_$type . file2 --relative=subdir + check_$type subdir file2 --relative + check_$type . dir/file2 --relative=sub done test_done diff --git a/t/t4047-diff-dirstat.sh b/t/t4047-diff-dirstat.sh index 3b8b7921d6..447a8ffa3a 100755 --- a/t/t4047-diff-dirstat.sh +++ b/t/t4047-diff-dirstat.sh @@ -248,7 +248,8 @@ EOF git rm -r src/move/unchanged && git rm -r src/move/changed && git rm -r src/move/rearranged && - git commit -m "changes" + git commit -m "changes" && + git config diff.renames false ' cat <<EOF >expect_diff_stat diff --git a/t/t4051-diff-function-context.sh b/t/t4051-diff-function-context.sh index 001d678e09..2d76a971c4 100755 --- a/t/t4051-diff-function-context.sh +++ b/t/t4051-diff-function-context.sh @@ -3,90 +3,210 @@ test_description='diff function context' . ./test-lib.sh -. "$TEST_DIRECTORY"/diff-lib.sh +dir="$TEST_DIRECTORY/t4051" -cat <<\EOF >hello.c -#include <stdio.h> - -static int a(void) -{ - /* - * Dummy. - */ +commit_and_tag () { + tag=$1 && + shift && + git add "$@" && + test_tick && + git commit -m "$tag" && + git tag "$tag" } -static int hello_world(void) -{ - /* Classic. */ - printf("Hello world.\n"); - - /* Success! */ - return 0; +first_context_line () { + awk ' + found {print; exit} + /^@@/ {found = 1} + ' } -static int b(void) -{ - /* - * Dummy, too. - */ + +last_context_line () { + sed -ne \$p } -int main(int argc, char **argv) -{ - a(); - b(); - return hello_world(); +check_diff () { + name=$1 + desc=$2 + options="-W $3" + + test_expect_success "$desc" ' + git diff $options "$name^" "$name" >"$name.diff" + ' + + test_expect_success ' diff applies' ' + test_when_finished "git reset --hard" && + git checkout --detach "$name^" && + git apply --index "$name.diff" && + git diff --exit-code "$name" + ' } -EOF test_expect_success 'setup' ' - git add hello.c && - test_tick && - git commit -m initial && - - grep -v Classic <hello.c >hello.c.new && - mv hello.c.new hello.c -' - -cat <<\EOF >expected -diff --git a/hello.c b/hello.c ---- a/hello.c -+++ b/hello.c -@@ -10,8 +10,7 @@ static int a(void) - static int hello_world(void) - { -- /* Classic. */ - printf("Hello world.\n"); - - /* Success! */ - return 0; - } -EOF - -test_expect_success 'diff -U0 -W' ' - git diff -U0 -W >actual && - compare_diff_patch actual expected -' - -cat <<\EOF >expected -diff --git a/hello.c b/hello.c ---- a/hello.c -+++ b/hello.c -@@ -9,9 +9,8 @@ static int a(void) - - static int hello_world(void) - { -- /* Classic. */ - printf("Hello world.\n"); - - /* Success! */ - return 0; - } -EOF - -test_expect_success 'diff -W' ' - git diff -W >actual && - compare_diff_patch actual expected + cat "$dir/includes.c" "$dir/dummy.c" "$dir/dummy.c" "$dir/hello.c" \ + "$dir/dummy.c" "$dir/dummy.c" >file.c && + commit_and_tag initial file.c && + + grep -v "delete me from hello" <file.c >file.c.new && + mv file.c.new file.c && + commit_and_tag changed_hello file.c && + + grep -v "delete me from includes" <file.c >file.c.new && + mv file.c.new file.c && + commit_and_tag changed_includes file.c && + + cat "$dir/appended1.c" >>file.c && + commit_and_tag appended file.c && + + cat "$dir/appended2.c" >>file.c && + commit_and_tag extended file.c && + + grep -v "Begin of second part" <file.c >file.c.new && + mv file.c.new file.c && + commit_and_tag long_common_tail file.c && + + git checkout initial && + cat "$dir/hello.c" "$dir/dummy.c" >file.c && + commit_and_tag hello_dummy file.c && + + # overlap function context of 1st change and -u context of 2nd change + grep -v "delete me from hello" <"$dir/hello.c" >file.c && + sed "2a\\ + extra line" <"$dir/dummy.c" >>file.c && + commit_and_tag changed_hello_dummy file.c && + + git checkout initial && + grep -v "delete me from hello" <file.c >file.c.new && + mv file.c.new file.c && + cat "$dir/appended1.c" >>file.c && + commit_and_tag changed_hello_appended file.c +' + +check_diff changed_hello 'changed function' + +test_expect_success ' context includes comment' ' + grep "^ .*Hello comment" changed_hello.diff +' + +test_expect_success ' context includes begin' ' + grep "^ .*Begin of hello" changed_hello.diff +' + +test_expect_success ' context includes end' ' + grep "^ .*End of hello" changed_hello.diff +' + +test_expect_success ' context does not include other functions' ' + test $(grep -c "^[ +-].*Begin" changed_hello.diff) -le 1 +' + +test_expect_success ' context does not include preceding empty lines' ' + test "$(first_context_line <changed_hello.diff)" != " " +' + +test_expect_success ' context does not include trailing empty lines' ' + test "$(last_context_line <changed_hello.diff)" != " " +' + +check_diff changed_includes 'changed includes' + +test_expect_success ' context includes begin' ' + grep "^ .*Begin.h" changed_includes.diff +' + +test_expect_success ' context includes end' ' + grep "^ .*End.h" changed_includes.diff +' + +test_expect_success ' context does not include other functions' ' + test $(grep -c "^[ +-].*Begin" changed_includes.diff) -le 1 +' + +test_expect_success ' context does not include trailing empty lines' ' + test "$(last_context_line <changed_includes.diff)" != " " +' + +check_diff appended 'appended function' + +test_expect_success ' context includes begin' ' + grep "^[+].*Begin of first part" appended.diff +' + +test_expect_success ' context includes end' ' + grep "^[+].*End of first part" appended.diff +' + +test_expect_success ' context does not include other functions' ' + test $(grep -c "^[ +-].*Begin" appended.diff) -le 1 +' + +check_diff extended 'appended function part' + +test_expect_success ' context includes begin' ' + grep "^ .*Begin of first part" extended.diff +' + +test_expect_success ' context includes end' ' + grep "^[+].*End of second part" extended.diff +' + +test_expect_success ' context does not include other functions' ' + test $(grep -c "^[ +-].*Begin" extended.diff) -le 2 +' + +test_expect_success ' context does not include preceding empty lines' ' + test "$(first_context_line <extended.diff)" != " " +' + +check_diff long_common_tail 'change with long common tail and no context' -U0 + +test_expect_success ' context includes begin' ' + grep "^ .*Begin of first part" long_common_tail.diff +' + +test_expect_success ' context includes end' ' + grep "^ .*End of second part" long_common_tail.diff +' + +test_expect_success ' context does not include other functions' ' + test $(grep -c "^[ +-].*Begin" long_common_tail.diff) -le 2 +' + +test_expect_success ' context does not include preceding empty lines' ' + test "$(first_context_line <long_common_tail.diff.diff)" != " " +' + +check_diff changed_hello_appended 'changed function plus appended function' + +test_expect_success ' context includes begin' ' + grep "^ .*Begin of hello" changed_hello_appended.diff && + grep "^[+].*Begin of first part" changed_hello_appended.diff +' + +test_expect_success ' context includes end' ' + grep "^ .*End of hello" changed_hello_appended.diff && + grep "^[+].*End of first part" changed_hello_appended.diff +' + +test_expect_success ' context does not include other functions' ' + test $(grep -c "^[ +-].*Begin" changed_hello_appended.diff) -le 2 +' + +check_diff changed_hello_dummy 'changed two consecutive functions' + +test_expect_success ' context includes begin' ' + grep "^ .*Begin of hello" changed_hello_dummy.diff && + grep "^ .*Begin of dummy" changed_hello_dummy.diff +' + +test_expect_success ' context includes end' ' + grep "^ .*End of hello" changed_hello_dummy.diff && + grep "^ .*End of dummy" changed_hello_dummy.diff +' + +test_expect_success ' overlapping hunks are merged' ' + test $(grep -c "^@@" changed_hello_dummy.diff) -eq 1 ' test_done diff --git a/t/t4051/appended1.c b/t/t4051/appended1.c new file mode 100644 index 0000000000..a9f56f11db --- /dev/null +++ b/t/t4051/appended1.c @@ -0,0 +1,15 @@ + +int appended(void) // Begin of first part +{ + int i; + char *s = "a string"; + + printf("%s\n", s); + + for (i = 99; + i >= 0; + i--) { + printf("%d bottles of beer on the wall\n", i); + } + + printf("End of first part\n"); diff --git a/t/t4051/appended2.c b/t/t4051/appended2.c new file mode 100644 index 0000000000..e651f7147b --- /dev/null +++ b/t/t4051/appended2.c @@ -0,0 +1,35 @@ + printf("Begin of second part\n"); + + /* + * Lorem ipsum dolor sit amet, consectetuer sadipscing elitr, + * sed diam nonumy eirmod tempor invidunt ut labore et dolore + * magna aliquyam erat, sed diam voluptua. At vero eos et + * accusam et justo duo dolores et ea rebum. Stet clita kasd + * gubergren, no sea takimata sanctus est Lorem ipsum dolor + * sit amet. + * + * Lorem ipsum dolor sit amet, consectetuer sadipscing elitr, + * sed diam nonumy eirmod tempor invidunt ut labore et dolore + * magna aliquyam erat, sed diam voluptua. At vero eos et + * accusam et justo duo dolores et ea rebum. Stet clita kasd + * gubergren, no sea takimata sanctus est Lorem ipsum dolor + * sit amet. + * + * Lorem ipsum dolor sit amet, consectetuer sadipscing elitr, + * sed diam nonumy eirmod tempor invidunt ut labore et dolore + * magna aliquyam erat, sed diam voluptua. At vero eos et + * accusam et justo duo dolores et ea rebum. Stet clita kasd + * gubergren, no sea takimata sanctus est Lorem ipsum dolor + * sit amet. + * + * Lorem ipsum dolor sit amet, consectetuer sadipscing elitr, + * sed diam nonumy eirmod tempor invidunt ut labore et dolore + * magna aliquyam erat, sed diam voluptua. At vero eos et + * accusam et justo duo dolores et ea rebum. Stet clita kasd + * gubergren, no sea takimata sanctus est Lorem ipsum dolor + * sit amet. + * + */ + + return 0; +} // End of second part diff --git a/t/t4051/dummy.c b/t/t4051/dummy.c new file mode 100644 index 0000000000..a43016e870 --- /dev/null +++ b/t/t4051/dummy.c @@ -0,0 +1,7 @@ + +static int dummy(void) // Begin of dummy +{ + int rc = 0; + + return rc; +} // End of dummy diff --git a/t/t4051/hello.c b/t/t4051/hello.c new file mode 100644 index 0000000000..73e767e178 --- /dev/null +++ b/t/t4051/hello.c @@ -0,0 +1,24 @@ + +/* + * Hello comment. + */ +static void hello(void) // Begin of hello +{ + /* + * Classic. + */ + putchar('H'); + putchar('e'); + putchar('l'); + putchar('l'); + putchar('o'); + putchar(' '); + /* delete me from hello */ + putchar('w'); + putchar('o'); + putchar('r'); + putchar('l'); + putchar('d'); + putchar('.'); + putchar('\n'); +} // End of hello diff --git a/t/t4051/includes.c b/t/t4051/includes.c new file mode 100644 index 0000000000..efc68f8bf6 --- /dev/null +++ b/t/t4051/includes.c @@ -0,0 +1,20 @@ +#include <Begin.h> +#include <unistd.h> +#include <stdio.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stddef.h> +#include <stdlib.h> +#include <stdarg.h> +/* delete me from includes */ +#include <string.h> +#include <sys/types.h> +#include <dirent.h> +#include <sys/time.h> +#include <time.h> +#include <signal.h> +#include <assert.h> +#include <regex.h> +#include <utime.h> +#include <syslog.h> +#include <End.h> diff --git a/t/t4052-stat-output.sh b/t/t4052-stat-output.sh index 9f563db20a..6e2cf933f7 100755 --- a/t/t4052-stat-output.sh +++ b/t/t4052-stat-output.sh @@ -19,17 +19,33 @@ test_expect_success 'preparation' ' git commit -m message "$name" ' +cat >expect72 <<-'EOF' + ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 + +EOF +test_expect_success "format-patch: small change with long name gives more space to the name" ' + git format-patch -1 --stdout >output && + grep " | " output >actual && + test_cmp expect72 actual +' + while read cmd args do - cat >expect <<-'EOF' + cat >expect80 <<-'EOF' ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 + EOF test_expect_success "$cmd: small change with long name gives more space to the name" ' git $cmd $args >output && grep " | " output >actual && - test_cmp expect actual + test_cmp expect80 actual ' +done <<\EOF +diff HEAD^ HEAD --stat +show --stat +log -1 --stat +EOF +while read cmd args +do cat >expect <<-'EOF' ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 + EOF @@ -79,11 +95,11 @@ test_expect_success 'preparation for big change tests' ' git commit -m message abcd ' -cat >expect80 <<'EOF' - abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +cat >expect72 <<'EOF' + abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ EOF -cat >expect80-graph <<'EOF' -| abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +cat >expect72-graph <<'EOF' +| abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ EOF cat >expect200 <<'EOF' abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -107,7 +123,7 @@ do test_cmp "$expect-graph" actual ' done <<\EOF -ignores expect80 format-patch -1 --stdout +ignores expect72 format-patch -1 --stdout respects expect200 diff HEAD^ HEAD --stat respects expect200 show --stat respects expect200 log -1 --stat @@ -135,7 +151,7 @@ do test_cmp "$expect-graph" actual ' done <<\EOF -ignores expect80 format-patch -1 --stdout +ignores expect72 format-patch -1 --stdout respects expect40 diff HEAD^ HEAD --stat respects expect40 show --stat respects expect40 log -1 --stat @@ -163,7 +179,7 @@ do test_cmp "$expect-graph" actual ' done <<\EOF -ignores expect80 format-patch -1 --stdout +ignores expect72 format-patch -1 --stdout respects expect40 diff HEAD^ HEAD --stat respects expect40 show --stat respects expect40 log -1 --stat @@ -250,11 +266,11 @@ show --stat log -1 --stat EOF -cat >expect80 <<'EOF' - ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 ++++++++++++++++++++ +cat >expect72 <<'EOF' + ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 +++++++++++++++++ EOF -cat >expect80-graph <<'EOF' -| ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 ++++++++++++++++++++ +cat >expect72-graph <<'EOF' +| ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 +++++++++++++++++ EOF cat >expect200 <<'EOF' aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -278,7 +294,7 @@ do test_cmp "$expect-graph" actual ' done <<\EOF -ignores expect80 format-patch -1 --stdout +ignores expect72 format-patch -1 --stdout respects expect200 diff HEAD^ HEAD --stat respects expect200 show --stat respects expect200 log -1 --stat @@ -308,7 +324,7 @@ do test_cmp "$expect-graph" actual ' done <<\EOF -ignores expect80 format-patch -1 --stdout +ignores expect72 format-patch -1 --stdout respects expect1 diff HEAD^ HEAD --stat respects expect1 show --stat respects expect1 log -1 --stat diff --git a/t/t4053-diff-no-index.sh b/t/t4053-diff-no-index.sh index 6eb83211b5..453e6c35eb 100755 --- a/t/t4053-diff-no-index.sh +++ b/t/t4053-diff-no-index.sh @@ -89,4 +89,42 @@ test_expect_success 'turning a file into a directory' ' ) ' +test_expect_success 'diff from repo subdir shows real paths (explicit)' ' + echo "diff --git a/../../non/git/a b/../../non/git/b" >expect && + test_expect_code 1 \ + git -C repo/sub \ + diff --no-index ../../non/git/a ../../non/git/b >actual && + head -n 1 <actual >actual.head && + test_cmp expect actual.head +' + +test_expect_success 'diff from repo subdir shows real paths (implicit)' ' + echo "diff --git a/../../non/git/a b/../../non/git/b" >expect && + test_expect_code 1 \ + git -C repo/sub \ + diff ../../non/git/a ../../non/git/b >actual && + head -n 1 <actual >actual.head && + test_cmp expect actual.head +' + +test_expect_success 'diff --no-index from repo subdir respects config (explicit)' ' + echo "diff --git ../../non/git/a ../../non/git/b" >expect && + test_config -C repo diff.noprefix true && + test_expect_code 1 \ + git -C repo/sub \ + diff --no-index ../../non/git/a ../../non/git/b >actual && + head -n 1 <actual >actual.head && + test_cmp expect actual.head +' + +test_expect_success 'diff --no-index from repo subdir respects config (implicit)' ' + echo "diff --git ../../non/git/a ../../non/git/b" >expect && + test_config -C repo diff.noprefix true && + test_expect_code 1 \ + git -C repo/sub \ + diff ../../non/git/a ../../non/git/b >actual && + head -n 1 <actual >actual.head && + test_cmp expect actual.head +' + test_done diff --git a/t/t4054-diff-bogus-tree.sh b/t/t4054-diff-bogus-tree.sh index 1d6efab3c5..18f42c5fff 100755 --- a/t/t4054-diff-bogus-tree.sh +++ b/t/t4054-diff-bogus-tree.sh @@ -3,8 +3,6 @@ test_description='test diff with a bogus tree containing the null sha1' . ./test-lib.sh -empty_tree=4b825dc642cb6eb9a060e54bf8d69288fbee4904 - test_expect_success 'create bogus tree' ' bogus_tree=$( printf "100644 fooQQQQQQQQQQQQQQQQQQQQQ" | @@ -22,13 +20,13 @@ test_expect_success 'create tree with matching file' ' test_expect_success 'raw diff shows null sha1 (addition)' ' echo ":000000 100644 $_z40 $_z40 A foo" >expect && - git diff-tree $empty_tree $bogus_tree >actual && + git diff-tree $EMPTY_TREE $bogus_tree >actual && test_cmp expect actual ' test_expect_success 'raw diff shows null sha1 (removal)' ' echo ":100644 000000 $_z40 $_z40 D foo" >expect && - git diff-tree $bogus_tree $empty_tree >actual && + git diff-tree $bogus_tree $EMPTY_TREE >actual && test_cmp expect actual ' @@ -57,11 +55,11 @@ test_expect_success 'raw diff shows null sha1 (index)' ' ' test_expect_success 'patch fails due to bogus sha1 (addition)' ' - test_must_fail git diff-tree -p $empty_tree $bogus_tree + test_must_fail git diff-tree -p $EMPTY_TREE $bogus_tree ' test_expect_success 'patch fails due to bogus sha1 (removal)' ' - test_must_fail git diff-tree -p $bogus_tree $empty_tree + test_must_fail git diff-tree -p $bogus_tree $EMPTY_TREE ' test_expect_success 'patch fails due to bogus sha1 (modification)' ' diff --git a/t/t4056-diff-order.sh b/t/t4056-diff-order.sh index c0460bb0e5..43dd474a12 100755 --- a/t/t4056-diff-order.sh +++ b/t/t4056-diff-order.sh @@ -68,6 +68,12 @@ test_expect_success POSIXPERM,SANITY 'unreadable orderfile' ' test_must_fail git diff -Ounreadable_file --name-only HEAD^..HEAD ' +test_expect_success "orderfile using option from subdir with --output" ' + mkdir subdir && + git -C subdir diff -O../order_file_1 --output ../actual --name-only HEAD^..HEAD && + test_cmp expect_1 actual +' + for i in 1 2 do test_expect_success "orderfile using option ($i)" ' diff --git a/t/t4059-diff-submodule-not-initialized.sh b/t/t4059-diff-submodule-not-initialized.sh new file mode 100755 index 0000000000..49bca7b48d --- /dev/null +++ b/t/t4059-diff-submodule-not-initialized.sh @@ -0,0 +1,127 @@ +#!/bin/sh +# +# Copyright (c) 2016 Jacob Keller, based on t4041 by Jens Lehmann +# + +test_description='Test for submodule diff on non-checked out submodule + +This test tries to verify that add_submodule_odb works when the submodule was +initialized previously but the checkout has since been removed. +' + +. ./test-lib.sh + +# Tested non-UTF-8 encoding +test_encoding="ISO8859-1" + +# String "added" in German (translated with Google Translate), encoded in UTF-8, +# used in sample commit log messages in add_file() function below. +added=$(printf "hinzugef\303\274gt") + +add_file () { + ( + cd "$1" && + shift && + for name + do + echo "$name" >"$name" && + git add "$name" && + test_tick && + # "git commit -m" would break MinGW, as Windows refuse to pass + # $test_encoding encoded parameter to git. + echo "Add $name ($added $name)" | iconv -f utf-8 -t $test_encoding | + git -c "i18n.commitEncoding=$test_encoding" commit -F - + done >/dev/null && + git rev-parse --short --verify HEAD + ) +} + +commit_file () { + test_tick && + git commit "$@" -m "Commit $*" >/dev/null +} + +test_expect_success 'setup - submodules' ' + test_create_repo sm2 && + add_file . foo && + add_file sm2 foo1 foo2 && + smhead1=$(git -C sm2 rev-parse --short --verify HEAD) +' + +test_expect_success 'setup - git submodule add' ' + git submodule add ./sm2 sm1 && + commit_file sm1 .gitmodules && + git diff-tree -p --no-commit-id --submodule=log HEAD -- sm1 >actual && + cat >expected <<-EOF && + Submodule sm1 0000000...$smhead1 (new submodule) + EOF + test_cmp expected actual +' + +test_expect_success 'submodule directory removed' ' + rm -rf sm1 && + git diff-tree -p --no-commit-id --submodule=log HEAD -- sm1 >actual && + cat >expected <<-EOF && + Submodule sm1 0000000...$smhead1 (new submodule) + EOF + test_cmp expected actual +' + +test_expect_success 'setup - submodule multiple commits' ' + git submodule update --checkout sm1 && + smhead2=$(add_file sm1 foo3 foo4) && + commit_file sm1 && + git diff-tree -p --no-commit-id --submodule=log HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 $smhead1..$smhead2: + > Add foo4 ($added foo4) + > Add foo3 ($added foo3) + EOF + test_cmp expected actual +' + +test_expect_success 'submodule removed multiple commits' ' + rm -rf sm1 && + git diff-tree -p --no-commit-id --submodule=log HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 $smhead1..$smhead2: + > Add foo4 ($added foo4) + > Add foo3 ($added foo3) + EOF + test_cmp expected actual +' + +test_expect_success 'submodule not initialized in new clone' ' + git clone . sm3 && + git -C sm3 diff-tree -p --no-commit-id --submodule=log HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 $smhead1...$smhead2 (commits not present) + EOF + test_cmp expected actual +' + +test_expect_success 'setup submodule moved' ' + git submodule update --checkout sm1 && + git mv sm1 sm4 && + commit_file sm4 && + git diff-tree -p --no-commit-id --submodule=log HEAD >actual && + cat >expected <<-EOF && + Submodule sm4 0000000...$smhead2 (new submodule) + EOF + test_cmp expected actual +' + +test_expect_success 'submodule moved then removed' ' + smhead3=$(add_file sm4 foo6 foo7) && + commit_file sm4 && + rm -rf sm4 && + git diff-tree -p --no-commit-id --submodule=log HEAD >actual && + cat >expected <<-EOF && + Submodule sm4 $smhead2..$smhead3: + > Add foo7 ($added foo7) + > Add foo6 ($added foo6) + EOF + test_cmp expected actual +' + +test_done diff --git a/t/t4060-diff-submodule-option-diff-format.sh b/t/t4060-diff-submodule-option-diff-format.sh new file mode 100755 index 0000000000..4b168d0ed7 --- /dev/null +++ b/t/t4060-diff-submodule-option-diff-format.sh @@ -0,0 +1,821 @@ +#!/bin/sh +# +# Copyright (c) 2009 Jens Lehmann, based on t7401 by Ping Yin +# Copyright (c) 2011 Alexey Shumkin (+ non-UTF-8 commit encoding tests) +# Copyright (c) 2016 Jacob Keller (copy + convert to --submodule=diff) +# + +test_description='Support for diff format verbose submodule difference in git diff + +This test tries to verify the sanity of --submodule=diff option of git diff. +' + +. ./test-lib.sh + +# Tested non-UTF-8 encoding +test_encoding="ISO8859-1" + +# String "added" in German (translated with Google Translate), encoded in UTF-8, +# used in sample commit log messages in add_file() function below. +added=$(printf "hinzugef\303\274gt") + +add_file () { + ( + cd "$1" && + shift && + for name + do + echo "$name" >"$name" && + git add "$name" && + test_tick && + # "git commit -m" would break MinGW, as Windows refuse to pass + # $test_encoding encoded parameter to git. + echo "Add $name ($added $name)" | iconv -f utf-8 -t $test_encoding | + git -c "i18n.commitEncoding=$test_encoding" commit -F - + done >/dev/null && + git rev-parse --short --verify HEAD + ) +} + +commit_file () { + test_tick && + git commit "$@" -m "Commit $*" >/dev/null +} + +test_expect_success 'setup repository' ' + test_create_repo sm1 && + add_file . foo && + head1=$(add_file sm1 foo1 foo2) && + fullhead1=$(git -C sm1 rev-parse --verify HEAD) +' + +test_expect_success 'added submodule' ' + git add sm1 && + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 0000000...$head1 (new submodule) + diff --git a/sm1/foo1 b/sm1/foo1 + new file mode 100644 + index 0000000..1715acd + --- /dev/null + +++ b/sm1/foo1 + @@ -0,0 +1 @@ + +foo1 + diff --git a/sm1/foo2 b/sm1/foo2 + new file mode 100644 + index 0000000..54b060e + --- /dev/null + +++ b/sm1/foo2 + @@ -0,0 +1 @@ + +foo2 + EOF + test_cmp expected actual +' + +test_expect_success 'added submodule, set diff.submodule' ' + test_config diff.submodule log && + git add sm1 && + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 0000000...$head1 (new submodule) + diff --git a/sm1/foo1 b/sm1/foo1 + new file mode 100644 + index 0000000..1715acd + --- /dev/null + +++ b/sm1/foo1 + @@ -0,0 +1 @@ + +foo1 + diff --git a/sm1/foo2 b/sm1/foo2 + new file mode 100644 + index 0000000..54b060e + --- /dev/null + +++ b/sm1/foo2 + @@ -0,0 +1 @@ + +foo2 + EOF + test_cmp expected actual +' + +test_expect_success '--submodule=short overrides diff.submodule' ' + test_config diff.submodule log && + git add sm1 && + git diff --submodule=short --cached >actual && + cat >expected <<-EOF && + diff --git a/sm1 b/sm1 + new file mode 160000 + index 0000000..$head1 + --- /dev/null + +++ b/sm1 + @@ -0,0 +1 @@ + +Subproject commit $fullhead1 + EOF + test_cmp expected actual +' + +test_expect_success 'diff.submodule does not affect plumbing' ' + test_config diff.submodule log && + git diff-index -p HEAD >actual && + cat >expected <<-EOF && + diff --git a/sm1 b/sm1 + new file mode 160000 + index 0000000..$head1 + --- /dev/null + +++ b/sm1 + @@ -0,0 +1 @@ + +Subproject commit $fullhead1 + EOF + test_cmp expected actual +' + +commit_file sm1 && +head2=$(add_file sm1 foo3) + +test_expect_success 'modified submodule(forward)' ' + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 $head1..$head2: + diff --git a/sm1/foo3 b/sm1/foo3 + new file mode 100644 + index 0000000..c1ec6c6 + --- /dev/null + +++ b/sm1/foo3 + @@ -0,0 +1 @@ + +foo3 + EOF + test_cmp expected actual +' + +test_expect_success 'modified submodule(forward)' ' + git diff --submodule=diff >actual && + cat >expected <<-EOF && + Submodule sm1 $head1..$head2: + diff --git a/sm1/foo3 b/sm1/foo3 + new file mode 100644 + index 0000000..c1ec6c6 + --- /dev/null + +++ b/sm1/foo3 + @@ -0,0 +1 @@ + +foo3 + EOF + test_cmp expected actual +' + +test_expect_success 'modified submodule(forward) --submodule' ' + git diff --submodule >actual && + cat >expected <<-EOF && + Submodule sm1 $head1..$head2: + > Add foo3 ($added foo3) + EOF + test_cmp expected actual +' + +fullhead2=$(cd sm1; git rev-parse --verify HEAD) +test_expect_success 'modified submodule(forward) --submodule=short' ' + git diff --submodule=short >actual && + cat >expected <<-EOF && + diff --git a/sm1 b/sm1 + index $head1..$head2 160000 + --- a/sm1 + +++ b/sm1 + @@ -1 +1 @@ + -Subproject commit $fullhead1 + +Subproject commit $fullhead2 + EOF + test_cmp expected actual +' + +commit_file sm1 && +head3=$( + cd sm1 && + git reset --hard HEAD~2 >/dev/null && + git rev-parse --short --verify HEAD +) + +test_expect_success 'modified submodule(backward)' ' + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 $head2..$head3 (rewind): + diff --git a/sm1/foo2 b/sm1/foo2 + deleted file mode 100644 + index 54b060e..0000000 + --- a/sm1/foo2 + +++ /dev/null + @@ -1 +0,0 @@ + -foo2 + diff --git a/sm1/foo3 b/sm1/foo3 + deleted file mode 100644 + index c1ec6c6..0000000 + --- a/sm1/foo3 + +++ /dev/null + @@ -1 +0,0 @@ + -foo3 + EOF + test_cmp expected actual +' + +head4=$(add_file sm1 foo4 foo5) +test_expect_success 'modified submodule(backward and forward)' ' + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 $head2...$head4: + diff --git a/sm1/foo2 b/sm1/foo2 + deleted file mode 100644 + index 54b060e..0000000 + --- a/sm1/foo2 + +++ /dev/null + @@ -1 +0,0 @@ + -foo2 + diff --git a/sm1/foo3 b/sm1/foo3 + deleted file mode 100644 + index c1ec6c6..0000000 + --- a/sm1/foo3 + +++ /dev/null + @@ -1 +0,0 @@ + -foo3 + diff --git a/sm1/foo4 b/sm1/foo4 + new file mode 100644 + index 0000000..a0016db + --- /dev/null + +++ b/sm1/foo4 + @@ -0,0 +1 @@ + +foo4 + diff --git a/sm1/foo5 b/sm1/foo5 + new file mode 100644 + index 0000000..d6f2413 + --- /dev/null + +++ b/sm1/foo5 + @@ -0,0 +1 @@ + +foo5 + EOF + test_cmp expected actual +' + +commit_file sm1 && +mv sm1 sm1-bak && +echo sm1 >sm1 && +head5=$(git hash-object sm1 | cut -c1-7) && +git add sm1 && +rm -f sm1 && +mv sm1-bak sm1 + +test_expect_success 'typechanged submodule(submodule->blob), --cached' ' + git diff --submodule=diff --cached >actual && + cat >expected <<-EOF && + Submodule sm1 $head4...0000000 (submodule deleted) + diff --git a/sm1/foo1 b/sm1/foo1 + deleted file mode 100644 + index 1715acd..0000000 + --- a/sm1/foo1 + +++ /dev/null + @@ -1 +0,0 @@ + -foo1 + diff --git a/sm1/foo4 b/sm1/foo4 + deleted file mode 100644 + index a0016db..0000000 + --- a/sm1/foo4 + +++ /dev/null + @@ -1 +0,0 @@ + -foo4 + diff --git a/sm1/foo5 b/sm1/foo5 + deleted file mode 100644 + index d6f2413..0000000 + --- a/sm1/foo5 + +++ /dev/null + @@ -1 +0,0 @@ + -foo5 + diff --git a/sm1 b/sm1 + new file mode 100644 + index 0000000..9da5fb8 + --- /dev/null + +++ b/sm1 + @@ -0,0 +1 @@ + +sm1 + EOF + test_cmp expected actual +' + +test_expect_success 'typechanged submodule(submodule->blob)' ' + git diff --submodule=diff >actual && + cat >expected <<-EOF && + diff --git a/sm1 b/sm1 + deleted file mode 100644 + index 9da5fb8..0000000 + --- a/sm1 + +++ /dev/null + @@ -1 +0,0 @@ + -sm1 + Submodule sm1 0000000...$head4 (new submodule) + diff --git a/sm1/foo1 b/sm1/foo1 + new file mode 100644 + index 0000000..1715acd + --- /dev/null + +++ b/sm1/foo1 + @@ -0,0 +1 @@ + +foo1 + diff --git a/sm1/foo4 b/sm1/foo4 + new file mode 100644 + index 0000000..a0016db + --- /dev/null + +++ b/sm1/foo4 + @@ -0,0 +1 @@ + +foo4 + diff --git a/sm1/foo5 b/sm1/foo5 + new file mode 100644 + index 0000000..d6f2413 + --- /dev/null + +++ b/sm1/foo5 + @@ -0,0 +1 @@ + +foo5 + EOF + test_cmp expected actual +' + +rm -rf sm1 && +git checkout-index sm1 +test_expect_success 'typechanged submodule(submodule->blob)' ' + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 $head4...0000000 (submodule deleted) + diff --git a/sm1 b/sm1 + new file mode 100644 + index 0000000..9da5fb8 + --- /dev/null + +++ b/sm1 + @@ -0,0 +1 @@ + +sm1 + EOF + test_cmp expected actual +' + +rm -f sm1 && +test_create_repo sm1 && +head6=$(add_file sm1 foo6 foo7) +fullhead6=$(cd sm1; git rev-parse --verify HEAD) +test_expect_success 'nonexistent commit' ' + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 $head4...$head6 (commits not present) + EOF + test_cmp expected actual +' + +commit_file +test_expect_success 'typechanged submodule(blob->submodule)' ' + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + diff --git a/sm1 b/sm1 + deleted file mode 100644 + index 9da5fb8..0000000 + --- a/sm1 + +++ /dev/null + @@ -1 +0,0 @@ + -sm1 + Submodule sm1 0000000...$head6 (new submodule) + diff --git a/sm1/foo6 b/sm1/foo6 + new file mode 100644 + index 0000000..462398b + --- /dev/null + +++ b/sm1/foo6 + @@ -0,0 +1 @@ + +foo6 + diff --git a/sm1/foo7 b/sm1/foo7 + new file mode 100644 + index 0000000..6e9262c + --- /dev/null + +++ b/sm1/foo7 + @@ -0,0 +1 @@ + +foo7 + EOF + test_cmp expected actual +' + +commit_file sm1 && +test_expect_success 'submodule is up to date' ' + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + EOF + test_cmp expected actual +' + +test_expect_success 'submodule contains untracked content' ' + echo new > sm1/new-file && + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 contains untracked content + EOF + test_cmp expected actual +' + +test_expect_success 'submodule contains untracked content (untracked ignored)' ' + git diff-index -p --ignore-submodules=untracked --submodule=diff HEAD >actual && + ! test -s actual +' + +test_expect_success 'submodule contains untracked content (dirty ignored)' ' + git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual && + ! test -s actual +' + +test_expect_success 'submodule contains untracked content (all ignored)' ' + git diff-index -p --ignore-submodules=all --submodule=diff HEAD >actual && + ! test -s actual +' + +test_expect_success 'submodule contains untracked and modified content' ' + echo new > sm1/foo6 && + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 contains untracked content + Submodule sm1 contains modified content + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..3e75765 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1 @@ + -foo6 + +new + EOF + test_cmp expected actual +' + +# NOT OK +test_expect_success 'submodule contains untracked and modified content (untracked ignored)' ' + echo new > sm1/foo6 && + git diff-index -p --ignore-submodules=untracked --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 contains modified content + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..3e75765 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1 @@ + -foo6 + +new + EOF + test_cmp expected actual +' + +test_expect_success 'submodule contains untracked and modified content (dirty ignored)' ' + echo new > sm1/foo6 && + git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual && + ! test -s actual +' + +test_expect_success 'submodule contains untracked and modified content (all ignored)' ' + echo new > sm1/foo6 && + git diff-index -p --ignore-submodules --submodule=diff HEAD >actual && + ! test -s actual +' + +test_expect_success 'submodule contains modified content' ' + rm -f sm1/new-file && + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 contains modified content + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..3e75765 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1 @@ + -foo6 + +new + EOF + test_cmp expected actual +' + +(cd sm1; git commit -mchange foo6 >/dev/null) && +head8=$(cd sm1; git rev-parse --short --verify HEAD) && +test_expect_success 'submodule is modified' ' + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 17243c9..$head8: + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..3e75765 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1 @@ + -foo6 + +new + EOF + test_cmp expected actual +' + +test_expect_success 'modified submodule contains untracked content' ' + echo new > sm1/new-file && + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 contains untracked content + Submodule sm1 17243c9..$head8: + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..3e75765 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1 @@ + -foo6 + +new + EOF + test_cmp expected actual +' + +test_expect_success 'modified submodule contains untracked content (untracked ignored)' ' + git diff-index -p --ignore-submodules=untracked --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 17243c9..$head8: + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..3e75765 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1 @@ + -foo6 + +new + EOF + test_cmp expected actual +' + +test_expect_success 'modified submodule contains untracked content (dirty ignored)' ' + git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 17243c9..cfce562: + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..3e75765 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1 @@ + -foo6 + +new + EOF + test_cmp expected actual +' + +test_expect_success 'modified submodule contains untracked content (all ignored)' ' + git diff-index -p --ignore-submodules=all --submodule=diff HEAD >actual && + ! test -s actual +' + +test_expect_success 'modified submodule contains untracked and modified content' ' + echo modification >> sm1/foo6 && + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 contains untracked content + Submodule sm1 contains modified content + Submodule sm1 17243c9..cfce562: + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..dfda541 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1,2 @@ + -foo6 + +new + +modification + EOF + test_cmp expected actual +' + +test_expect_success 'modified submodule contains untracked and modified content (untracked ignored)' ' + echo modification >> sm1/foo6 && + git diff-index -p --ignore-submodules=untracked --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 contains modified content + Submodule sm1 17243c9..cfce562: + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..e20e2d9 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1,3 @@ + -foo6 + +new + +modification + +modification + EOF + test_cmp expected actual +' + +test_expect_success 'modified submodule contains untracked and modified content (dirty ignored)' ' + echo modification >> sm1/foo6 && + git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 17243c9..cfce562: + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..3e75765 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1 @@ + -foo6 + +new + EOF + test_cmp expected actual +' + +test_expect_success 'modified submodule contains untracked and modified content (all ignored)' ' + echo modification >> sm1/foo6 && + git diff-index -p --ignore-submodules --submodule=diff HEAD >actual && + ! test -s actual +' + +# NOT OK +test_expect_success 'modified submodule contains modified content' ' + rm -f sm1/new-file && + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 contains modified content + Submodule sm1 17243c9..cfce562: + diff --git a/sm1/foo6 b/sm1/foo6 + index 462398b..ac466ca 100644 + --- a/sm1/foo6 + +++ b/sm1/foo6 + @@ -1 +1,5 @@ + -foo6 + +new + +modification + +modification + +modification + +modification + EOF + test_cmp expected actual +' + +rm -rf sm1 +test_expect_success 'deleted submodule' ' + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 17243c9...0000000 (submodule deleted) + EOF + test_cmp expected actual +' + +test_expect_success 'create second submodule' ' + test_create_repo sm2 && + head7=$(add_file sm2 foo8 foo9) && + git add sm2 +' + +test_expect_success 'multiple submodules' ' + git diff-index -p --submodule=diff HEAD >actual && + cat >expected <<-EOF && + Submodule sm1 17243c9...0000000 (submodule deleted) + Submodule sm2 0000000...a5a65c9 (new submodule) + diff --git a/sm2/foo8 b/sm2/foo8 + new file mode 100644 + index 0000000..db9916b + --- /dev/null + +++ b/sm2/foo8 + @@ -0,0 +1 @@ + +foo8 + diff --git a/sm2/foo9 b/sm2/foo9 + new file mode 100644 + index 0000000..9c3b4f6 + --- /dev/null + +++ b/sm2/foo9 + @@ -0,0 +1 @@ + +foo9 + EOF + test_cmp expected actual +' + +test_expect_success 'path filter' ' + git diff-index -p --submodule=diff HEAD sm2 >actual && + cat >expected <<-EOF && + Submodule sm2 0000000...a5a65c9 (new submodule) + diff --git a/sm2/foo8 b/sm2/foo8 + new file mode 100644 + index 0000000..db9916b + --- /dev/null + +++ b/sm2/foo8 + @@ -0,0 +1 @@ + +foo8 + diff --git a/sm2/foo9 b/sm2/foo9 + new file mode 100644 + index 0000000..9c3b4f6 + --- /dev/null + +++ b/sm2/foo9 + @@ -0,0 +1 @@ + +foo9 + EOF + test_cmp expected actual +' + +commit_file sm2 +test_expect_success 'given commit' ' + git diff-index -p --submodule=diff HEAD^ >actual && + cat >expected <<-EOF && + Submodule sm1 17243c9...0000000 (submodule deleted) + Submodule sm2 0000000...a5a65c9 (new submodule) + diff --git a/sm2/foo8 b/sm2/foo8 + new file mode 100644 + index 0000000..db9916b + --- /dev/null + +++ b/sm2/foo8 + @@ -0,0 +1 @@ + +foo8 + diff --git a/sm2/foo9 b/sm2/foo9 + new file mode 100644 + index 0000000..9c3b4f6 + --- /dev/null + +++ b/sm2/foo9 + @@ -0,0 +1 @@ + +foo9 + EOF + test_cmp expected actual +' + +test_expect_success 'setup .git file for sm2' ' + (cd sm2 && + REAL="$(pwd)/../.real" && + mv .git "$REAL" + echo "gitdir: $REAL" >.git) +' + +test_expect_success 'diff --submodule=diff with .git file' ' + git diff --submodule=diff HEAD^ >actual && + cat >expected <<-EOF && + Submodule sm1 17243c9...0000000 (submodule deleted) + Submodule sm2 0000000...a5a65c9 (new submodule) + diff --git a/sm2/foo8 b/sm2/foo8 + new file mode 100644 + index 0000000..db9916b + --- /dev/null + +++ b/sm2/foo8 + @@ -0,0 +1 @@ + +foo8 + diff --git a/sm2/foo9 b/sm2/foo9 + new file mode 100644 + index 0000000..9c3b4f6 + --- /dev/null + +++ b/sm2/foo9 + @@ -0,0 +1 @@ + +foo9 + EOF + test_cmp expected actual +' + +test_expect_success 'setup nested submodule' ' + git submodule add -f ./sm2 && + git commit -a -m "add sm2" && + git -C sm2 submodule add ../sm2 nested && + git -C sm2 commit -a -m "nested sub" +' + +test_expect_success 'move nested submodule HEAD' ' + echo "nested content" >sm2/nested/file && + git -C sm2/nested add file && + git -C sm2/nested commit --allow-empty -m "new HEAD" +' + +test_expect_success 'diff --submodule=diff with moved nested submodule HEAD' ' + cat >expected <<-EOF && + Submodule nested a5a65c9..b55928c: + diff --git a/nested/file b/nested/file + new file mode 100644 + index 0000000..ca281f5 + --- /dev/null + +++ b/nested/file + @@ -0,0 +1 @@ + +nested content + EOF + git -C sm2 diff --submodule=diff >actual 2>err && + test_must_be_empty err && + test_cmp expected actual +' + +test_expect_success 'diff --submodule=diff recurses into nested submodules' ' + cat >expected <<-EOF && + Submodule sm2 contains modified content + Submodule sm2 a5a65c9..280969a: + diff --git a/sm2/.gitmodules b/sm2/.gitmodules + new file mode 100644 + index 0000000..3a816b8 + --- /dev/null + +++ b/sm2/.gitmodules + @@ -0,0 +1,3 @@ + +[submodule "nested"] + + path = nested + + url = ../sm2 + Submodule nested 0000000...b55928c (new submodule) + diff --git a/sm2/nested/file b/sm2/nested/file + new file mode 100644 + index 0000000..ca281f5 + --- /dev/null + +++ b/sm2/nested/file + @@ -0,0 +1 @@ + +nested content + diff --git a/sm2/nested/foo8 b/sm2/nested/foo8 + new file mode 100644 + index 0000000..db9916b + --- /dev/null + +++ b/sm2/nested/foo8 + @@ -0,0 +1 @@ + +foo8 + diff --git a/sm2/nested/foo9 b/sm2/nested/foo9 + new file mode 100644 + index 0000000..9c3b4f6 + --- /dev/null + +++ b/sm2/nested/foo9 + @@ -0,0 +1 @@ + +foo9 + EOF + git diff --submodule=diff >actual 2>err && + test_must_be_empty err && + test_cmp expected actual +' + +test_done diff --git a/t/t4061-diff-indent.sh b/t/t4061-diff-indent.sh new file mode 100755 index 0000000000..2affd7a100 --- /dev/null +++ b/t/t4061-diff-indent.sh @@ -0,0 +1,368 @@ +#!/bin/sh + +test_description='Test diff indent heuristic. + +' +. ./test-lib.sh +. "$TEST_DIRECTORY"/diff-lib.sh + +# Compare two diff outputs. Ignore "index" lines, because we don't +# care about SHA-1s or file modes. +compare_diff () { + sed -e "/^index /d" <"$1" >.tmp-1 + sed -e "/^index /d" <"$2" >.tmp-2 + test_cmp .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2 +} + +# Compare blame output using the expectation for a diff as reference. +# Only look for the lines coming from non-boundary commits. +compare_blame () { + sed -n -e "1,4d" -e "s/^\+//p" <"$1" >.tmp-1 + sed -ne "s/^[^^][^)]*) *//p" <"$2" >.tmp-2 + test_cmp .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2 +} + +test_expect_success 'prepare' ' + cat <<-\EOF >spaces.txt && + 1 + 2 + a + + b + 3 + 4 + EOF + + cat <<-\EOF >functions.c && + 1 + 2 + /* function */ + foo() { + foo + } + + 3 + 4 + EOF + + git add spaces.txt functions.c && + test_tick && + git commit -m initial && + git branch old && + + cat <<-\EOF >spaces.txt && + 1 + 2 + a + + b + a + + b + 3 + 4 + EOF + + cat <<-\EOF >functions.c && + 1 + 2 + /* function */ + bar() { + foo + } + + /* function */ + foo() { + foo + } + + 3 + 4 + EOF + + git add spaces.txt functions.c && + test_tick && + git commit -m initial && + git branch new && + + tr "_" " " <<-\EOF >spaces-expect && + diff --git a/spaces.txt b/spaces.txt + --- a/spaces.txt + +++ b/spaces.txt + @@ -3,5 +3,8 @@ + a + _ + b + +a + + + +b + 3 + 4 + EOF + + tr "_" " " <<-\EOF >spaces-compacted-expect && + diff --git a/spaces.txt b/spaces.txt + --- a/spaces.txt + +++ b/spaces.txt + @@ -2,6 +2,9 @@ + 2 + a + _ + +b + +a + + + b + 3 + 4 + EOF + + tr "_" " " <<-\EOF >functions-expect && + diff --git a/functions.c b/functions.c + --- a/functions.c + +++ b/functions.c + @@ -1,6 +1,11 @@ + 1 + 2 + /* function */ + +bar() { + + foo + +} + + + +/* function */ + foo() { + foo + } + EOF + + tr "_" " " <<-\EOF >functions-compacted-expect + diff --git a/functions.c b/functions.c + --- a/functions.c + +++ b/functions.c + @@ -1,5 +1,10 @@ + 1 + 2 + +/* function */ + +bar() { + + foo + +} + + + /* function */ + foo() { + foo + EOF +' + +# --- diff tests ---------------------------------------------------------- + +test_expect_success 'diff: ugly spaces' ' + git diff --no-indent-heuristic old new -- spaces.txt >out && + compare_diff spaces-expect out +' + +test_expect_success 'diff: --no-indent-heuristic overrides config' ' + git -c diff.indentHeuristic=true diff --no-indent-heuristic old new -- spaces.txt >out2 && + compare_diff spaces-expect out2 +' + +test_expect_success 'diff: nice spaces with --indent-heuristic' ' + git -c diff.indentHeuristic=false diff --indent-heuristic old new -- spaces.txt >out-compacted && + compare_diff spaces-compacted-expect out-compacted +' + +test_expect_success 'diff: nice spaces with diff.indentHeuristic=true' ' + git -c diff.indentHeuristic=true diff old new -- spaces.txt >out-compacted2 && + compare_diff spaces-compacted-expect out-compacted2 +' + +test_expect_success 'diff: --indent-heuristic with --patience' ' + git diff --indent-heuristic --patience old new -- spaces.txt >out-compacted3 && + compare_diff spaces-compacted-expect out-compacted3 +' + +test_expect_success 'diff: --indent-heuristic with --histogram' ' + git diff --indent-heuristic --histogram old new -- spaces.txt >out-compacted4 && + compare_diff spaces-compacted-expect out-compacted4 +' + +test_expect_success 'diff: ugly functions' ' + git diff --no-indent-heuristic old new -- functions.c >out && + compare_diff functions-expect out +' + +test_expect_success 'diff: nice functions with --indent-heuristic' ' + git diff --indent-heuristic old new -- functions.c >out-compacted && + compare_diff functions-compacted-expect out-compacted +' + +# --- blame tests --------------------------------------------------------- + +test_expect_success 'blame: nice spaces with --indent-heuristic' ' + git blame --indent-heuristic old..new -- spaces.txt >out-blame-compacted && + compare_blame spaces-compacted-expect out-blame-compacted +' + +test_expect_success 'blame: nice spaces with diff.indentHeuristic=true' ' + git -c diff.indentHeuristic=true blame old..new -- spaces.txt >out-blame-compacted2 && + compare_blame spaces-compacted-expect out-blame-compacted2 +' + +test_expect_success 'blame: ugly spaces with --no-indent-heuristic' ' + git blame --no-indent-heuristic old..new -- spaces.txt >out-blame && + compare_blame spaces-expect out-blame +' + +test_expect_success 'blame: ugly spaces with diff.indentHeuristic=false' ' + git -c diff.indentHeuristic=false blame old..new -- spaces.txt >out-blame2 && + compare_blame spaces-expect out-blame2 +' + +test_expect_success 'blame: --no-indent-heuristic overrides config' ' + git -c diff.indentHeuristic=true blame --no-indent-heuristic old..new -- spaces.txt >out-blame3 && + git blame old..new -- spaces.txt >out-blame && + compare_blame spaces-expect out-blame3 +' + +test_expect_success 'blame: --indent-heuristic overrides config' ' + git -c diff.indentHeuristic=false blame --indent-heuristic old..new -- spaces.txt >out-blame-compacted3 && + compare_blame spaces-compacted-expect out-blame-compacted2 +' + +# --- diff-tree tests ----------------------------------------------------- + +test_expect_success 'diff-tree: nice spaces with --indent-heuristic' ' + git diff-tree --indent-heuristic -p old new -- spaces.txt >out-diff-tree-compacted && + compare_diff spaces-compacted-expect out-diff-tree-compacted +' + +test_expect_success 'diff-tree: nice spaces with diff.indentHeuristic=true' ' + git -c diff.indentHeuristic=true diff-tree -p old new -- spaces.txt >out-diff-tree-compacted2 && + compare_diff spaces-compacted-expect out-diff-tree-compacted2 +' + +test_expect_success 'diff-tree: ugly spaces with --no-indent-heuristic' ' + git diff-tree --no-indent-heuristic -p old new -- spaces.txt >out-diff-tree && + compare_diff spaces-expect out-diff-tree +' + +test_expect_success 'diff-tree: ugly spaces with diff.indentHeuristic=false' ' + git -c diff.indentHeuristic=false diff-tree -p old new -- spaces.txt >out-diff-tree2 && + compare_diff spaces-expect out-diff-tree2 +' + +test_expect_success 'diff-tree: --indent-heuristic overrides config' ' + git -c diff.indentHeuristic=false diff-tree --indent-heuristic -p old new -- spaces.txt >out-diff-tree-compacted3 && + compare_diff spaces-compacted-expect out-diff-tree-compacted3 +' + +test_expect_success 'diff-tree: --no-indent-heuristic overrides config' ' + git -c diff.indentHeuristic=true diff-tree --no-indent-heuristic -p old new -- spaces.txt >out-diff-tree3 && + compare_diff spaces-expect out-diff-tree3 +' + +# --- diff-index tests ---------------------------------------------------- + +test_expect_success 'diff-index: nice spaces with --indent-heuristic' ' + git checkout -B diff-index && + git reset --soft HEAD~ && + git diff-index --indent-heuristic -p old -- spaces.txt >out-diff-index-compacted && + compare_diff spaces-compacted-expect out-diff-index-compacted && + git checkout -f master +' + +test_expect_success 'diff-index: nice spaces with diff.indentHeuristic=true' ' + git checkout -B diff-index && + git reset --soft HEAD~ && + git -c diff.indentHeuristic=true diff-index -p old -- spaces.txt >out-diff-index-compacted2 && + compare_diff spaces-compacted-expect out-diff-index-compacted2 && + git checkout -f master +' + +test_expect_success 'diff-index: ugly spaces with --no-indent-heuristic' ' + git checkout -B diff-index && + git reset --soft HEAD~ && + git diff-index --no-indent-heuristic -p old -- spaces.txt >out-diff-index && + compare_diff spaces-expect out-diff-index && + git checkout -f master +' + +test_expect_success 'diff-index: ugly spaces with diff.indentHeuristic=false' ' + git checkout -B diff-index && + git reset --soft HEAD~ && + git -c diff.indentHeuristic=false diff-index -p old -- spaces.txt >out-diff-index2 && + compare_diff spaces-expect out-diff-index2 && + git checkout -f master +' + +test_expect_success 'diff-index: --indent-heuristic overrides config' ' + git checkout -B diff-index && + git reset --soft HEAD~ && + git -c diff.indentHeuristic=false diff-index --indent-heuristic -p old -- spaces.txt >out-diff-index-compacted3 && + compare_diff spaces-compacted-expect out-diff-index-compacted3 && + git checkout -f master +' + +test_expect_success 'diff-index: --no-indent-heuristic overrides config' ' + git checkout -B diff-index && + git reset --soft HEAD~ && + git -c diff.indentHeuristic=true diff-index --no-indent-heuristic -p old -- spaces.txt >out-diff-index3 && + compare_diff spaces-expect out-diff-index3 && + git checkout -f master +' + +# --- diff-files tests ---------------------------------------------------- + +test_expect_success 'diff-files: nice spaces with --indent-heuristic' ' + git checkout -B diff-files && + git reset HEAD~ && + git diff-files --indent-heuristic -p spaces.txt >out-diff-files-raw && + grep -v index out-diff-files-raw >out-diff-files-compacted && + compare_diff spaces-compacted-expect out-diff-files-compacted && + git checkout -f master +' + +test_expect_success 'diff-files: nice spaces with diff.indentHeuristic=true' ' + git checkout -B diff-files && + git reset HEAD~ && + git -c diff.indentHeuristic=true diff-files -p spaces.txt >out-diff-files-raw2 && + grep -v index out-diff-files-raw2 >out-diff-files-compacted2 && + compare_diff spaces-compacted-expect out-diff-files-compacted2 && + git checkout -f master +' + +test_expect_success 'diff-files: ugly spaces with --no-indent-heuristic' ' + git checkout -B diff-files && + git reset HEAD~ && + git diff-files --no-indent-heuristic -p spaces.txt >out-diff-files-raw && + grep -v index out-diff-files-raw >out-diff-files && + compare_diff spaces-expect out-diff-files && + git checkout -f master +' + +test_expect_success 'diff-files: ugly spaces with diff.indentHeuristic=false' ' + git checkout -B diff-files && + git reset HEAD~ && + git -c diff.indentHeuristic=false diff-files -p spaces.txt >out-diff-files-raw2 && + grep -v index out-diff-files-raw2 >out-diff-files && + compare_diff spaces-expect out-diff-files && + git checkout -f master +' + +test_expect_success 'diff-files: --indent-heuristic overrides config' ' + git checkout -B diff-files && + git reset HEAD~ && + git -c diff.indentHeuristic=false diff-files --indent-heuristic -p spaces.txt >out-diff-files-raw3 && + grep -v index out-diff-files-raw3 >out-diff-files-compacted && + compare_diff spaces-compacted-expect out-diff-files-compacted && + git checkout -f master +' + +test_expect_success 'diff-files: --no-indent-heuristic overrides config' ' + git checkout -B diff-files && + git reset HEAD~ && + git -c diff.indentHeuristic=true diff-files --no-indent-heuristic -p spaces.txt >out-diff-files-raw4 && + grep -v index out-diff-files-raw4 >out-diff-files && + compare_diff spaces-expect out-diff-files && + git checkout -f master +' + +test_done diff --git a/t/t4062-diff-pickaxe.sh b/t/t4062-diff-pickaxe.sh new file mode 100755 index 0000000000..1130c8019b --- /dev/null +++ b/t/t4062-diff-pickaxe.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# +# Copyright (c) 2016 Johannes Schindelin +# + +test_description='Pickaxe options' + +. ./test-lib.sh + +test_expect_success setup ' + test_commit initial && + printf "%04096d" 0 >4096-zeroes.txt && + git add 4096-zeroes.txt && + test_tick && + git commit -m "A 4k file" +' + +# OpenBSD only supports up to 255 repetitions, so repeat twice for 64*64=4096. +test_expect_success '-G matches' ' + git diff --name-only -G "^(0{64}){64}$" HEAD^ >out && + test 4096-zeroes.txt = "$(cat out)" +' + +test_expect_success '-S --pickaxe-regex' ' + git diff --name-only -S0 --pickaxe-regex HEAD^ >out && + verbose test 4096-zeroes.txt = "$(cat out)" +' + +test_done diff --git a/t/t4063-diff-blobs.sh b/t/t4063-diff-blobs.sh new file mode 100755 index 0000000000..bc69e26c52 --- /dev/null +++ b/t/t4063-diff-blobs.sh @@ -0,0 +1,96 @@ +#!/bin/sh + +test_description='test direct comparison of blobs via git-diff' +. ./test-lib.sh + +run_diff () { + # use full-index to make it easy to match the index line + git diff --full-index "$@" >diff +} + +check_index () { + grep "^index $1\\.\\.$2" diff +} + +check_mode () { + grep "^old mode $1" diff && + grep "^new mode $2" diff +} + +check_paths () { + grep "^diff --git a/$1 b/$2" diff +} + +test_expect_success 'create some blobs' ' + echo one >one && + echo two >two && + chmod +x two && + git add . && + + # cover systems where modes are ignored + git update-index --chmod=+x two && + + git commit -m base && + + sha1_one=$(git rev-parse HEAD:one) && + sha1_two=$(git rev-parse HEAD:two) +' + +test_expect_success 'diff by sha1' ' + run_diff $sha1_one $sha1_two +' +test_expect_success 'index of sha1 diff' ' + check_index $sha1_one $sha1_two +' +test_expect_success 'sha1 diff uses arguments as paths' ' + check_paths $sha1_one $sha1_two +' +test_expect_success 'sha1 diff has no mode change' ' + ! grep mode diff +' + +test_expect_success 'diff by tree:path (run)' ' + run_diff HEAD:one HEAD:two +' +test_expect_success 'index of tree:path diff' ' + check_index $sha1_one $sha1_two +' +test_expect_success 'tree:path diff uses filenames as paths' ' + check_paths one two +' +test_expect_success 'tree:path diff shows mode change' ' + check_mode 100644 100755 +' + +test_expect_success 'diff by ranged tree:path' ' + run_diff HEAD:one..HEAD:two +' +test_expect_success 'index of ranged tree:path diff' ' + check_index $sha1_one $sha1_two +' +test_expect_success 'ranged tree:path diff uses filenames as paths' ' + check_paths one two +' +test_expect_success 'ranged tree:path diff shows mode change' ' + check_mode 100644 100755 +' + +test_expect_success 'diff blob against file' ' + run_diff HEAD:one two +' +test_expect_success 'index of blob-file diff' ' + check_index $sha1_one $sha1_two +' +test_expect_success 'blob-file diff uses filename as paths' ' + check_paths one two +' +test_expect_success FILEMODE 'blob-file diff shows mode change' ' + check_mode 100644 100755 +' + +test_expect_success 'blob-file diff prefers filename to sha1' ' + run_diff $sha1_one two && + check_paths two two +' + +test_done diff --git a/t/t4064-diff-oidfind.sh b/t/t4064-diff-oidfind.sh new file mode 100755 index 0000000000..3bdf317af8 --- /dev/null +++ b/t/t4064-diff-oidfind.sh @@ -0,0 +1,68 @@ +#!/bin/sh + +test_description='test finding specific blobs in the revision walking' +. ./test-lib.sh + +test_expect_success 'setup ' ' + git commit --allow-empty -m "empty initial commit" && + + echo "Hello, world!" >greeting && + git add greeting && + git commit -m "add the greeting blob" && # borrowed from Git from the Bottom Up + git tag -m "the blob" greeting $(git rev-parse HEAD:greeting) && + + echo asdf >unrelated && + git add unrelated && + git commit -m "unrelated history" && + + git revert HEAD^ && + + git commit --allow-empty -m "another unrelated commit" +' + +test_expect_success 'find the greeting blob' ' + cat >expect <<-EOF && + Revert "add the greeting blob" + add the greeting blob + EOF + + git log --format=%s --find-object=greeting^{blob} >actual && + + test_cmp expect actual +' + +test_expect_success 'setup a tree' ' + mkdir a && + echo asdf >a/file && + git add a/file && + git commit -m "add a file in a subdirectory" +' + +test_expect_success 'find a tree' ' + cat >expect <<-EOF && + add a file in a subdirectory + EOF + + git log --format=%s -t --find-object=HEAD:a >actual && + + test_cmp expect actual +' + +test_expect_success 'setup a submodule' ' + test_create_repo sub && + test_commit -C sub sub && + git submodule add ./sub sub && + git commit -a -m "add sub" +' + +test_expect_success 'find a submodule' ' + cat >expect <<-EOF && + add sub + EOF + + git log --format=%s --find-object=HEAD:sub >actual && + + test_cmp expect actual +' + +test_done diff --git a/t/t4065-diff-anchored.sh b/t/t4065-diff-anchored.sh new file mode 100755 index 0000000000..b3f510f040 --- /dev/null +++ b/t/t4065-diff-anchored.sh @@ -0,0 +1,94 @@ +#!/bin/sh + +test_description='anchored diff algorithm' + +. ./test-lib.sh + +test_expect_success '--anchored' ' + printf "a\nb\nc\n" >pre && + printf "c\na\nb\n" >post && + + # normally, c is moved to produce the smallest diff + test_expect_code 1 git diff --no-index pre post >diff && + grep "^+c" diff && + + # with anchor, a is moved + test_expect_code 1 git diff --no-index --anchored=c pre post >diff && + grep "^+a" diff +' + +test_expect_success '--anchored multiple' ' + printf "a\nb\nc\nd\ne\nf\n" >pre && + printf "c\na\nb\nf\nd\ne\n" >post && + + # with 1 anchor, c is not moved, but f is moved + test_expect_code 1 git diff --no-index --anchored=c pre post >diff && + grep "^+a" diff && # a is moved instead of c + grep "^+f" diff && + + # with 2 anchors, c and f are not moved + test_expect_code 1 git diff --no-index --anchored=c --anchored=f pre post >diff && + grep "^+a" diff && + grep "^+d" diff # d is moved instead of f +' + +test_expect_success '--anchored with nonexistent line has no effect' ' + printf "a\nb\nc\n" >pre && + printf "c\na\nb\n" >post && + + test_expect_code 1 git diff --no-index --anchored=x pre post >diff && + grep "^+c" diff +' + +test_expect_success '--anchored with non-unique line has no effect' ' + printf "a\nb\nc\nd\ne\nc\n" >pre && + printf "c\na\nb\nc\nd\ne\n" >post && + + test_expect_code 1 git diff --no-index --anchored=c pre post >diff && + grep "^+c" diff +' + +test_expect_success 'diff still produced with impossible multiple --anchored' ' + printf "a\nb\nc\n" >pre && + printf "c\na\nb\n" >post && + + test_expect_code 1 git diff --no-index --anchored=a --anchored=c pre post >diff && + mv post expected_post && + + # Ensure that the diff is correct by applying it and then + # comparing the result with the original + git apply diff && + diff expected_post post +' + +test_expect_success 'later algorithm arguments override earlier ones' ' + printf "a\nb\nc\n" >pre && + printf "c\na\nb\n" >post && + + test_expect_code 1 git diff --no-index --patience --anchored=c pre post >diff && + grep "^+a" diff && + + test_expect_code 1 git diff --no-index --anchored=c --patience pre post >diff && + grep "^+c" diff && + + test_expect_code 1 git diff --no-index --histogram --anchored=c pre post >diff && + grep "^+a" diff && + + test_expect_code 1 git diff --no-index --anchored=c --histogram pre post >diff && + grep "^+c" diff +' + +test_expect_success '--anchored works with other commands like "git show"' ' + printf "a\nb\nc\n" >file && + git add file && + git commit -m foo && + printf "c\na\nb\n" >file && + git add file && + git commit -m foo && + + # with anchor, a is moved + git show --patience --anchored=c >diff && + grep "^+a" diff +' + +test_done diff --git a/t/t4107-apply-ignore-whitespace.sh b/t/t4107-apply-ignore-whitespace.sh index 9e29b5262d..ac72eeaf27 100755 --- a/t/t4107-apply-ignore-whitespace.sh +++ b/t/t4107-apply-ignore-whitespace.sh @@ -178,4 +178,18 @@ test_expect_success 'patch5 fails (--no-ignore-whitespace)' ' test_must_fail git apply --no-ignore-whitespace patch5.patch ' +test_expect_success 'apply --ignore-space-change --inaccurate-eof' ' + echo 1 >file && + git apply --ignore-space-change --inaccurate-eof <<-\EOF && + diff --git a/file b/file + --- a/file + +++ b/file + @@ -1 +1 @@ + -1 + +2 + EOF + printf 2 >expect && + test_cmp expect file +' + test_done diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index d350065f25..4fc27c51f7 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -467,21 +467,42 @@ test_expect_success 'same, but with CR-LF line endings && cr-at-eol set' ' test_cmp one expect ' -test_expect_success 'same, but with CR-LF line endings && cr-at-eol unset' ' +test_expect_success 'CR-LF line endings && add line && text=auto' ' git config --unset core.whitespace && printf "a\r\n" >one && + cp one save-one && + git add one && printf "b\r\n" >>one && - printf "c\r\n" >>one && + cp one expect && + git diff -- one >patch && + mv save-one one && + echo "one text=auto" >.gitattributes && + git apply patch && + test_cmp one expect +' + +test_expect_success 'CR-LF line endings && change line && text=auto' ' + printf "a\r\n" >one && cp one save-one && - printf " \r\n" >>one && git add one && + printf "b\r\n" >one && cp one expect && - printf "d\r\n" >>one && git diff -- one >patch && mv save-one one && - echo d >>expect && + echo "one text=auto" >.gitattributes && + git apply patch && + test_cmp one expect +' - git apply --ignore-space-change --whitespace=fix patch && +test_expect_success 'LF in repo, CRLF in worktree && change line && text=auto' ' + printf "a\n" >one && + git add one && + printf "b\r\n" >one && + git diff -- one >patch && + printf "a\r\n" >one && + echo "one text=auto" >.gitattributes && + git -c core.eol=CRLF apply patch && + printf "b\r\n" >expect && test_cmp one expect ' diff --git a/t/t4129-apply-samemode.sh b/t/t4129-apply-samemode.sh index c268298eaf..5cdd76dfa7 100755 --- a/t/t4129-apply-samemode.sh +++ b/t/t4129-apply-samemode.sh @@ -13,7 +13,9 @@ test_expect_success setup ' echo modified >file && git diff --stat -p >patch-0.txt && chmod +x file && - git diff --stat -p >patch-1.txt + git diff --stat -p >patch-1.txt && + sed "s/^\(new mode \).*/\1/" <patch-1.txt >patch-empty-mode.txt && + sed "s/^\(new mode \).*/\1garbage/" <patch-1.txt >patch-bogus-mode.txt ' test_expect_success FILEMODE 'same mode (no index)' ' @@ -59,4 +61,16 @@ test_expect_success FILEMODE 'mode update (index only)' ' git ls-files -s file | grep "^100755" ' +test_expect_success FILEMODE 'empty mode is rejected' ' + git reset --hard && + test_must_fail git apply patch-empty-mode.txt 2>err && + test_i18ngrep "invalid mode" err +' + +test_expect_success FILEMODE 'bogus mode is rejected' ' + git reset --hard && + test_must_fail git apply patch-bogus-mode.txt 2>err && + test_i18ngrep "invalid mode" err +' + test_done diff --git a/t/t4130-apply-criss-cross-rename.sh b/t/t4130-apply-criss-cross-rename.sh index d173acde0f..f8a313bcb9 100755 --- a/t/t4130-apply-criss-cross-rename.sh +++ b/t/t4130-apply-criss-cross-rename.sh @@ -13,9 +13,13 @@ create_file() { } test_expect_success 'setup' ' - create_file file1 "File1 contents" && - create_file file2 "File2 contents" && - create_file file3 "File3 contents" && + # Ensure that file sizes are different, because on Windows + # lstat() does not discover inode numbers, and we need + # other properties to discover swapped files + # (mtime is not always different, either). + create_file file1 "some content" && + create_file file2 "some other content" && + create_file file3 "again something else" && git add file1 file2 file3 && git commit -m 1 ' diff --git a/t/t4133-apply-filenames.sh b/t/t4133-apply-filenames.sh index 2ecb4216b7..c5ed3b17c4 100755 --- a/t/t4133-apply-filenames.sh +++ b/t/t4133-apply-filenames.sh @@ -35,4 +35,28 @@ test_expect_success 'apply diff with inconsistent filenames in headers' ' test_i18ngrep "inconsistent old filename" err ' +test_expect_success 'apply diff with new filename missing from headers' ' + cat >missing_new_filename.diff <<-\EOF && + diff --git a/f b/f + index 0000000..d00491f + --- a/f + @@ -0,0 +1 @@ + +1 + EOF + test_must_fail git apply missing_new_filename.diff 2>err && + test_i18ngrep "lacks filename information" err +' + +test_expect_success 'apply diff with old filename missing from headers' ' + cat >missing_old_filename.diff <<-\EOF && + diff --git a/f b/f + index d00491f..0000000 + +++ b/f + @@ -1 +0,0 @@ + -1 + EOF + test_must_fail git apply missing_old_filename.diff 2>err && + test_i18ngrep "lacks filename information" err +' + test_done diff --git a/t/t4135-apply-weird-filenames.sh b/t/t4135-apply-weird-filenames.sh index bf5dc57286..c7c688fcc4 100755 --- a/t/t4135-apply-weird-filenames.sh +++ b/t/t4135-apply-weird-filenames.sh @@ -19,7 +19,8 @@ test_expect_success 'setup' ' test_when_finished "rm -f \"tab embedded.txt\"" && test_when_finished "rm -f '\''\"quoteembedded\".txt'\''" && - if touch -- "tab embedded.txt" '\''"quoteembedded".txt'\'' + if test_have_prereq !MINGW && + touch -- "tab embedded.txt" '\''"quoteembedded".txt'\'' then test_set_prereq FUNNYNAMES fi @@ -88,4 +89,21 @@ test_expect_success 'traditional, whitespace-damaged, colon in timezone' ' test_cmp expected "post image.txt" ' +cat >diff-from-svn <<\EOF +Index: Makefile +=================================================================== +diff --git a/branches/Makefile +deleted file mode 100644 +--- a/branches/Makefile (revision 13) ++++ /dev/null (nonexistent) +@@ +1 0,0 @@ +- +EOF + +test_expect_success 'apply handles a diff generated by Subversion' ' + >Makefile && + git apply -p2 diff-from-svn && + test_path_is_missing Makefile +' + test_done diff --git a/t/t4136-apply-check.sh b/t/t4136-apply-check.sh index a321f7c245..6d92872318 100755 --- a/t/t4136-apply-check.sh +++ b/t/t4136-apply-check.sh @@ -16,4 +16,35 @@ test_expect_success 'apply --check exits non-zero with unrecognized input' ' EOF ' +test_expect_success 'apply exits non-zero with no-op patch' ' + cat >input <<-\EOF && + diff --get a/1 b/1 + index 6696ea4..606eddd 100644 + --- a/1 + +++ b/1 + @@ -1,1 +1,1 @@ + 1 + EOF + test_must_fail git apply --stat input && + test_must_fail git apply --check input +' + +test_expect_success 'invalid combination: create and copy' ' + test_must_fail git apply --check - <<-\EOF + diff --git a/1 b/2 + new file mode 100644 + copy from 1 + copy to 2 + EOF +' + +test_expect_success 'invalid combination: create and rename' ' + test_must_fail git apply --check - <<-\EOF + diff --git a/1 b/2 + new file mode 100644 + rename from 1 + rename to 2 + EOF +' + test_done diff --git a/t/t4150-am.sh b/t/t4150-am.sh index 306e6f39ac..1eccfb71d0 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -40,6 +40,8 @@ test_expect_success 'setup: messages' ' dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. + + Reported-by: A N Other <a.n.other@example.com> EOF cat >failmail <<-\EOF && @@ -67,6 +69,19 @@ test_expect_success 'setup: messages' ' EOF + cat >scissors-msg <<-\EOF && + Test git-am with scissors line + + This line should be included in the commit message. + EOF + + cat - scissors-msg >no-scissors-msg <<-\EOF && + This line should not be included in the commit message with --scissors enabled. + + - - >8 - - remove everything above this line - - >8 - - + + EOF + signoff="Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" ' @@ -80,7 +95,7 @@ test_expect_success setup ' echo world >>file && git add file && test_tick && - git commit -s -F msg && + git commit -F msg && git tag second && git format-patch --stdout first >patch1 && @@ -104,6 +119,48 @@ test_expect_success setup ' echo "X-Fake-Field: Line Three" && git format-patch --stdout first | sed -e "1d" } > patch1-ws.eml && + { + sed -ne "1p" msg && + echo && + echo "From: $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" && + echo "Date: $GIT_AUTHOR_DATE" && + echo && + sed -e "1,2d" msg && + echo "---" && + git diff-tree --no-commit-id --stat -p second + } >patch1-stgit.eml && + mkdir stgit-series && + cp patch1-stgit.eml stgit-series/patch && + { + echo "# This series applies on GIT commit $(git rev-parse first)" && + echo "patch" + } >stgit-series/series && + { + echo "# HG changeset patch" && + echo "# User $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" && + echo "# Date $test_tick 25200" && + echo "# $(git show --pretty="%aD" -s second)" && + echo "# Node ID $_z40" && + echo "# Parent $_z40" && + cat msg && + echo && + git diff-tree --no-commit-id -p second + } >patch1-hg.eml && + + + echo scissors-file >scissors-file && + git add scissors-file && + git commit -F scissors-msg && + git tag scissors && + git format-patch --stdout scissors^ >scissors-patch.eml && + git reset --hard HEAD^ && + + echo no-scissors-file >no-scissors-file && + git add no-scissors-file && + git commit -F no-scissors-msg && + git tag no-scissors && + git format-patch --stdout no-scissors^ >no-scissors-patch.eml && + git reset --hard HEAD^ && sed -n -e "3,\$p" msg >file && git add file && @@ -154,6 +211,18 @@ test_expect_success 'am applies patch correctly' ' test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)" ' +test_expect_success 'am fails if index is dirty' ' + test_when_finished "rm -f dirtyfile" && + rm -fr .git/rebase-apply && + git reset --hard && + git checkout first && + echo dirtyfile >dirtyfile && + git add dirtyfile && + test_must_fail git am patch1 && + test_path_is_dir .git/rebase-apply && + test_cmp_rev first HEAD +' + test_expect_success 'am applies patch e-mail not in a mbox' ' rm -fr .git/rebase-apply && git reset --hard && @@ -187,6 +256,183 @@ test_expect_success 'am applies patch e-mail with preceding whitespace' ' test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)" ' +test_expect_success 'am applies stgit patch' ' + rm -fr .git/rebase-apply && + git checkout -f first && + git am patch1-stgit.eml && + test_path_is_missing .git/rebase-apply && + git diff --exit-code second && + test_cmp_rev second HEAD && + test_cmp_rev second^ HEAD^ +' + +test_expect_success 'am --patch-format=stgit applies stgit patch' ' + rm -fr .git/rebase-apply && + git checkout -f first && + git am --patch-format=stgit <patch1-stgit.eml && + test_path_is_missing .git/rebase-apply && + git diff --exit-code second && + test_cmp_rev second HEAD && + test_cmp_rev second^ HEAD^ +' + +test_expect_success 'am applies stgit series' ' + rm -fr .git/rebase-apply && + git checkout -f first && + git am stgit-series/series && + test_path_is_missing .git/rebase-apply && + git diff --exit-code second && + test_cmp_rev second HEAD && + test_cmp_rev second^ HEAD^ +' + +test_expect_success 'am applies hg patch' ' + rm -fr .git/rebase-apply && + git checkout -f first && + git am patch1-hg.eml && + test_path_is_missing .git/rebase-apply && + git diff --exit-code second && + test_cmp_rev second HEAD && + test_cmp_rev second^ HEAD^ +' + +test_expect_success 'am --patch-format=hg applies hg patch' ' + rm -fr .git/rebase-apply && + git checkout -f first && + git am --patch-format=hg <patch1-hg.eml && + test_path_is_missing .git/rebase-apply && + git diff --exit-code second && + test_cmp_rev second HEAD && + test_cmp_rev second^ HEAD^ +' + +test_expect_success 'am with applypatch-msg hook' ' + test_when_finished "rm -f .git/hooks/applypatch-msg" && + rm -fr .git/rebase-apply && + git reset --hard && + git checkout first && + mkdir -p .git/hooks && + write_script .git/hooks/applypatch-msg <<-\EOF && + cat "$1" >actual-msg && + echo hook-message >"$1" + EOF + git am patch1 && + test_path_is_missing .git/rebase-apply && + git diff --exit-code second && + echo hook-message >expected && + git log -1 --format=format:%B >actual && + test_cmp expected actual && + git log -1 --format=format:%B second >expected && + test_cmp expected actual-msg +' + +test_expect_success 'am with failing applypatch-msg hook' ' + test_when_finished "rm -f .git/hooks/applypatch-msg" && + rm -fr .git/rebase-apply && + git reset --hard && + git checkout first && + mkdir -p .git/hooks && + write_script .git/hooks/applypatch-msg <<-\EOF && + exit 1 + EOF + test_must_fail git am patch1 && + test_path_is_dir .git/rebase-apply && + git diff --exit-code first && + test_cmp_rev first HEAD +' + +test_expect_success 'am with pre-applypatch hook' ' + test_when_finished "rm -f .git/hooks/pre-applypatch" && + rm -fr .git/rebase-apply && + git reset --hard && + git checkout first && + mkdir -p .git/hooks && + write_script .git/hooks/pre-applypatch <<-\EOF && + git diff first >diff.actual + exit 0 + EOF + git am patch1 && + test_path_is_missing .git/rebase-apply && + git diff --exit-code second && + test_cmp_rev second HEAD && + git diff first..second >diff.expected && + test_cmp diff.expected diff.actual +' + +test_expect_success 'am with failing pre-applypatch hook' ' + test_when_finished "rm -f .git/hooks/pre-applypatch" && + rm -fr .git/rebase-apply && + git reset --hard && + git checkout first && + mkdir -p .git/hooks && + write_script .git/hooks/pre-applypatch <<-\EOF && + exit 1 + EOF + test_must_fail git am patch1 && + test_path_is_dir .git/rebase-apply && + git diff --exit-code second && + test_cmp_rev first HEAD +' + +test_expect_success 'am with post-applypatch hook' ' + test_when_finished "rm -f .git/hooks/post-applypatch" && + rm -fr .git/rebase-apply && + git reset --hard && + git checkout first && + mkdir -p .git/hooks && + write_script .git/hooks/post-applypatch <<-\EOF && + git rev-parse HEAD >head.actual + git diff second >diff.actual + exit 0 + EOF + git am patch1 && + test_path_is_missing .git/rebase-apply && + test_cmp_rev second HEAD && + git rev-parse second >head.expected && + test_cmp head.expected head.actual && + git diff second >diff.expected && + test_cmp diff.expected diff.actual +' + +test_expect_success 'am with failing post-applypatch hook' ' + test_when_finished "rm -f .git/hooks/post-applypatch" && + rm -fr .git/rebase-apply && + git reset --hard && + git checkout first && + mkdir -p .git/hooks && + write_script .git/hooks/post-applypatch <<-\EOF && + git rev-parse HEAD >head.actual + exit 1 + EOF + git am patch1 && + test_path_is_missing .git/rebase-apply && + git diff --exit-code second && + test_cmp_rev second HEAD && + git rev-parse second >head.expected && + test_cmp head.expected head.actual +' + +test_expect_success 'am --scissors cuts the message at the scissors line' ' + rm -fr .git/rebase-apply && + git reset --hard && + git checkout second && + git am --scissors scissors-patch.eml && + test_path_is_missing .git/rebase-apply && + git diff --exit-code scissors && + test_cmp_rev scissors HEAD +' + +test_expect_success 'am --no-scissors overrides mailinfo.scissors' ' + rm -fr .git/rebase-apply && + git reset --hard && + git checkout second && + test_config mailinfo.scissors true && + git am --no-scissors no-scissors-patch.eml && + test_path_is_missing .git/rebase-apply && + git diff --exit-code no-scissors && + test_cmp_rev no-scissors HEAD +' + test_expect_success 'setup: new author and committer' ' GIT_AUTHOR_NAME="Another Thor" && GIT_AUTHOR_EMAIL="a.thor@example.com" && @@ -222,13 +468,15 @@ test_expect_success 'am --signoff adds Signed-off-by: line' ' git reset --hard && git checkout -b master2 first && git am --signoff <patch2 && - printf "%s\n" "$signoff" >expected && - echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >>expected && - git cat-file commit HEAD^ | grep "Signed-off-by:" >actual && - test_cmp expected actual && - echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >expected && - git cat-file commit HEAD | grep "Signed-off-by:" >actual && - test_cmp expected actual + { + printf "third\n\nSigned-off-by: %s <%s>\n\n" \ + "$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" && + cat msg && + printf "Signed-off-by: %s <%s>\n\n" \ + "$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" + } >expected-log && + git log --pretty=%B -2 HEAD >actual && + test_cmp expected-log actual ' test_expect_success 'am stays in branch' ' @@ -238,17 +486,60 @@ test_expect_success 'am stays in branch' ' ' test_expect_success 'am --signoff does not add Signed-off-by: line if already there' ' - git format-patch --stdout HEAD^ >patch3 && - sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2] [foo," patch3 >patch4 && - rm -fr .git/rebase-apply && - git reset --hard && - git checkout HEAD^ && - git am --signoff patch4 && - git cat-file commit HEAD >actual && - test $(grep -c "^Signed-off-by:" actual) -eq 1 + git format-patch --stdout first >patch3 && + git reset --hard first && + git am --signoff <patch3 && + git log --pretty=%B -2 HEAD >actual && + test_cmp expected-log actual +' + +test_expect_success 'am --signoff adds Signed-off-by: if another author is preset' ' + NAME="A N Other" && + EMAIL="a.n.other@example.com" && + { + printf "third\n\nSigned-off-by: %s <%s>\nSigned-off-by: %s <%s>\n\n" \ + "$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \ + "$NAME" "$EMAIL" && + cat msg && + printf "Signed-off-by: %s <%s>\nSigned-off-by: %s <%s>\n\n" \ + "$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \ + "$NAME" "$EMAIL" + } >expected-log && + git reset --hard first && + GIT_COMMITTER_NAME="$NAME" GIT_COMMITTER_EMAIL="$EMAIL" \ + git am --signoff <patch3 && + git log --pretty=%B -2 HEAD >actual && + test_cmp expected-log actual +' + +test_expect_success 'am --signoff duplicates Signed-off-by: if it is not the last one' ' + NAME="A N Other" && + EMAIL="a.n.other@example.com" && + { + printf "third\n\nSigned-off-by: %s <%s>\n\ +Signed-off-by: %s <%s>\nSigned-off-by: %s <%s>\n\n" \ + "$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \ + "$NAME" "$EMAIL" \ + "$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" && + cat msg && + printf "Signed-off-by: %s <%s>\nSigned-off-by: %s <%s>\n\ +Signed-off-by: %s <%s>\n\n" \ + "$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" \ + "$NAME" "$EMAIL" \ + "$GIT_COMMITTER_NAME" "$GIT_COMMITTER_EMAIL" + } >expected-log && + git format-patch --stdout first >patch3 && + git reset --hard first && + git am --signoff <patch3 && + git log --pretty=%B -2 HEAD >actual && + test_cmp expected-log actual ' test_expect_success 'am without --keep removes Re: and [PATCH] stuff' ' + git format-patch --stdout HEAD^ >tmp && + sed -e "/^Subject/ s,\[PATCH,Re: Re: Re: & 1/5 v2] [foo," tmp >patch4 && + git reset --hard HEAD^ && + git am <patch4 && git rev-parse HEAD >expected && git rev-parse master2 >actual && test_cmp expected actual @@ -274,15 +565,21 @@ test_expect_success 'am --keep-non-patch really keeps the non-patch part' ' grep "^\[foo\] third" actual ' -test_expect_success 'am -3 falls back to 3-way merge' ' +test_expect_success 'setup am -3' ' rm -fr .git/rebase-apply && git reset --hard && - git checkout -b lorem2 master2 && + git checkout -b base3way master2 && sed -n -e "3,\$p" msg >file && head -n 9 msg >>file && git add file && test_tick && - git commit -m "copied stuff" && + git commit -m "copied stuff" +' + +test_expect_success 'am -3 falls back to 3-way merge' ' + rm -fr .git/rebase-apply && + git reset --hard && + git checkout -b lorem2 base3way && git am -3 lorem-move.patch && test_path_is_missing .git/rebase-apply && git diff --exit-code lorem @@ -291,17 +588,31 @@ test_expect_success 'am -3 falls back to 3-way merge' ' test_expect_success 'am -3 -p0 can read --no-prefix patch' ' rm -fr .git/rebase-apply && git reset --hard && - git checkout -b lorem3 master2 && - sed -n -e "3,\$p" msg >file && - head -n 9 msg >>file && - git add file && - test_tick && - git commit -m "copied stuff" && + git checkout -b lorem3 base3way && git am -3 -p0 lorem-zero.patch && test_path_is_missing .git/rebase-apply && git diff --exit-code lorem ' +test_expect_success 'am with config am.threeWay falls back to 3-way merge' ' + rm -fr .git/rebase-apply && + git reset --hard && + git checkout -b lorem4 base3way && + test_config am.threeWay 1 && + git am lorem-move.patch && + test_path_is_missing .git/rebase-apply && + git diff --exit-code lorem +' + +test_expect_success 'am with config am.threeWay overridden by --no-3way' ' + rm -fr .git/rebase-apply && + git reset --hard && + git checkout -b lorem5 base3way && + test_config am.threeWay 1 && + test_must_fail git am --no-3way lorem-move.patch && + test_path_is_dir .git/rebase-apply +' + test_expect_success 'am can rename a file' ' grep "^rename from" rename.patch && rm -fr .git/rebase-apply && @@ -338,12 +649,7 @@ test_expect_success 'am -3 can rename a file after falling back to 3-way merge' test_expect_success 'am -3 -q is quiet' ' rm -fr .git/rebase-apply && git checkout -f lorem2 && - git reset master2 --hard && - sed -n -e "3,\$p" msg >file && - head -n 9 msg >>file && - git add file && - test_tick && - git commit -m "copied stuff" && + git reset base3way --hard && git am -3 -q lorem-move.patch >output.out 2>&1 && ! test -s output.out ' @@ -356,6 +662,11 @@ test_expect_success 'am pauses on conflict' ' test -d .git/rebase-apply ' +test_expect_success 'am --show-current-patch' ' + git am --show-current-patch >actual.patch && + test_cmp .git/rebase-apply/0001 actual.patch +' + test_expect_success 'am --skip works' ' echo goodbye >expected && git am --skip && @@ -370,6 +681,20 @@ test_expect_success 'am --abort removes a stray directory' ' test_path_is_missing .git/rebase-apply ' +test_expect_success 'am refuses patches when paused' ' + rm -fr .git/rebase-apply && + git reset --hard && + git checkout lorem2^^ && + + test_must_fail git am lorem-move.patch && + test_path_is_dir .git/rebase-apply && + test_cmp_rev lorem2^^ HEAD && + + test_must_fail git am <lorem-move.patch && + test_path_is_dir .git/rebase-apply && + test_cmp_rev lorem2^^ HEAD +' + test_expect_success 'am --resolved works' ' echo goodbye >expected && rm -fr .git/rebase-apply && @@ -384,6 +709,31 @@ test_expect_success 'am --resolved works' ' test_cmp expected another ' +test_expect_success 'am --resolved fails if index has no changes' ' + rm -fr .git/rebase-apply && + git reset --hard && + git checkout lorem2^^ && + test_must_fail git am lorem-move.patch && + test_path_is_dir .git/rebase-apply && + test_cmp_rev lorem2^^ HEAD && + test_must_fail git am --resolved && + test_path_is_dir .git/rebase-apply && + test_cmp_rev lorem2^^ HEAD +' + +test_expect_success 'am --resolved fails if index has unmerged entries' ' + rm -fr .git/rebase-apply && + git reset --hard && + git checkout second && + test_must_fail git am -3 lorem-move.patch && + test_path_is_dir .git/rebase-apply && + test_cmp_rev second HEAD && + test_must_fail git am --resolved >err && + test_path_is_dir .git/rebase-apply && + test_cmp_rev second HEAD && + test_i18ngrep "still have unmerged paths" err +' + test_expect_success 'am takes patches from a Pine mailbox' ' rm -fr .git/rebase-apply && git reset --hard && @@ -548,6 +898,18 @@ test_expect_success 'am --message-id really adds the message id' ' test_cmp expected actual ' +test_expect_success 'am.messageid really adds the message id' ' + rm -fr .git/rebase-apply && + git reset --hard && + git checkout HEAD^ && + test_config am.messageid true && + git am patch1.eml && + test_path_is_missing .git/rebase-apply && + git cat-file commit HEAD | tail -n1 >actual && + grep Message-Id patch1.eml >expected && + test_cmp expected actual +' + test_expect_success 'am --message-id -s signs off after the message id' ' rm -fr .git/rebase-apply && git reset --hard && @@ -559,4 +921,145 @@ test_expect_success 'am --message-id -s signs off after the message id' ' test_cmp expected actual ' +test_expect_success 'am -3 works with rerere' ' + rm -fr .git/rebase-apply && + git reset --hard && + + # make patches one->two and two->three... + test_commit one file && + test_commit two file && + test_commit three file && + git format-patch -2 --stdout >seq.patch && + + # and create a situation that conflicts... + git reset --hard one && + test_commit other file && + + # enable rerere... + test_config rerere.enabled true && + test_when_finished "rm -rf .git/rr-cache" && + + # ...and apply. Our resolution is to skip the first + # patch, and the rerere the second one. + test_must_fail git am -3 seq.patch && + test_must_fail git am --skip && + echo resolved >file && + git add file && + git am --resolved && + + # now apply again, and confirm that rerere engaged (we still + # expect failure from am because rerere does not auto-commit + # for us). + git reset --hard other && + test_must_fail git am -3 seq.patch && + test_must_fail git am --skip && + echo resolved >expect && + test_cmp expect file +' + +test_expect_success 'am -s unexpected trailer block' ' + rm -fr .git/rebase-apply && + git reset --hard && + echo signed >file && + git add file && + cat >msg <<-EOF && + subject here + + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + [jc: tweaked log message] + Signed-off-by: J C H <j@c.h> + EOF + git commit -F msg && + git cat-file commit HEAD | sed -e '1,/^$/d' >original && + git format-patch --stdout -1 >patch && + + git reset --hard HEAD^ && + git am -s patch && + ( + cat original && + echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" + ) >expect && + git cat-file commit HEAD | sed -e '1,/^$/d' >actual && + test_cmp expect actual && + + cat >msg <<-\EOF && + subject here + + We make sure that there is a blank line between the log + message proper and Signed-off-by: line added. + EOF + git reset HEAD^ && + git commit -F msg file && + git cat-file commit HEAD | sed -e '1,/^$/d' >original && + git format-patch --stdout -1 >patch && + + git reset --hard HEAD^ && + git am -s patch && + + ( + cat original && + echo && + echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" + ) >expect && + git cat-file commit HEAD | sed -e '1,/^$/d' >actual && + test_cmp expect actual +' + +test_expect_success 'am --patch-format=mboxrd handles mboxrd' ' + rm -fr .git/rebase-apply && + git checkout -f first && + echo mboxrd >>file && + git add file && + cat >msg <<-\INPUT_END && + mboxrd should escape the body + + From could trip up a loose mbox parser + >From extra escape for reversibility + INPUT_END + git commit -F msg && + git format-patch --pretty=mboxrd --stdout -1 >mboxrd1 && + grep "^>From could trip up a loose mbox parser" mboxrd1 && + git checkout -f first && + git am --patch-format=mboxrd mboxrd1 && + git cat-file commit HEAD | tail -n4 >out && + test_cmp msg out +' + +test_expect_success 'am works with multi-line in-body headers' ' + FORTY="String that has a length of more than forty characters" && + LONG="$FORTY $FORTY" && + rm -fr .git/rebase-apply && + git checkout -f first && + echo one >> file && + git commit -am "$LONG + + Body test" --author="$LONG <long@example.com>" && + git format-patch --stdout -1 >patch && + # bump from, date, and subject down to in-body header + perl -lpe " + if (/^From:/) { + print \"From: x <x\@example.com>\"; + print \"Date: Sat, 1 Jan 2000 00:00:00 +0000\"; + print \"Subject: x\n\"; + } + " patch >msg && + git checkout HEAD^ && + git am msg && + # Ensure that the author and full message are present + git cat-file commit HEAD | grep "^author.*long@example.com" && + git cat-file commit HEAD | grep "^$LONG$" +' + +test_expect_success 'am --quit keeps HEAD where it is' ' + mkdir .git/rebase-apply && + >.git/rebase-apply/last && + >.git/rebase-apply/next && + git rev-parse HEAD^ >.git/ORIG_HEAD && + git rev-parse HEAD >expected && + git am --quit && + test_path_is_missing .git/rebase-apply && + git rev-parse HEAD >actual && + test_cmp expected actual +' + test_done diff --git a/t/t4151-am-abort.sh b/t/t4151-am-abort.sh index 8d90634ab8..9d8d3c72e7 100755 --- a/t/t4151-am-abort.sh +++ b/t/t4151-am-abort.sh @@ -14,6 +14,7 @@ test_expect_success setup ' git add file-1 file-2 && git commit -m initial && git tag initial && + git format-patch --stdout --root initial >initial.patch && for i in 2 3 4 5 6 do echo $i >>file-1 && @@ -45,9 +46,8 @@ do test_expect_success "am$with3 --skip continue after failed am$with3" ' test_must_fail git am$with3 --skip >output && - test_i18ngrep "^Applying" output >output.applying && - test_i18ngrep "^Applying: 6$" output.applying && - test_i18ncmp file-2-expect file-2 && + test_i18ngrep "^Applying: 6$" output && + test_cmp file-2-expect file-2 && test ! -f .git/MERGE_RR ' @@ -63,6 +63,28 @@ do done +test_expect_success 'am -3 --skip removes otherfile-4' ' + git reset --hard initial && + test_must_fail git am -3 0003-*.patch && + test 3 -eq $(git ls-files -u | wc -l) && + test 4 = "$(cat otherfile-4)" && + git am --skip && + test_cmp_rev initial HEAD && + test -z "$(git ls-files -u)" && + test_path_is_missing otherfile-4 +' + +test_expect_success 'am -3 --abort removes otherfile-4' ' + git reset --hard initial && + test_must_fail git am -3 0003-*.patch && + test 3 -eq $(git ls-files -u | wc -l) && + test 4 = "$(cat otherfile-4)" && + git am --abort && + test_cmp_rev initial HEAD && + test -z "$(git ls-files -u)" && + test_path_is_missing otherfile-4 +' + test_expect_success 'am --abort will keep the local commits intact' ' test_must_fail git am 0004-*.patch && test_commit unrelated && @@ -72,4 +94,101 @@ test_expect_success 'am --abort will keep the local commits intact' ' test_cmp expect actual ' +test_expect_success 'am --abort will keep dirty index intact' ' + git reset --hard initial && + echo dirtyfile >dirtyfile && + cp dirtyfile dirtyfile.expected && + git add dirtyfile && + test_must_fail git am 0001-*.patch && + test_cmp_rev initial HEAD && + test_path_is_file dirtyfile && + test_cmp dirtyfile.expected dirtyfile && + git am --abort && + test_cmp_rev initial HEAD && + test_path_is_file dirtyfile && + test_cmp dirtyfile.expected dirtyfile +' + +test_expect_success 'am -3 stops on conflict on unborn branch' ' + git checkout -f --orphan orphan && + git reset && + rm -f otherfile-4 && + test_must_fail git am -3 0003-*.patch && + test 2 -eq $(git ls-files -u | wc -l) && + test 4 = "$(cat otherfile-4)" +' + +test_expect_success 'am -3 --skip clears index on unborn branch' ' + test_path_is_dir .git/rebase-apply && + echo tmpfile >tmpfile && + git add tmpfile && + git am --skip && + test -z "$(git ls-files)" && + test_path_is_missing otherfile-4 && + test_path_is_missing tmpfile +' + +test_expect_success 'am -3 --abort removes otherfile-4 on unborn branch' ' + git checkout -f --orphan orphan && + git reset && + rm -f otherfile-4 file-1 && + test_must_fail git am -3 0003-*.patch && + test 2 -eq $(git ls-files -u | wc -l) && + test 4 = "$(cat otherfile-4)" && + git am --abort && + test -z "$(git ls-files -u)" && + test_path_is_missing otherfile-4 +' + +test_expect_success 'am -3 --abort on unborn branch removes applied commits' ' + git checkout -f --orphan orphan && + git reset && + rm -f otherfile-4 otherfile-2 file-1 file-2 && + test_must_fail git am -3 initial.patch 0003-*.patch && + test 3 -eq $(git ls-files -u | wc -l) && + test 4 = "$(cat otherfile-4)" && + git am --abort && + test -z "$(git ls-files -u)" && + test_path_is_missing otherfile-4 && + test_path_is_missing file-1 && + test_path_is_missing file-2 && + test 0 -eq $(git log --oneline 2>/dev/null | wc -l) && + test refs/heads/orphan = "$(git symbolic-ref HEAD)" +' + +test_expect_success 'am --abort on unborn branch will keep local commits intact' ' + git checkout -f --orphan orphan && + git reset && + test_must_fail git am 0004-*.patch && + test_commit unrelated2 && + git rev-parse HEAD >expect && + git am --abort && + git rev-parse HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'am --skip leaves index stat info alone' ' + git checkout -f --orphan skip-stat-info && + git reset && + test_commit skip-should-be-untouched && + test-tool chmtime =0 skip-should-be-untouched.t && + git update-index --refresh && + git diff-files --exit-code --quiet && + test_must_fail git am 0001-*.patch && + git am --skip && + git diff-files --exit-code --quiet +' + +test_expect_success 'am --abort leaves index stat info alone' ' + git checkout -f --orphan abort-stat-info && + git reset && + test_commit abort-should-be-untouched && + test-tool chmtime =0 abort-should-be-untouched.t && + git update-index --refresh && + git diff-files --exit-code --quiet && + test_must_fail git am 0001-*.patch && + git am --abort && + git diff-files --exit-code --quiet +' + test_done diff --git a/t/t4153-am-resume-override-opts.sh b/t/t4153-am-resume-override-opts.sh new file mode 100755 index 0000000000..8ea22d1bcb --- /dev/null +++ b/t/t4153-am-resume-override-opts.sh @@ -0,0 +1,102 @@ +#!/bin/sh + +test_description='git-am command-line options override saved options' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-terminal.sh + +format_patch () { + git format-patch --stdout -1 "$1" >"$1".eml +} + +test_expect_success 'setup' ' + test_commit initial file && + test_commit first file && + + git checkout initial && + git mv file file2 && + test_tick && + git commit -m renamed-file && + git tag renamed-file && + + git checkout -b side initial && + test_commit side1 file && + test_commit side2 file && + + format_patch side1 && + format_patch side2 +' + +test_expect_success TTY '--3way overrides --no-3way' ' + rm -fr .git/rebase-apply && + git reset --hard && + git checkout renamed-file && + + # Applying side1 will fail as the file has been renamed. + test_must_fail git am --no-3way side[12].eml && + test_path_is_dir .git/rebase-apply && + test_cmp_rev renamed-file HEAD && + test -z "$(git ls-files -u)" && + + # Applying side1 with am --3way will succeed due to the threeway-merge. + # Applying side2 will fail as --3way does not apply to it. + test_must_fail test_terminal git am --3way </dev/zero && + test_path_is_dir .git/rebase-apply && + test side1 = "$(cat file2)" +' + +test_expect_success '--no-quiet overrides --quiet' ' + rm -fr .git/rebase-apply && + git reset --hard && + git checkout first && + + # Applying side1 will be quiet. + test_must_fail git am --quiet side[123].eml >out && + test_path_is_dir .git/rebase-apply && + test_i18ngrep ! "^Applying: " out && + echo side1 >file && + git add file && + + # Applying side1 will not be quiet. + # Applying side2 will be quiet. + git am --no-quiet --continue >out && + echo "Applying: side1" >expected && + test_i18ncmp expected out +' + +test_expect_success '--signoff overrides --no-signoff' ' + rm -fr .git/rebase-apply && + git reset --hard && + git checkout first && + + test_must_fail git am --no-signoff side[12].eml && + test_path_is_dir .git/rebase-apply && + echo side1 >file && + git add file && + git am --signoff --continue && + + # Applied side1 will be signed off + echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >expected && + git cat-file commit HEAD^ | grep "Signed-off-by:" >actual && + test_cmp expected actual && + + # Applied side2 will not be signed off + test $(git cat-file commit HEAD | grep -c "Signed-off-by:") -eq 0 +' + +test_expect_success TTY '--reject overrides --no-reject' ' + rm -fr .git/rebase-apply && + git reset --hard && + git checkout first && + rm -f file.rej && + + test_must_fail git am --no-reject side1.eml && + test_path_is_dir .git/rebase-apply && + test_path_is_missing file.rej && + + test_must_fail test_terminal git am --reject </dev/zero && + test_path_is_dir .git/rebase-apply && + test_path_is_file file.rej +' + +test_done diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index ed9c91e25b..deafaa3e07 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh @@ -166,7 +166,7 @@ test_expect_success 'first postimage wins' ' git commit -q -a -m "prefer first over second" && test -f $rr/postimage && - oldmtimepost=$(test-chmtime -v -60 $rr/postimage | cut -f 1) && + oldmtimepost=$(test-tool chmtime -v -60 $rr/postimage | cut -f 1) && git checkout -b third master && git show second^:a1 | sed "s/To die: t/To die! T/" >a1 && @@ -179,17 +179,32 @@ test_expect_success 'first postimage wins' ' ' test_expect_success 'rerere updates postimage timestamp' ' - newmtimepost=$(test-chmtime -v +0 $rr/postimage | cut -f 1) && + newmtimepost=$(test-tool chmtime -v +0 $rr/postimage | cut -f 1) && test $oldmtimepost -lt $newmtimepost ' test_expect_success 'rerere clear' ' - rm $rr/postimage && + mv $rr/postimage .git/post-saved && echo "$sha1 a1" | perl -pe "y/\012/\000/" >.git/MERGE_RR && git rerere clear && ! test -d $rr ' +test_expect_success 'leftover directory' ' + git reset --hard && + mkdir -p $rr && + test_must_fail git merge first && + test -f $rr/preimage +' + +test_expect_success 'missing preimage' ' + git reset --hard && + mkdir -p $rr && + cp .git/post-saved $rr/postimage && + test_must_fail git merge first && + test -f $rr/preimage +' + test_expect_success 'set up for garbage collection tests' ' mkdir -p $rr && echo Hello >$rr/preimage && @@ -205,9 +220,9 @@ test_expect_success 'set up for garbage collection tests' ' almost_60_days_ago=$((60-60*86400)) && just_over_60_days_ago=$((-1-60*86400)) && - test-chmtime =$just_over_60_days_ago $rr/preimage && - test-chmtime =$almost_60_days_ago $rr/postimage && - test-chmtime =$almost_15_days_ago $rr2/preimage + test-tool chmtime =$just_over_60_days_ago $rr/preimage && + test-tool chmtime =$almost_60_days_ago $rr/postimage && + test-tool chmtime =$almost_15_days_ago $rr2/preimage ' test_expect_success 'gc preserves young or recently used records' ' @@ -217,13 +232,50 @@ test_expect_success 'gc preserves young or recently used records' ' ' test_expect_success 'old records rest in peace' ' - test-chmtime =$just_over_60_days_ago $rr/postimage && - test-chmtime =$just_over_15_days_ago $rr2/preimage && + test-tool chmtime =$just_over_60_days_ago $rr/postimage && + test-tool chmtime =$just_over_15_days_ago $rr2/preimage && git rerere gc && ! test -f $rr/preimage && ! test -f $rr2/preimage ' +rerere_gc_custom_expiry_test () { + five_days="$1" right_now="$2" + test_expect_success "rerere gc with custom expiry ($five_days, $right_now)" ' + rm -fr .git/rr-cache && + rr=.git/rr-cache/$_z40 && + mkdir -p "$rr" && + >"$rr/preimage" && + >"$rr/postimage" && + + two_days_ago=$((-2*86400)) && + test-tool chmtime =$two_days_ago "$rr/preimage" && + test-tool chmtime =$two_days_ago "$rr/postimage" && + + find .git/rr-cache -type f | sort >original && + + git -c "gc.rerereresolved=$five_days" \ + -c "gc.rerereunresolved=$five_days" rerere gc && + find .git/rr-cache -type f | sort >actual && + test_cmp original actual && + + git -c "gc.rerereresolved=$five_days" \ + -c "gc.rerereunresolved=$right_now" rerere gc && + find .git/rr-cache -type f | sort >actual && + test_cmp original actual && + + git -c "gc.rerereresolved=$right_now" \ + -c "gc.rerereunresolved=$right_now" rerere gc && + find .git/rr-cache -type f | sort >actual && + >expect && + test_cmp expect actual + ' +} + +rerere_gc_custom_expiry_test 5 0 + +rerere_gc_custom_expiry_test 5.days.ago now + test_expect_success 'setup: file2 added differently in two branches' ' git reset --hard && @@ -391,4 +443,141 @@ test_expect_success 'rerere -h' ' test_i18ngrep [Uu]sage help ' +concat_insert () { + last=$1 + shift + cat early && printf "%s\n" "$@" && cat late "$last" +} + +count_pre_post () { + find .git/rr-cache/ -type f -name "preimage*" >actual && + test_line_count = "$1" actual && + find .git/rr-cache/ -type f -name "postimage*" >actual && + test_line_count = "$2" actual +} + +merge_conflict_resolve () { + git reset --hard && + test_must_fail git merge six.1 && + # Resolution is to replace 7 with 6.1 and 6.2 (i.e. take both) + concat_insert short 6.1 6.2 >file1 && + concat_insert long 6.1 6.2 >file2 +} + +test_expect_success 'multiple identical conflicts' ' + rm -fr .git/rr-cache && + mkdir .git/rr-cache && + git reset --hard && + + test_seq 1 6 >early && + >late && + test_seq 11 15 >short && + test_seq 111 120 >long && + concat_insert short >file1 && + concat_insert long >file2 && + git add file1 file2 && + git commit -m base && + git tag base && + git checkout -b six.1 && + concat_insert short 6.1 >file1 && + concat_insert long 6.1 >file2 && + git add file1 file2 && + git commit -m 6.1 && + git checkout -b six.2 HEAD^ && + concat_insert short 6.2 >file1 && + concat_insert long 6.2 >file2 && + git add file1 file2 && + git commit -m 6.2 && + + # At this point, six.1 and six.2 + # - derive from common ancestor that has two files + # 1...6 7 11..15 (file1) and 1...6 7 111..120 (file2) + # - six.1 replaces these 7s with 6.1 + # - six.2 replaces these 7s with 6.2 + + merge_conflict_resolve && + + # Check that rerere knows that file1 and file2 have conflicts + + printf "%s\n" file1 file2 >expect && + git ls-files -u | sed -e "s/^.* //" | sort -u >actual && + test_cmp expect actual && + + git rerere status | sort >actual && + test_cmp expect actual && + + git rerere remaining >actual && + test_cmp expect actual && + + count_pre_post 2 0 && + + # Pretend that the conflicts were made quite some time ago + find .git/rr-cache/ -type f | xargs test-tool chmtime -172800 && + + # Unresolved entries have not expired yet + git -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc && + count_pre_post 2 0 && + + # Unresolved entries have expired + git -c gc.rerereresolved=5 -c gc.rerereunresolved=1 rerere gc && + count_pre_post 0 0 && + + # Recreate the conflicted state + merge_conflict_resolve && + count_pre_post 2 0 && + + # Clear it + git rerere clear && + count_pre_post 0 0 && + + # Recreate the conflicted state + merge_conflict_resolve && + count_pre_post 2 0 && + + # We resolved file1 and file2 + git rerere && + >expect && + git rerere remaining >actual && + test_cmp expect actual && + + # We must have recorded both of them + count_pre_post 2 2 && + + # Now we should be able to resolve them both + git reset --hard && + test_must_fail git merge six.1 && + git rerere && + + >expect && + git rerere remaining >actual && + test_cmp expect actual && + + concat_insert short 6.1 6.2 >file1.expect && + concat_insert long 6.1 6.2 >file2.expect && + test_cmp file1.expect file1 && + test_cmp file2.expect file2 && + + # Forget resolution for file2 + git rerere forget file2 && + echo file2 >expect && + git rerere status >actual && + test_cmp expect actual && + count_pre_post 2 1 && + + # file2 already has correct resolution, so record it again + git rerere && + + # Pretend that the resolutions are old again + find .git/rr-cache/ -type f | xargs test-tool chmtime -172800 && + + # Resolved entries have not expired yet + git -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc && + + count_pre_post 2 2 && + + # Resolved entries have expired + git -c gc.rerereresolved=1 -c gc.rerereunresolved=5 rerere gc && + count_pre_post 0 0 +' + test_done diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh index 7600a3e3e8..ff6649ed9a 100755 --- a/t/t4201-shortlog.sh +++ b/t/t4201-shortlog.sh @@ -9,6 +9,7 @@ test_description='git shortlog . ./test-lib.sh test_expect_success 'setup' ' + test_tick && echo 1 >a1 && git add a1 && tree=$(git write-tree) && @@ -59,7 +60,7 @@ fuzz() { file=$1 && sed " s/$_x40/OBJECT_NAME/g - s/$_x05/OBJID/g + s/$_x35/OBJID/g s/^ \{6\}[CTa].*/ SUBJECT/g s/^ \{8\}[^ ].*/ CONTINUATION/g " <"$file" >"$file.fuzzy" && @@ -81,7 +82,7 @@ test_expect_success 'pretty format' ' test_expect_success '--abbrev' ' sed s/SUBJECT/OBJID/ expect.template >expect && - git shortlog --format="%h" --abbrev=5 HEAD >log && + git shortlog --format="%h" --abbrev=35 HEAD >log && fuzz log >log.predictable && test_cmp expect log.predictable ' @@ -115,11 +116,22 @@ EOF ' test_expect_success !MINGW 'shortlog from non-git directory' ' - git log HEAD >log && + git log --no-expand-tabs HEAD >log && GIT_DIR=non-existing git shortlog -w <log >out && test_cmp expect out ' +test_expect_success !MINGW 'shortlog can read --format=raw output' ' + git log --format=raw HEAD >log && + GIT_DIR=non-existing git shortlog -w <log >out && + test_cmp expect out +' + +test_expect_success 'shortlog from non-git directory refuses extra arguments' ' + test_must_fail env GIT_DIR=non-existing git shortlog foo 2>out && + test_i18ngrep "too many arguments" out +' + test_expect_success 'shortlog should add newline when input line matches wraplen' ' cat >expect <<\EOF && A U Thor (2): @@ -172,26 +184,35 @@ test_expect_success !MINGW 'shortlog encoding' ' git shortlog HEAD~2.. > out && test_cmp expect out' -test_expect_success 'shortlog ignores commits with missing authors' ' - git commit --allow-empty -m normal && - git commit --allow-empty -m soon-to-be-broken && - git cat-file commit HEAD >commit.tmp && - sed "/^author/d" commit.tmp >broken.tmp && - commit=$(git hash-object -w -t commit --stdin <broken.tmp) && - git update-ref HEAD $commit && - cat >expect <<-\EOF && - A U Thor (1): - normal +test_expect_success 'shortlog with revision pseudo options' ' + git shortlog --all && + git shortlog --branches && + git shortlog --exclude=refs/heads/m* --all +' + +test_expect_success 'shortlog with --output=<file>' ' + git shortlog --output=shortlog -1 master >output && + test ! -s output && + test_line_count = 3 shortlog +' +test_expect_success 'shortlog --committer (internal)' ' + git checkout --orphan side && + git commit --allow-empty -m one && + git commit --allow-empty -m two && + GIT_COMMITTER_NAME="Sin Nombre" git commit --allow-empty -m three && + + cat >expect <<-\EOF && + 2 C O Mitter + 1 Sin Nombre EOF - git shortlog HEAD~2.. >actual && + git shortlog -nsc HEAD >actual && test_cmp expect actual ' -test_expect_success 'shortlog with revision pseudo options' ' - git shortlog --all && - git shortlog --branches && - git shortlog --exclude=refs/heads/m* --all +test_expect_success 'shortlog --committer (external)' ' + git log --format=full | git shortlog -nsc >actual && + test_cmp expect actual ' test_done diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 1b2e981a00..25b1f8cc73 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -4,6 +4,7 @@ test_description='git log' . ./test-lib.sh . "$TEST_DIRECTORY/lib-gpg.sh" +. "$TEST_DIRECTORY/lib-terminal.sh" test_expect_success setup ' @@ -101,8 +102,8 @@ test_expect_success 'oneline' ' test_expect_success 'diff-filter=A' ' - git log --pretty="format:%s" --diff-filter=A HEAD > actual && - git log --pretty="format:%s" --diff-filter A HEAD > actual-separate && + git log --no-renames --pretty="format:%s" --diff-filter=A HEAD > actual && + git log --no-renames --pretty="format:%s" --diff-filter A HEAD > actual-separate && printf "fifth\nfourth\nthird\ninitial" > expect && test_cmp expect actual && test_cmp expect actual-separate @@ -119,7 +120,7 @@ test_expect_success 'diff-filter=M' ' test_expect_success 'diff-filter=D' ' - actual=$(git log --pretty="format:%s" --diff-filter=D HEAD) && + actual=$(git log --no-renames --pretty="format:%s" --diff-filter=D HEAD) && expect=$(echo sixth ; echo third) && verbose test "$actual" = "$expect" @@ -146,7 +147,30 @@ test_expect_success 'git log --follow' ' actual=$(git log --follow --pretty="format:%s" ichi) && expect=$(echo third ; echo second ; echo initial) && verbose test "$actual" = "$expect" +' + +test_expect_success 'git config log.follow works like --follow' ' + test_config log.follow true && + actual=$(git log --pretty="format:%s" ichi) && + expect=$(echo third ; echo second ; echo initial) && + verbose test "$actual" = "$expect" +' +test_expect_success 'git config log.follow does not die with multiple paths' ' + test_config log.follow true && + git log --pretty="format:%s" ichi ein +' + +test_expect_success 'git config log.follow does not die with no paths' ' + test_config log.follow true && + git log -- +' + +test_expect_success 'git config log.follow is overridden by --no-follow' ' + test_config log.follow true && + actual=$(git log --no-follow --pretty="format:%s" ichi) && + expect="third" && + verbose test "$actual" = "$expect" ' cat > expect << EOF @@ -165,6 +189,16 @@ test_expect_success 'git log --no-walk=sorted <commits> sorts by commit time' ' ' cat > expect << EOF +=== 804a787 sixth +=== 394ef78 fifth +=== 5d31159 fourth +EOF +test_expect_success 'git log --line-prefix="=== " --no-walk <commits> sorts by commit time' ' + git log --line-prefix="=== " --no-walk --oneline 5d31159 804a787 394ef78 > actual && + test_cmp expect actual +' + +cat > expect << EOF 5d31159 fourth 804a787 sixth 394ef78 fifth @@ -197,14 +231,47 @@ second initial EOF test_expect_success 'log --invert-grep --grep' ' - git log --pretty="tformat:%s" --invert-grep --grep=th --grep=Sec >actual && - test_cmp expect actual + # Fixed + git -c grep.patternType=fixed log --pretty="tformat:%s" --invert-grep --grep=th --grep=Sec >actual && + test_cmp expect actual && + + # POSIX basic + git -c grep.patternType=basic log --pretty="tformat:%s" --invert-grep --grep=t[h] --grep=S[e]c >actual && + test_cmp expect actual && + + # POSIX extended + git -c grep.patternType=basic log --pretty="tformat:%s" --invert-grep --grep=t[h] --grep=S[e]c >actual && + test_cmp expect actual && + + # PCRE + if test_have_prereq PCRE + then + git -c grep.patternType=perl log --pretty="tformat:%s" --invert-grep --grep=t[h] --grep=S[e]c >actual && + test_cmp expect actual + fi ' test_expect_success 'log --invert-grep --grep -i' ' echo initial >expect && - git log --pretty="tformat:%s" --invert-grep -i --grep=th --grep=Sec >actual && - test_cmp expect actual + + # Fixed + git -c grep.patternType=fixed log --pretty="tformat:%s" --invert-grep -i --grep=th --grep=Sec >actual && + test_cmp expect actual && + + # POSIX basic + git -c grep.patternType=basic log --pretty="tformat:%s" --invert-grep -i --grep=t[h] --grep=S[e]c >actual && + test_cmp expect actual && + + # POSIX extended + git -c grep.patternType=extended log --pretty="tformat:%s" --invert-grep -i --grep=t[h] --grep=S[e]c >actual && + test_cmp expect actual && + + # PCRE + if test_have_prereq PCRE + then + git -c grep.patternType=perl log --pretty="tformat:%s" --invert-grep -i --grep=t[h] --grep=S[e]c >actual && + test_cmp expect actual + fi ' test_expect_success 'log --grep option parsing' ' @@ -222,16 +289,157 @@ test_expect_success 'log -i --grep' ' test_expect_success 'log --grep -i' ' echo Second >expect && + + # Fixed git log -1 --pretty="tformat:%s" --grep=sec -i >actual && - test_cmp expect actual + test_cmp expect actual && + + # POSIX basic + git -c grep.patternType=basic log -1 --pretty="tformat:%s" --grep=s[e]c -i >actual && + test_cmp expect actual && + + # POSIX extended + git -c grep.patternType=extended log -1 --pretty="tformat:%s" --grep=s[e]c -i >actual && + test_cmp expect actual && + + # PCRE + if test_have_prereq PCRE + then + git -c grep.patternType=perl log -1 --pretty="tformat:%s" --grep=s[e]c -i >actual && + test_cmp expect actual + fi ' test_expect_success 'log -F -E --grep=<ere> uses ere' ' echo second >expect && - git log -1 --pretty="tformat:%s" -F -E --grep=s.c.nd >actual && + # basic would need \(s\) to do the same + git log -1 --pretty="tformat:%s" -F -E --grep="(s).c.nd" >actual && test_cmp expect actual ' +test_expect_success PCRE 'log -F -E --perl-regexp --grep=<pcre> uses PCRE' ' + test_when_finished "rm -rf num_commits" && + git init num_commits && + ( + cd num_commits && + test_commit 1d && + test_commit 2e + ) && + + # In PCRE \d in [\d] is like saying "0-9", and matches the 2 + # in 2e... + echo 2e >expect && + git -C num_commits log -1 --pretty="tformat:%s" -F -E --perl-regexp --grep="[\d]" >actual && + test_cmp expect actual && + + # ...in POSIX basic and extended it is the same as [d], + # i.e. "d", which matches 1d, but does not match 2e. + echo 1d >expect && + git -C num_commits log -1 --pretty="tformat:%s" -F -E --grep="[\d]" >actual && + test_cmp expect actual +' + +test_expect_success 'log with grep.patternType configuration' ' + >expect && + git -c grep.patterntype=fixed \ + log -1 --pretty=tformat:%s --grep=s.c.nd >actual && + test_cmp expect actual +' + +test_expect_success 'log with grep.patternType configuration and command line' ' + echo second >expect && + git -c grep.patterntype=fixed \ + log -1 --pretty=tformat:%s --basic-regexp --grep=s.c.nd >actual && + test_cmp expect actual +' + +test_expect_success 'log with various grep.patternType configurations & command-lines' ' + git init pattern-type && + ( + cd pattern-type && + test_commit 1 file A && + + # The tagname is overridden here because creating a + # tag called "(1|2)" as test_commit would otherwise + # implicitly do would fail on e.g. MINGW. + test_commit "(1|2)" file B 2 && + + echo "(1|2)" >expect.fixed && + cp expect.fixed expect.basic && + cp expect.fixed expect.extended && + cp expect.fixed expect.perl && + + # A strcmp-like match with fixed. + git -c grep.patternType=fixed log --pretty=tformat:%s \ + --grep="(1|2)" >actual.fixed && + + # POSIX basic matches (, | and ) literally. + git -c grep.patternType=basic log --pretty=tformat:%s \ + --grep="(.|.)" >actual.basic && + + # POSIX extended needs to have | escaped to match it + # literally, whereas under basic this is the same as + # (|2), i.e. it would also match "1". This test checks + # for extended by asserting that it is not matching + # what basic would match. + git -c grep.patternType=extended log --pretty=tformat:%s \ + --grep="\|2" >actual.extended && + if test_have_prereq PCRE + then + # Only PCRE would match [\d]\| with only + # "(1|2)" due to [\d]. POSIX basic would match + # both it and "1" since similarly to the + # extended match above it is the same as + # \([\d]\|\). POSIX extended would + # match neither. + git -c grep.patternType=perl log --pretty=tformat:%s \ + --grep="[\d]\|" >actual.perl && + test_cmp expect.perl actual.perl + fi && + test_cmp expect.fixed actual.fixed && + test_cmp expect.basic actual.basic && + test_cmp expect.extended actual.extended && + + git log --pretty=tformat:%s -F \ + --grep="(1|2)" >actual.fixed.short-arg && + git log --pretty=tformat:%s -E \ + --grep="\|2" >actual.extended.short-arg && + if test_have_prereq PCRE + then + git log --pretty=tformat:%s -P \ + --grep="[\d]\|" >actual.perl.short-arg + else + test_must_fail git log -P \ + --grep="[\d]\|" + fi && + test_cmp expect.fixed actual.fixed.short-arg && + test_cmp expect.extended actual.extended.short-arg && + if test_have_prereq PCRE + then + test_cmp expect.perl actual.perl.short-arg + fi && + + git log --pretty=tformat:%s --fixed-strings \ + --grep="(1|2)" >actual.fixed.long-arg && + git log --pretty=tformat:%s --basic-regexp \ + --grep="(.|.)" >actual.basic.long-arg && + git log --pretty=tformat:%s --extended-regexp \ + --grep="\|2" >actual.extended.long-arg && + if test_have_prereq PCRE + then + git log --pretty=tformat:%s --perl-regexp \ + --grep="[\d]\|" >actual.perl.long-arg && + test_cmp expect.perl actual.perl.long-arg + else + test_must_fail git log --perl-regexp \ + --grep="[\d]\|" + fi && + test_cmp expect.fixed actual.fixed.long-arg && + test_cmp expect.basic actual.basic.long-arg && + test_cmp expect.extended actual.extended.long-arg + ) +' + cat > expect <<EOF * Second * sixth @@ -247,6 +455,21 @@ test_expect_success 'simple log --graph' ' test_cmp expect actual ' +cat > expect <<EOF +123 * Second +123 * sixth +123 * fifth +123 * fourth +123 * third +123 * second +123 * initial +EOF + +test_expect_success 'simple log --graph --line-prefix="123 "' ' + git log --graph --line-prefix="123 " --pretty=tformat:%s >actual && + test_cmp expect actual +' + test_expect_success 'set up merge history' ' git checkout -b side HEAD~4 && test_commit side-1 1 1 && @@ -276,6 +499,49 @@ test_expect_success 'log --graph with merge' ' test_cmp expect actual ' +cat > expect <<\EOF +| | | * Merge branch 'side' +| | | |\ +| | | | * side-2 +| | | | * side-1 +| | | * | Second +| | | * | sixth +| | | * | fifth +| | | * | fourth +| | | |/ +| | | * third +| | | * second +| | | * initial +EOF + +test_expect_success 'log --graph --line-prefix="| | | " with merge' ' + git log --line-prefix="| | | " --graph --date-order --pretty=tformat:%s | + sed "s/ *\$//" >actual && + test_cmp expect actual +' + +cat > expect.colors <<\EOF +* Merge branch 'side' +<BLUE>|<RESET><CYAN>\<RESET> +<BLUE>|<RESET> * side-2 +<BLUE>|<RESET> * side-1 +* <CYAN>|<RESET> Second +* <CYAN>|<RESET> sixth +* <CYAN>|<RESET> fifth +* <CYAN>|<RESET> fourth +<CYAN>|<RESET><CYAN>/<RESET> +* third +* second +* initial +EOF + +test_expect_success 'log --graph with merge with log.graphColors' ' + test_config log.graphColors " blue,invalid-color, cyan, red , " && + git log --color=always --graph --date-order --pretty=tformat:%s | + test_decode_color | sed "s/ *\$//" >actual && + test_cmp expect.colors actual +' + test_expect_success 'log --raw --graph -m with merge' ' git log --raw --graph --oneline -m master | head -n 500 >actual && grep "initial" actual @@ -293,7 +559,7 @@ cat > expect <<\EOF | | | | Merge branch 'side' | | -| * commit side +| * commit tags/side-2 | | Author: A U Thor <author@example.com> | | | | side-2 @@ -415,7 +681,7 @@ test_expect_success 'log --graph with merge' ' ' test_expect_success 'log.decorate configuration' ' - git log --oneline >expect.none && + git log --oneline --no-decorate >expect.none && git log --oneline --decorate >expect.short && git log --oneline --decorate=full >expect.full && @@ -471,6 +737,126 @@ test_expect_success 'log.decorate configuration' ' ' +test_expect_success 'decorate-refs with glob' ' + cat >expect.decorate <<-\EOF && + Merge-tag-reach + Merge-tags-octopus-a-and-octopus-b + seventh + octopus-b (octopus-b) + octopus-a (octopus-a) + reach + EOF + git log -n6 --decorate=short --pretty="tformat:%f%d" \ + --decorate-refs="heads/octopus*" >actual && + test_cmp expect.decorate actual +' + +test_expect_success 'decorate-refs without globs' ' + cat >expect.decorate <<-\EOF && + Merge-tag-reach + Merge-tags-octopus-a-and-octopus-b + seventh + octopus-b + octopus-a + reach (tag: reach) + EOF + git log -n6 --decorate=short --pretty="tformat:%f%d" \ + --decorate-refs="tags/reach" >actual && + test_cmp expect.decorate actual +' + +test_expect_success 'multiple decorate-refs' ' + cat >expect.decorate <<-\EOF && + Merge-tag-reach + Merge-tags-octopus-a-and-octopus-b + seventh + octopus-b (octopus-b) + octopus-a (octopus-a) + reach (tag: reach) + EOF + git log -n6 --decorate=short --pretty="tformat:%f%d" \ + --decorate-refs="heads/octopus*" \ + --decorate-refs="tags/reach" >actual && + test_cmp expect.decorate actual +' + +test_expect_success 'decorate-refs-exclude with glob' ' + cat >expect.decorate <<-\EOF && + Merge-tag-reach (HEAD -> master) + Merge-tags-octopus-a-and-octopus-b + seventh (tag: seventh) + octopus-b (tag: octopus-b) + octopus-a (tag: octopus-a) + reach (tag: reach, reach) + EOF + git log -n6 --decorate=short --pretty="tformat:%f%d" \ + --decorate-refs-exclude="heads/octopus*" >actual && + test_cmp expect.decorate actual +' + +test_expect_success 'decorate-refs-exclude without globs' ' + cat >expect.decorate <<-\EOF && + Merge-tag-reach (HEAD -> master) + Merge-tags-octopus-a-and-octopus-b + seventh (tag: seventh) + octopus-b (tag: octopus-b, octopus-b) + octopus-a (tag: octopus-a, octopus-a) + reach (reach) + EOF + git log -n6 --decorate=short --pretty="tformat:%f%d" \ + --decorate-refs-exclude="tags/reach" >actual && + test_cmp expect.decorate actual +' + +test_expect_success 'multiple decorate-refs-exclude' ' + cat >expect.decorate <<-\EOF && + Merge-tag-reach (HEAD -> master) + Merge-tags-octopus-a-and-octopus-b + seventh (tag: seventh) + octopus-b (tag: octopus-b) + octopus-a (tag: octopus-a) + reach (reach) + EOF + git log -n6 --decorate=short --pretty="tformat:%f%d" \ + --decorate-refs-exclude="heads/octopus*" \ + --decorate-refs-exclude="tags/reach" >actual && + test_cmp expect.decorate actual +' + +test_expect_success 'decorate-refs and decorate-refs-exclude' ' + cat >expect.decorate <<-\EOF && + Merge-tag-reach (master) + Merge-tags-octopus-a-and-octopus-b + seventh + octopus-b + octopus-a + reach (reach) + EOF + git log -n6 --decorate=short --pretty="tformat:%f%d" \ + --decorate-refs="heads/*" \ + --decorate-refs-exclude="heads/oc*" >actual && + test_cmp expect.decorate actual +' + +test_expect_success 'log.decorate config parsing' ' + git log --oneline --decorate=full >expect.full && + git log --oneline --decorate=short >expect.short && + + test_config log.decorate full && + test_config log.mailmap true && + git log --oneline >actual && + test_cmp expect.full actual && + git log --oneline --decorate=short >actual && + test_cmp expect.short actual +' + +test_expect_success TTY 'log output on a TTY' ' + git log --color --oneline --decorate >expect.short && + + test_terminal git log --oneline >actual && + test_cmp expect.short actual +' + test_expect_success 'reflog is expected format' ' git log -g --abbrev-commit --pretty=oneline >expect && git reflog >actual && @@ -825,11 +1211,336 @@ sanitize_output () { } test_expect_success 'log --graph with diff and stats' ' - git log --graph --pretty=short --stat -p >actual && + git log --no-renames --graph --pretty=short --stat -p >actual && + sanitize_output >actual.sanitized <actual && + test_i18ncmp expect actual.sanitized +' + +cat >expect <<\EOF +*** * commit COMMIT_OBJECT_NAME +*** |\ Merge: MERGE_PARENTS +*** | | Author: A U Thor <author@example.com> +*** | | +*** | | Merge HEADS DESCRIPTION +*** | | +*** | * commit COMMIT_OBJECT_NAME +*** | | Author: A U Thor <author@example.com> +*** | | +*** | | reach +*** | | --- +*** | | reach.t | 1 + +*** | | 1 file changed, 1 insertion(+) +*** | | +*** | | diff --git a/reach.t b/reach.t +*** | | new file mode 100644 +*** | | index 0000000..10c9591 +*** | | --- /dev/null +*** | | +++ b/reach.t +*** | | @@ -0,0 +1 @@ +*** | | +reach +*** | | +*** | \ +*** *-. \ commit COMMIT_OBJECT_NAME +*** |\ \ \ Merge: MERGE_PARENTS +*** | | | | Author: A U Thor <author@example.com> +*** | | | | +*** | | | | Merge HEADS DESCRIPTION +*** | | | | +*** | | * | commit COMMIT_OBJECT_NAME +*** | | |/ Author: A U Thor <author@example.com> +*** | | | +*** | | | octopus-b +*** | | | --- +*** | | | octopus-b.t | 1 + +*** | | | 1 file changed, 1 insertion(+) +*** | | | +*** | | | diff --git a/octopus-b.t b/octopus-b.t +*** | | | new file mode 100644 +*** | | | index 0000000..d5fcad0 +*** | | | --- /dev/null +*** | | | +++ b/octopus-b.t +*** | | | @@ -0,0 +1 @@ +*** | | | +octopus-b +*** | | | +*** | * | commit COMMIT_OBJECT_NAME +*** | |/ Author: A U Thor <author@example.com> +*** | | +*** | | octopus-a +*** | | --- +*** | | octopus-a.t | 1 + +*** | | 1 file changed, 1 insertion(+) +*** | | +*** | | diff --git a/octopus-a.t b/octopus-a.t +*** | | new file mode 100644 +*** | | index 0000000..11ee015 +*** | | --- /dev/null +*** | | +++ b/octopus-a.t +*** | | @@ -0,0 +1 @@ +*** | | +octopus-a +*** | | +*** * | commit COMMIT_OBJECT_NAME +*** |/ Author: A U Thor <author@example.com> +*** | +*** | seventh +*** | --- +*** | seventh.t | 1 + +*** | 1 file changed, 1 insertion(+) +*** | +*** | diff --git a/seventh.t b/seventh.t +*** | new file mode 100644 +*** | index 0000000..9744ffc +*** | --- /dev/null +*** | +++ b/seventh.t +*** | @@ -0,0 +1 @@ +*** | +seventh +*** | +*** * commit COMMIT_OBJECT_NAME +*** |\ Merge: MERGE_PARENTS +*** | | Author: A U Thor <author@example.com> +*** | | +*** | | Merge branch 'tangle' +*** | | +*** | * commit COMMIT_OBJECT_NAME +*** | |\ Merge: MERGE_PARENTS +*** | | | Author: A U Thor <author@example.com> +*** | | | +*** | | | Merge branch 'side' (early part) into tangle +*** | | | +*** | * | commit COMMIT_OBJECT_NAME +*** | |\ \ Merge: MERGE_PARENTS +*** | | | | Author: A U Thor <author@example.com> +*** | | | | +*** | | | | Merge branch 'master' (early part) into tangle +*** | | | | +*** | * | | commit COMMIT_OBJECT_NAME +*** | | | | Author: A U Thor <author@example.com> +*** | | | | +*** | | | | tangle-a +*** | | | | --- +*** | | | | tangle-a | 1 + +*** | | | | 1 file changed, 1 insertion(+) +*** | | | | +*** | | | | diff --git a/tangle-a b/tangle-a +*** | | | | new file mode 100644 +*** | | | | index 0000000..7898192 +*** | | | | --- /dev/null +*** | | | | +++ b/tangle-a +*** | | | | @@ -0,0 +1 @@ +*** | | | | +a +*** | | | | +*** * | | | commit COMMIT_OBJECT_NAME +*** |\ \ \ \ Merge: MERGE_PARENTS +*** | | | | | Author: A U Thor <author@example.com> +*** | | | | | +*** | | | | | Merge branch 'side' +*** | | | | | +*** | * | | | commit COMMIT_OBJECT_NAME +*** | | |_|/ Author: A U Thor <author@example.com> +*** | |/| | +*** | | | | side-2 +*** | | | | --- +*** | | | | 2 | 1 + +*** | | | | 1 file changed, 1 insertion(+) +*** | | | | +*** | | | | diff --git a/2 b/2 +*** | | | | new file mode 100644 +*** | | | | index 0000000..0cfbf08 +*** | | | | --- /dev/null +*** | | | | +++ b/2 +*** | | | | @@ -0,0 +1 @@ +*** | | | | +2 +*** | | | | +*** | * | | commit COMMIT_OBJECT_NAME +*** | | | | Author: A U Thor <author@example.com> +*** | | | | +*** | | | | side-1 +*** | | | | --- +*** | | | | 1 | 1 + +*** | | | | 1 file changed, 1 insertion(+) +*** | | | | +*** | | | | diff --git a/1 b/1 +*** | | | | new file mode 100644 +*** | | | | index 0000000..d00491f +*** | | | | --- /dev/null +*** | | | | +++ b/1 +*** | | | | @@ -0,0 +1 @@ +*** | | | | +1 +*** | | | | +*** * | | | commit COMMIT_OBJECT_NAME +*** | | | | Author: A U Thor <author@example.com> +*** | | | | +*** | | | | Second +*** | | | | --- +*** | | | | one | 1 + +*** | | | | 1 file changed, 1 insertion(+) +*** | | | | +*** | | | | diff --git a/one b/one +*** | | | | new file mode 100644 +*** | | | | index 0000000..9a33383 +*** | | | | --- /dev/null +*** | | | | +++ b/one +*** | | | | @@ -0,0 +1 @@ +*** | | | | +case +*** | | | | +*** * | | | commit COMMIT_OBJECT_NAME +*** | |_|/ Author: A U Thor <author@example.com> +*** |/| | +*** | | | sixth +*** | | | --- +*** | | | a/two | 1 - +*** | | | 1 file changed, 1 deletion(-) +*** | | | +*** | | | diff --git a/a/two b/a/two +*** | | | deleted file mode 100644 +*** | | | index 9245af5..0000000 +*** | | | --- a/a/two +*** | | | +++ /dev/null +*** | | | @@ -1 +0,0 @@ +*** | | | -ni +*** | | | +*** * | | commit COMMIT_OBJECT_NAME +*** | | | Author: A U Thor <author@example.com> +*** | | | +*** | | | fifth +*** | | | --- +*** | | | a/two | 1 + +*** | | | 1 file changed, 1 insertion(+) +*** | | | +*** | | | diff --git a/a/two b/a/two +*** | | | new file mode 100644 +*** | | | index 0000000..9245af5 +*** | | | --- /dev/null +*** | | | +++ b/a/two +*** | | | @@ -0,0 +1 @@ +*** | | | +ni +*** | | | +*** * | | commit COMMIT_OBJECT_NAME +*** |/ / Author: A U Thor <author@example.com> +*** | | +*** | | fourth +*** | | --- +*** | | ein | 1 + +*** | | 1 file changed, 1 insertion(+) +*** | | +*** | | diff --git a/ein b/ein +*** | | new file mode 100644 +*** | | index 0000000..9d7e69f +*** | | --- /dev/null +*** | | +++ b/ein +*** | | @@ -0,0 +1 @@ +*** | | +ichi +*** | | +*** * | commit COMMIT_OBJECT_NAME +*** |/ Author: A U Thor <author@example.com> +*** | +*** | third +*** | --- +*** | ichi | 1 + +*** | one | 1 - +*** | 2 files changed, 1 insertion(+), 1 deletion(-) +*** | +*** | diff --git a/ichi b/ichi +*** | new file mode 100644 +*** | index 0000000..9d7e69f +*** | --- /dev/null +*** | +++ b/ichi +*** | @@ -0,0 +1 @@ +*** | +ichi +*** | diff --git a/one b/one +*** | deleted file mode 100644 +*** | index 9d7e69f..0000000 +*** | --- a/one +*** | +++ /dev/null +*** | @@ -1 +0,0 @@ +*** | -ichi +*** | +*** * commit COMMIT_OBJECT_NAME +*** | Author: A U Thor <author@example.com> +*** | +*** | second +*** | --- +*** | one | 2 +- +*** | 1 file changed, 1 insertion(+), 1 deletion(-) +*** | +*** | diff --git a/one b/one +*** | index 5626abf..9d7e69f 100644 +*** | --- a/one +*** | +++ b/one +*** | @@ -1 +1 @@ +*** | -one +*** | +ichi +*** | +*** * commit COMMIT_OBJECT_NAME +*** Author: A U Thor <author@example.com> +*** +*** initial +*** --- +*** one | 1 + +*** 1 file changed, 1 insertion(+) +*** +*** diff --git a/one b/one +*** new file mode 100644 +*** index 0000000..5626abf +*** --- /dev/null +*** +++ b/one +*** @@ -0,0 +1 @@ +*** +one +EOF + +test_expect_success 'log --line-prefix="*** " --graph with diff and stats' ' + git log --line-prefix="*** " --no-renames --graph --pretty=short --stat -p >actual && sanitize_output >actual.sanitized <actual && test_i18ncmp expect actual.sanitized ' +cat >expect <<-\EOF +* reach +| +| A reach.t +* Merge branch 'tangle' +* Merge branch 'side' +|\ +| * side-2 +| +| A 2 +* Second +| +| A one +* sixth + + D a/two +EOF + +test_expect_success 'log --graph with --name-status' ' + git log --graph --format=%s --name-status tangle..reach >actual && + sanitize_output <actual >actual.sanitized && + test_cmp expect actual.sanitized +' + +cat >expect <<-\EOF +* reach +| +| reach.t +* Merge branch 'tangle' +* Merge branch 'side' +|\ +| * side-2 +| +| 2 +* Second +| +| one +* sixth + + a/two +EOF + +test_expect_success 'log --graph with --name-only' ' + git log --graph --format=%s --name-only tangle..reach >actual && + sanitize_output <actual >actual.sanitized && + test_cmp expect actual.sanitized +' + test_expect_success 'dotdot is a parent directory' ' mkdir -p a/b && ( echo sixth && echo fifth ) >expect && @@ -837,12 +1548,15 @@ test_expect_success 'dotdot is a parent directory' ' test_cmp expect actual ' -test_expect_success GPG 'log --graph --show-signature' ' +test_expect_success GPG 'setup signed branch' ' test_when_finished "git reset --hard && git checkout master" && git checkout -b signed master && echo foo >foo && git add foo && - git commit -S -m signed_commit && + git commit -S -m signed_commit +' + +test_expect_success GPG 'log --graph --show-signature' ' git log --graph --show-signature -n1 signed >actual && grep "^| gpg: Signature made" actual && grep "^| gpg: Good signature" actual @@ -867,8 +1581,91 @@ test_expect_success GPG 'log --graph --show-signature for merged tag' ' grep "^| | gpg: Good signature" actual ' +test_expect_success GPG '--no-show-signature overrides --show-signature' ' + git log -1 --show-signature --no-show-signature signed >actual && + ! grep "^gpg:" actual +' + +test_expect_success GPG 'log.showsignature=true behaves like --show-signature' ' + test_config log.showsignature true && + git log -1 signed >actual && + grep "gpg: Signature made" actual && + grep "gpg: Good signature" actual +' + +test_expect_success GPG '--no-show-signature overrides log.showsignature=true' ' + test_config log.showsignature true && + git log -1 --no-show-signature signed >actual && + ! grep "^gpg:" actual +' + +test_expect_success GPG '--show-signature overrides log.showsignature=false' ' + test_config log.showsignature false && + git log -1 --show-signature signed >actual && + grep "gpg: Signature made" actual && + grep "gpg: Good signature" actual +' + test_expect_success 'log --graph --no-walk is forbidden' ' test_must_fail git log --graph --no-walk ' +test_expect_success 'log diagnoses bogus HEAD' ' + git init empty && + test_must_fail git -C empty log 2>stderr && + test_i18ngrep does.not.have.any.commits stderr && + echo 1234abcd >empty/.git/refs/heads/master && + test_must_fail git -C empty log 2>stderr && + test_i18ngrep broken stderr && + echo "ref: refs/heads/invalid.lock" >empty/.git/HEAD && + test_must_fail git -C empty log 2>stderr && + test_i18ngrep broken stderr && + test_must_fail git -C empty log --default totally-bogus 2>stderr && + test_i18ngrep broken stderr +' + +test_expect_success 'log does not default to HEAD when rev input is given' ' + >expect && + git log --branches=does-not-exist >actual && + test_cmp expect actual +' + +test_expect_success 'set up --source tests' ' + git checkout --orphan source-a && + test_commit one && + test_commit two && + git checkout -b source-b HEAD^ && + test_commit three +' + +test_expect_success 'log --source paints branch names' ' + cat >expect <<-\EOF && + 09e12a9 source-b three + 8e393e1 source-a two + 1ac6c77 source-b one + EOF + git log --oneline --source source-a source-b >actual && + test_cmp expect actual +' + +test_expect_success 'log --source paints tag names' ' + git tag -m tagged source-tag && + cat >expect <<-\EOF && + 09e12a9 source-tag three + 8e393e1 source-a two + 1ac6c77 source-tag one + EOF + git log --oneline --source source-tag source-a >actual && + test_cmp expect actual +' + +test_expect_success 'log --source paints symmetric ranges' ' + cat >expect <<-\EOF && + 09e12a9 source-b three + 8e393e1 source-a two + EOF + git log --oneline --source source-a...source-b >actual && + test_cmp expect actual +' + test_done diff --git a/t/t4204-patch-id.sh b/t/t4204-patch-id.sh index baa9d3c82e..0288c17ec6 100755 --- a/t/t4204-patch-id.sh +++ b/t/t4204-patch-id.sh @@ -30,11 +30,11 @@ test_expect_success 'patch-id output is well-formed' ' #calculate patch id. Make sure output is not empty. calc_patch_id () { - name="$1" + patch_name="$1" shift git patch-id "$@" | - sed "s/ .*//" >patch-id_"$name" && - test_line_count -gt 0 patch-id_"$name" + sed "s/ .*//" >patch-id_"$patch_name" && + test_line_count -gt 0 patch-id_"$patch_name" } get_top_diff () { @@ -143,6 +143,20 @@ test_expect_success 'patch-id supports git-format-patch MIME output' ' test_cmp patch-id_master patch-id_same ' +test_expect_success 'patch-id respects config from subdir' ' + test_config patchid.stable true && + mkdir subdir && + + # copy these because test_patch_id() looks for them in + # the current directory + cp bar-then-foo foo-then-bar subdir && + + ( + cd subdir && + test_patch_id irrelevant patchid.stable=true + ) +' + cat >nonl <<\EOF diff --git i/a w/a index e69de29..2e65efe 100644 diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh index 7398605e7b..591f35daaf 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh @@ -126,12 +126,12 @@ test_expect_success 'NUL separation with --stat' ' test_i18ncmp expected actual ' -test_expect_failure 'NUL termination with --stat' ' +test_expect_failure C_LOCALE_OUTPUT 'NUL termination with --stat' ' stat0_part=$(git diff --stat HEAD^ HEAD) && stat1_part=$(git diff-tree --no-commit-id --stat --root HEAD^) && printf "add bar\n$stat0_part\n\0$(commit_msg)\n$stat1_part\n0" >expected && git log -z --stat --pretty="tformat:%s" >actual && - test_i18ncmp expected actual + test_cmp expected actual ' test_expect_success 'setup more commits' ' @@ -145,253 +145,310 @@ test_expect_success 'setup more commits' ' test_expect_success 'left alignment formatting' ' git log --pretty="tformat:%<(40)%s" >actual && - qz_to_tab_space <<EOF >expected && -message two Z -message one Z -add bar Z -$(commit_msg) Z -EOF + qz_to_tab_space <<-EOF >expected && + message two Z + message one Z + add bar Z + $(commit_msg) Z + EOF test_cmp expected actual ' test_expect_success 'left alignment formatting. i18n.logOutputEncoding' ' git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%<(40)%s" >actual && - qz_to_tab_space <<EOF | iconv -f utf-8 -t $test_encoding >expected && -message two Z -message one Z -add bar Z -$(commit_msg) Z -EOF + qz_to_tab_space <<-EOF | iconv -f utf-8 -t $test_encoding >expected && + message two Z + message one Z + add bar Z + $(commit_msg) Z + EOF test_cmp expected actual ' test_expect_success 'left alignment formatting at the nth column' ' git log --pretty="tformat:%h %<|(40)%s" >actual && - qz_to_tab_space <<EOF >expected && -$head1 message two Z -$head2 message one Z -$head3 add bar Z -$head4 $(commit_msg) Z -EOF + qz_to_tab_space <<-EOF >expected && + $head1 message two Z + $head2 message one Z + $head3 add bar Z + $head4 $(commit_msg) Z + EOF + test_cmp expected actual +' + +test_expect_success 'left alignment formatting at the nth column' ' + COLUMNS=50 git log --pretty="tformat:%h %<|(-10)%s" >actual && + qz_to_tab_space <<-EOF >expected && + $head1 message two Z + $head2 message one Z + $head3 add bar Z + $head4 $(commit_msg) Z + EOF test_cmp expected actual ' test_expect_success 'left alignment formatting at the nth column. i18n.logOutputEncoding' ' git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%h %<|(40)%s" >actual && - qz_to_tab_space <<EOF | iconv -f utf-8 -t $test_encoding >expected && -$head1 message two Z -$head2 message one Z -$head3 add bar Z -$head4 $(commit_msg) Z -EOF + qz_to_tab_space <<-EOF | iconv -f utf-8 -t $test_encoding >expected && + $head1 message two Z + $head2 message one Z + $head3 add bar Z + $head4 $(commit_msg) Z + EOF test_cmp expected actual ' test_expect_success 'left alignment formatting with no padding' ' git log --pretty="tformat:%<(1)%s" >actual && - cat <<EOF >expected && -message two -message one -add bar -$(commit_msg) -EOF + cat <<-EOF >expected && + message two + message one + add bar + $(commit_msg) + EOF test_cmp expected actual ' test_expect_success 'left alignment formatting with no padding. i18n.logOutputEncoding' ' git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%<(1)%s" >actual && - cat <<EOF | iconv -f utf-8 -t $test_encoding >expected && -message two -message one -add bar -$(commit_msg) -EOF + cat <<-EOF | iconv -f utf-8 -t $test_encoding >expected && + message two + message one + add bar + $(commit_msg) + EOF test_cmp expected actual ' test_expect_success 'left alignment formatting with trunc' ' git log --pretty="tformat:%<(10,trunc)%s" >actual && - qz_to_tab_space <<EOF >expected && -message .. -message .. -add bar Z -initial... -EOF + qz_to_tab_space <<-\EOF >expected && + message .. + message .. + add bar Z + initial... + EOF test_cmp expected actual ' test_expect_success 'left alignment formatting with trunc. i18n.logOutputEncoding' ' git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%<(10,trunc)%s" >actual && - qz_to_tab_space <<EOF | iconv -f utf-8 -t $test_encoding >expected && -message .. -message .. -add bar Z -initial... -EOF + qz_to_tab_space <<-\EOF | iconv -f utf-8 -t $test_encoding >expected && + message .. + message .. + add bar Z + initial... + EOF test_cmp expected actual ' test_expect_success 'left alignment formatting with ltrunc' ' git log --pretty="tformat:%<(10,ltrunc)%s" >actual && - qz_to_tab_space <<EOF >expected && -..sage two -..sage one -add bar Z -..${sample_utf8_part}lich -EOF + qz_to_tab_space <<-EOF >expected && + ..sage two + ..sage one + add bar Z + ..${sample_utf8_part}lich + EOF test_cmp expected actual ' test_expect_success 'left alignment formatting with ltrunc. i18n.logOutputEncoding' ' git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%<(10,ltrunc)%s" >actual && - qz_to_tab_space <<EOF | iconv -f utf-8 -t $test_encoding >expected && -..sage two -..sage one -add bar Z -..${sample_utf8_part}lich -EOF + qz_to_tab_space <<-EOF | iconv -f utf-8 -t $test_encoding >expected && + ..sage two + ..sage one + add bar Z + ..${sample_utf8_part}lich + EOF test_cmp expected actual ' test_expect_success 'left alignment formatting with mtrunc' ' git log --pretty="tformat:%<(10,mtrunc)%s" >actual && - qz_to_tab_space <<EOF >expected && -mess.. two -mess.. one -add bar Z -init..lich -EOF + qz_to_tab_space <<-\EOF >expected && + mess.. two + mess.. one + add bar Z + init..lich + EOF test_cmp expected actual ' test_expect_success 'left alignment formatting with mtrunc. i18n.logOutputEncoding' ' git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%<(10,mtrunc)%s" >actual && - qz_to_tab_space <<EOF | iconv -f utf-8 -t $test_encoding >expected && -mess.. two -mess.. one -add bar Z -init..lich -EOF + qz_to_tab_space <<-\EOF | iconv -f utf-8 -t $test_encoding >expected && + mess.. two + mess.. one + add bar Z + init..lich + EOF test_cmp expected actual ' test_expect_success 'right alignment formatting' ' git log --pretty="tformat:%>(40)%s" >actual && - qz_to_tab_space <<EOF >expected && -Z message two -Z message one -Z add bar -Z $(commit_msg) -EOF + qz_to_tab_space <<-EOF >expected && + Z message two + Z message one + Z add bar + Z $(commit_msg) + EOF test_cmp expected actual ' test_expect_success 'right alignment formatting. i18n.logOutputEncoding' ' git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%>(40)%s" >actual && - qz_to_tab_space <<EOF | iconv -f utf-8 -t $test_encoding >expected && -Z message two -Z message one -Z add bar -Z $(commit_msg) -EOF + qz_to_tab_space <<-EOF | iconv -f utf-8 -t $test_encoding >expected && + Z message two + Z message one + Z add bar + Z $(commit_msg) + EOF test_cmp expected actual ' test_expect_success 'right alignment formatting at the nth column' ' git log --pretty="tformat:%h %>|(40)%s" >actual && - qz_to_tab_space <<EOF >expected && -$head1 message two -$head2 message one -$head3 add bar -$head4 $(commit_msg) -EOF + qz_to_tab_space <<-EOF >expected && + $head1 message two + $head2 message one + $head3 add bar + $head4 $(commit_msg) + EOF + test_cmp expected actual +' + +test_expect_success 'right alignment formatting at the nth column' ' + COLUMNS=50 git log --pretty="tformat:%h %>|(-10)%s" >actual && + qz_to_tab_space <<-EOF >expected && + $head1 message two + $head2 message one + $head3 add bar + $head4 $(commit_msg) + EOF test_cmp expected actual ' test_expect_success 'right alignment formatting at the nth column. i18n.logOutputEncoding' ' git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%h %>|(40)%s" >actual && - qz_to_tab_space <<EOF | iconv -f utf-8 -t $test_encoding >expected && -$head1 message two -$head2 message one -$head3 add bar -$head4 $(commit_msg) -EOF + qz_to_tab_space <<-EOF | iconv -f utf-8 -t $test_encoding >expected && + $head1 message two + $head2 message one + $head3 add bar + $head4 $(commit_msg) + EOF + test_cmp expected actual +' + +# Note: Space between 'message' and 'two' should be in the same column +# as in previous test. +test_expect_success 'right alignment formatting at the nth column with --graph. i18n.logOutputEncoding' ' + git -c i18n.logOutputEncoding=$test_encoding log --graph --pretty="tformat:%h %>|(40)%s" >actual && + iconv -f utf-8 -t $test_encoding >expected <<-EOF && + * $head1 message two + * $head2 message one + * $head3 add bar + * $head4 $(commit_msg) + EOF test_cmp expected actual ' test_expect_success 'right alignment formatting with no padding' ' git log --pretty="tformat:%>(1)%s" >actual && - cat <<EOF >expected && -message two -message one -add bar -$(commit_msg) -EOF + cat <<-EOF >expected && + message two + message one + add bar + $(commit_msg) + EOF + test_cmp expected actual +' + +test_expect_success 'right alignment formatting with no padding and with --graph' ' + git log --graph --pretty="tformat:%>(1)%s" >actual && + cat <<-EOF >expected && + * message two + * message one + * add bar + * $(commit_msg) + EOF test_cmp expected actual ' test_expect_success 'right alignment formatting with no padding. i18n.logOutputEncoding' ' git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%>(1)%s" >actual && - cat <<EOF | iconv -f utf-8 -t $test_encoding >expected && -message two -message one -add bar -$(commit_msg) -EOF + cat <<-EOF | iconv -f utf-8 -t $test_encoding >expected && + message two + message one + add bar + $(commit_msg) + EOF test_cmp expected actual ' test_expect_success 'center alignment formatting' ' git log --pretty="tformat:%><(40)%s" >actual && - qz_to_tab_space <<EOF >expected && -Z message two Z -Z message one Z -Z add bar Z -Z $(commit_msg) Z -EOF + qz_to_tab_space <<-EOF >expected && + Z message two Z + Z message one Z + Z add bar Z + Z $(commit_msg) Z + EOF test_cmp expected actual ' test_expect_success 'center alignment formatting. i18n.logOutputEncoding' ' git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%><(40)%s" >actual && - qz_to_tab_space <<EOF | iconv -f utf-8 -t $test_encoding >expected && -Z message two Z -Z message one Z -Z add bar Z -Z $(commit_msg) Z -EOF + qz_to_tab_space <<-EOF | iconv -f utf-8 -t $test_encoding >expected && + Z message two Z + Z message one Z + Z add bar Z + Z $(commit_msg) Z + EOF test_cmp expected actual ' test_expect_success 'center alignment formatting at the nth column' ' git log --pretty="tformat:%h %><|(40)%s" >actual && - qz_to_tab_space <<EOF >expected && -$head1 message two Z -$head2 message one Z -$head3 add bar Z -$head4 $(commit_msg) Z -EOF + qz_to_tab_space <<-EOF >expected && + $head1 message two Z + $head2 message one Z + $head3 add bar Z + $head4 $(commit_msg) Z + EOF + test_cmp expected actual +' + +test_expect_success 'center alignment formatting at the nth column' ' + COLUMNS=70 git log --pretty="tformat:%h %><|(-30)%s" >actual && + qz_to_tab_space <<-EOF >expected && + $head1 message two Z + $head2 message one Z + $head3 add bar Z + $head4 $(commit_msg) Z + EOF test_cmp expected actual ' test_expect_success 'center alignment formatting at the nth column. i18n.logOutputEncoding' ' git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%h %><|(40)%s" >actual && - qz_to_tab_space <<EOF | iconv -f utf-8 -t $test_encoding >expected && -$head1 message two Z -$head2 message one Z -$head3 add bar Z -$head4 $(commit_msg) Z -EOF + qz_to_tab_space <<-EOF | iconv -f utf-8 -t $test_encoding >expected && + $head1 message two Z + $head2 message one Z + $head3 add bar Z + $head4 $(commit_msg) Z + EOF test_cmp expected actual ' test_expect_success 'center alignment formatting with no padding' ' git log --pretty="tformat:%><(1)%s" >actual && - cat <<EOF >expected && -message two -message one -add bar -$(commit_msg) -EOF + cat <<-EOF >expected && + message two + message one + add bar + $(commit_msg) + EOF test_cmp expected actual ' @@ -400,34 +457,34 @@ EOF old_head1=$(git rev-parse --verify HEAD~0) test_expect_success 'center alignment formatting with no padding. i18n.logOutputEncoding' ' git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%><(1)%s" >actual && - cat <<EOF | iconv -f utf-8 -t $test_encoding >expected && -message two -message one -add bar -$(commit_msg) -EOF + cat <<-EOF | iconv -f utf-8 -t $test_encoding >expected && + message two + message one + add bar + $(commit_msg) + EOF test_cmp expected actual ' test_expect_success 'left/right alignment formatting with stealing' ' git commit --amend -m short --author "long long long <long@me.com>" && git log --pretty="tformat:%<(10,trunc)%s%>>(10,ltrunc)% an" >actual && - cat <<EOF >expected && -short long long long -message .. A U Thor -add bar A U Thor -initial... A U Thor -EOF + cat <<-\EOF >expected && + short long long long + message .. A U Thor + add bar A U Thor + initial... A U Thor + EOF test_cmp expected actual ' test_expect_success 'left/right alignment formatting with stealing. i18n.logOutputEncoding' ' git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%<(10,trunc)%s%>>(10,ltrunc)% an" >actual && - cat <<EOF | iconv -f utf-8 -t $test_encoding >expected && -short long long long -message .. A U Thor -add bar A U Thor -initial... A U Thor -EOF + cat <<-\EOF | iconv -f utf-8 -t $test_encoding >expected && + short long long long + message .. A U Thor + add bar A U Thor + initial... A U Thor + EOF test_cmp expected actual ' @@ -447,8 +504,10 @@ test_expect_success 'ISO and ISO-strict date formats display the same values' ' ' # get new digests (with no abbreviations) -head1=$(git rev-parse --verify HEAD~0) && -head2=$(git rev-parse --verify HEAD~1) && +test_expect_success 'set up log decoration tests' ' + head1=$(git rev-parse --verify HEAD~0) && + head2=$(git rev-parse --verify HEAD~1) +' test_expect_success 'log decoration properly follows tag chain' ' git tag -a tag1 -m tag1 && @@ -456,24 +515,87 @@ test_expect_success 'log decoration properly follows tag chain' ' git tag -d tag1 && git commit --amend -m shorter && git log --no-walk --tags --pretty="%H %d" --decorate=full >actual && - cat <<EOF >expected && -$head1 (tag: refs/tags/tag2) -$head2 (tag: refs/tags/message-one) -$old_head1 (tag: refs/tags/message-two) -EOF + cat <<-EOF >expected && + $head1 (tag: refs/tags/tag2) + $head2 (tag: refs/tags/message-one) + $old_head1 (tag: refs/tags/message-two) + EOF sort actual >actual1 && test_cmp expected actual1 ' test_expect_success 'clean log decoration' ' git log --no-walk --tags --pretty="%H %D" --decorate=full >actual && - cat >expected <<EOF && -$head1 tag: refs/tags/tag2 -$head2 tag: refs/tags/message-one -$old_head1 tag: refs/tags/message-two -EOF + cat >expected <<-EOF && + $head1 tag: refs/tags/tag2 + $head2 tag: refs/tags/message-one + $old_head1 tag: refs/tags/message-two + EOF sort actual >actual1 && test_cmp expected actual1 ' +cat >trailers <<EOF +Signed-off-by: A U Thor <author@example.com> +Acked-by: A U Thor <author@example.com> +[ v2 updated patch description ] +Signed-off-by: A U Thor + <author@example.com> +EOF + +unfold () { + perl -0pe 's/\n\s+/ /g' +} + +test_expect_success 'set up trailer tests' ' + echo "Some contents" >trailerfile && + git add trailerfile && + git commit -F - <<-EOF + trailers: this commit message has trailers + + This commit is a test commit with trailers at the end. We parse this + message and display the trailers using %(trailers). + + $(cat trailers) + EOF +' + +test_expect_success 'pretty format %(trailers) shows trailers' ' + git log --no-walk --pretty="%(trailers)" >actual && + { + cat trailers && + echo + } >expect && + test_cmp expect actual +' + +test_expect_success '%(trailers:only) shows only "key: value" trailers' ' + git log --no-walk --pretty="%(trailers:only)" >actual && + { + grep -v patch.description <trailers && + echo + } >expect && + test_cmp expect actual +' + +test_expect_success '%(trailers:unfold) unfolds trailers' ' + git log --no-walk --pretty="%(trailers:unfold)" >actual && + { + unfold <trailers && + echo + } >expect && + test_cmp expect actual +' + +test_expect_success ':only and :unfold work together' ' + git log --no-walk --pretty="%(trailers:only,unfold)" >actual && + git log --no-walk --pretty="%(trailers:unfold,only)" >reverse && + test_cmp actual reverse && + { + grep -v patch.description <trailers | unfold && + echo + } >expect && + test_cmp expect actual +' + test_done diff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh index f8008b6a3d..60f040cab8 100755 --- a/t/t4207-log-decoration-colors.sh +++ b/t/t4207-log-decoration-colors.sh @@ -7,11 +7,6 @@ test_description='Test for "git log --decorate" colors' . ./test-lib.sh -get_color () -{ - git config --get-color no.such.slot "$1" -} - test_expect_success setup ' git config diff.color.commit yellow && git config color.decorate.branch green && @@ -20,14 +15,14 @@ test_expect_success setup ' git config color.decorate.stash magenta && git config color.decorate.HEAD cyan && - c_reset=$(get_color reset) && + c_reset="<RESET>" && - c_commit=$(get_color yellow) && - c_branch=$(get_color green) && - c_remoteBranch=$(get_color red) && - c_tag=$(get_color "reverse bold yellow") && - c_stash=$(get_color magenta) && - c_HEAD=$(get_color cyan) && + c_commit="<YELLOW>" && + c_branch="<GREEN>" && + c_remoteBranch="<RED>" && + c_tag="<BOLD;REVERSE;YELLOW>" && + c_stash="<MAGENTA>" && + c_HEAD="<CYAN>" && test_commit A && git clone . other && @@ -44,7 +39,7 @@ test_expect_success setup ' ' cat >expected <<EOF -${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD${c_reset}${c_commit} ->\ +${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD ->\ ${c_reset}${c_branch}master${c_reset}${c_commit},\ ${c_reset}${c_tag}tag: v1.0${c_reset}${c_commit},\ ${c_reset}${c_tag}tag: B${c_reset}${c_commit})${c_reset} B @@ -59,7 +54,8 @@ EOF # to this test since it does not contain any decoration, hence --first-parent test_expect_success 'Commit Decorations Colored Correctly' ' git log --first-parent --abbrev=10 --all --decorate --oneline --color=always | - sed "s/[0-9a-f]\{10,10\}/COMMIT_ID/" >out && + sed "s/[0-9a-f]\{10,10\}/COMMIT_ID/" | + test_decode_color >out && test_cmp expected out ' diff --git a/t/t4208-log-magic-pathspec.sh b/t/t4208-log-magic-pathspec.sh index d8f23f488e..a1705f70cf 100755 --- a/t/t4208-log-magic-pathspec.sh +++ b/t/t4208-log-magic-pathspec.sh @@ -18,7 +18,7 @@ test_expect_success '"git log :/" should not be ambiguous' ' test_expect_success '"git log :/a" should be ambiguous (applied both rev and worktree)' ' : >a && test_must_fail git log :/a 2>error && - grep ambiguous error + test_i18ngrep ambiguous error ' test_expect_success '"git log :/a -- " should not be ambiguous' ' @@ -29,9 +29,15 @@ test_expect_success '"git log -- :/a" should not be ambiguous' ' git log -- :/a ' +# This differs from the ":/a" check above in that :/in looks like a pathspec, +# but doesn't match an actual file. +test_expect_success '"git log :/in" should not be ambiguous' ' + git log :/in +' + test_expect_success '"git log :" should be ambiguous' ' test_must_fail git log : 2>error && - grep ambiguous error + test_i18ngrep ambiguous error ' test_expect_success 'git log -- :' ' @@ -46,6 +52,32 @@ test_expect_success 'git log HEAD -- :/' ' test_cmp expected actual ' +test_expect_success '"git log :^sub" is not ambiguous' ' + git log :^sub +' + +test_expect_success '"git log :^does-not-exist" does not match anything' ' + test_must_fail git log :^does-not-exist +' + +test_expect_success '"git log :!" behaves the same as :^' ' + git log :!sub && + test_must_fail git log :!does-not-exist +' + +test_expect_success '"git log :(exclude)sub" is not ambiguous' ' + git log ":(exclude)sub" +' + +test_expect_success '"git log :(exclude)sub --" must resolve as an object' ' + test_must_fail git log ":(exclude)sub" -- +' + +test_expect_success '"git log :(unknown-magic) complains of bogus magic' ' + test_must_fail git log ":(unknown-magic)" 2>error && + test_i18ngrep pathspec.magic error +' + test_expect_success 'command line pathspec parsing for "git log"' ' git reset --hard && >a && @@ -61,4 +93,23 @@ test_expect_success 'command line pathspec parsing for "git log"' ' git log --merge -- a ' +test_expect_success 'tree_entry_interesting does not match past submodule boundaries' ' + test_when_finished "rm -rf repo submodule" && + git init submodule && + test_commit -C submodule initial && + git init repo && + >"repo/[bracket]" && + git -C repo add "[bracket]" && + test_tick && + git -C repo commit -m bracket && + git -C repo rev-list HEAD -- "[bracket]" >expect && + + git -C repo submodule add ../submodule && + test_tick && + git -C repo commit -m submodule && + + git -C repo rev-list HEAD -- "[bracket]" >actual && + test_cmp expect actual +' + test_done diff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh index 4451127eb2..d0377fae5c 100755 --- a/t/t4211-line-log.sh +++ b/t/t4211-line-log.sh @@ -99,4 +99,21 @@ test_expect_success '-L with --first-parent and a merge' ' git log --first-parent -L 1,1:b.c ' +test_expect_success '-L with --output' ' + git checkout parallel-change && + git log --output=log -L :main:b.c >output && + test ! -s output && + test_line_count = 70 log +' + +test_expect_success 'range_set_union' ' + test_seq 500 > c.c && + git add c.c && + git commit -m "many lines" && + test_seq 1000 > c.c && + git add c.c && + git commit -m "modify many lines" && + git log $(for x in $(test_seq 200); do echo -L $((2*x)),+1:c.c; done) +' + test_done diff --git a/t/t4213-log-tabexpand.sh b/t/t4213-log-tabexpand.sh new file mode 100755 index 0000000000..7f90f58c03 --- /dev/null +++ b/t/t4213-log-tabexpand.sh @@ -0,0 +1,105 @@ +#!/bin/sh + +test_description='log/show --expand-tabs' + +. ./test-lib.sh + +HT=" " +title='tab indent at the beginning of the title line' +body='tab indent on a line in the body' + +# usage: count_expand $indent $numSP $numHT @format_args +count_expand () +{ + expect= + count=$(( $1 + $2 )) ;# expected spaces + while test $count -gt 0 + do + expect="$expect " + count=$(( $count - 1 )) + done + shift 2 + count=$1 ;# expected tabs + while test $count -gt 0 + do + expect="$expect$HT" + count=$(( $count - 1 )) + done + shift + + # The remainder of the command line is "git show -s" options + case " $* " in + *' --pretty=short '*) + line=$title ;; + *) + line=$body ;; + esac + + # Prefix the output with the command line arguments, and + # replace SP with a dot both in the expecte and actual output + # so that test_cmp would show the difference together with the + # breakage in a way easier to consume by the debugging user. + { + echo "git show -s $*" + echo "$expect$line" + } | sed -e 's/ /./g' >expect + + { + echo "git show -s $*" + git show -s "$@" | + sed -n -e "/$line\$/p" + } | sed -e 's/ /./g' >actual + + test_cmp expect actual +} + +test_expand () +{ + fmt=$1 + case "$fmt" in + *=raw | *=short | *=email) + default="0 1" ;; + *) + default="8 0" ;; + esac + case "$fmt" in + *=email) + in=0 ;; + *) + in=4 ;; + esac + test_expect_success "expand/no-expand${fmt:+ for $fmt}" ' + count_expand $in $default $fmt && + count_expand $in 8 0 $fmt --expand-tabs && + count_expand $in 8 0 --expand-tabs $fmt && + count_expand $in 8 0 $fmt --expand-tabs=8 && + count_expand $in 8 0 --expand-tabs=8 $fmt && + count_expand $in 0 1 $fmt --no-expand-tabs && + count_expand $in 0 1 --no-expand-tabs $fmt && + count_expand $in 0 1 $fmt --expand-tabs=0 && + count_expand $in 0 1 --expand-tabs=0 $fmt && + count_expand $in 4 0 $fmt --expand-tabs=4 && + count_expand $in 4 0 --expand-tabs=4 $fmt + ' +} + +test_expect_success 'setup' ' + test_tick && + sed -e "s/Q/$HT/g" <<-EOF >msg && + Q$title + + Q$body + EOF + git commit --allow-empty -F msg +' + +test_expand "" +test_expand --pretty +test_expand --pretty=short +test_expand --pretty=medium +test_expand --pretty=full +test_expand --pretty=fuller +test_expand --pretty=raw +test_expand --pretty=email + +test_done diff --git a/t/t4254-am-corrupt.sh b/t/t4254-am-corrupt.sh index 85716dd6ec..168739c721 100755 --- a/t/t4254-am-corrupt.sh +++ b/t/t4254-am-corrupt.sh @@ -29,9 +29,9 @@ test_expect_success 'try to apply corrupted patch' ' ' test_expect_success 'compare diagnostic; ensure file is still here' ' - echo "fatal: git diff header lacks filename information (line 4)" >expected && + echo "error: git diff header lacks filename information (line 4)" >expected && test_path_is_file f && - test_cmp expected actual + test_i18ncmp expected actual ' test_done diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 4b68bbafbe..af4d9b8876 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -101,7 +101,7 @@ test_expect_success \ ten=0123456789 && hundred=$ten$ten$ten$ten$ten$ten$ten$ten$ten$ten && echo long filename >a/four$hundred && mkdir a/bin && - test-genrandom "frotz" 500000 >a/bin/sh && + test-tool genrandom "frotz" 500000 >a/bin/sh && printf "A\$Format:%s\$O" "$SUBSTFORMAT" >a/substfile1 && printf "A not substituted O" >a/substfile2 && if test_have_prereq SYMLINKS; then @@ -179,11 +179,20 @@ test_expect_success 'git archive --remote' \ 'git archive --remote=. HEAD >b5.tar && test_cmp_bin b.tar b5.tar' +test_expect_success 'git archive --remote with configured remote' ' + git config remote.foo.url . && + ( + cd a && + git archive --remote=foo --output=../b5-nick.tar HEAD + ) && + test_cmp_bin b.tar b5-nick.tar +' + test_expect_success \ 'validate file modification time' \ 'mkdir extract && "$TAR" xf b.tar -C extract a/a && - test-chmtime -v +0 extract/a/a |cut -f 1 >b.mtime && + test-tool chmtime -v +0 extract/a/a |cut -f 1 >b.mtime && echo "1117231200" >expected.mtime && test_cmp expected.mtime b.mtime' @@ -197,9 +206,15 @@ test_expect_success 'git archive with --output, override inferred format' ' test_cmp_bin b.tar d4.zip ' -test_expect_success \ - 'git archive --list outside of a git repo' \ - 'GIT_DIR=some/non-existing/directory git archive --list' +test_expect_success 'git archive --list outside of a git repo' ' + nongit git archive --list +' + +test_expect_success 'git archive --remote outside of a git repo' ' + git archive HEAD >expect.tar && + nongit git archive --remote="$PWD" HEAD >actual.tar && + test_cmp_bin expect.tar actual.tar +' test_expect_success 'clients cannot access unreachable commits' ' test_commit unreachable && @@ -319,4 +334,78 @@ test_expect_success 'catch non-matching pathspec' ' test_must_fail git archive -v HEAD -- "*.abc" >/dev/null ' +# Pull the size and date of each entry in a tarfile using the system tar. +# +# We'll pull out only the year from the date; that avoids any question of +# timezones impacting the result (as long as we keep our test times away from a +# year boundary; our reference times are all in August). +# +# The output of tar_info is expected to be "<size> <year>", both in decimal. It +# ignores the return value of tar. We have to do this, because some of our test +# input is only partial (the real data is 64GB in some cases). +tar_info () { + "$TAR" tvf "$1" | + awk '{ + split($4, date, "-") + print $3 " " date[1] + }' +} + +# See if our system tar can handle a tar file with huge sizes and dates far in +# the future, and that we can actually parse its output. +# +# The reference file was generated by GNU tar, and the magic time and size are +# both octal 01000000000001, which overflows normal ustar fields. +test_lazy_prereq TAR_HUGE ' + echo "68719476737 4147" >expect && + tar_info "$TEST_DIRECTORY"/t5000/huge-and-future.tar >actual && + test_cmp expect actual +' + +test_expect_success LONG_IS_64BIT 'set up repository with huge blob' ' + obj_d=19 && + obj_f=f9c8273ec45a8938e6999cb59b3ff66739902a && + obj=${obj_d}${obj_f} && + mkdir -p .git/objects/$obj_d && + cp "$TEST_DIRECTORY"/t5000/$obj .git/objects/$obj_d/$obj_f && + rm -f .git/index && + git update-index --add --cacheinfo 100644,$obj,huge && + git commit -m huge +' + +# We expect git to die with SIGPIPE here (otherwise we +# would generate the whole 64GB). +test_expect_success LONG_IS_64BIT 'generate tar with huge size' ' + { + git archive HEAD + echo $? >exit-code + } | test_copy_bytes 4096 >huge.tar && + echo 141 >expect && + test_cmp expect exit-code +' + +test_expect_success TAR_HUGE,LONG_IS_64BIT 'system tar can read our huge size' ' + echo 68719476737 >expect && + tar_info huge.tar | cut -d" " -f1 >actual && + test_cmp expect actual +' + +test_expect_success TIME_IS_64BIT 'set up repository with far-future commit' ' + rm -f .git/index && + echo content >file && + git add file && + GIT_COMMITTER_DATE="@68719476737 +0000" \ + git commit -m "tempori parendum" +' + +test_expect_success TIME_IS_64BIT 'generate tar with future mtime' ' + git archive HEAD >future.tar +' + +test_expect_success TAR_HUGE,TIME_IS_64BIT,TIME_T_IS_64BIT 'system tar can read our future mtime' ' + echo 4147 >expect && + tar_info future.tar | cut -d" " -f2 >actual && + test_cmp expect actual +' + test_done diff --git a/t/t5000/19f9c8273ec45a8938e6999cb59b3ff66739902a b/t/t5000/19f9c8273ec45a8938e6999cb59b3ff66739902a Binary files differnew file mode 100644 index 0000000000..5cbe9ec312 --- /dev/null +++ b/t/t5000/19f9c8273ec45a8938e6999cb59b3ff66739902a diff --git a/t/t5000/huge-and-future.tar b/t/t5000/huge-and-future.tar Binary files differnew file mode 100644 index 0000000000..63155e1855 --- /dev/null +++ b/t/t5000/huge-and-future.tar diff --git a/t/t5001-archive-attr.sh b/t/t5001-archive-attr.sh index b04d955bfa..e9aa97117a 100755 --- a/t/t5001-archive-attr.sh +++ b/t/t5001-archive-attr.sh @@ -7,11 +7,15 @@ test_description='git archive attribute tests' SUBSTFORMAT='%H (%h)%n' test_expect_exists() { - test_expect_success " $1 exists" "test -e $1" + test_expect_${2:-success} " $1 exists" "test -e $1" } test_expect_missing() { - test_expect_success " $1 does not exist" "test ! -e $1" + test_expect_${2:-success} " $1 does not exist" "test ! -e $1" +} + +extract_tar_to_dir () { + (mkdir "$1" && cd "$1" && "$TAR" xf -) <"$1.tar" } test_expect_success 'setup' ' @@ -21,12 +25,19 @@ test_expect_success 'setup' ' echo ignored by tree >ignored-by-tree && echo ignored-by-tree export-ignore >.gitattributes && - git add ignored-by-tree .gitattributes && + mkdir ignored-by-tree.d && + >ignored-by-tree.d/file && + echo ignored-by-tree.d export-ignore >>.gitattributes && + git add ignored-by-tree ignored-by-tree.d .gitattributes && echo ignored by worktree >ignored-by-worktree && echo ignored-by-worktree export-ignore >.gitattributes && git add ignored-by-worktree && + mkdir excluded-by-pathspec.d && + >excluded-by-pathspec.d/file && + git add excluded-by-pathspec.d && + printf "A\$Format:%s\$O" "$SUBSTFORMAT" >nosubstfile && printf "A\$Format:%s\$O" "$SUBSTFORMAT" >substfile1 && printf "A not substituted O" >substfile2 && @@ -46,7 +57,37 @@ test_expect_success 'git archive' ' test_expect_missing archive/ignored test_expect_missing archive/ignored-by-tree +test_expect_missing archive/ignored-by-tree.d +test_expect_missing archive/ignored-by-tree.d/file test_expect_exists archive/ignored-by-worktree +test_expect_exists archive/excluded-by-pathspec.d +test_expect_exists archive/excluded-by-pathspec.d/file + +test_expect_success 'git archive with pathspec' ' + git archive HEAD ":!excluded-by-pathspec.d" >archive-pathspec.tar && + extract_tar_to_dir archive-pathspec +' + +test_expect_missing archive-pathspec/ignored +test_expect_missing archive-pathspec/ignored-by-tree +test_expect_missing archive-pathspec/ignored-by-tree.d +test_expect_missing archive-pathspec/ignored-by-tree.d/file +test_expect_exists archive-pathspec/ignored-by-worktree +test_expect_missing archive-pathspec/excluded-by-pathspec.d +test_expect_missing archive-pathspec/excluded-by-pathspec.d/file + +test_expect_success 'git archive with wildcard pathspec' ' + git archive HEAD ":!excluded-by-p*" >archive-pathspec-wildcard.tar && + extract_tar_to_dir archive-pathspec-wildcard +' + +test_expect_missing archive-pathspec-wildcard/ignored +test_expect_missing archive-pathspec-wildcard/ignored-by-tree +test_expect_missing archive-pathspec-wildcard/ignored-by-tree.d +test_expect_missing archive-pathspec-wildcard/ignored-by-tree.d/file +test_expect_exists archive-pathspec-wildcard/ignored-by-worktree +test_expect_missing archive-pathspec-wildcard/excluded-by-pathspec.d +test_expect_missing archive-pathspec-wildcard/excluded-by-pathspec.d/file test_expect_success 'git archive with worktree attributes' ' git archive --worktree-attributes HEAD >worktree.tar && diff --git a/t/t5002-archive-attr-pattern.sh b/t/t5002-archive-attr-pattern.sh index 6667d159ab..bda6d7d7e9 100755 --- a/t/t5002-archive-attr-pattern.sh +++ b/t/t5002-archive-attr-pattern.sh @@ -76,7 +76,7 @@ test_expect_missing archive/deep/and/slashless/ && test_expect_missing archive/deep/and/slashless/foo && test_expect_missing archive/deep/with/wildcard/ && test_expect_missing archive/deep/with/wildcard/foo && -test_expect_exists archive/one-level-lower/ +test_expect_missing archive/one-level-lower/ test_expect_missing archive/one-level-lower/two-levels-lower/ignored-only-if-dir/ test_expect_missing archive/one-level-lower/two-levels-lower/ignored-ony-if-dir/ignored-by-ignored-dir diff --git a/t/t5003-archive-zip.sh b/t/t5003-archive-zip.sh index 14744b2a4b..55c7870997 100755 --- a/t/t5003-archive-zip.sh +++ b/t/t5003-archive-zip.sh @@ -64,6 +64,12 @@ check_zip() { test_cmp_bin $original/nodiff.crlf $extracted/nodiff.crlf && test_cmp_bin $original/nodiff.lf $extracted/nodiff.lf " + + test_expect_success UNZIP " validate that custom diff is unchanged " " + test_cmp_bin $original/custom.cr $extracted/custom.cr && + test_cmp_bin $original/custom.crlf $extracted/custom.crlf && + test_cmp_bin $original/custom.lf $extracted/custom.lf + " } test_expect_success \ @@ -78,6 +84,9 @@ test_expect_success \ printf "text\r" >a/nodiff.cr && printf "text\r\n" >a/nodiff.crlf && printf "text\n" >a/nodiff.lf && + printf "text\r" >a/custom.cr && + printf "text\r\n" >a/custom.crlf && + printf "text\n" >a/custom.lf && printf "\0\r" >a/binary.cr && printf "\0\r\n" >a/binary.crlf && printf "\0\n" >a/binary.lf && @@ -112,15 +121,20 @@ test_expect_success 'add files to repository' ' test_expect_success 'setup export-subst and diff attributes' ' echo "a/nodiff.* -diff" >>.git/info/attributes && echo "a/diff.* diff" >>.git/info/attributes && + echo "a/custom.* diff=custom" >>.git/info/attributes && + git config diff.custom.binary true && echo "substfile?" export-subst >>.git/info/attributes && git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \ >a/substfile1 ' -test_expect_success \ - 'create bare clone' \ - 'git clone --bare . bare.git && - cp .git/info/attributes bare.git/info/attributes' +test_expect_success 'create bare clone' ' + git clone --bare . bare.git && + cp .git/info/attributes bare.git/info/attributes && + # Recreate our changes to .git/config rather than just copying it, as + # we do not want to clobber core.bare or other settings. + git -C bare.git config diff.custom.binary true +' test_expect_success \ 'remove ignored file' \ diff --git a/t/t5004-archive-corner-cases.sh b/t/t5004-archive-corner-cases.sh index 654addaae3..ced44355ca 100755 --- a/t/t5004-archive-corner-cases.sh +++ b/t/t5004-archive-corner-cases.sh @@ -27,6 +27,9 @@ check_dir() { test_cmp expect actual } +test_lazy_prereq UNZIP_ZIP64_SUPPORT ' + "$GIT_UNZIP" -v | grep ZIP64_SUPPORT +' # bsdtar/libarchive versions before 3.1.3 consider a tar file with a # global pax header that is not followed by a file record as corrupt. @@ -105,14 +108,101 @@ test_expect_success 'archive empty subtree with no pathspec' ' git archive --format=tar $root_tree >subtree-all.tar && make_dir extract && "$TAR" xf subtree-all.tar -C extract && - check_dir extract sub + check_dir extract ' test_expect_success 'archive empty subtree by direct pathspec' ' git archive --format=tar $root_tree -- sub >subtree-path.tar && make_dir extract && "$TAR" xf subtree-path.tar -C extract && - check_dir extract sub + check_dir extract +' + +ZIPINFO=zipinfo + +test_lazy_prereq ZIPINFO ' + n=$("$ZIPINFO" "$TEST_DIRECTORY"/t5004/empty.zip | sed -n "2s/.* //p") + test "x$n" = "x0" +' + +test_expect_success ZIPINFO 'zip archive with many entries' ' + # add a directory with 256 files + mkdir 00 && + for a in 0 1 2 3 4 5 6 7 8 9 a b c d e f + do + for b in 0 1 2 3 4 5 6 7 8 9 a b c d e f + do + : >00/$a$b + done + done && + git add 00 && + git commit -m "256 files in 1 directory" && + + # duplicate it to get 65536 files in 256 directories + subtree=$(git write-tree --prefix=00/) && + for c in 0 1 2 3 4 5 6 7 8 9 a b c d e f + do + for d in 0 1 2 3 4 5 6 7 8 9 a b c d e f + do + echo "040000 tree $subtree $c$d" + done + done >tree && + tree=$(git mktree <tree) && + + # zip them + git archive -o many.zip $tree && + + # check the number of entries in the ZIP file directory + expr 65536 + 256 >expect && + "$ZIPINFO" many.zip | head -2 | sed -n "2s/.* //p" >actual && + test_cmp expect actual +' + +test_expect_success EXPENSIVE,UNZIP,UNZIP_ZIP64_SUPPORT \ + 'zip archive bigger than 4GB' ' + # build string containing 65536 characters + s=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef && + s=$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s && + s=$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s$s && + + # create blob with a length of 65536 + 1 bytes + blob=$(echo $s | git hash-object -w --stdin) && + + # create tree containing 65500 entries of that blob + for i in $(test_seq 1 65500) + do + echo "100644 blob $blob $i" + done >tree && + tree=$(git mktree <tree) && + + # zip it, creating an archive a bit bigger than 4GB + git archive -0 -o many-big.zip $tree && + + "$GIT_UNZIP" -t many-big.zip 9999 65500 && + "$GIT_UNZIP" -t many-big.zip +' + +test_expect_success EXPENSIVE,LONG_IS_64BIT,UNZIP,UNZIP_ZIP64_SUPPORT,ZIPINFO \ + 'zip archive with files bigger than 4GB' ' + # Pack created with: + # dd if=/dev/zero of=file bs=1M count=4100 && git hash-object -w file + mkdir -p .git/objects/pack && + ( + cd .git/objects/pack && + "$GIT_UNZIP" "$TEST_DIRECTORY"/t5004/big-pack.zip + ) && + blob=754a93d6fada4c6873360e6cb4b209132271ab0e && + size=$(expr 4100 "*" 1024 "*" 1024) && + + # create a tree containing the file + tree=$(echo "100644 blob $blob big-file" | git mktree) && + + # zip it, creating an archive with a file bigger than 4GB + git archive -o big.zip $tree && + + "$GIT_UNZIP" -t big.zip && + "$ZIPINFO" big.zip >big.lst && + grep $size big.lst ' test_done diff --git a/t/t5004/big-pack.zip b/t/t5004/big-pack.zip Binary files differnew file mode 100644 index 0000000000..caaf614eee --- /dev/null +++ b/t/t5004/big-pack.zip diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh index e97cfb2ab8..9690dcad4f 100755 --- a/t/t5100-mailinfo.sh +++ b/t/t5100-mailinfo.sh @@ -7,37 +7,39 @@ test_description='git mailinfo and git mailsplit test' . ./test-lib.sh +DATA="$TEST_DIRECTORY/t5100" + test_expect_success 'split sample box' \ - 'git mailsplit -o. "$TEST_DIRECTORY"/t5100/sample.mbox >last && - last=`cat last` && + 'git mailsplit -o. "$DATA/sample.mbox" >last && + last=$(cat last) && echo total is $last && - test `cat last` = 17' + test $(cat last) = 18' check_mailinfo () { mail=$1 opt=$2 mo="$mail$opt" - git mailinfo -u $opt msg$mo patch$mo <$mail >info$mo && - test_cmp "$TEST_DIRECTORY"/t5100/msg$mo msg$mo && - test_cmp "$TEST_DIRECTORY"/t5100/patch$mo patch$mo && - test_cmp "$TEST_DIRECTORY"/t5100/info$mo info$mo + git mailinfo -u $opt "msg$mo" "patch$mo" <"$mail" >"info$mo" && + test_cmp "$DATA/msg$mo" "msg$mo" && + test_cmp "$DATA/patch$mo" "patch$mo" && + test_cmp "$DATA/info$mo" "info$mo" } -for mail in `echo 00*` +for mail in 00* do test_expect_success "mailinfo $mail" ' - check_mailinfo $mail "" && - if test -f "$TEST_DIRECTORY"/t5100/msg$mail--scissors + check_mailinfo "$mail" "" && + if test -f "$DATA/msg$mail--scissors" then - check_mailinfo $mail --scissors + check_mailinfo "$mail" --scissors fi && - if test -f "$TEST_DIRECTORY"/t5100/msg$mail--no-inbody-headers + if test -f "$DATA/msg$mail--no-inbody-headers" then - check_mailinfo $mail --no-inbody-headers + check_mailinfo "$mail" --no-inbody-headers fi && - if test -f "$TEST_DIRECTORY"/t5100/msg$mail--message-id + if test -f "$DATA/msg$mail--message-id" then - check_mailinfo $mail --message-id + check_mailinfo "$mail" --message-id fi ' done @@ -45,29 +47,29 @@ done test_expect_success 'split box with rfc2047 samples' \ 'mkdir rfc2047 && - git mailsplit -orfc2047 "$TEST_DIRECTORY"/t5100/rfc2047-samples.mbox \ + git mailsplit -orfc2047 "$DATA/rfc2047-samples.mbox" \ >rfc2047/last && - last=`cat rfc2047/last` && + last=$(cat rfc2047/last) && echo total is $last && - test `cat rfc2047/last` = 11' + test $(cat rfc2047/last) = 11' -for mail in `echo rfc2047/00*` +for mail in rfc2047/00* do test_expect_success "mailinfo $mail" ' - git mailinfo -u $mail-msg $mail-patch <$mail >$mail-info && + git mailinfo -u "$mail-msg" "$mail-patch" <"$mail" >"$mail-info" && echo msg && - test_cmp "$TEST_DIRECTORY"/t5100/empty $mail-msg && + test_cmp "$DATA/empty" "$mail-msg" && echo patch && - test_cmp "$TEST_DIRECTORY"/t5100/empty $mail-patch && + test_cmp "$DATA/empty" "$mail-patch" && echo info && - test_cmp "$TEST_DIRECTORY"/t5100/rfc2047-info-$(basename $mail) $mail-info + test_cmp "$DATA/rfc2047-info-$(basename $mail)" "$mail-info" ' done test_expect_success 'respect NULs' ' - git mailsplit -d3 -o. "$TEST_DIRECTORY"/t5100/nul-plain && - test_cmp "$TEST_DIRECTORY"/t5100/nul-plain 001 && + git mailsplit -d3 -o. "$DATA/nul-plain" && + test_cmp "$DATA/nul-plain" 001 && (cat 001 | git mailinfo msg patch) && test_line_count = 4 patch @@ -75,40 +77,140 @@ test_expect_success 'respect NULs' ' test_expect_success 'Preserve NULs out of MIME encoded message' ' - git mailsplit -d5 -o. "$TEST_DIRECTORY"/t5100/nul-b64.in && - test_cmp "$TEST_DIRECTORY"/t5100/nul-b64.in 00001 && + git mailsplit -d5 -o. "$DATA/nul-b64.in" && + test_cmp "$DATA/nul-b64.in" 00001 && git mailinfo msg patch <00001 && - test_cmp "$TEST_DIRECTORY"/t5100/nul-b64.expect patch + test_cmp "$DATA/nul-b64.expect" patch ' test_expect_success 'mailinfo on from header without name works' ' mkdir info-from && - git mailsplit -oinfo-from "$TEST_DIRECTORY"/t5100/info-from.in && - test_cmp "$TEST_DIRECTORY"/t5100/info-from.in info-from/0001 && + git mailsplit -oinfo-from "$DATA/info-from.in" && + test_cmp "$DATA/info-from.in" info-from/0001 && git mailinfo info-from/msg info-from/patch \ <info-from/0001 >info-from/out && - test_cmp "$TEST_DIRECTORY"/t5100/info-from.expect info-from/out + test_cmp "$DATA/info-from.expect" info-from/out ' test_expect_success 'mailinfo finds headers after embedded From line' ' mkdir embed-from && - git mailsplit -oembed-from "$TEST_DIRECTORY"/t5100/embed-from.in && - test_cmp "$TEST_DIRECTORY"/t5100/embed-from.in embed-from/0001 && + git mailsplit -oembed-from "$DATA/embed-from.in" && + test_cmp "$DATA/embed-from.in" embed-from/0001 && git mailinfo embed-from/msg embed-from/patch \ <embed-from/0001 >embed-from/out && - test_cmp "$TEST_DIRECTORY"/t5100/embed-from.expect embed-from/out + test_cmp "$DATA/embed-from.expect" embed-from/out ' test_expect_success 'mailinfo on message with quoted >From' ' mkdir quoted-from && - git mailsplit -oquoted-from "$TEST_DIRECTORY"/t5100/quoted-from.in && - test_cmp "$TEST_DIRECTORY"/t5100/quoted-from.in quoted-from/0001 && + git mailsplit -oquoted-from "$DATA/quoted-from.in" && + test_cmp "$DATA/quoted-from.in" quoted-from/0001 && git mailinfo quoted-from/msg quoted-from/patch \ <quoted-from/0001 >quoted-from/out && - test_cmp "$TEST_DIRECTORY"/t5100/quoted-from.expect quoted-from/msg + test_cmp "$DATA/quoted-from.expect" quoted-from/msg +' + +test_expect_success 'mailinfo unescapes with --mboxrd' ' + mkdir mboxrd && + git mailsplit -omboxrd --mboxrd \ + "$DATA/sample.mboxrd" >last && + test x"$(cat last)" = x2 && + for i in 0001 0002 + do + git mailinfo mboxrd/msg mboxrd/patch \ + <mboxrd/$i >mboxrd/out && + test_cmp "$DATA/${i}mboxrd" mboxrd/msg + done && + sp=" " && + echo "From " >expect && + echo "From " >>expect && + echo >> expect && + cat >sp <<-INPUT_END && + From mboxrd Mon Sep 17 00:00:00 2001 + From: trailing spacer <sp@example.com> + Subject: [PATCH] a commit with trailing space + + From$sp + >From$sp + + INPUT_END + + git mailsplit -f2 -omboxrd --mboxrd <sp >last && + test x"$(cat last)" = x1 && + git mailinfo mboxrd/msg mboxrd/patch <mboxrd/0003 && + test_cmp expect mboxrd/msg +' + +test_expect_success 'mailinfo handles rfc2822 quoted-string' ' + mkdir quoted-string && + git mailinfo /dev/null /dev/null <"$DATA/quoted-string.in" \ + >quoted-string/info && + test_cmp "$DATA/quoted-string.expect" quoted-string/info +' + +test_expect_success 'mailinfo handles rfc2822 comment' ' + mkdir comment && + git mailinfo /dev/null /dev/null <"$DATA/comment.in" \ + >comment/info && + test_cmp "$DATA/comment.expect" comment/info +' + +test_expect_success 'mailinfo with mailinfo.scissors config' ' + test_config mailinfo.scissors true && + ( + mkdir sub && + cd sub && + git mailinfo ../msg0014.sc ../patch0014.sc <../0014 >../info0014.sc + ) && + test_cmp "$DATA/msg0014--scissors" msg0014.sc && + test_cmp "$DATA/patch0014--scissors" patch0014.sc && + test_cmp "$DATA/info0014--scissors" info0014.sc +' + + +test_expect_success 'mailinfo no options' ' + subj="$(echo "Subject: [PATCH] [other] [PATCH] message" | + git mailinfo /dev/null /dev/null)" && + test z"$subj" = z"Subject: message" +' + +test_expect_success 'mailinfo -k' ' + subj="$(echo "Subject: [PATCH] [other] [PATCH] message" | + git mailinfo -k /dev/null /dev/null)" && + test z"$subj" = z"Subject: [PATCH] [other] [PATCH] message" +' + +test_expect_success 'mailinfo -b no [PATCH]' ' + subj="$(echo "Subject: [other] message" | + git mailinfo -b /dev/null /dev/null)" && + test z"$subj" = z"Subject: [other] message" +' + +test_expect_success 'mailinfo -b leading [PATCH]' ' + subj="$(echo "Subject: [PATCH] [other] message" | + git mailinfo -b /dev/null /dev/null)" && + test z"$subj" = z"Subject: [other] message" +' + +test_expect_success 'mailinfo -b double [PATCH]' ' + subj="$(echo "Subject: [PATCH] [PATCH] message" | + git mailinfo -b /dev/null /dev/null)" && + test z"$subj" = z"Subject: message" +' + +test_expect_failure 'mailinfo -b trailing [PATCH]' ' + subj="$(echo "Subject: [other] [PATCH] message" | + git mailinfo -b /dev/null /dev/null)" && + test z"$subj" = z"Subject: [other] message" +' + +test_expect_failure 'mailinfo -b separated double [PATCH]' ' + subj="$(echo "Subject: [PATCH] [other] [PATCH] message" | + git mailinfo -b /dev/null /dev/null)" && + test z"$subj" = z"Subject: [other] message" ' test_done diff --git a/t/t5100/0001mboxrd b/t/t5100/0001mboxrd new file mode 100644 index 0000000000..494ec554b9 --- /dev/null +++ b/t/t5100/0001mboxrd @@ -0,0 +1,4 @@ +From the beginning, mbox should have been mboxrd +>From escaped +From not mangled but this line should have been escaped + diff --git a/t/t5100/0002mboxrd b/t/t5100/0002mboxrd new file mode 100644 index 0000000000..71343d41f2 --- /dev/null +++ b/t/t5100/0002mboxrd @@ -0,0 +1,5 @@ + >From unchanged + From also unchanged +no trailing space, no escaping necessary and '>' was intended: +>From + diff --git a/t/t5100/comment.expect b/t/t5100/comment.expect new file mode 100644 index 0000000000..7228177984 --- /dev/null +++ b/t/t5100/comment.expect @@ -0,0 +1,5 @@ +Author: A U Thor (this is (really) a comment (honestly)) +Email: somebody@example.com +Subject: testing comments +Date: Sun, 25 May 2008 00:38:18 -0700 + diff --git a/t/t5100/comment.in b/t/t5100/comment.in new file mode 100644 index 0000000000..c53a192dfe --- /dev/null +++ b/t/t5100/comment.in @@ -0,0 +1,9 @@ +From 1234567890123456789012345678901234567890 Mon Sep 17 00:00:00 2001 +From: "A U Thor" <somebody@example.com> (this is \(really\) a comment (honestly)) +Date: Sun, 25 May 2008 00:38:18 -0700 +Subject: [PATCH] testing comments + + + +--- +patch diff --git a/t/t5100/info0018 b/t/t5100/info0018 new file mode 100644 index 0000000000..d53e7491c7 --- /dev/null +++ b/t/t5100/info0018 @@ -0,0 +1,5 @@ +Author: Another Thor +Email: a.thor@example.com +Subject: This one contains a tab and a space +Date: Fri, 9 Jun 2006 00:44:16 -0700 + diff --git a/t/t5100/info0018--no-inbody-headers b/t/t5100/info0018--no-inbody-headers new file mode 100644 index 0000000000..30b17bd913 --- /dev/null +++ b/t/t5100/info0018--no-inbody-headers @@ -0,0 +1,5 @@ +Author: A U Thor +Email: a.u.thor@example.com +Subject: check multiline inbody headers +Date: Fri, 9 Jun 2006 00:44:16 -0700 + diff --git a/t/t5100/msg0015 b/t/t5100/msg0015 index 4abb3d5c6c..e69de29bb2 100644 --- a/t/t5100/msg0015 +++ b/t/t5100/msg0015 @@ -1,2 +0,0 @@ - - a list - - of stuff diff --git a/t/t5100/msg0018 b/t/t5100/msg0018 new file mode 100644 index 0000000000..56de83d7fc --- /dev/null +++ b/t/t5100/msg0018 @@ -0,0 +1,2 @@ +a commit message + diff --git a/t/t5100/msg0018--no-inbody-headers b/t/t5100/msg0018--no-inbody-headers new file mode 100644 index 0000000000..b1e05d3862 --- /dev/null +++ b/t/t5100/msg0018--no-inbody-headers @@ -0,0 +1,8 @@ +From: Another Thor + <a.thor@example.com> +Subject: This one contains + a tab + and a space + +a commit message + diff --git a/t/t5100/patch0018 b/t/t5100/patch0018 new file mode 100644 index 0000000000..789df6d030 --- /dev/null +++ b/t/t5100/patch0018 @@ -0,0 +1,6 @@ +diff --git a/foo b/foo +index e69de29..d95f3ad 100644 +--- a/foo ++++ b/foo +@@ -0,0 +1 @@ ++content diff --git a/t/t5100/patch0018--no-inbody-headers b/t/t5100/patch0018--no-inbody-headers new file mode 100644 index 0000000000..789df6d030 --- /dev/null +++ b/t/t5100/patch0018--no-inbody-headers @@ -0,0 +1,6 @@ +diff --git a/foo b/foo +index e69de29..d95f3ad 100644 +--- a/foo ++++ b/foo +@@ -0,0 +1 @@ ++content diff --git a/t/t5100/quoted-string.expect b/t/t5100/quoted-string.expect new file mode 100644 index 0000000000..cab1bcebf9 --- /dev/null +++ b/t/t5100/quoted-string.expect @@ -0,0 +1,5 @@ +Author: Author "The Author" Name +Email: somebody@example.com +Subject: testing quoted-pair +Date: Sun, 25 May 2008 00:38:18 -0700 + diff --git a/t/t5100/quoted-string.in b/t/t5100/quoted-string.in new file mode 100644 index 0000000000..e2e627ae23 --- /dev/null +++ b/t/t5100/quoted-string.in @@ -0,0 +1,9 @@ +From 1234567890123456789012345678901234567890 Mon Sep 17 00:00:00 2001 +From: "Author \"The Author\" Name" <somebody@example.com> +Date: Sun, 25 May 2008 00:38:18 -0700 +Subject: [PATCH] testing quoted-pair + + + +--- +patch diff --git a/t/t5100/sample.mbox b/t/t5100/sample.mbox index 8b2ae064c3..6d4d0e4474 100644 --- a/t/t5100/sample.mbox +++ b/t/t5100/sample.mbox @@ -699,3 +699,22 @@ index e69de29..d95f3ad 100644 +++ b/foo @@ -0,0 +1 @@ +New content +From nobody Mon Sep 17 00:00:00 2001 +From: A U Thor <a.u.thor@example.com> +Subject: check multiline inbody headers +Date: Fri, 9 Jun 2006 00:44:16 -0700 + +From: Another Thor + <a.thor@example.com> +Subject: This one contains + a tab + and a space + +a commit message + +diff --git a/foo b/foo +index e69de29..d95f3ad 100644 +--- a/foo ++++ b/foo +@@ -0,0 +1 @@ ++content diff --git a/t/t5100/sample.mboxrd b/t/t5100/sample.mboxrd new file mode 100644 index 0000000000..79ad5ae0e7 --- /dev/null +++ b/t/t5100/sample.mboxrd @@ -0,0 +1,19 @@ +From mboxrd Mon Sep 17 00:00:00 2001 +From: mboxrd writer <mboxrd@example.com> +Date: Fri, 9 Jun 2006 00:44:16 -0700 +Subject: [PATCH] a commit with escaped From lines + +>From the beginning, mbox should have been mboxrd +>>From escaped +From not mangled but this line should have been escaped + +From mboxrd Mon Sep 17 00:00:00 2001 +From: mboxrd writer <mboxrd@example.com> +Date: Fri, 9 Jun 2006 00:44:16 -0700 +Subject: [PATCH 2/2] another with fake From lines + + >From unchanged + From also unchanged +no trailing space, no escaping necessary and '>' was intended: +>From + diff --git a/t/t5150-request-pull.sh b/t/t5150-request-pull.sh index 82c33b88e7..08c210f035 100755 --- a/t/t5150-request-pull.sh +++ b/t/t5150-request-pull.sh @@ -68,7 +68,7 @@ test_expect_success 'setup: two scripts for reading pull requests' ' cat <<-\EOT >read-request.sed && #!/bin/sed -nf # Note that a request could ask for "tag $tagname" - / in the git repository at:$/!d + / in the Git repository at:$/!d n /^$/ n s/ tag \([^ ]*\)$/ tag--\1/ @@ -192,7 +192,7 @@ test_expect_success 'pull request format' ' SUBJECT (DATE) - are available in the git repository at: + are available in the Git repository at: URL BRANCH diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index 20c1961515..65ff60f2ee 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -8,7 +8,7 @@ test_description='git pack-object ' . ./test-lib.sh -TRASH=`pwd` +TRASH=$(pwd) test_expect_success \ 'setup' \ @@ -16,12 +16,12 @@ test_expect_success \ perl -e "print \"a\" x 4096;" > a && perl -e "print \"b\" x 4096;" > b && perl -e "print \"c\" x 4096;" > c && - test-genrandom "seed a" 2097152 > a_big && - test-genrandom "seed b" 2097152 > b_big && + test-tool genrandom "seed a" 2097152 > a_big && + test-tool genrandom "seed b" 2097152 > b_big && git update-index --add a a_big b b_big c && cat c >d && echo foo >>d && git update-index --add d && - tree=`git write-tree` && - commit=`git commit-tree $tree </dev/null` && { + tree=$(git write-tree) && + commit=$(git commit-tree $tree </dev/null) && { echo $tree && echo $commit && git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\) .*/\\1/" @@ -29,7 +29,7 @@ test_expect_success \ git diff-tree --root -p $commit && while read object do - t=`git cat-file -t $object` && + t=$(git cat-file -t $object) && git cat-file $t $object || return 1 done <obj-list } >expect' @@ -147,7 +147,7 @@ test_expect_success \ git diff-tree --root -p $commit && while read object do - t=`git cat-file -t $object` && + t=$(git cat-file -t $object) && git cat-file $t $object || return 1 done <obj-list } >current && @@ -162,7 +162,7 @@ test_expect_success \ git diff-tree --root -p $commit && while read object do - t=`git cat-file -t $object` && + t=$(git cat-file -t $object) && git cat-file $t $object || return 1 done <obj-list } >current && @@ -177,7 +177,7 @@ test_expect_success \ git diff-tree --root -p $commit && while read object do - t=`git cat-file -t $object` && + t=$(git cat-file -t $object) && git cat-file $t $object || return 1 done <obj-list } >current && @@ -252,8 +252,8 @@ test_expect_success \ test_expect_success \ 'verify-pack catches a corrupted sum of the index file itself' \ - 'l=`wc -c <test-3.idx` && - l=`expr $l - 20` && + 'l=$(wc -c <test-3.idx) && + l=$(expr $l - 20) && cat test-1-${packname_1}.pack >test-3.pack && printf "%20s" "" | dd of=test-3.idx count=20 bs=1 conv=notrunc seek=$l && if git verify-pack test-3.pack @@ -284,6 +284,12 @@ test_expect_success \ git index-pack test-3.pack && cmp test-3.idx test-3-${packname_3}.idx && + cat test-1-${packname_1}.pack >test-4.pack && + rm -f test-4.keep && + git index-pack --keep=why test-4.pack && + cmp test-1-${packname_1}.idx test-4.idx && + test -f test-4.keep && + :' test_expect_success 'unpacking with --strict' ' @@ -305,8 +311,8 @@ test_expect_success 'unpacking with --strict' ' rm -f .git/index && tail -n 10 LIST | git update-index --index-info && ST=$(git write-tree) && - PACK5=$( git rev-list --objects "$LIST" "$LI" "$ST" | \ - git pack-objects test-5 ) && + git rev-list --objects "$LIST" "$LI" "$ST" >actual && + PACK5=$( git pack-objects test-5 <actual ) && PACK6=$( ( echo "$LIST" echo "$LI" @@ -352,8 +358,8 @@ test_expect_success 'index-pack with --strict' ' rm -f .git/index && tail -n 10 LIST | git update-index --index-info && ST=$(git write-tree) && - PACK5=$( git rev-list --objects "$LIST" "$LI" "$ST" | \ - git pack-objects test-5 ) && + git rev-list --objects "$LIST" "$LI" "$ST" >actual && + PACK5=$( git pack-objects test-5 <actual ) && PACK6=$( ( echo "$LIST" echo "$LI" @@ -400,6 +406,57 @@ test_expect_success 'verify resulting packs' ' git verify-pack test-11-*.pack ' +test_expect_success 'set up pack for non-repo tests' ' + # make sure we have a pack with no matching index file + cp test-1-*.pack foo.pack +' + +test_expect_success 'index-pack --stdin complains of non-repo' ' + nongit test_must_fail git index-pack --stdin <foo.pack && + test_path_is_missing non-repo/.git +' + +test_expect_success 'index-pack <pack> works in non-repo' ' + nongit git index-pack ../foo.pack && + test_path_is_file foo.idx +' + +test_expect_success !PTHREADS,C_LOCALE_OUTPUT 'index-pack --threads=N or pack.threads=N warns when no pthreads' ' + test_must_fail git index-pack --threads=2 2>err && + grep ^warning: err >warnings && + test_line_count = 1 warnings && + grep -F "no threads support, ignoring --threads=2" err && + + test_must_fail git -c pack.threads=2 index-pack 2>err && + grep ^warning: err >warnings && + test_line_count = 1 warnings && + grep -F "no threads support, ignoring pack.threads" err && + + test_must_fail git -c pack.threads=2 index-pack --threads=4 2>err && + grep ^warning: err >warnings && + test_line_count = 2 warnings && + grep -F "no threads support, ignoring --threads=4" err && + grep -F "no threads support, ignoring pack.threads" err +' + +test_expect_success !PTHREADS,C_LOCALE_OUTPUT 'pack-objects --threads=N or pack.threads=N warns when no pthreads' ' + git pack-objects --threads=2 --stdout --all </dev/null >/dev/null 2>err && + grep ^warning: err >warnings && + test_line_count = 1 warnings && + grep -F "no threads support, ignoring --threads" err && + + git -c pack.threads=2 pack-objects --stdout --all </dev/null >/dev/null 2>err && + grep ^warning: err >warnings && + test_line_count = 1 warnings && + grep -F "no threads support, ignoring pack.threads" err && + + git -c pack.threads=2 pack-objects --threads=4 --stdout --all </dev/null >/dev/null 2>err && + grep ^warning: err >warnings && + test_line_count = 2 warnings && + grep -F "no threads support, ignoring --threads" err && + grep -F "no threads support, ignoring pack.threads" err +' + # # WARNING! # diff --git a/t/t5301-sliding-window.sh b/t/t5301-sliding-window.sh index 2fc5af6007..76f9798ab9 100755 --- a/t/t5301-sliding-window.sh +++ b/t/t5301-sliding-window.sh @@ -12,16 +12,16 @@ test_expect_success \ for i in a b c do echo $i >$i && - test-genrandom "$i" 32768 >>$i && + test-tool genrandom "$i" 32768 >>$i && git update-index --add $i || return 1 done && echo d >d && cat c >>d && git update-index --add d && - tree=`git write-tree` && - commit1=`git commit-tree $tree </dev/null` && + tree=$(git write-tree) && + commit1=$(git commit-tree $tree </dev/null) && git update-ref HEAD $commit1 && git repack -a -d && - test "`git count-objects`" = "0 objects, 0 kilobytes" && - pack1=`ls .git/objects/pack/*.pack` && + test "$(git count-objects)" = "0 objects, 0 kilobytes" && + pack1=$(ls .git/objects/pack/*.pack) && test -f "$pack1"' test_expect_success \ @@ -43,11 +43,11 @@ test_expect_success \ 'repack -a -d, packedGit{WindowSize,Limit} == 1 page' \ 'git config core.packedGitWindowSize 512 && git config core.packedGitLimit 512 && - commit2=`git commit-tree $tree -p $commit1 </dev/null` && + commit2=$(git commit-tree $tree -p $commit1 </dev/null) && git update-ref HEAD $commit2 && git repack -a -d && - test "`git count-objects`" = "0 objects, 0 kilobytes" && - pack2=`ls .git/objects/pack/*.pack` && + test "$(git count-objects)" = "0 objects, 0 kilobytes" && + pack2=$(ls .git/objects/pack/*.pack) && test -f "$pack2" && test "$pack1" \!= "$pack2"' diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh index 61bc8da560..bb9b8bb309 100755 --- a/t/t5302-pack-index.sh +++ b/t/t5302-pack-index.sh @@ -14,21 +14,21 @@ test_expect_success \ i=1 && while test $i -le 100 do - iii=`printf '%03i' $i` - test-genrandom "bar" 200 > wide_delta_$iii && - test-genrandom "baz $iii" 50 >> wide_delta_$iii && - test-genrandom "foo"$i 100 > deep_delta_$iii && - test-genrandom "foo"`expr $i + 1` 100 >> deep_delta_$iii && - test-genrandom "foo"`expr $i + 2` 100 >> deep_delta_$iii && + iii=$(printf '%03i' $i) + test-tool genrandom "bar" 200 > wide_delta_$iii && + test-tool genrandom "baz $iii" 50 >> wide_delta_$iii && + test-tool genrandom "foo"$i 100 > deep_delta_$iii && + test-tool genrandom "foo"$(expr $i + 1) 100 >> deep_delta_$iii && + test-tool genrandom "foo"$(expr $i + 2) 100 >> deep_delta_$iii && echo $iii >file_$iii && - test-genrandom "$iii" 8192 >>file_$iii && + test-tool genrandom "$iii" 8192 >>file_$iii && git update-index --add file_$iii deep_delta_$iii wide_delta_$iii && - i=`expr $i + 1` || return 1 + i=$(expr $i + 1) || return 1 done && - { echo 101 && test-genrandom 100 8192; } >file_101 && + { echo 101 && test-tool genrandom 100 8192; } >file_101 && git update-index --add file_101 && - tree=`git write-tree` && - commit=`git commit-tree $tree </dev/null` && { + tree=$(git write-tree) && + commit=$(git commit-tree $tree </dev/null) && { echo $tree && git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\) .*/\\1/" } >obj-list && @@ -152,10 +152,10 @@ test_expect_success \ '[index v1] 2) create a stealth corruption in a delta base reference' \ '# This test assumes file_101 is a delta smaller than 16 bytes. # It should be against file_100 but we substitute its base for file_099 - sha1_101=`git hash-object file_101` && - sha1_099=`git hash-object file_099` && - offs_101=`index_obj_offset 1.idx $sha1_101` && - nr_099=`index_obj_nr 1.idx $sha1_099` && + sha1_101=$(git hash-object file_101) && + sha1_099=$(git hash-object file_099) && + offs_101=$(index_obj_offset 1.idx $sha1_101) && + nr_099=$(index_obj_nr 1.idx $sha1_099) && chmod +w ".git/objects/pack/pack-${pack1}.pack" && dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($offs_101 + 1)) \ if=".git/objects/pack/pack-${pack1}.idx" \ @@ -193,10 +193,10 @@ test_expect_success \ '[index v2] 2) create a stealth corruption in a delta base reference' \ '# This test assumes file_101 is a delta smaller than 16 bytes. # It should be against file_100 but we substitute its base for file_099 - sha1_101=`git hash-object file_101` && - sha1_099=`git hash-object file_099` && - offs_101=`index_obj_offset 1.idx $sha1_101` && - nr_099=`index_obj_nr 1.idx $sha1_099` && + sha1_101=$(git hash-object file_101) && + sha1_099=$(git hash-object file_099) && + offs_101=$(index_obj_offset 1.idx $sha1_101) && + nr_099=$(index_obj_nr 1.idx $sha1_099) && chmod +w ".git/objects/pack/pack-${pack1}.pack" && dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($offs_101 + 1)) \ if=".git/objects/pack/pack-${pack1}.idx" \ @@ -222,11 +222,11 @@ test_expect_success \ 'rm -f .git/objects/pack/* && git index-pack --index-version=2 --stdin < "test-1-${pack1}.pack" && git verify-pack ".git/objects/pack/pack-${pack1}.pack" && - obj=`git hash-object file_001` && - nr=`index_obj_nr ".git/objects/pack/pack-${pack1}.idx" $obj` && + obj=$(git hash-object file_001) && + nr=$(index_obj_nr ".git/objects/pack/pack-${pack1}.idx" $obj) && chmod +w ".git/objects/pack/pack-${pack1}.idx" && printf xxxx | dd of=".git/objects/pack/pack-${pack1}.idx" conv=notrunc \ - bs=1 count=4 seek=$((8 + 256 * 4 + `wc -l <obj-list` * 20 + $nr * 4)) && + bs=1 count=4 seek=$((8 + 256 * 4 + $(wc -l <obj-list) * 20 + $nr * 4)) && ( while read obj do git cat-file -p $obj >/dev/null || exit 1 done <obj-list ) && @@ -259,7 +259,12 @@ EOF thirtyeight=${tag#??} && rm -f .git/objects/${tag%$thirtyeight}/$thirtyeight && git index-pack --strict tag-test-${pack1}.pack 2>err && - grep "^error:.* expected .tagger. line" err + grep "^warning:.* expected .tagger. line" err +' + +test_expect_success 'index-pack --fsck-objects also warns upon missing tagger in tag' ' + git index-pack --fsck-objects tag-test-${pack1}.pack 2>err && + grep "^warning:.* expected .tagger. line" err ' test_done diff --git a/t/t5303-pack-corruption-resilience.sh b/t/t5303-pack-corruption-resilience.sh index 663b02bbb1..3634e258f8 100755 --- a/t/t5303-pack-corruption-resilience.sh +++ b/t/t5303-pack-corruption-resilience.sh @@ -19,36 +19,36 @@ test_description='resilience to pack corruptions with redundant objects' # 3) object header is always 2 bytes. create_test_files() { - test-genrandom "foo" 2000 > file_1 && - test-genrandom "foo" 1800 > file_2 && - test-genrandom "foo" 1800 > file_3 && + test-tool genrandom "foo" 2000 > file_1 && + test-tool genrandom "foo" 1800 > file_2 && + test-tool genrandom "foo" 1800 > file_3 && echo " base " >> file_1 && echo " delta1 " >> file_2 && echo " delta delta2 " >> file_3 && - test-genrandom "bar" 150 >> file_2 && - test-genrandom "baz" 100 >> file_3 + test-tool genrandom "bar" 150 >> file_2 && + test-tool genrandom "baz" 100 >> file_3 } create_new_pack() { rm -rf .git && git init && - blob_1=`git hash-object -t blob -w file_1` && - blob_2=`git hash-object -t blob -w file_2` && - blob_3=`git hash-object -t blob -w file_3` && - pack=`printf "$blob_1\n$blob_2\n$blob_3\n" | - git pack-objects $@ .git/objects/pack/pack` && + blob_1=$(git hash-object -t blob -w file_1) && + blob_2=$(git hash-object -t blob -w file_2) && + blob_3=$(git hash-object -t blob -w file_3) && + pack=$(printf "$blob_1\n$blob_2\n$blob_3\n" | + git pack-objects $@ .git/objects/pack/pack) && pack=".git/objects/pack/pack-${pack}" && git verify-pack -v ${pack}.pack } do_repack() { - pack=`printf "$blob_1\n$blob_2\n$blob_3\n" | - git pack-objects $@ .git/objects/pack/pack` && + pack=$(printf "$blob_1\n$blob_2\n$blob_3\n" | + git pack-objects $@ .git/objects/pack/pack) && pack=".git/objects/pack/pack-${pack}" } do_corrupt_object() { - ofs=`git show-index < ${pack}.idx | grep $1 | cut -f1 -d" "` && + ofs=$(git show-index < ${pack}.idx | grep $1 | cut -f1 -d" ") && ofs=$(($ofs + $2)) && chmod +w ${pack}.pack && dd of=${pack}.pack bs=1 conv=notrunc seek=$ofs && diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh index 0794d33dad..f0f6e2a5f3 100755 --- a/t/t5304-prune.sh +++ b/t/t5304-prune.sh @@ -15,7 +15,7 @@ add_blob() { BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") && verbose test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && test_path_is_file $BLOB_FILE && - test-chmtime =+0 $BLOB_FILE + test-tool chmtime =+0 $BLOB_FILE } test_expect_success setup ' @@ -33,7 +33,7 @@ test_expect_success 'prune stale packs' ' orig_pack=$(echo .git/objects/pack/*.pack) && : > .git/objects/tmp_1.pack && : > .git/objects/tmp_2.pack && - test-chmtime =-86501 .git/objects/tmp_1.pack && + test-tool chmtime =-86501 .git/objects/tmp_1.pack && git prune --expire 1.day && test_path_is_file $orig_pack && test_path_is_file .git/objects/tmp_2.pack && @@ -47,7 +47,7 @@ test_expect_success 'prune --expire' ' git prune --expire=1.hour.ago && verbose test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && test_path_is_file $BLOB_FILE && - test-chmtime =-86500 $BLOB_FILE && + test-tool chmtime =-86500 $BLOB_FILE && git prune --expire 1.day && verbose test $before = $(git count-objects | sed "s/ .*//") && test_path_is_missing $BLOB_FILE @@ -57,11 +57,11 @@ test_expect_success 'prune --expire' ' test_expect_success 'gc: implicit prune --expire' ' add_blob && - test-chmtime =-$((2*$week-30)) $BLOB_FILE && + test-tool chmtime =-$((2*$week-30)) $BLOB_FILE && git gc && verbose test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && test_path_is_file $BLOB_FILE && - test-chmtime =-$((2*$week+1)) $BLOB_FILE && + test-tool chmtime =-$((2*$week+1)) $BLOB_FILE && git gc && verbose test $before = $(git count-objects | sed "s/ .*//") && test_path_is_missing $BLOB_FILE @@ -141,7 +141,7 @@ test_expect_success 'prune: do not prune heads listed as an argument' ' test_expect_success 'gc --no-prune' ' add_blob && - test-chmtime =-$((5001*$day)) $BLOB_FILE && + test-tool chmtime =-$((5001*$day)) $BLOB_FILE && git config gc.pruneExpire 2.days.ago && git gc --no-prune && verbose test 1 = $(git count-objects | sed "s/ .*//") && @@ -163,7 +163,7 @@ test_expect_success 'gc respects gc.pruneExpire' ' test_expect_success 'gc --prune=<date>' ' add_blob && - test-chmtime =-$((5001*$day)) $BLOB_FILE && + test-tool chmtime =-$((5001*$day)) $BLOB_FILE && git gc --prune=5002.days.ago && test_path_is_file $BLOB_FILE && git gc --prune=5000.days.ago && @@ -205,7 +205,7 @@ test_expect_success 'prune --expire=never' ' test_expect_success 'gc: prune old objects after local clone' ' add_blob && - test-chmtime =-$((2*$week+1)) $BLOB_FILE && + test-tool chmtime =-$((2*$week+1)) $BLOB_FILE && git clone --no-hardlinks . aclone && ( cd aclone && @@ -218,6 +218,8 @@ test_expect_success 'gc: prune old objects after local clone' ' ' test_expect_success 'garbage report in count-objects -v' ' + test_when_finished "rm -f .git/objects/pack/fake*" && + test_when_finished "rm -f .git/objects/pack/foo*" && : >.git/objects/pack/foo && : >.git/objects/pack/foo.bar && : >.git/objects/pack/foo.keep && @@ -243,8 +245,28 @@ EOF test_cmp expected actual ' +test_expect_success 'clean pack garbage with gc' ' + test_when_finished "rm -f .git/objects/pack/fake*" && + test_when_finished "rm -f .git/objects/pack/foo*" && + : >.git/objects/pack/foo.keep && + : >.git/objects/pack/foo.pack && + : >.git/objects/pack/fake.idx && + : >.git/objects/pack/fake2.keep && + : >.git/objects/pack/fake2.idx && + : >.git/objects/pack/fake3.keep && + git gc && + git count-objects -v 2>stderr && + grep "^warning:" stderr | sort >actual && + cat >expected <<\EOF && +warning: no corresponding .idx or .pack: .git/objects/pack/fake3.keep +warning: no corresponding .idx: .git/objects/pack/foo.keep +warning: no corresponding .idx: .git/objects/pack/foo.pack +EOF + test_cmp expected actual +' + test_expect_success 'prune .git/shallow' ' - SHA1=`echo hi|git commit-tree HEAD^{tree}` && + SHA1=$(echo hi|git commit-tree HEAD^{tree}) && echo $SHA1 >.git/shallow && git prune --dry-run >out && grep $SHA1 .git/shallow && @@ -261,4 +283,41 @@ test_expect_success 'prune: handle alternate object database' ' git -C B prune ' +test_expect_success 'prune: handle index in multiple worktrees' ' + git worktree add second-worktree && + echo "new blob for second-worktree" >second-worktree/blob && + git -C second-worktree add blob && + git prune --expire=now && + git -C second-worktree show :blob >actual && + test_cmp second-worktree/blob actual +' + +test_expect_success 'prune: handle HEAD in multiple worktrees' ' + git worktree add --detach third-worktree && + echo "new blob for third-worktree" >third-worktree/blob && + git -C third-worktree add blob && + git -C third-worktree commit -m "third" && + rm .git/worktrees/third-worktree/index && + test_must_fail git -C third-worktree show :blob && + git prune --expire=now && + git -C third-worktree show HEAD:blob >actual && + test_cmp third-worktree/blob actual +' + +test_expect_success 'prune: handle HEAD reflog in multiple worktrees' ' + git config core.logAllRefUpdates true && + echo "lost blob for third-worktree" >expected && + ( + cd third-worktree && + cat ../expected >blob && + git add blob && + git commit -m "second commit in third" && + git reset --hard HEAD^ + ) && + git prune --expire=now && + SHA1=`git hash-object expected` && + git -C third-worktree show "$SHA1" >actual && + test_cmp expected actual +' + test_done diff --git a/t/t5305-include-tag.sh b/t/t5305-include-tag.sh index 21517c70cd..a5eca210b8 100755 --- a/t/t5305-include-tag.sh +++ b/t/t5305-include-tag.sh @@ -3,20 +3,20 @@ test_description='git pack-object --include-tag' . ./test-lib.sh -TRASH=`pwd` +TRASH=$(pwd) test_expect_success setup ' echo c >d && git update-index --add d && - tree=`git write-tree` && - commit=`git commit-tree $tree </dev/null` && + tree=$(git write-tree) && + commit=$(git commit-tree $tree </dev/null) && echo "object $commit" >sig && echo "type commit" >>sig && echo "tag mytag" >>sig && echo "tagger $(git var GIT_COMMITTER_IDENT)" >>sig && echo >>sig && echo "our test tag" >>sig && - tag=`git mktag <sig` && + tag=$(git mktag <sig) && rm d sig && git update-ref refs/tags/mytag $tag && { echo $tree && @@ -25,58 +25,94 @@ test_expect_success setup ' } >obj-list ' -rm -rf clone.git test_expect_success 'pack without --include-tag' ' - packname_1=$(git pack-objects \ + packname=$(git pack-objects \ --window=0 \ - test-1 <obj-list) + test-no-include <obj-list) ' test_expect_success 'unpack objects' ' - ( - GIT_DIR=clone.git && - export GIT_DIR && - git init && - git unpack-objects -n <test-1-${packname_1}.pack && - git unpack-objects <test-1-${packname_1}.pack - ) + rm -rf clone.git && + git init clone.git && + git -C clone.git unpack-objects <test-no-include-${packname}.pack ' test_expect_success 'check unpacked result (have commit, no tag)' ' git rev-list --objects $commit >list.expect && - ( - test_must_fail env GIT_DIR=clone.git git cat-file -e $tag && - git rev-list --objects $commit - ) >list.actual && + test_must_fail git -C clone.git cat-file -e $tag && + git -C clone.git rev-list --objects $commit >list.actual && test_cmp list.expect list.actual ' -rm -rf clone.git test_expect_success 'pack with --include-tag' ' - packname_1=$(git pack-objects \ + packname=$(git pack-objects \ --window=0 \ --include-tag \ - test-2 <obj-list) + test-include <obj-list) ' test_expect_success 'unpack objects' ' - ( - GIT_DIR=clone.git && - export GIT_DIR && - git init && - git unpack-objects -n <test-2-${packname_1}.pack && - git unpack-objects <test-2-${packname_1}.pack - ) + rm -rf clone.git && + git init clone.git && + git -C clone.git unpack-objects <test-include-${packname}.pack ' test_expect_success 'check unpacked result (have commit, have tag)' ' git rev-list --objects mytag >list.expect && - ( - GIT_DIR=clone.git && - export GIT_DIR && - git rev-list --objects $tag - ) >list.actual && + git -C clone.git rev-list --objects $tag >list.actual && test_cmp list.expect list.actual ' +# A tag of a tag, where the "inner" tag is not otherwise +# reachable, and a full peel points to a commit reachable from HEAD. +test_expect_success 'create hidden inner tag' ' + test_commit commit && + git tag -m inner inner HEAD && + git tag -m outer outer inner && + git tag -d inner +' + +test_expect_success 'pack explicit outer tag' ' + packname=$( + { + echo HEAD && + echo outer + } | + git pack-objects --revs test-hidden-explicit + ) +' + +test_expect_success 'unpack objects' ' + rm -rf clone.git && + git init clone.git && + git -C clone.git unpack-objects <test-hidden-explicit-${packname}.pack +' + +test_expect_success 'check unpacked result (have all objects)' ' + git -C clone.git rev-list --objects $(git rev-parse outer HEAD) +' + +test_expect_success 'pack implied outer tag' ' + packname=$( + echo HEAD | + git pack-objects --revs --include-tag test-hidden-implied + ) +' + +test_expect_success 'unpack objects' ' + rm -rf clone.git && + git init clone.git && + git -C clone.git unpack-objects <test-hidden-implied-${packname}.pack +' + +test_expect_success 'check unpacked result (have all objects)' ' + git -C clone.git rev-list --objects $(git rev-parse outer HEAD) +' + +test_expect_success 'single-branch clone can transfer tag' ' + rm -rf clone.git && + git clone --no-local --single-branch -b master . clone.git && + git -C clone.git fsck +' + test_done diff --git a/t/t5308-pack-detect-duplicates.sh b/t/t5308-pack-detect-duplicates.sh index 9c5a8766ab..156ae9e9d3 100755 --- a/t/t5308-pack-detect-duplicates.sh +++ b/t/t5308-pack-detect-duplicates.sh @@ -56,20 +56,11 @@ test_expect_success 'create batch-check test vectors' ' EOF ' -test_expect_success 'lookup in duplicated pack (binary search)' ' +test_expect_success 'lookup in duplicated pack' ' git cat-file --batch-check <input >actual && test_cmp expect actual ' -test_expect_success 'lookup in duplicated pack (GIT_USE_LOOKUP)' ' - ( - GIT_USE_LOOKUP=1 && - export GIT_USE_LOOKUP && - git cat-file --batch-check <input >actual - ) && - test_cmp expect actual -' - test_expect_success 'index-pack can reject packs with duplicates' ' clear_packs && create_pack dups.pack 2 && diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh index 6003490192..f6d600fd82 100755 --- a/t/t5310-pack-bitmaps.sh +++ b/t/t5310-pack-bitmaps.sh @@ -7,15 +7,30 @@ objpath () { echo ".git/objects/$(echo "$1" | sed -e 's|\(..\)|\1/|')" } +# show objects present in pack ($1 should be associated *.idx) +list_packed_objects () { + git show-index <"$1" | cut -d' ' -f2 +} + +# has_any pattern-file content-file +# tests whether content-file has any entry from pattern-file with entries being +# whole lines. +has_any () { + grep -Ff "$1" "$2" +} + test_expect_success 'setup repo with moderate-sized history' ' - for i in $(test_seq 1 10); do + for i in $(test_seq 1 10) + do test_commit $i done && git checkout -b other HEAD~5 && - for i in $(test_seq 1 10); do + for i in $(test_seq 1 10) + do test_commit side-$i done && git checkout master && + bitmaptip=$(git rev-parse master) && blob=$(echo tagged-blob | git hash-object -w --stdin) && git tag tagged-blob $blob && git config repack.writebitmaps true && @@ -47,12 +62,24 @@ rev_list_tests() { test_cmp expect actual ' + test_expect_success "counting commits with limit ($state)" ' + git rev-list --count -n 1 HEAD >expect && + git rev-list --use-bitmap-index --count -n 1 HEAD >actual && + test_cmp expect actual + ' + test_expect_success "counting non-linear history ($state)" ' git rev-list --count other...master >expect && git rev-list --use-bitmap-index --count other...master >actual && test_cmp expect actual ' + test_expect_success "counting commits with limiting ($state)" ' + git rev-list --count HEAD -- 1.t >expect && + git rev-list --use-bitmap-index --count HEAD -- 1.t >actual && + test_cmp expect actual + ' + test_expect_success "enumerate --objects ($state)" ' git rev-list --objects --use-bitmap-index HEAD >tmp && cut -d" " -f1 <tmp >tmp2 && @@ -79,7 +106,8 @@ test_expect_success 'clone from bitmapped repository' ' ' test_expect_success 'setup further non-bitmapped commits' ' - for i in $(test_seq 1 10); do + for i in $(test_seq 1 10) + do test_commit further-$i done ' @@ -93,12 +121,10 @@ test_expect_success 'fetch (partial bitmap)' ' test_cmp expect actual ' -test_expect_success 'incremental repack cannot create bitmaps' ' +test_expect_success 'incremental repack fails when bitmaps are requested' ' test_commit more-1 && - find .git/objects/pack -name "*.bitmap" >expect && - git repack -d && - find .git/objects/pack -name "*.bitmap" >actual && - test_cmp expect actual + test_must_fail git repack -d 2>err && + test_i18ngrep "Incremental repacks are incompatible with bitmap" err ' test_expect_success 'incremental repack can disable bitmaps' ' @@ -106,6 +132,83 @@ test_expect_success 'incremental repack can disable bitmaps' ' git repack -d --no-write-bitmap-index ' +test_expect_success 'pack-objects respects --local (non-local loose)' ' + git init --bare alt.git && + echo $(pwd)/alt.git/objects >.git/objects/info/alternates && + echo content1 >file1 && + # non-local loose object which is not present in bitmapped pack + altblob=$(GIT_DIR=alt.git git hash-object -w file1) && + # non-local loose object which is also present in bitmapped pack + git cat-file blob $blob | GIT_DIR=alt.git git hash-object -w --stdin && + git add file1 && + test_tick && + git commit -m commit_file1 && + echo HEAD | git pack-objects --local --stdout --revs >1.pack && + git index-pack 1.pack && + list_packed_objects 1.idx >1.objects && + printf "%s\n" "$altblob" "$blob" >nonlocal-loose && + ! has_any nonlocal-loose 1.objects +' + +test_expect_success 'pack-objects respects --honor-pack-keep (local non-bitmapped pack)' ' + echo content2 >file2 && + blob2=$(git hash-object -w file2) && + git add file2 && + test_tick && + git commit -m commit_file2 && + printf "%s\n" "$blob2" "$bitmaptip" >keepobjects && + pack2=$(git pack-objects pack2 <keepobjects) && + mv pack2-$pack2.* .git/objects/pack/ && + >.git/objects/pack/pack2-$pack2.keep && + rm $(objpath $blob2) && + echo HEAD | git pack-objects --honor-pack-keep --stdout --revs >2a.pack && + git index-pack 2a.pack && + list_packed_objects 2a.idx >2a.objects && + ! has_any keepobjects 2a.objects +' + +test_expect_success 'pack-objects respects --local (non-local pack)' ' + mv .git/objects/pack/pack2-$pack2.* alt.git/objects/pack/ && + echo HEAD | git pack-objects --local --stdout --revs >2b.pack && + git index-pack 2b.pack && + list_packed_objects 2b.idx >2b.objects && + ! has_any keepobjects 2b.objects +' + +test_expect_success 'pack-objects respects --honor-pack-keep (local bitmapped pack)' ' + ls .git/objects/pack/ | grep bitmap >output && + test_line_count = 1 output && + packbitmap=$(basename $(cat output) .bitmap) && + list_packed_objects .git/objects/pack/$packbitmap.idx >packbitmap.objects && + test_when_finished "rm -f .git/objects/pack/$packbitmap.keep" && + >.git/objects/pack/$packbitmap.keep && + echo HEAD | git pack-objects --honor-pack-keep --stdout --revs >3a.pack && + git index-pack 3a.pack && + list_packed_objects 3a.idx >3a.objects && + ! has_any packbitmap.objects 3a.objects +' + +test_expect_success 'pack-objects respects --local (non-local bitmapped pack)' ' + mv .git/objects/pack/$packbitmap.* alt.git/objects/pack/ && + test_when_finished "mv alt.git/objects/pack/$packbitmap.* .git/objects/pack/" && + echo HEAD | git pack-objects --local --stdout --revs >3b.pack && + git index-pack 3b.pack && + list_packed_objects 3b.idx >3b.objects && + ! has_any packbitmap.objects 3b.objects +' + +test_expect_success 'pack-objects to file can use bitmap' ' + # make sure we still have 1 bitmap index from previous tests + ls .git/objects/pack/ | grep bitmap >output && + test_line_count = 1 output && + # verify equivalent packs are generated with/without using bitmap index + packasha1=$(git pack-objects --no-use-bitmap-index --all packa </dev/null) && + packbsha1=$(git pack-objects --use-bitmap-index --all packb </dev/null) && + list_packed_objects <packa-$packasha1.idx >packa.objects && + list_packed_objects <packb-$packbsha1.idx >packb.objects && + test_cmp packa.objects packb.objects +' + test_expect_success 'full repack, reusing previous bitmaps' ' git repack -ad && ls .git/objects/pack/ | grep bitmap >output && @@ -131,6 +234,20 @@ test_expect_success 'create objects for missing-HAVE tests' ' EOF ' +test_expect_success 'pack-objects respects --incremental' ' + cat >revs2 <<-EOF && + HEAD + $commit + EOF + git pack-objects --incremental --stdout --revs <revs2 >4.pack && + git index-pack 4.pack && + list_packed_objects 4.idx >4.objects && + test_line_count = 4 4.objects && + git rev-list --objects $commit >revlist && + cut -d" " -f1 revlist |sort >objects && + test_cmp 4.objects objects +' + test_expect_success 'pack with missing blob' ' rm $(objpath $blob) && git pack-objects --stdout --revs <revs >/dev/null @@ -146,10 +263,6 @@ test_expect_success 'pack with missing parent' ' git pack-objects --stdout --revs <revs >/dev/null ' -test_lazy_prereq JGIT ' - type jgit -' - test_expect_success JGIT 'we can read jgit bitmaps' ' git clone . compat-jgit && ( @@ -171,7 +284,7 @@ test_expect_success JGIT 'jgit can read our bitmaps' ' ' test_expect_success 'splitting packs does not generate bogus bitmaps' ' - test-genrandom foo $((1024 * 1024)) >rand && + test-tool genrandom foo $((1024 * 1024)) >rand && git add rand && git commit -m "commit with big file" && git -c pack.packSizeLimit=500k repack -adb && @@ -179,4 +292,43 @@ test_expect_success 'splitting packs does not generate bogus bitmaps' ' git -C no-bitmaps.git fetch .. HEAD ' +test_expect_success 'set up reusable pack' ' + rm -f .git/objects/pack/*.keep && + git repack -adb && + reusable_pack () { + git for-each-ref --format="%(objectname)" | + git pack-objects --delta-base-offset --revs --stdout "$@" + } +' + +test_expect_success 'pack reuse respects --honor-pack-keep' ' + test_when_finished "rm -f .git/objects/pack/*.keep" && + for i in .git/objects/pack/*.pack + do + >${i%.pack}.keep + done && + reusable_pack --honor-pack-keep >empty.pack && + git index-pack empty.pack && + >expect && + git show-index <empty.idx >actual && + test_cmp expect actual +' + +test_expect_success 'pack reuse respects --local' ' + mv .git/objects/pack/* alt.git/objects/pack/ && + test_when_finished "mv alt.git/objects/pack/* .git/objects/pack/" && + reusable_pack --local >empty.pack && + git index-pack empty.pack && + >expect && + git show-index <empty.idx >actual && + test_cmp expect actual +' + +test_expect_success 'pack reuse respects --incremental' ' + reusable_pack --incremental >empty.pack && + git index-pack empty.pack && + >expect && + git show-index <empty.idx >actual && + test_cmp expect actual +' test_done diff --git a/t/t5312-prune-corruption.sh b/t/t5312-prune-corruption.sh index 8e98b44083..da9d59940d 100755 --- a/t/t5312-prune-corruption.sh +++ b/t/t5312-prune-corruption.sh @@ -12,7 +12,7 @@ delete objects that cannot be recovered. test_expect_success 'disable reflogs' ' git config core.logallrefupdates false && - rm -rf .git/logs + git reflog expire --expire=all --all ' test_expect_success 'create history reachable only from a bogus-named ref' ' diff --git a/t/t5313-pack-bounds-checks.sh b/t/t5313-pack-bounds-checks.sh new file mode 100755 index 0000000000..4fe4ad9d61 --- /dev/null +++ b/t/t5313-pack-bounds-checks.sh @@ -0,0 +1,185 @@ +#!/bin/sh + +test_description='bounds-checking of access to mmapped on-disk file formats' +. ./test-lib.sh + +clear_base () { + test_when_finished 'restore_base' && + rm -f $base +} + +restore_base () { + cp base-backup/* .git/objects/pack/ +} + +do_pack () { + pack_objects=$1; shift + sha1=$( + for i in $pack_objects + do + echo $i + done | git pack-objects "$@" .git/objects/pack/pack + ) && + pack=.git/objects/pack/pack-$sha1.pack && + idx=.git/objects/pack/pack-$sha1.idx && + chmod +w $pack $idx && + test_when_finished 'rm -f "$pack" "$idx"' +} + +munge () { + printf "$3" | dd of="$1" bs=1 conv=notrunc seek=$2 +} + +# Offset in a v2 .idx to its initial and extended offset tables. For an index +# with "nr" objects, this is: +# +# magic(4) + version(4) + fan-out(4*256) + sha1s(20*nr) + crc(4*nr), +# +# for the initial, and another ofs(4*nr) past that for the extended. +# +ofs_table () { + echo $((4 + 4 + 4*256 + 20*$1 + 4*$1)) +} +extended_table () { + echo $(($(ofs_table "$1") + 4*$1)) +} + +test_expect_success 'set up base packfile and variables' ' + # the hash of this content starts with ff, which + # makes some later computations much simpler + echo 74 >file && + git add file && + git commit -m base && + git repack -ad && + base=$(echo .git/objects/pack/*) && + chmod +w $base && + mkdir base-backup && + cp $base base-backup/ && + object=$(git rev-parse HEAD:file) +' + +test_expect_success 'pack/index object count mismatch' ' + do_pack $object && + munge $pack 8 "\377\0\0\0" && + clear_base && + + # We enumerate the objects from the completely-fine + # .idx, but notice later that the .pack is bogus + # and fail to show any data. + echo "$object missing" >expect && + git cat-file --batch-all-objects --batch-check >actual && + test_cmp expect actual && + + # ...and here fail to load the object (without segfaulting), + # but fallback to a good copy if available. + test_must_fail git cat-file blob $object && + restore_base && + git cat-file blob $object >actual && + test_cmp file actual && + + # ...and make sure that index-pack --verify, which has its + # own reading routines, does not segfault. + test_must_fail git index-pack --verify $pack +' + +test_expect_success 'matched bogus object count' ' + do_pack $object && + munge $pack 8 "\377\0\0\0" && + munge $idx $((255 * 4)) "\377\0\0\0" && + clear_base && + + # Unlike above, we should notice early that the .idx is totally + # bogus, and not even enumerate its contents. + >expect && + git cat-file --batch-all-objects --batch-check >actual && + test_cmp expect actual && + + # But as before, we can do the same object-access checks. + test_must_fail git cat-file blob $object && + restore_base && + git cat-file blob $object >actual && + test_cmp file actual && + + test_must_fail git index-pack --verify $pack +' + +# Note that we cannot check the fallback case for these +# further .idx tests, as we notice the problem in functions +# whose interface doesn't allow an error return (like use_pack()), +# and thus we just die(). +# +# There's also no point in doing enumeration tests, as +# we are munging offsets here, which are about looking up +# specific objects. + +test_expect_success 'bogus object offset (v1)' ' + do_pack $object --index-version=1 && + munge $idx $((4 * 256)) "\377\0\0\0" && + clear_base && + test_must_fail git cat-file blob $object && + test_must_fail git index-pack --verify $pack +' + +test_expect_success 'bogus object offset (v2, no msb)' ' + do_pack $object --index-version=2 && + munge $idx $(ofs_table 1) "\0\377\0\0" && + clear_base && + test_must_fail git cat-file blob $object && + test_must_fail git index-pack --verify $pack +' + +test_expect_success 'bogus offset into v2 extended table' ' + do_pack $object --index-version=2 && + munge $idx $(ofs_table 1) "\377\0\0\0" && + clear_base && + test_must_fail git cat-file blob $object && + test_must_fail git index-pack --verify $pack +' + +test_expect_success 'bogus offset inside v2 extended table' ' + # We need two objects here, so we can plausibly require + # an extended table (if the first object were larger than 2^31). + # + # Note that the value is important here. We want $object as + # the second entry in sorted-sha1 order. The sha1 of 1485 starts + # with "000", which sorts before that of $object (which starts + # with "fff"). + second=$(echo 1485 | git hash-object -w --stdin) && + do_pack "$object $second" --index-version=2 && + + # We have to make extra room for the table, so we cannot + # just munge in place as usual. + { + dd if=$idx bs=1 count=$(($(ofs_table 2) + 4)) && + printf "\200\0\0\0" && + printf "\377\0\0\0\0\0\0\0" && + dd if=$idx bs=1 skip=$(extended_table 2) + } >tmp && + mv tmp "$idx" && + clear_base && + test_must_fail git cat-file blob $object && + test_must_fail git index-pack --verify $pack +' + +test_expect_success 'bogus OFS_DELTA in packfile' ' + # Generate a pack with a delta in it. + base=$(test-tool genrandom foo 3000 | git hash-object --stdin -w) && + delta=$(test-tool genrandom foo 2000 | git hash-object --stdin -w) && + do_pack "$base $delta" --delta-base-offset && + rm -f .git/objects/??/* && + + # Double check that we have the delta we expect. + echo $base >expect && + echo $delta | git cat-file --batch-check="%(deltabase)" >actual && + test_cmp expect actual && + + # Now corrupt it. We assume the varint size for the delta is small + # enough to fit in the first byte (which it should be, since it + # is a pure deletion from the base), and that original ofs_delta + # takes 2 bytes (which it should, as it should be ~3000). + ofs=$(git show-index <$idx | grep $delta | cut -d" " -f1) && + munge $pack $(($ofs + 1)) "\177\377" && + test_must_fail git cat-file blob $delta >/dev/null +' + +test_done diff --git a/t/t5314-pack-cycle-detection.sh b/t/t5314-pack-cycle-detection.sh new file mode 100755 index 0000000000..f31995d3d2 --- /dev/null +++ b/t/t5314-pack-cycle-detection.sh @@ -0,0 +1,113 @@ +#!/bin/sh + +test_description='test handling of inter-pack delta cycles during repack + +The goal here is to create a situation where we have two blobs, A and B, with A +as a delta against B in one pack, and vice versa in the other. Then if we can +persuade a full repack to find A from one pack and B from the other, that will +give us a cycle when we attempt to reuse those deltas. + +The trick is in the "persuade" step, as it depends on the internals of how +pack-objects picks which pack to reuse the deltas from. But we can assume +that it does so in one of two general strategies: + + 1. Using a static ordering of packs. In this case, no inter-pack cycles can + happen. Any objects with a delta relationship must be present in the same + pack (i.e., no "--thin" packs on disk), so we will find all related objects + from that pack. So assuming there are no cycles within a single pack (and + we avoid generating them via pack-objects or importing them via + index-pack), then our result will have no cycles. + + So this case should pass the tests no matter how we arrange things. + + 2. Picking the next pack to examine based on locality (i.e., where we found + something else recently). + + In this case, we want to make sure that we find the delta versions of A and + B and not their base versions. We can do this by putting two blobs in each + pack. The first is a "dummy" blob that can only be found in the pack in + question. And then the second is the actual delta we want to find. + + The two blobs must be present in the same tree, not present in other trees, + and the dummy pathname must sort before the delta path. + +The setup below focuses on case 2. We have two commits HEAD and HEAD^, each +which has two files: "dummy" and "file". Then we can make two packs which +contain: + + [pack one] + HEAD:dummy + HEAD:file (as delta against HEAD^:file) + HEAD^:file (as base) + + [pack two] + HEAD^:dummy + HEAD^:file (as delta against HEAD:file) + HEAD:file (as base) + +Then no matter which order we start looking at the packs in, we know that we +will always find a delta for "file", because its lookup will always come +immediately after the lookup for "dummy". +' +. ./test-lib.sh + + + +# Create a pack containing the the tree $1 and blob $1:file, with +# the latter stored as a delta against $2:file. +# +# We convince pack-objects to make the delta in the direction of our choosing +# by marking $2 as a preferred-base edge. That results in $1:file as a thin +# delta, and index-pack completes it by adding $2:file as a base. +# +# Note that the two variants of "file" must be similar enough to convince git +# to create the delta. +make_pack () { + { + printf '%s\n' "-$(git rev-parse $2)" + printf '%s dummy\n' "$(git rev-parse $1:dummy)" + printf '%s file\n' "$(git rev-parse $1:file)" + } | + git pack-objects --stdout | + git index-pack --stdin --fix-thin +} + +test_expect_success 'setup' ' + test-tool genrandom base 4096 >base && + for i in one two + do + # we want shared content here to encourage deltas... + cp base file && + echo $i >>file && + + # ...whereas dummy should be short, because we do not want + # deltas that would create duplicates when we --fix-thin + echo $i >dummy && + + git add file dummy && + test_tick && + git commit -m $i || + return 1 + done && + + make_pack HEAD^ HEAD && + make_pack HEAD HEAD^ +' + +test_expect_success 'repack' ' + # We first want to check that we do not have any internal errors, + # and also that we do not hit the last-ditch cycle-breaking code + # in write_object(), which will issue a warning to stderr. + >expect && + git repack -ad 2>stderr && + test_cmp expect stderr && + + # And then double-check that the resulting pack is usable (i.e., + # we did not fail to notice any cycles). We know we are accessing + # the objects via the new pack here, because "repack -d" will have + # removed the others. + git cat-file blob HEAD:file >/dev/null && + git cat-file blob HEAD^:file >/dev/null +' + +test_done diff --git a/t/t5315-pack-objects-compression.sh b/t/t5315-pack-objects-compression.sh new file mode 100755 index 0000000000..34c47dae09 --- /dev/null +++ b/t/t5315-pack-objects-compression.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +test_description='pack-object compression configuration' + +. ./test-lib.sh + +# This should be moved to test-lib.sh together with the +# copy in t0021 after both topics have graduated to 'master'. +file_size () { + perl -e 'print -s $ARGV[0]' "$1" +} + +test_expect_success setup ' + printf "%2000000s" X | + git hash-object -w --stdin >object-name && + # make sure it resulted in a loose object + ob=$(sed -e "s/\(..\).*/\1/" object-name) && + ject=$(sed -e "s/..\(.*\)/\1/" object-name) && + test -f .git/objects/$ob/$ject +' + +while read expect config +do + test_expect_success "pack-objects with $config" ' + test_when_finished "rm -f pack-*.*" && + git $config pack-objects pack <object-name && + sz=$(file_size pack-*.pack) && + case "$expect" in + small) test "$sz" -le 100000 ;; + large) test "$sz" -ge 100000 ;; + esac + ' +done <<\EOF +large -c core.compression=0 +small -c core.compression=9 +large -c core.compression=0 -c pack.compression=0 +large -c core.compression=9 -c pack.compression=0 +small -c core.compression=0 -c pack.compression=9 +small -c core.compression=9 -c pack.compression=9 +large -c pack.compression=0 +small -c pack.compression=9 +EOF + +test_done diff --git a/t/t5316-pack-delta-depth.sh b/t/t5316-pack-delta-depth.sh new file mode 100755 index 0000000000..0f06c40eb1 --- /dev/null +++ b/t/t5316-pack-delta-depth.sh @@ -0,0 +1,97 @@ +#!/bin/sh + +test_description='pack-objects breaks long cross-pack delta chains' +. ./test-lib.sh + +# This mirrors a repeated push setup: +# +# 1. A client repeatedly modifies some files, makes a +# commit, and pushes the result. It does this N times +# before we get around to repacking. +# +# 2. Each push generates a thin pack with the new version of +# various objects. Let's consider some file in the root tree +# which is updated in each commit. +# +# When generating push number X, we feed commit X-1 (and +# thus blob X-1) as a preferred base. The resulting pack has +# blob X as a thin delta against blob X-1. +# +# On the receiving end, "index-pack --fix-thin" will +# complete the pack with a base copy of blob X-1. +# +# 3. In older versions of git, if we used the delta from +# pack X, then we'd always find blob X-1 as a base in the +# same pack (and generate a fresh delta). +# +# But with the pack mru, we jump from delta to delta +# following the traversal order: +# +# a. We grab blob X from pack X as a delta, putting it at +# the tip of our mru list. +# +# b. Eventually we move onto commit X-1. We need other +# objects which are only in pack X-1 (in the test code +# below, it's the containing tree). That puts pack X-1 +# at the tip of our mru list. +# +# c. Eventually we look for blob X-1, and we find the +# version in pack X-1 (because it's the mru tip). +# +# Now we have blob X as a delta against X-1, which is a delta +# against X-2, and so forth. +# +# In the real world, these small pushes would get exploded by +# unpack-objects rather than "index-pack --fix-thin", but the +# same principle applies to larger pushes (they only need one +# repeatedly-modified file to generate the delta chain). + +test_expect_success 'create series of packs' ' + test-tool genrandom foo 4096 >content && + prev= && + for i in $(test_seq 1 10) + do + cat content >file && + echo $i >>file && + git add file && + git commit -m $i && + cur=$(git rev-parse HEAD^{tree}) && + { + test -n "$prev" && echo "-$prev" + echo $cur + echo "$(git rev-parse :file) file" + } | git pack-objects --stdout >tmp && + git index-pack --stdin --fix-thin <tmp || return 1 + prev=$cur + done +' + +max_chain() { + git index-pack --verify-stat-only "$1" >output && + perl -lne ' + /chain length = (\d+)/ and $len = $1; + END { print $len } + ' output +} + +# Note that this whole setup is pretty reliant on the current +# packing heuristics. We double-check that our test case +# actually produces a long chain. If it doesn't, it should be +# adjusted (or scrapped if the heuristics have become too unreliable) +test_expect_success 'packing produces a long delta' ' + # Use --window=0 to make sure we are seeing reused deltas, + # not computing a new long chain. + pack=$(git pack-objects --all --window=0 </dev/null pack) && + echo 9 >expect && + max_chain pack-$pack.pack >actual && + test_i18ncmp expect actual +' + +test_expect_success '--depth limits depth' ' + pack=$(git pack-objects --all --depth=5 </dev/null pack) && + echo 5 >expect && + max_chain pack-$pack.pack >actual && + test_i18ncmp expect actual +' + +test_done diff --git a/t/t5317-pack-objects-filter-objects.sh b/t/t5317-pack-objects-filter-objects.sh new file mode 100755 index 0000000000..1b0acc383b --- /dev/null +++ b/t/t5317-pack-objects-filter-objects.sh @@ -0,0 +1,375 @@ +#!/bin/sh + +test_description='git pack-objects using object filtering' + +. ./test-lib.sh + +# Test blob:none filter. + +test_expect_success 'setup r1' ' + echo "{print \$1}" >print_1.awk && + echo "{print \$2}" >print_2.awk && + + git init r1 && + for n in 1 2 3 4 5 + do + echo "This is file: $n" > r1/file.$n + git -C r1 add file.$n + git -C r1 commit -m "$n" + done +' + +test_expect_success 'verify blob count in normal packfile' ' + git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r1 pack-objects --rev --stdout >all.pack <<-EOF && + HEAD + EOF + git -C r1 index-pack ../all.pack && + git -C r1 verify-pack -v ../all.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify blob:none packfile has no blobs' ' + git -C r1 pack-objects --rev --stdout --filter=blob:none >filter.pack <<-EOF && + HEAD + EOF + git -C r1 index-pack ../filter.pack && + git -C r1 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + nr=$(wc -l <observed) && + test 0 -eq $nr +' + +test_expect_success 'verify normal and blob:none packfiles have same commits/trees' ' + git -C r1 verify-pack -v ../all.pack \ + | grep -E "commit|tree" \ + | awk -f print_1.awk \ + | sort >expected && + git -C r1 verify-pack -v ../filter.pack \ + | grep -E "commit|tree" \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +# Test blob:limit=<n>[kmg] filter. +# We boundary test around the size parameter. The filter is strictly less than +# the value, so size 500 and 1000 should have the same results, but 1001 should +# filter more. + +test_expect_success 'setup r2' ' + git init r2 && + for n in 1000 10000 + do + printf "%"$n"s" X > r2/large.$n + git -C r2 add large.$n + git -C r2 commit -m "$n" + done +' + +test_expect_success 'verify blob count in normal packfile' ' + git -C r2 ls-files -s large.1000 large.10000 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r2 pack-objects --rev --stdout >all.pack <<-EOF && + HEAD + EOF + git -C r2 index-pack ../all.pack && + git -C r2 verify-pack -v ../all.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify blob:limit=500 omits all blobs' ' + git -C r2 pack-objects --rev --stdout --filter=blob:limit=500 >filter.pack <<-EOF && + HEAD + EOF + git -C r2 index-pack ../filter.pack && + git -C r2 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + nr=$(wc -l <observed) && + test 0 -eq $nr +' + +test_expect_success 'verify blob:limit=1000' ' + git -C r2 pack-objects --rev --stdout --filter=blob:limit=1000 >filter.pack <<-EOF && + HEAD + EOF + git -C r2 index-pack ../filter.pack && + git -C r2 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + nr=$(wc -l <observed) && + test 0 -eq $nr +' + +test_expect_success 'verify blob:limit=1001' ' + git -C r2 ls-files -s large.1000 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r2 pack-objects --rev --stdout --filter=blob:limit=1001 >filter.pack <<-EOF && + HEAD + EOF + git -C r2 index-pack ../filter.pack && + git -C r2 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify blob:limit=10001' ' + git -C r2 ls-files -s large.1000 large.10000 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r2 pack-objects --rev --stdout --filter=blob:limit=10001 >filter.pack <<-EOF && + HEAD + EOF + git -C r2 index-pack ../filter.pack && + git -C r2 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify blob:limit=1k' ' + git -C r2 ls-files -s large.1000 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r2 pack-objects --rev --stdout --filter=blob:limit=1k >filter.pack <<-EOF && + HEAD + EOF + git -C r2 index-pack ../filter.pack && + git -C r2 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify blob:limit=1m' ' + git -C r2 ls-files -s large.1000 large.10000 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r2 pack-objects --rev --stdout --filter=blob:limit=1m >filter.pack <<-EOF && + HEAD + EOF + git -C r2 index-pack ../filter.pack && + git -C r2 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify normal and blob:limit packfiles have same commits/trees' ' + git -C r2 verify-pack -v ../all.pack \ + | grep -E "commit|tree" \ + | awk -f print_1.awk \ + | sort >expected && + git -C r2 verify-pack -v ../filter.pack \ + | grep -E "commit|tree" \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +# Test sparse:path=<path> filter. +# Use a local file containing a sparse-checkout specification to filter +# out blobs not required for the corresponding sparse-checkout. We do not +# require sparse-checkout to actually be enabled. + +test_expect_success 'setup r3' ' + git init r3 && + mkdir r3/dir1 && + for n in sparse1 sparse2 + do + echo "This is file: $n" > r3/$n + git -C r3 add $n + echo "This is file: dir1/$n" > r3/dir1/$n + git -C r3 add dir1/$n + done && + git -C r3 commit -m "sparse" && + echo dir1/ >pattern1 && + echo sparse1 >pattern2 +' + +test_expect_success 'verify blob count in normal packfile' ' + git -C r3 ls-files -s sparse1 sparse2 dir1/sparse1 dir1/sparse2 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r3 pack-objects --rev --stdout >all.pack <<-EOF && + HEAD + EOF + git -C r3 index-pack ../all.pack && + git -C r3 verify-pack -v ../all.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify sparse:path=pattern1' ' + git -C r3 ls-files -s dir1/sparse1 dir1/sparse2 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r3 pack-objects --rev --stdout --filter=sparse:path=../pattern1 >filter.pack <<-EOF && + HEAD + EOF + git -C r3 index-pack ../filter.pack && + git -C r3 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify normal and sparse:path=pattern1 packfiles have same commits/trees' ' + git -C r3 verify-pack -v ../all.pack \ + | grep -E "commit|tree" \ + | awk -f print_1.awk \ + | sort >expected && + git -C r3 verify-pack -v ../filter.pack \ + | grep -E "commit|tree" \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify sparse:path=pattern2' ' + git -C r3 ls-files -s sparse1 dir1/sparse1 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r3 pack-objects --rev --stdout --filter=sparse:path=../pattern2 >filter.pack <<-EOF && + HEAD + EOF + git -C r3 index-pack ../filter.pack && + git -C r3 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify normal and sparse:path=pattern2 packfiles have same commits/trees' ' + git -C r3 verify-pack -v ../all.pack \ + | grep -E "commit|tree" \ + | awk -f print_1.awk \ + | sort >expected && + git -C r3 verify-pack -v ../filter.pack \ + | grep -E "commit|tree" \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +# Test sparse:oid=<oid-ish> filter. +# Like sparse:path, but we get the sparse-checkout specification from +# a blob rather than a file on disk. + +test_expect_success 'setup r4' ' + git init r4 && + mkdir r4/dir1 && + for n in sparse1 sparse2 + do + echo "This is file: $n" > r4/$n + git -C r4 add $n + echo "This is file: dir1/$n" > r4/dir1/$n + git -C r4 add dir1/$n + done && + echo dir1/ >r4/pattern && + git -C r4 add pattern && + git -C r4 commit -m "pattern" +' + +test_expect_success 'verify blob count in normal packfile' ' + git -C r4 ls-files -s pattern sparse1 sparse2 dir1/sparse1 dir1/sparse2 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r4 pack-objects --rev --stdout >all.pack <<-EOF && + HEAD + EOF + git -C r4 index-pack ../all.pack && + git -C r4 verify-pack -v ../all.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify sparse:oid=OID' ' + git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 \ + | awk -f print_2.awk \ + | sort >expected && + oid=$(git -C r4 ls-files -s pattern | awk -f print_2.awk) && + git -C r4 pack-objects --rev --stdout --filter=sparse:oid=$oid >filter.pack <<-EOF && + HEAD + EOF + git -C r4 index-pack ../filter.pack && + git -C r4 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify sparse:oid=oid-ish' ' + git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r4 pack-objects --rev --stdout --filter=sparse:oid=master:pattern >filter.pack <<-EOF && + HEAD + EOF + git -C r4 index-pack ../filter.pack && + git -C r4 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + +# Delete some loose objects and use pack-objects, but WITHOUT any filtering. +# This models previously omitted objects that we did not receive. + +test_expect_success 'setup r1 - delete loose blobs' ' + git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \ + | awk -f print_2.awk \ + | sort >expected && + for id in `cat expected | sed "s|..|&/|"` + do + rm r1/.git/objects/$id + done +' + +test_expect_success 'verify pack-objects fails w/ missing objects' ' + test_must_fail git -C r1 pack-objects --rev --stdout >miss.pack <<-EOF + HEAD + EOF +' + +test_expect_success 'verify pack-objects fails w/ --missing=error' ' + test_must_fail git -C r1 pack-objects --rev --stdout --missing=error >miss.pack <<-EOF + HEAD + EOF +' + +test_expect_success 'verify pack-objects w/ --missing=allow-any' ' + git -C r1 pack-objects --rev --stdout --missing=allow-any >miss.pack <<-EOF + HEAD + EOF +' + +test_done diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index 04cea97f87..911eae1bf7 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -128,6 +128,18 @@ test_expect_success 'denyNonFastforwards trumps --force' ' test "$victim_orig" = "$victim_head" ' +test_expect_success 'send-pack --all sends all branches' ' + # make sure we have at least 2 branches with different + # values, just to be thorough + git branch other-branch HEAD^ && + + git init --bare all.git && + git send-pack --all all.git && + git for-each-ref refs/heads >expect && + git -C all.git for-each-ref refs/heads >actual && + test_cmp expect actual +' + test_expect_success 'push --all excludes remote-tracking hierarchy' ' mkdir parent && ( @@ -168,7 +180,7 @@ test_expect_success 'receive-pack runs auto-gc in remote repo' ' # And create a file that follows the temporary object naming # convention for the auto-gc to remove : >.git/objects/tmp_test_object && - test-chmtime =-1209601 .git/objects/tmp_test_object + test-tool chmtime =-1209601 .git/objects/tmp_test_object ) && ( cd parent && @@ -243,4 +255,45 @@ test_expect_success 'deny pushing to delete current branch' ' ) ' +extract_ref_advertisement () { + perl -lne ' + # \\ is there to skip capabilities after \0 + /push< ([^\\]+)/ or next; + exit 0 if $1 eq "0000"; + print $1; + ' +} + +test_expect_success 'receive-pack de-dupes .have lines' ' + git init shared && + git -C shared commit --allow-empty -m both && + git clone -s shared fork && + ( + cd shared && + git checkout -b only-shared && + git commit --allow-empty -m only-shared && + git update-ref refs/heads/foo HEAD + ) && + + # Notable things in this expectation: + # - local refs are not de-duped + # - .have does not duplicate locals + # - .have does not duplicate itself + local=$(git -C fork rev-parse HEAD) && + shared=$(git -C shared rev-parse only-shared) && + cat >expect <<-EOF && + $local refs/heads/master + $local refs/remotes/origin/HEAD + $local refs/remotes/origin/master + $shared .have + EOF + + GIT_TRACE_PACKET=$(pwd)/trace \ + git push \ + --receive-pack="unset GIT_TRACE_PACKET; git-receive-pack" \ + fork HEAD:foo && + extract_ref_advertisement <trace >refs && + test_cmp expect refs +' + test_done diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index 3a9b77576f..0680dec808 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -14,7 +14,7 @@ test_description='Testing multi_ack pack fetching' add () { name=$1 && text="$@" && - branch=`echo $name | sed -e 's/^\(.\).*$/\1/'` && + branch=$(echo $name | sed -e 's/^\(.\).*$/\1/') && parents="" && shift && @@ -50,18 +50,18 @@ pull_to_client () { case "$heads" in *B*) echo $BTIP > .git/refs/heads/B;; esac && - git symbolic-ref HEAD refs/heads/`echo $heads \ - | sed -e "s/^\(.\).*$/\1/"` && + git symbolic-ref HEAD refs/heads/$(echo $heads \ + | sed -e "s/^\(.\).*$/\1/") && git fsck --full && mv .git/objects/pack/pack-* . && - p=`ls -1 pack-*.pack` && + p=$(ls -1 pack-*.pack) && git unpack-objects <$p && git fsck --full && - idx=`echo pack-*.idx` && - pack_count=`git show-index <$idx | wc -l` && + idx=$(echo pack-*.idx) && + pack_count=$(git show-index <$idx | wc -l) && test $pack_count = $count && rm -f pack-* ) @@ -132,13 +132,13 @@ test_expect_success 'single given branch clone' ' test_expect_success 'clone shallow depth 1' ' git clone --no-single-branch --depth 1 "file://$(pwd)/." shallow0 && - test "`git --git-dir=shallow0/.git rev-list --count HEAD`" = 1 + test "$(git --git-dir=shallow0/.git rev-list --count HEAD)" = 1 ' test_expect_success 'clone shallow depth 1 with fsck' ' git config --global fetch.fsckobjects true && git clone --no-single-branch --depth 1 "file://$(pwd)/." shallow0fsck && - test "`git --git-dir=shallow0fsck/.git rev-list --count HEAD`" = 1 && + test "$(git --git-dir=shallow0fsck/.git rev-list --count HEAD)" = 1 && git config --global --unset fetch.fsckobjects ' @@ -147,7 +147,7 @@ test_expect_success 'clone shallow' ' ' test_expect_success 'clone shallow depth count' ' - test "`git --git-dir=shallow/.git rev-list --count HEAD`" = 2 + test "$(git --git-dir=shallow/.git rev-list --count HEAD)" = 2 ' test_expect_success 'clone shallow object count' ' @@ -259,7 +259,8 @@ test_expect_success 'clone shallow object count' ' test_expect_success 'pull in shallow repo with missing merge base' ' ( cd shallow && - test_must_fail git pull --depth 4 .. A + git fetch --depth 4 .. A + test_must_fail git merge --allow-unrelated-histories FETCH_HEAD ) ' @@ -273,15 +274,16 @@ test_expect_success 'additional simple shallow deepenings' ' ' test_expect_success 'clone shallow depth count' ' - test "`git --git-dir=shallow/.git rev-list --count HEAD`" = 11 + test "$(git --git-dir=shallow/.git rev-list --count HEAD)" = 11 ' test_expect_success 'clone shallow object count' ' ( cd shallow && + git prune && git count-objects -v ) > count.shallow && - grep "^count: 55" count.shallow + grep "^count: 54" count.shallow ' test_expect_success 'fetch --no-shallow on full repo' ' @@ -480,25 +482,25 @@ test_expect_success 'set up tests of missing reference' ' test_expect_success 'test lonely missing ref' ' ( cd client && - test_must_fail git fetch-pack --no-progress .. refs/heads/xyzzy - ) >/dev/null 2>error-m && - test_cmp expect-error error-m + test_must_fail git fetch-pack --no-progress .. refs/heads/xyzzy 2>../error-m + ) && + test_i18ncmp expect-error error-m ' test_expect_success 'test missing ref after existing' ' ( cd client && - test_must_fail git fetch-pack --no-progress .. refs/heads/A refs/heads/xyzzy - ) >/dev/null 2>error-em && - test_cmp expect-error error-em + test_must_fail git fetch-pack --no-progress .. refs/heads/A refs/heads/xyzzy 2>../error-em + ) && + test_i18ncmp expect-error error-em ' test_expect_success 'test missing ref before existing' ' ( cd client && - test_must_fail git fetch-pack --no-progress .. refs/heads/xyzzy refs/heads/A - ) >/dev/null 2>error-me && - test_cmp expect-error error-me + test_must_fail git fetch-pack --no-progress .. refs/heads/xyzzy refs/heads/A 2>../error-me + ) && + test_i18ncmp expect-error error-me ' test_expect_success 'test --all, --depth, and explicit head' ' @@ -531,6 +533,55 @@ test_expect_success 'shallow fetch with tags does not break the repository' ' git fsck ) ' + +test_expect_success 'fetch-pack can fetch a raw sha1' ' + git init hidden && + ( + cd hidden && + test_commit 1 && + test_commit 2 && + git update-ref refs/hidden/one HEAD^ && + git config transfer.hiderefs refs/hidden && + git config uploadpack.allowtipsha1inwant true + ) && + git fetch-pack hidden $(git -C hidden rev-parse refs/hidden/one) +' + +test_expect_success 'fetch-pack can fetch a raw sha1 that is advertised as a ref' ' + rm -rf server client && + git init server && + test_commit -C server 1 && + + git init client && + git -C client fetch-pack ../server \ + $(git -C server rev-parse refs/heads/master) +' + +test_expect_success 'fetch-pack can fetch a raw sha1 overlapping a named ref' ' + rm -rf server client && + git init server && + test_commit -C server 1 && + test_commit -C server 2 && + + git init client && + git -C client fetch-pack ../server \ + $(git -C server rev-parse refs/tags/1) refs/tags/1 +' + +test_expect_success 'fetch-pack cannot fetch a raw sha1 that is not advertised as a ref' ' + rm -rf server && + + git init server && + test_commit -C server 5 && + git -C server tag -d 5 && + test_commit -C server 6 && + + git init client && + test_must_fail git -C client fetch-pack ../server \ + $(git -C server rev-parse refs/heads/master^) 2>err && + test_i18ngrep "Server does not allow request for unadvertised object" err +' + check_prot_path () { cat >expected <<-EOF && Diag: url=$1 @@ -542,7 +593,6 @@ check_prot_path () { } check_prot_host_port_path () { - local diagport case "$2" in *ssh*) pp=ssh @@ -637,4 +687,135 @@ test_expect_success MINGW 'fetch-pack --diag-url c:repo' ' check_prot_path c:repo file c:repo ' +test_expect_success 'clone shallow since ...' ' + test_create_repo shallow-since && + ( + cd shallow-since && + GIT_COMMITTER_DATE="100000000 +0700" git commit --allow-empty -m one && + GIT_COMMITTER_DATE="200000000 +0700" git commit --allow-empty -m two && + GIT_COMMITTER_DATE="300000000 +0700" git commit --allow-empty -m three && + git clone --shallow-since "300000000 +0700" "file://$(pwd)/." ../shallow11 && + git -C ../shallow11 log --pretty=tformat:%s HEAD >actual && + echo three >expected && + test_cmp expected actual + ) +' + +test_expect_success 'fetch shallow since ...' ' + git -C shallow11 fetch --shallow-since "200000000 +0700" origin && + git -C shallow11 log --pretty=tformat:%s origin/master >actual && + cat >expected <<-\EOF && + three + two + EOF + test_cmp expected actual +' + +test_expect_success 'shallow clone exclude tag two' ' + test_create_repo shallow-exclude && + ( + cd shallow-exclude && + test_commit one && + test_commit two && + test_commit three && + git clone --shallow-exclude two "file://$(pwd)/." ../shallow12 && + git -C ../shallow12 log --pretty=tformat:%s HEAD >actual && + echo three >expected && + test_cmp expected actual + ) +' + +test_expect_success 'fetch exclude tag one' ' + git -C shallow12 fetch --shallow-exclude one origin && + git -C shallow12 log --pretty=tformat:%s origin/master >actual && + test_write_lines three two >expected && + test_cmp expected actual +' + +test_expect_success 'fetching deepen' ' + test_create_repo shallow-deepen && + ( + cd shallow-deepen && + test_commit one && + test_commit two && + test_commit three && + git clone --depth 1 "file://$(pwd)/." deepen && + test_commit four && + git -C deepen log --pretty=tformat:%s master >actual && + echo three >expected && + test_cmp expected actual && + git -C deepen fetch --deepen=1 && + git -C deepen log --pretty=tformat:%s origin/master >actual && + cat >expected <<-\EOF && + four + three + two + EOF + test_cmp expected actual + ) +' + +test_expect_success 'filtering by size' ' + rm -rf server client && + test_create_repo server && + test_commit -C server one && + test_config -C server uploadpack.allowfilter 1 && + + test_create_repo client && + git -C client fetch-pack --filter=blob:limit=0 ../server HEAD && + + # Ensure that object is not inadvertently fetched + test_must_fail git -C client cat-file -e $(git hash-object server/one.t) +' + +test_expect_success 'filtering by size has no effect if support for it is not advertised' ' + rm -rf server client && + test_create_repo server && + test_commit -C server one && + + test_create_repo client && + git -C client fetch-pack --filter=blob:limit=0 ../server HEAD 2> err && + + # Ensure that object is fetched + git -C client cat-file -e $(git hash-object server/one.t) && + + test_i18ngrep "filtering not recognized by server" err +' + +fetch_filter_blob_limit_zero () { + SERVER="$1" + URL="$2" + + rm -rf "$SERVER" client && + test_create_repo "$SERVER" && + test_commit -C "$SERVER" one && + test_config -C "$SERVER" uploadpack.allowfilter 1 && + + git clone "$URL" client && + test_config -C client extensions.partialclone origin && + + test_commit -C "$SERVER" two && + + git -C client fetch --filter=blob:limit=0 origin HEAD:somewhere && + + # Ensure that commit is fetched, but blob is not + test_config -C client extensions.partialclone "arbitrary string" && + git -C client cat-file -e $(git -C "$SERVER" rev-parse two) && + test_must_fail git -C client cat-file -e $(git hash-object "$SERVER/two.t") +} + +test_expect_success 'fetch with --filter=blob:limit=0' ' + fetch_filter_blob_limit_zero server server +' + +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +test_expect_success 'fetch with --filter=blob:limit=0 and HTTP' ' + fetch_filter_blob_limit_zero "$HTTPD_DOCUMENT_ROOT_PATH/server" "$HTTPD_URL/smart/server" +' + +stop_httpd + + test_done diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh index 69ee13c8be..49d3621a92 100755 --- a/t/t5504-fetch-receive-strict.sh +++ b/t/t5504-fetch-receive-strict.sh @@ -115,4 +115,55 @@ test_expect_success 'push with transfer.fsckobjects' ' test_cmp exp act ' +cat >bogus-commit <<EOF +tree $EMPTY_TREE +author Bugs Bunny 1234567890 +0000 +committer Bugs Bunny <bugs@bun.ni> 1234567890 +0000 + +This commit object intentionally broken +EOF + +test_expect_success 'push with receive.fsck.skipList' ' + commit="$(git hash-object -t commit -w --stdin <bogus-commit)" && + git push . $commit:refs/heads/bogus && + rm -rf dst && + git init dst && + git --git-dir=dst/.git config receive.fsckObjects true && + test_must_fail git push --porcelain dst bogus && + git --git-dir=dst/.git config receive.fsck.skipList SKIP && + echo $commit >dst/.git/SKIP && + git push --porcelain dst bogus +' + +test_expect_success 'push with receive.fsck.missingEmail=warn' ' + commit="$(git hash-object -t commit -w --stdin <bogus-commit)" && + git push . $commit:refs/heads/bogus && + rm -rf dst && + git init dst && + git --git-dir=dst/.git config receive.fsckobjects true && + test_must_fail git push --porcelain dst bogus && + git --git-dir=dst/.git config \ + receive.fsck.missingEmail warn && + git push --porcelain dst bogus >act 2>&1 && + grep "missingEmail" act && + git --git-dir=dst/.git branch -D bogus && + git --git-dir=dst/.git config --add \ + receive.fsck.missingEmail ignore && + git --git-dir=dst/.git config --add \ + receive.fsck.badDate warn && + git push --porcelain dst bogus >act 2>&1 && + ! grep "missingEmail" act +' + +test_expect_success \ + 'receive.fsck.unterminatedHeader=warn triggers error' ' + rm -rf dst && + git init dst && + git --git-dir=dst/.git config receive.fsckobjects true && + git --git-dir=dst/.git config \ + receive.fsck.unterminatedheader warn && + test_must_fail git push --porcelain dst HEAD >act 2>&1 && + grep "Cannot demote unterminatedheader" act +' + test_done diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 7a8499ce66..a6c0178f3a 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -51,6 +51,11 @@ test_expect_success setup ' git clone one test ' +test_expect_success 'add remote whose URL agrees with url.<...>.insteadOf' ' + test_config url.git@host.com:team/repo.git.insteadOf myremote && + git remote add myremote git@host.com:team/repo.git +' + test_expect_success C_LOCALE_OUTPUT 'remote information for the origin' ' ( cd test && @@ -85,7 +90,7 @@ test_expect_success C_LOCALE_OUTPUT 'check remote-tracking' ' test_expect_success 'remote forces tracking branches' ' ( cd test && - case `git config remote.second.fetch` in + case $(git config remote.second.fetch) in +*) true ;; *) false ;; esac @@ -139,6 +144,58 @@ test_expect_success 'remove remote protects local branches' ' ) ' +test_expect_success 'remove errors out early when deleting non-existent branch' ' + ( + cd test && + echo "fatal: No such remote: foo" >expect && + test_must_fail git remote rm foo 2>actual && + test_i18ncmp expect actual + ) +' + +test_expect_success 'remove remote with a branch without configured merge' ' + test_when_finished "( + git -C test checkout master; + git -C test branch -D two; + git -C test config --remove-section remote.two; + git -C test config --remove-section branch.second; + true + )" && + ( + cd test && + git remote add two ../two && + git fetch two && + git checkout -b second two/master^0 && + git config branch.second.remote two && + git checkout master && + git remote rm two + ) +' + +test_expect_success 'rename errors out early when deleting non-existent branch' ' + ( + cd test && + echo "fatal: No such remote: foo" >expect && + test_must_fail git remote rename foo bar 2>actual && + test_i18ncmp expect actual + ) +' + +test_expect_success 'add existing foreign_vcs remote' ' + test_config remote.foo.vcs bar && + echo "fatal: remote foo already exists." >expect && + test_must_fail git remote add foo bar 2>actual && + test_i18ncmp expect actual +' + +test_expect_success 'add existing foreign_vcs remote' ' + test_config remote.foo.vcs bar && + test_config remote.bar.vcs bar && + echo "fatal: remote bar already exists." >expect && + test_must_fail git remote rename foo bar 2>actual && + test_i18ncmp expect actual +' + cat >test/expect <<EOF * remote origin Fetch URL: $(pwd)/one @@ -687,7 +744,7 @@ test_expect_success 'rename a remote' ' ( cd four && git remote rename origin upstream && - rmdir .git/refs/remotes/origin && + test -z "$(git for-each-ref refs/remotes/origin)" && test "$(git symbolic-ref refs/remotes/upstream/HEAD)" = "refs/remotes/upstream/master" && test "$(git rev-parse upstream/master)" = "$(git rev-parse master)" && test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/remotes/upstream/*" && @@ -726,6 +783,13 @@ test_expect_success 'rename a remote with name prefix of other remote' ' ) ' +test_expect_success 'rename succeeds with existing remote.<target>.prune' ' + git clone one four.four && + test_when_finished git config --global --unset remote.upstream.prune && + git config --global remote.upstream.prune true && + git -C four.four remote rename origin upstream +' + cat >remotes_origin <<EOF URL: $(pwd)/one Push: refs/heads/master:refs/heads/upstream @@ -919,6 +983,28 @@ test_expect_success 'new remote' ' cmp expect actual ' +get_url_test () { + cat >expect && + git remote get-url "$@" >actual && + test_cmp expect actual +} + +test_expect_success 'get-url on new remote' ' + echo foo | get_url_test someremote && + echo foo | get_url_test --all someremote && + echo foo | get_url_test --push someremote && + echo foo | get_url_test --push --all someremote +' + +test_expect_success 'remote set-url with locked config' ' + test_when_finished "rm -f .git/config.lock" && + git config --get-all remote.someremote.url >expect && + >.git/config.lock && + test_must_fail git remote set-url someremote baz && + git config --get-all remote.someremote.url >actual && + cmp expect actual +' + test_expect_success 'remote set-url bar' ' git remote set-url someremote bar && echo bar >expect && @@ -961,6 +1047,13 @@ test_expect_success 'remote set-url --push zot' ' cmp expect actual ' +test_expect_success 'get-url with different urls' ' + echo baz | get_url_test someremote && + echo baz | get_url_test --all someremote && + echo zot | get_url_test --push someremote && + echo zot | get_url_test --push --all someremote +' + test_expect_success 'remote set-url --push qux zot' ' git remote set-url --push someremote qux zot && echo qux >expect && @@ -995,6 +1088,14 @@ test_expect_success 'remote set-url --push --add aaa' ' cmp expect actual ' +test_expect_success 'get-url on multi push remote' ' + echo foo | get_url_test --push someremote && + get_url_test --push --all someremote <<-\EOF + foo + aaa + EOF +' + test_expect_success 'remote set-url --push bar aaa' ' git remote set-url --push someremote bar aaa && echo foo >expect && @@ -1039,6 +1140,14 @@ test_expect_success 'remote set-url --add bbb' ' cmp expect actual ' +test_expect_success 'get-url on multi fetch remote' ' + echo baz | get_url_test someremote && + get_url_test --all someremote <<-\EOF + baz + bbb + EOF +' + test_expect_success 'remote set-url --delete .*' ' test_must_fail git remote set-url --delete someremote .\* && echo "YYY" >expect && @@ -1099,7 +1208,7 @@ test_expect_success 'extra args: setup' ' test_extra_arg () { test_expect_success "extra args: $*" " test_must_fail git remote $* bogus_extra_arg 2>actual && - grep '^usage:' actual + test_i18ngrep '^usage:' actual " } @@ -1108,6 +1217,7 @@ test_extra_arg rename origin newname test_extra_arg remove origin test_extra_arg set-head origin master # set-branches takes any number of args +test_extra_arg get-url origin newurl test_extra_arg set-url origin newurl oldurl # show takes any number of args # prune takes any number of args diff --git a/t/t5506-remote-groups.sh b/t/t5506-remote-groups.sh index 530b01678e..83d5558c0e 100755 --- a/t/t5506-remote-groups.sh +++ b/t/t5506-remote-groups.sh @@ -20,7 +20,7 @@ update_repos() { } repo_fetched() { - if test "`git log -1 --pretty=format:%s $1 --`" = "`cat mark`"; then + if test "$(git log -1 --pretty=format:%s $1 --)" = "$(cat mark)"; then echo >&2 "repo was fetched: $1" return 0 fi diff --git a/t/t5507-remote-environment.sh b/t/t5507-remote-environment.sh new file mode 100755 index 0000000000..e6149295b1 --- /dev/null +++ b/t/t5507-remote-environment.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +test_description='check environment showed to remote side of transports' +. ./test-lib.sh + +test_expect_success 'set up "remote" push situation' ' + test_commit one && + git config push.default current && + git init remote +' + +test_expect_success 'set up fake ssh' ' + GIT_SSH_COMMAND="f() { + cd \"\$TRASH_DIRECTORY\" && + eval \"\$2\" + }; f" && + export GIT_SSH_COMMAND && + export TRASH_DIRECTORY +' + +# due to receive.denyCurrentBranch=true +test_expect_success 'confirm default push fails' ' + test_must_fail git push remote +' + +test_expect_success 'config does not travel over same-machine push' ' + test_must_fail git -c receive.denyCurrentBranch=false push remote +' + +test_expect_success 'config does not travel over ssh push' ' + test_must_fail git -c receive.denyCurrentBranch=false push host:remote +' + +test_done diff --git a/t/t5509-fetch-push-namespaces.sh b/t/t5509-fetch-push-namespaces.sh index cc0b31f6b0..75c570adca 100755 --- a/t/t5509-fetch-push-namespaces.sh +++ b/t/t5509-fetch-push-namespaces.sh @@ -4,6 +4,7 @@ test_description='fetch/push involving ref namespaces' . ./test-lib.sh test_expect_success setup ' + git config --global protocol.ext.allow user && test_tick && git init original && ( @@ -82,4 +83,45 @@ test_expect_success 'mirroring a repository using a ref namespace' ' ) ' +test_expect_success 'hide namespaced refs with transfer.hideRefs' ' + GIT_NAMESPACE=namespace \ + git -C pushee -c transfer.hideRefs=refs/tags \ + ls-remote "ext::git %s ." >actual && + printf "$commit1\trefs/heads/master\n" >expected && + test_cmp expected actual +' + +test_expect_success 'check that transfer.hideRefs does not match unstripped refs' ' + GIT_NAMESPACE=namespace \ + git -C pushee -c transfer.hideRefs=refs/namespaces/namespace/refs/tags \ + ls-remote "ext::git %s ." >actual && + printf "$commit1\trefs/heads/master\n" >expected && + printf "$commit0\trefs/tags/0\n" >>expected && + printf "$commit1\trefs/tags/1\n" >>expected && + test_cmp expected actual +' + +test_expect_success 'hide full refs with transfer.hideRefs' ' + GIT_NAMESPACE=namespace \ + git -C pushee -c transfer.hideRefs="^refs/namespaces/namespace/refs/tags" \ + ls-remote "ext::git %s ." >actual && + printf "$commit1\trefs/heads/master\n" >expected && + test_cmp expected actual +' + +test_expect_success 'try to update a hidden ref' ' + test_config -C pushee transfer.hideRefs refs/heads/master && + test_must_fail git -C original push pushee-namespaced master +' + +test_expect_success 'try to update a ref that is not hidden' ' + test_config -C pushee transfer.hideRefs refs/namespaces/namespace/refs/heads/master && + git -C original push pushee-namespaced master +' + +test_expect_success 'try to update a hidden full ref' ' + test_config -C pushee transfer.hideRefs "^refs/namespaces/namespace/refs/heads/master" && + test_must_fail git -C original push pushee-namespaced master +' + test_done diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 0ba9db0884..ae5a530a2d 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -7,7 +7,7 @@ test_description='Per branch config variables affects "git fetch". . ./test-lib.sh -D=`pwd` +D=$(pwd) test_bundle_object_count () { git verify-pack -v "$1" >verify.out && @@ -64,8 +64,8 @@ test_expect_success "fetch test" ' cd two && git fetch && test -f .git/refs/heads/one && - mine=`git rev-parse refs/heads/one` && - his=`cd ../one && git rev-parse refs/heads/master` && + mine=$(git rev-parse refs/heads/one) && + his=$(cd ../one && git rev-parse refs/heads/master) && test "z$mine" = "z$his" ' @@ -75,8 +75,8 @@ test_expect_success "fetch test for-merge" ' git fetch && test -f .git/refs/heads/two && test -f .git/refs/heads/one && - master_in_two=`cd ../two && git rev-parse master` && - one_in_two=`cd ../two && git rev-parse one` && + master_in_two=$(cd ../two && git rev-parse master) && + one_in_two=$(cd ../two && git rev-parse one) && { echo "$one_in_two " echo "$master_in_two not-for-merge" @@ -222,12 +222,9 @@ test_expect_success 'fetch uses remote ref names to describe new refs' ' ( cd descriptive && git fetch o 2>actual && - grep " -> refs/crazyheads/descriptive-branch$" actual | - test_i18ngrep "new branch" && - grep " -> descriptive-tag$" actual | - test_i18ngrep "new tag" && - grep " -> crazy$" actual | - test_i18ngrep "new ref" + test_i18ngrep "new branch.* -> refs/crazyheads/descriptive-branch$" actual && + test_i18ngrep "new tag.* -> descriptive-tag$" actual && + test_i18ngrep "new ref.* -> crazy$" actual ) && git checkout master ' @@ -314,42 +311,6 @@ test_expect_success 'bundle should be able to create a full history' ' ' -! rsync --help > /dev/null 2> /dev/null && -say 'Skipping rsync tests because rsync was not found' || { -test_expect_success 'fetch via rsync' ' - git pack-refs && - mkdir rsynced && - (cd rsynced && - git init --bare && - git fetch "rsync:../.git" master:refs/heads/master && - git gc --prune && - test $(git rev-parse master) = $(cd .. && git rev-parse master) && - git fsck --full) -' - -test_expect_success 'push via rsync' ' - mkdir rsynced2 && - (cd rsynced2 && - git init) && - (cd rsynced && - git push "rsync:../rsynced2/.git" master) && - (cd rsynced2 && - git gc --prune && - test $(git rev-parse master) = $(cd .. && git rev-parse master) && - git fsck --full) -' - -test_expect_success 'push via rsync' ' - mkdir rsynced3 && - (cd rsynced3 && - git init) && - git push --all "rsync:rsynced3/.git" && - (cd rsynced3 && - test $(git rev-parse master) = $(cd .. && git rev-parse master) && - git fsck --full) -' -} - test_expect_success 'fetch with a non-applying branch.<name>.merge' ' git config branch.master.remote yeti && git config branch.master.merge refs/heads/bigfoot && @@ -579,82 +540,232 @@ test_expect_success "should be able to fetch with duplicate refspecs" ' set_config_tristate () { # var=$1 val=$2 case "$2" in - unset) test_unconfig "$1" ;; - *) git config "$1" "$2" ;; + unset) + test_unconfig "$1" + ;; + *) + git config "$1" "$2" + key=$(echo $1 | sed -e 's/^remote\.origin/fetch/') + git_fetch_c="$git_fetch_c -c $key=$2" + ;; esac } test_configured_prune () { - fetch_prune=$1 remote_origin_prune=$2 cmdline=$3 expected=$4 + test_configured_prune_type "$@" "name" + test_configured_prune_type "$@" "link" +} - test_expect_success "prune fetch.prune=$1 remote.origin.prune=$2${3:+ $3}; $4" ' +test_configured_prune_type () { + fetch_prune=$1 + remote_origin_prune=$2 + fetch_prune_tags=$3 + remote_origin_prune_tags=$4 + expected_branch=$5 + expected_tag=$6 + cmdline=$7 + mode=$8 + + if test -z "$cmdline_setup" + then + test_expect_success 'setup cmdline_setup variable for subsequent test' ' + remote_url="file://$(git -C one config remote.origin.url)" && + remote_fetch="$(git -C one config remote.origin.fetch)" && + cmdline_setup="\"$remote_url\" \"$remote_fetch\"" + ' + fi + + if test "$mode" = 'link' + then + new_cmdline="" + + if test "$cmdline" = "" + then + new_cmdline=$cmdline_setup + else + new_cmdline=$(printf "%s" "$cmdline" | perl -pe 's[origin(?!/)]["'"$remote_url"'"]g') + fi + + if test "$fetch_prune_tags" = 'true' || + test "$remote_origin_prune_tags" = 'true' + then + if ! printf '%s' "$cmdline\n" | grep -q refs/remotes/origin/ + then + new_cmdline="$new_cmdline refs/tags/*:refs/tags/*" + fi + fi + + cmdline="$new_cmdline" + fi + + test_expect_success "$mode prune fetch.prune=$1 remote.origin.prune=$2 fetch.pruneTags=$3 remote.origin.pruneTags=$4${7:+ $7}; branch:$5 tag:$6" ' # make sure a newbranch is there in . and also in one git branch -f newbranch && + git tag -f newtag && ( cd one && test_unconfig fetch.prune && + test_unconfig fetch.pruneTags && test_unconfig remote.origin.prune && - git fetch && - git rev-parse --verify refs/remotes/origin/newbranch + test_unconfig remote.origin.pruneTags && + git fetch '"$cmdline_setup"' && + git rev-parse --verify refs/remotes/origin/newbranch && + git rev-parse --verify refs/tags/newtag ) && # now remove it git branch -d newbranch && + git tag -d newtag && # then test ( cd one && + git_fetch_c="" && set_config_tristate fetch.prune $fetch_prune && + set_config_tristate fetch.pruneTags $fetch_prune_tags && set_config_tristate remote.origin.prune $remote_origin_prune && - - git fetch $cmdline && - case "$expected" in + set_config_tristate remote.origin.pruneTags $remote_origin_prune_tags && + + if test "$mode" != "link" + then + git_fetch_c="" + fi && + git$git_fetch_c fetch '"$cmdline"' && + case "$expected_branch" in pruned) test_must_fail git rev-parse --verify refs/remotes/origin/newbranch ;; kept) git rev-parse --verify refs/remotes/origin/newbranch ;; + esac && + case "$expected_tag" in + pruned) + test_must_fail git rev-parse --verify refs/tags/newtag + ;; + kept) + git rev-parse --verify refs/tags/newtag + ;; esac ) ' } -test_configured_prune unset unset "" kept -test_configured_prune unset unset "--no-prune" kept -test_configured_prune unset unset "--prune" pruned - -test_configured_prune false unset "" kept -test_configured_prune false unset "--no-prune" kept -test_configured_prune false unset "--prune" pruned - -test_configured_prune true unset "" pruned -test_configured_prune true unset "--prune" pruned -test_configured_prune true unset "--no-prune" kept - -test_configured_prune unset false "" kept -test_configured_prune unset false "--no-prune" kept -test_configured_prune unset false "--prune" pruned - -test_configured_prune false false "" kept -test_configured_prune false false "--no-prune" kept -test_configured_prune false false "--prune" pruned - -test_configured_prune true false "" kept -test_configured_prune true false "--prune" pruned -test_configured_prune true false "--no-prune" kept - -test_configured_prune unset true "" pruned -test_configured_prune unset true "--no-prune" kept -test_configured_prune unset true "--prune" pruned - -test_configured_prune false true "" pruned -test_configured_prune false true "--no-prune" kept -test_configured_prune false true "--prune" pruned - -test_configured_prune true true "" pruned -test_configured_prune true true "--prune" pruned -test_configured_prune true true "--no-prune" kept +# $1 config: fetch.prune +# $2 config: remote.<name>.prune +# $3 config: fetch.pruneTags +# $4 config: remote.<name>.pruneTags +# $5 expect: branch to be pruned? +# $6 expect: tag to be pruned? +# $7 git-fetch $cmdline: +# +# $1 $2 $3 $4 $5 $6 $7 +test_configured_prune unset unset unset unset kept kept "" +test_configured_prune unset unset unset unset kept kept "--no-prune" +test_configured_prune unset unset unset unset pruned kept "--prune" +test_configured_prune unset unset unset unset kept pruned \ + "--prune origin refs/tags/*:refs/tags/*" +test_configured_prune unset unset unset unset pruned pruned \ + "--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*" + +test_configured_prune false unset unset unset kept kept "" +test_configured_prune false unset unset unset kept kept "--no-prune" +test_configured_prune false unset unset unset pruned kept "--prune" + +test_configured_prune true unset unset unset pruned kept "" +test_configured_prune true unset unset unset pruned kept "--prune" +test_configured_prune true unset unset unset kept kept "--no-prune" + +test_configured_prune unset false unset unset kept kept "" +test_configured_prune unset false unset unset kept kept "--no-prune" +test_configured_prune unset false unset unset pruned kept "--prune" + +test_configured_prune false false unset unset kept kept "" +test_configured_prune false false unset unset kept kept "--no-prune" +test_configured_prune false false unset unset pruned kept "--prune" +test_configured_prune false false unset unset kept pruned \ + "--prune origin refs/tags/*:refs/tags/*" +test_configured_prune false false unset unset pruned pruned \ + "--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*" + +test_configured_prune true false unset unset kept kept "" +test_configured_prune true false unset unset pruned kept "--prune" +test_configured_prune true false unset unset kept kept "--no-prune" + +test_configured_prune unset true unset unset pruned kept "" +test_configured_prune unset true unset unset kept kept "--no-prune" +test_configured_prune unset true unset unset pruned kept "--prune" + +test_configured_prune false true unset unset pruned kept "" +test_configured_prune false true unset unset kept kept "--no-prune" +test_configured_prune false true unset unset pruned kept "--prune" + +test_configured_prune true true unset unset pruned kept "" +test_configured_prune true true unset unset pruned kept "--prune" +test_configured_prune true true unset unset kept kept "--no-prune" +test_configured_prune true true unset unset kept pruned \ + "--prune origin refs/tags/*:refs/tags/*" +test_configured_prune true true unset unset pruned pruned \ + "--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*" + +# --prune-tags on its own does nothing, needs --prune as well, same +# for for fetch.pruneTags without fetch.prune +test_configured_prune unset unset unset unset kept kept "--prune-tags" +test_configured_prune unset unset true unset kept kept "" +test_configured_prune unset unset unset true kept kept "" + +# These will prune the tags +test_configured_prune unset unset unset unset pruned pruned "--prune --prune-tags" +test_configured_prune true unset true unset pruned pruned "" +test_configured_prune unset true unset true pruned pruned "" + +# remote.<name>.pruneTags overrides fetch.pruneTags, just like +# remote.<name>.prune overrides fetch.prune if set. +test_configured_prune true unset true unset pruned pruned "" +test_configured_prune false true false true pruned pruned "" +test_configured_prune true false true false kept kept "" + +# When --prune-tags is supplied it's ignored if an explicit refspec is +# given, same for the configuration options. +test_configured_prune unset unset unset unset pruned kept \ + "--prune --prune-tags origin +refs/heads/*:refs/remotes/origin/*" +test_configured_prune unset unset true unset pruned kept \ + "--prune origin +refs/heads/*:refs/remotes/origin/*" +test_configured_prune unset unset unset true pruned kept \ + "--prune origin +refs/heads/*:refs/remotes/origin/*" + +# Pruning that also takes place if a file:// url replaces a named +# remote. However, because there's no implicit +# +refs/heads/*:refs/remotes/origin/* refspec and supplying it on the +# command-line negates --prune-tags, the branches will not be pruned. +test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "name" +test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "link" +test_configured_prune_type unset unset unset unset pruned pruned "origin --prune --prune-tags" "name" +test_configured_prune_type unset unset unset unset kept pruned "origin --prune --prune-tags" "link" +test_configured_prune_type unset unset unset unset pruned pruned "--prune --prune-tags origin" "name" +test_configured_prune_type unset unset unset unset kept pruned "--prune --prune-tags origin" "link" +test_configured_prune_type unset unset true unset pruned pruned "--prune origin" "name" +test_configured_prune_type unset unset true unset kept pruned "--prune origin" "link" +test_configured_prune_type unset unset unset true pruned pruned "--prune origin" "name" +test_configured_prune_type unset unset unset true kept pruned "--prune origin" "link" +test_configured_prune_type true unset true unset pruned pruned "origin" "name" +test_configured_prune_type true unset true unset kept pruned "origin" "link" +test_configured_prune_type unset true true unset pruned pruned "origin" "name" +test_configured_prune_type unset true true unset kept pruned "origin" "link" +test_configured_prune_type unset true unset true pruned pruned "origin" "name" +test_configured_prune_type unset true unset true kept pruned "origin" "link" + +# When all remote.origin.fetch settings are deleted a --prune +# --prune-tags still implicitly supplies refs/tags/*:refs/tags/* so +# tags, but not tracking branches, will be deleted. +test_expect_success 'remove remote.origin.fetch "one"' ' + ( + cd one && + git config --unset-all remote.origin.fetch + ) +' +test_configured_prune_type unset unset unset unset kept pruned "origin --prune --prune-tags" "name" +test_configured_prune_type unset unset unset unset kept pruned "origin --prune --prune-tags" "link" test_expect_success 'all boundary commits are excluded' ' test_commit base && @@ -680,7 +791,7 @@ test_expect_success 'fetch --prune prints the remotes url' ' git fetch --prune origin 2>&1 | head -n1 >../actual ) && echo "From ${D}/." >expect && - test_cmp expect actual + test_i18ncmp expect actual ' test_expect_success 'branchname D/F conflict resolved by --prune' ' @@ -708,4 +819,50 @@ test_expect_success 'fetching a one-level ref works' ' ) ' +test_expect_success 'fetching with auto-gc does not lock up' ' + write_script askyesno <<-\EOF && + echo "$*" && + false + EOF + git clone "file://$D" auto-gc && + test_commit test2 && + ( + cd auto-gc && + git config gc.autoPackLimit 1 && + git config gc.autoDetach false && + GIT_ASK_YESNO="$D/askyesno" git fetch >fetch.out 2>&1 && + ! grep "Should I try again" fetch.out + ) +' + +test_expect_success C_LOCALE_OUTPUT 'fetch aligned output' ' + git clone . full-output && + test_commit looooooooooooong-tag && + ( + cd full-output && + git -c fetch.output=full fetch origin >actual 2>&1 && + grep -e "->" actual | cut -c 22- >../actual + ) && + cat >expect <<-\EOF && + master -> origin/master + looooooooooooong-tag -> looooooooooooong-tag + EOF + test_cmp expect actual +' + +test_expect_success C_LOCALE_OUTPUT 'fetch compact output' ' + git clone . compact && + test_commit extraaa && + ( + cd compact && + git -c fetch.output=compact fetch origin >actual 2>&1 && + grep -e "->" actual | cut -c 22- >../actual + ) && + cat >expect <<-\EOF && + master -> origin/* + extraaa -> * + EOF + test_cmp expect actual +' + test_done diff --git a/t/t5511-refspec.sh b/t/t5511-refspec.sh index de6db86ccf..f541f30bc2 100755 --- a/t/t5511-refspec.sh +++ b/t/t5511-refspec.sh @@ -71,15 +71,18 @@ test_refspec fetch ':refs/remotes/frotz/HEAD-to-me' test_refspec push ':refs/remotes/frotz/delete me' invalid test_refspec fetch ':refs/remotes/frotz/HEAD to me' invalid -test_refspec fetch 'refs/heads/*/for-linus:refs/remotes/mine/*-blah' invalid -test_refspec push 'refs/heads/*/for-linus:refs/remotes/mine/*-blah' invalid +test_refspec fetch 'refs/heads/*/for-linus:refs/remotes/mine/*-blah' +test_refspec push 'refs/heads/*/for-linus:refs/remotes/mine/*-blah' -test_refspec fetch 'refs/heads*/for-linus:refs/remotes/mine/*' invalid -test_refspec push 'refs/heads*/for-linus:refs/remotes/mine/*' invalid +test_refspec fetch 'refs/heads*/for-linus:refs/remotes/mine/*' +test_refspec push 'refs/heads*/for-linus:refs/remotes/mine/*' test_refspec fetch 'refs/heads/*/*/for-linus:refs/remotes/mine/*' invalid test_refspec push 'refs/heads/*/*/for-linus:refs/remotes/mine/*' invalid +test_refspec fetch 'refs/heads/*g*/for-linus:refs/remotes/mine/*' invalid +test_refspec push 'refs/heads/*g*/for-linus:refs/remotes/mine/*' invalid + test_refspec fetch 'refs/heads/*/for-linus:refs/remotes/mine/*' test_refspec push 'refs/heads/*/for-linus:refs/remotes/mine/*' diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh index 3bd9759e0f..02106c9226 100755 --- a/t/t5512-ls-remote.sh +++ b/t/t5512-ls-remote.sh @@ -85,8 +85,15 @@ test_expect_success 'use branch.<name>.remote if possible' ' ' test_expect_success 'confuses pattern as remote when no remote specified' ' - cat >exp <<-\EOF && - fatal: '\''refs*master'\'' does not appear to be a git repository + if test_have_prereq MINGW + then + # Windows does not like asterisks in pathname + does_not_exist=master + else + does_not_exist="refs*master" + fi && + cat >exp <<-EOF && + fatal: '\''$does_not_exist'\'' does not appear to be a git repository fatal: Could not read from remote repository. Please make sure you have the correct access rights @@ -98,8 +105,8 @@ test_expect_success 'confuses pattern as remote when no remote specified' ' # fetch <branch>. # We could just as easily have used "master"; the "*" emphasizes its # role as a pattern. - test_must_fail git ls-remote refs*master >actual 2>&1 && - test_cmp exp actual + test_must_fail git ls-remote "$does_not_exist" >actual 2>&1 && + test_i18ncmp exp actual ' test_expect_success 'die with non-2 for wrong repository even with --exit-code' ' @@ -128,6 +135,11 @@ test_expect_success 'Report match with --exit-code' ' test_cmp expect actual ' +test_expect_success 'set up some extra tags for ref hiding' ' + git tag magic/one && + git tag magic/two +' + for configsection in transfer uploadpack do test_expect_success "Hide some refs with $configsection.hiderefs" ' @@ -138,6 +150,118 @@ do sed -e "/ refs\/tags\//d" >expect && test_cmp expect actual ' + + test_expect_success "Override hiding of $configsection.hiderefs" ' + test_when_finished "test_unconfig $configsection.hiderefs" && + git config --add $configsection.hiderefs refs/tags && + git config --add $configsection.hiderefs "!refs/tags/magic" && + git config --add $configsection.hiderefs refs/tags/magic/one && + git ls-remote . >actual && + grep refs/tags/magic/two actual && + ! grep refs/tags/magic/one actual + ' + done +test_expect_success 'overrides work between mixed transfer/upload-pack hideRefs' ' + test_config uploadpack.hiderefs refs/tags && + test_config transfer.hiderefs "!refs/tags/magic" && + git ls-remote . >actual && + grep refs/tags/magic actual +' + +test_expect_success 'ls-remote --symref' ' + cat >expect <<-\EOF && + ref: refs/heads/master HEAD + 1bd44cb9d13204b0fe1958db0082f5028a16eb3a HEAD + 1bd44cb9d13204b0fe1958db0082f5028a16eb3a refs/heads/master + 1bd44cb9d13204b0fe1958db0082f5028a16eb3a refs/remotes/origin/HEAD + 1bd44cb9d13204b0fe1958db0082f5028a16eb3a refs/remotes/origin/master + 1bd44cb9d13204b0fe1958db0082f5028a16eb3a refs/tags/mark + EOF + git ls-remote --symref >actual && + test_cmp expect actual +' + +test_expect_success 'ls-remote with filtered symref (refname)' ' + cat >expect <<-\EOF && + ref: refs/heads/master HEAD + 1bd44cb9d13204b0fe1958db0082f5028a16eb3a HEAD + EOF + git ls-remote --symref . HEAD >actual && + test_cmp expect actual +' + +test_expect_failure 'ls-remote with filtered symref (--heads)' ' + git symbolic-ref refs/heads/foo refs/tags/mark && + cat >expect <<-\EOF && + ref: refs/tags/mark refs/heads/foo + 1bd44cb9d13204b0fe1958db0082f5028a16eb3a refs/heads/foo + 1bd44cb9d13204b0fe1958db0082f5028a16eb3a refs/heads/master + EOF + git ls-remote --symref --heads . >actual && + test_cmp expect actual +' + +test_expect_success 'ls-remote --symref omits filtered-out matches' ' + cat >expect <<-\EOF && + 1bd44cb9d13204b0fe1958db0082f5028a16eb3a refs/heads/foo + 1bd44cb9d13204b0fe1958db0082f5028a16eb3a refs/heads/master + EOF + git ls-remote --symref --heads . >actual && + test_cmp expect actual && + git ls-remote --symref . "refs/heads/*" >actual && + test_cmp expect actual +' + +test_lazy_prereq GIT_DAEMON ' + test_tristate GIT_TEST_GIT_DAEMON && + test "$GIT_TEST_GIT_DAEMON" != false +' + +# This test spawns a daemon, so run it only if the user would be OK with +# testing with git-daemon. +test_expect_success PIPE,JGIT,GIT_DAEMON 'indicate no refs in standards-compliant empty remote' ' + JGIT_DAEMON_PORT=${JGIT_DAEMON_PORT-${this_test#t}} && + JGIT_DAEMON_PID= && + git init --bare empty.git && + >empty.git/git-daemon-export-ok && + mkfifo jgit_daemon_output && + { + jgit daemon --port="$JGIT_DAEMON_PORT" . >jgit_daemon_output & + JGIT_DAEMON_PID=$! + } && + test_when_finished kill "$JGIT_DAEMON_PID" && + { + read line && + case $line in + Exporting*) + ;; + *) + echo "Expected: Exporting" && + false;; + esac && + read line && + case $line in + "Listening on"*) + ;; + *) + echo "Expected: Listening on" && + false;; + esac + } <jgit_daemon_output && + # --exit-code asks the command to exit with 2 when no + # matching refs are found. + test_expect_code 2 git ls-remote --exit-code git://localhost:$JGIT_DAEMON_PORT/empty.git +' + +test_expect_success 'ls-remote works outside repository' ' + # It is important for this repo to be inside the nongit + # area, as we want a repo name that does not include + # slashes (because those inhibit some of our configuration + # lookups). + nongit git init --bare dst.git && + nongit git ls-remote dst.git +' + test_done diff --git a/t/t5515-fetch-merge-logic.sh b/t/t5515-fetch-merge-logic.sh index dbb927dec8..36b0dbc01c 100755 --- a/t/t5515-fetch-merge-logic.sh +++ b/t/t5515-fetch-merge-logic.sh @@ -128,8 +128,8 @@ do case "$cmd" in '' | '#'*) continue ;; esac - test=`echo "$cmd" | sed -e 's|[/ ][/ ]*|_|g'` - pfx=`printf "%04d" $test_count` + test=$(echo "$cmd" | sed -e 's|[/ ][/ ]*|_|g') + pfx=$(printf "%04d" $test_count) expect_f="$TEST_DIRECTORY/t5515/fetch.$test" actual_f="$pfx-fetch.$test" expect_r="$TEST_DIRECTORY/t5515/refs.$test" diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index ec22c98445..82239138d5 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -16,7 +16,7 @@ This test checks the following functionality: . ./test-lib.sh -D=`pwd` +D=$(pwd) mk_empty () { repo_name="$1" @@ -422,7 +422,7 @@ test_expect_success 'push tag with non-existent, incomplete dest' ' test_expect_success 'push sha1 with non-existent, incomplete dest' ' mk_test testrepo && - test_must_fail git push testrepo `git rev-parse master`:foo + test_must_fail git push testrepo $(git rev-parse master):foo ' @@ -1004,7 +1004,7 @@ test_expect_success 'push --porcelain' ' test_expect_success 'push --porcelain bad url' ' mk_empty testrepo && test_must_fail git push >.git/bar --porcelain asdfasdfasd refs/heads/master:refs/remotes/origin/master && - test_must_fail grep -q Done .git/bar + ! grep -q Done .git/bar ' test_expect_success 'push --porcelain rejected' ' @@ -1098,7 +1098,8 @@ test_expect_success 'fetch exact SHA1' ' test_must_fail git cat-file -t $the_commit && # fetching the hidden object should fail by default - test_must_fail git fetch -v ../testrepo $the_commit:refs/heads/copy && + test_must_fail git fetch -v ../testrepo $the_commit:refs/heads/copy 2>err && + test_i18ngrep "Server does not allow request for unadvertised object" err && test_must_fail git rev-parse --verify refs/heads/copy && # the server side can allow it to succeed @@ -1162,15 +1163,15 @@ do mk_empty shallow && ( cd shallow && - test_must_fail git fetch ../testrepo/.git $SHA1_3 && - test_must_fail git fetch ../testrepo/.git $SHA1_1 && + test_must_fail ok=sigpipe git fetch ../testrepo/.git $SHA1_3 && + test_must_fail ok=sigpipe git fetch ../testrepo/.git $SHA1_1 && git --git-dir=../testrepo/.git config uploadpack.allowreachablesha1inwant true && git fetch ../testrepo/.git $SHA1_1 && git cat-file commit $SHA1_1 && test_must_fail git cat-file commit $SHA1_2 && git fetch ../testrepo/.git $SHA1_2 && git cat-file commit $SHA1_2 && - test_must_fail git fetch ../testrepo/.git $SHA1_3 + test_must_fail ok=sigpipe git fetch ../testrepo/.git $SHA1_3 ) ' done @@ -1417,7 +1418,7 @@ test_expect_success 'receive.denyCurrentBranch = updateInstead' ' cd testrepo && git reset --hard HEAD^ && test $(git -C .. rev-parse HEAD^) = $(git rev-parse HEAD) && - test-chmtime +100 path1 + test-tool chmtime +100 path1 ) && git push testrepo master && ( diff --git a/t/t5517-push-mirror.sh b/t/t5517-push-mirror.sh index 12a5dfb17e..02f160aae0 100755 --- a/t/t5517-push-mirror.sh +++ b/t/t5517-push-mirror.sh @@ -4,7 +4,7 @@ test_description='pushing to a mirror repository' . ./test-lib.sh -D=`pwd` +D=$(pwd) invert () { if "$@"; then diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index af31f04c28..59c4b778d3 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -9,6 +9,24 @@ modify () { mv "$2.x" "$2" } +test_pull_autostash () { + git reset --hard before-rebase && + echo dirty >new_file && + git add new_file && + git pull "$@" . copy && + test_cmp_rev HEAD^ copy && + test "$(cat new_file)" = dirty && + test "$(cat file)" = "modified again" +} + +test_pull_autostash_fail () { + git reset --hard before-rebase && + echo dirty >new_file && + git add new_file && + test_must_fail git pull "$@" . copy 2>err && + test_i18ngrep "uncommitted changes." err +} + test_expect_success setup ' echo file >file && git add file && @@ -160,6 +178,18 @@ test_expect_success 'fail if no configuration for current branch' ' test "$(cat file)" = file ' +test_expect_success 'pull --all: fail if no configuration for current branch' ' + git remote add test_remote . && + test_when_finished "git remote remove test_remote" && + git checkout -b test copy^ && + test_when_finished "git checkout -f copy && git branch -D test" && + test_config branch.test.remote test_remote && + test "$(cat file)" = file && + test_must_fail git pull --all 2>err && + test_i18ngrep "There is no tracking information" err && + test "$(cat file)" = file +' + test_expect_success 'fail if upstream branch does not exist' ' git checkout -b test copy^ && test_when_finished "git checkout -f copy && git branch -D test" && @@ -181,7 +211,7 @@ test_expect_success 'fail if the index has unresolved entries' ' test -n "$(git ls-files -u)" && cp file expected && test_must_fail git pull . second 2>err && - test_i18ngrep "Pull is not possible because you have unmerged files" err && + test_i18ngrep "Pulling is not possible because you have unmerged files." err && test_cmp expected file && git add file && test -z "$(git ls-files -u)" && @@ -225,6 +255,73 @@ test_expect_success '--rebase' ' test new = "$(git show HEAD:file2)" ' +test_expect_success '--rebase fast forward' ' + git reset --hard before-rebase && + git checkout -b ff && + echo another modification >file && + git commit -m third file && + + git checkout to-rebase && + git pull --rebase . ff && + test "$(git rev-parse HEAD)" = "$(git rev-parse ff)" && + + # The above only validates the result. Did we actually bypass rebase? + git reflog -1 >reflog.actual && + sed "s/^[0-9a-f][0-9a-f]*/OBJID/" reflog.actual >reflog.fuzzy && + echo "OBJID HEAD@{0}: pull --rebase . ff: Fast-forward" >reflog.expected && + test_cmp reflog.expected reflog.fuzzy +' + +test_expect_success '--rebase --autostash fast forward' ' + test_when_finished " + git reset --hard + git checkout to-rebase + git branch -D to-rebase-ff + git branch -D behind" && + git branch behind && + git checkout -b to-rebase-ff && + echo another modification >>file && + git add file && + git commit -m mod && + + git checkout behind && + echo dirty >file && + git pull --rebase --autostash . to-rebase-ff && + test "$(git rev-parse HEAD)" = "$(git rev-parse to-rebase-ff)" +' + +test_expect_success '--rebase with conflicts shows advice' ' + test_when_finished "git rebase --abort; git checkout -f to-rebase" && + git checkout -b seq && + test_seq 5 >seq.txt && + git add seq.txt && + test_tick && + git commit -m "Add seq.txt" && + echo 6 >>seq.txt && + test_tick && + git commit -m "Append to seq.txt" seq.txt && + git checkout -b with-conflicts HEAD^ && + echo conflicting >>seq.txt && + test_tick && + git commit -m "Create conflict" seq.txt && + test_must_fail git pull --rebase . seq 2>err >out && + test_i18ngrep "Resolve all conflicts manually" out +' + +test_expect_success 'failed --rebase shows advice' ' + test_when_finished "git rebase --abort; git checkout -f to-rebase" && + git checkout -b diverging && + test_commit attributes .gitattributes "* text=auto" attrs && + sha1="$(printf "1\\r\\n" | git hash-object -w --stdin)" && + git update-index --cacheinfo 0644 $sha1 file && + git commit -m v1-with-cr && + # force checkout because `git reset --hard` will not leave clean `file` + git checkout -f -b fails-to-rebase HEAD^ && + test_commit v2-without-cr file "2" file2-lf && + test_must_fail git pull --rebase . diverging 2>err >out && + test_i18ngrep "Resolve all conflicts manually" out +' + test_expect_success '--rebase fails with multiple branches' ' git reset --hard before-rebase && test_must_fail git pull --rebase . copy master 2>err && @@ -233,6 +330,49 @@ test_expect_success '--rebase fails with multiple branches' ' test modified = "$(git show HEAD:file)" ' +test_expect_success 'pull --rebase succeeds with dirty working directory and rebase.autostash set' ' + test_config rebase.autostash true && + test_pull_autostash --rebase +' + +test_expect_success 'pull --rebase --autostash & rebase.autostash=true' ' + test_config rebase.autostash true && + test_pull_autostash --rebase --autostash +' + +test_expect_success 'pull --rebase --autostash & rebase.autostash=false' ' + test_config rebase.autostash false && + test_pull_autostash --rebase --autostash +' + +test_expect_success 'pull --rebase --autostash & rebase.autostash unset' ' + test_unconfig rebase.autostash && + test_pull_autostash --rebase --autostash +' + +test_expect_success 'pull --rebase --no-autostash & rebase.autostash=true' ' + test_config rebase.autostash true && + test_pull_autostash_fail --rebase --no-autostash +' + +test_expect_success 'pull --rebase --no-autostash & rebase.autostash=false' ' + test_config rebase.autostash false && + test_pull_autostash_fail --rebase --no-autostash +' + +test_expect_success 'pull --rebase --no-autostash & rebase.autostash unset' ' + test_unconfig rebase.autostash && + test_pull_autostash_fail --rebase --no-autostash +' + +for i in --autostash --no-autostash +do + test_expect_success "pull $i (without --rebase) is illegal" ' + test_must_fail git pull $i . copy 2>err && + test_i18ngrep "only valid with --rebase" err + ' +done + test_expect_success 'pull.rebase' ' git reset --hard before-rebase && test_config pull.rebase true && @@ -241,6 +381,16 @@ test_expect_success 'pull.rebase' ' test new = "$(git show HEAD:file2)" ' +test_expect_success 'pull --autostash & pull.rebase=true' ' + test_config pull.rebase true && + test_pull_autostash --autostash +' + +test_expect_success 'pull --no-autostash & pull.rebase=true' ' + test_config pull.rebase true && + test_pull_autostash_fail --no-autostash +' + test_expect_success 'branch.to-rebase.rebase' ' git reset --hard before-rebase && test_config branch.to-rebase.rebase true && @@ -258,6 +408,22 @@ test_expect_success 'branch.to-rebase.rebase should override pull.rebase' ' test new = "$(git show HEAD:file2)" ' +test_expect_success "pull --rebase warns on --verify-signatures" ' + git reset --hard before-rebase && + git pull --rebase --verify-signatures . copy 2>err && + test "$(git rev-parse HEAD^)" = "$(git rev-parse copy)" && + test new = "$(git show HEAD:file2)" && + test_i18ngrep "ignoring --verify-signatures for rebase" err +' + +test_expect_success "pull --rebase does not warn on --no-verify-signatures" ' + git reset --hard before-rebase && + git pull --rebase --no-verify-signatures . copy 2>err && + test "$(git rev-parse HEAD^)" = "$(git rev-parse copy)" && + test new = "$(git show HEAD:file2)" && + test_i18ngrep ! "verify-signatures" err +' + # add a feature branch, keep-merge, that is merged into master, so the # test can try preserving the merge commit (or not) with various # --rebase flags/pull.rebase settings. @@ -303,6 +469,16 @@ test_expect_success 'pull.rebase=preserve rebases and merges keep-merge' ' test "$(git rev-parse HEAD^2)" = "$(git rev-parse keep-merge)" ' +test_expect_success 'pull.rebase=interactive' ' + write_script "$TRASH_DIRECTORY/fake-editor" <<-\EOF && + echo I was here >fake.out && + false + EOF + test_set_editor "$TRASH_DIRECTORY/fake-editor" && + test_must_fail git pull --rebase=interactive . copy && + test "I was here" = "$(cat fake.out)" +' + test_expect_success 'pull.rebase=invalid fails' ' git reset --hard before-preserve-rebase && test_config pull.rebase invalid && @@ -365,6 +541,14 @@ test_expect_success '--rebase with rebased upstream' ' ' +test_expect_success '--rebase -f with rebased upstream' ' + test_when_finished "test_might_fail git rebase --abort" && + git reset --hard to-rebase-orig && + git pull --rebase -f me copy && + test "conflicting modification" = "$(cat file)" && + test file = "$(cat file2)" +' + test_expect_success '--rebase with rebased default upstream' ' git update-ref refs/remotes/me/copy copy-orig && diff --git a/t/t5521-pull-options.sh b/t/t5521-pull-options.sh index 56e7377189..c19d8dbc9d 100755 --- a/t/t5521-pull-options.sh +++ b/t/t5521-pull-options.sh @@ -130,4 +130,84 @@ test_expect_success 'git pull --dry-run' ' ) ' +test_expect_success 'git pull --all --dry-run' ' + test_when_finished "rm -rf cloneddry" && + git init clonedry && + ( + cd clonedry && + git remote add origin ../parent && + git pull --all --dry-run && + test_path_is_missing .git/FETCH_HEAD && + test_path_is_missing .git/refs/remotes/origin/master && + test_path_is_missing .git/index && + test_path_is_missing file + ) +' + +test_expect_success 'git pull --allow-unrelated-histories' ' + test_when_finished "rm -fr src dst" && + git init src && + ( + cd src && + test_commit one && + test_commit two + ) && + git clone src dst && + ( + cd src && + git checkout --orphan side HEAD^ && + test_commit three + ) && + ( + cd dst && + test_must_fail git pull ../src side && + git pull --allow-unrelated-histories ../src side + ) +' + +test_expect_success 'git pull does not add a sign-off line' ' + test_when_finished "rm -fr src dst actual" && + git init src && + test_commit -C src one && + git clone src dst && + test_commit -C src two && + git -C dst pull --no-ff && + git -C dst show -s --pretty="format:%(trailers)" HEAD >actual && + test_must_be_empty actual +' + +test_expect_success 'git pull --no-signoff does not add sign-off line' ' + test_when_finished "rm -fr src dst actual" && + git init src && + test_commit -C src one && + git clone src dst && + test_commit -C src two && + git -C dst pull --no-signoff --no-ff && + git -C dst show -s --pretty="format:%(trailers)" HEAD >actual && + test_must_be_empty actual +' + +test_expect_success 'git pull --signoff add a sign-off line' ' + test_when_finished "rm -fr src dst expected actual" && + echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" >expected && + git init src && + test_commit -C src one && + git clone src dst && + test_commit -C src two && + git -C dst pull --signoff --no-ff && + git -C dst show -s --pretty="format:%(trailers)" HEAD >actual && + test_cmp expected actual +' + +test_expect_success 'git pull --no-signoff flag cancels --signoff flag' ' + test_when_finished "rm -fr src dst actual" && + git init src && + test_commit -C src one && + git clone src dst && + test_commit -C src two && + git -C dst pull --signoff --no-signoff --no-ff && + git -C dst show -s --pretty="format:%(trailers)" HEAD >actual && + test_must_be_empty actual +' + test_done diff --git a/t/t5522-pull-symlink.sh b/t/t5522-pull-symlink.sh index 8e9b204e02..bcff460d0a 100755 --- a/t/t5522-pull-symlink.sh +++ b/t/t5522-pull-symlink.sh @@ -54,7 +54,7 @@ test_expect_success SYMLINKS 'pulling from real subdir' ' # git rev-parse --show-cdup printed a path relative to # clone-repo/subdir/, not subdir-link/. Git rev-parse --show-cdup # used the correct .git, but when the git pull shell script did -# "cd `git rev-parse --show-cdup`", it ended up in the wrong +# "cd $(git rev-parse --show-cdup)", it ended up in the wrong # directory. A POSIX shell's "cd" works a little differently # than chdir() in C; "cd -P" is much closer to chdir(). # diff --git a/t/t5523-push-upstream.sh b/t/t5523-push-upstream.sh index 3683df13a6..d6981ba304 100755 --- a/t/t5523-push-upstream.sh +++ b/t/t5523-push-upstream.sh @@ -75,7 +75,7 @@ test_expect_success TTY 'progress messages go to tty' ' ensure_fresh_upstream && test_terminal git push -u upstream master >out 2>err && - grep "Writing objects" err + test_i18ngrep "Writing objects" err ' test_expect_success 'progress messages do not go to non-tty' ' @@ -83,7 +83,7 @@ test_expect_success 'progress messages do not go to non-tty' ' # skip progress messages, since stderr is non-tty git push -u upstream master >out 2>err && - ! grep "Writing objects" err + test_i18ngrep ! "Writing objects" err ' test_expect_success 'progress messages go to non-tty (forced)' ' @@ -91,22 +91,22 @@ test_expect_success 'progress messages go to non-tty (forced)' ' # force progress messages to stderr, even though it is non-tty git push -u --progress upstream master >out 2>err && - grep "Writing objects" err + test_i18ngrep "Writing objects" err ' test_expect_success TTY 'push -q suppresses progress' ' ensure_fresh_upstream && test_terminal git push -u -q upstream master >out 2>err && - ! grep "Writing objects" err + test_i18ngrep ! "Writing objects" err ' test_expect_success TTY 'push --no-progress suppresses progress' ' ensure_fresh_upstream && test_terminal git push -u --no-progress upstream master >out 2>err && - ! grep "Unpacking objects" err && - ! grep "Writing objects" err + test_i18ngrep ! "Unpacking objects" err && + test_i18ngrep ! "Writing objects" err ' test_expect_success TTY 'quiet push' ' diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh index a4532b00d6..9cc4b569c0 100755 --- a/t/t5526-fetch-submodules.sh +++ b/t/t5526-fetch-submodules.sh @@ -16,7 +16,8 @@ add_upstream_commit() { git add subfile && git commit -m new subfile && head2=$(git rev-parse --short HEAD) && - echo "From $pwd/submodule" > ../expect.err && + echo "Fetching submodule submodule" > ../expect.err && + echo "From $pwd/submodule" >> ../expect.err && echo " $head1..$head2 master -> origin/master" >> ../expect.err ) && ( @@ -27,6 +28,7 @@ add_upstream_commit() { git add deepsubfile && git commit -m new deepsubfile && head2=$(git rev-parse --short HEAD) && + echo "Fetching submodule submodule/subdir/deepsubmodule" >> ../expect.err echo "From $pwd/deepsubmodule" >> ../expect.err && echo " $head1..$head2 master -> origin/master" >> ../expect.err ) @@ -56,9 +58,7 @@ test_expect_success setup ' ( cd downstream && git submodule update --init --recursive - ) && - echo "Fetching submodule submodule" > expect.out && - echo "Fetching submodule submodule/subdir/deepsubmodule" >> expect.out + ) ' test_expect_success "fetch --recurse-submodules recurses into submodules" ' @@ -67,10 +67,31 @@ test_expect_success "fetch --recurse-submodules recurses into submodules" ' cd downstream && git fetch --recurse-submodules >../actual.out 2>../actual.err ) && - test_i18ncmp expect.out actual.out && + test_must_be_empty actual.out && test_i18ncmp expect.err actual.err ' +test_expect_success "submodule.recurse option triggers recursive fetch" ' + add_upstream_commit && + ( + cd downstream && + git -c submodule.recurse fetch >../actual.out 2>../actual.err + ) && + test_must_be_empty actual.out && + test_i18ncmp expect.err actual.err +' + +test_expect_success "fetch --recurse-submodules -j2 has the same output behaviour" ' + add_upstream_commit && + ( + cd downstream && + GIT_TRACE="$TRASH_DIRECTORY/trace.out" git fetch --recurse-submodules -j2 2>../actual.err + ) && + test_must_be_empty actual.out && + test_i18ncmp expect.err actual.err && + grep "2 tasks" trace.out +' + test_expect_success "fetch alone only fetches superproject" ' add_upstream_commit && ( @@ -96,7 +117,7 @@ test_expect_success "using fetchRecurseSubmodules=true in .gitmodules recurses i git config -f .gitmodules submodule.submodule.fetchRecurseSubmodules true && git fetch >../actual.out 2>../actual.err ) && - test_i18ncmp expect.out actual.out && + test_must_be_empty actual.out && test_i18ncmp expect.err actual.err ' @@ -127,7 +148,7 @@ test_expect_success "--recurse-submodules overrides fetchRecurseSubmodules setti git config --unset -f .gitmodules submodule.submodule.fetchRecurseSubmodules && git config --unset submodule.submodule.fetchRecurseSubmodules ) && - test_i18ncmp expect.out actual.out && + test_must_be_empty actual.out && test_i18ncmp expect.err actual.err ' @@ -140,13 +161,22 @@ test_expect_success "--quiet propagates to submodules" ' ! test -s actual.err ' +test_expect_success "--quiet propagates to parallel submodules" ' + ( + cd downstream && + git fetch --recurse-submodules -j 2 --quiet >../actual.out 2>../actual.err + ) && + ! test -s actual.out && + ! test -s actual.err +' + test_expect_success "--dry-run propagates to submodules" ' add_upstream_commit && ( cd downstream && git fetch --recurse-submodules --dry-run >../actual.out 2>../actual.err ) && - test_i18ncmp expect.out actual.out && + test_must_be_empty actual.out && test_i18ncmp expect.err actual.err ' @@ -155,7 +185,7 @@ test_expect_success "Without --dry-run propagates to submodules" ' cd downstream && git fetch --recurse-submodules >../actual.out 2>../actual.err ) && - test_i18ncmp expect.out actual.out && + test_must_be_empty actual.out && test_i18ncmp expect.err actual.err ' @@ -163,10 +193,10 @@ test_expect_success "recurseSubmodules=true propagates into submodules" ' add_upstream_commit && ( cd downstream && - git config fetch.recurseSubmodules true + git config fetch.recurseSubmodules true && git fetch >../actual.out 2>../actual.err ) && - test_i18ncmp expect.out actual.out && + test_must_be_empty actual.out && test_i18ncmp expect.err actual.err ' @@ -180,7 +210,7 @@ test_expect_success "--recurse-submodules overrides config in submodule" ' ) && git fetch --recurse-submodules >../actual.out 2>../actual.err ) && - test_i18ncmp expect.out actual.out && + test_must_be_empty actual.out && test_i18ncmp expect.err actual.err ' @@ -188,7 +218,7 @@ test_expect_success "--no-recurse-submodules overrides config setting" ' add_upstream_commit && ( cd downstream && - git config fetch.recurseSubmodules true + git config fetch.recurseSubmodules true && git fetch --no-recurse-submodules >../actual.out 2>../actual.err ) && ! test -s actual.out && @@ -202,7 +232,7 @@ test_expect_success "Recursion doesn't happen when no new commits are fetched in cd submodule && git config --unset fetch.recurseSubmodules ) && - git config --unset fetch.recurseSubmodules + git config --unset fetch.recurseSubmodules && git fetch >../actual.out 2>../actual.err ) && ! test -s actual.out && @@ -214,16 +244,15 @@ test_expect_success "Recursion stops when no new submodule commits are fetched" git add submodule && git commit -m "new submodule" && head2=$(git rev-parse --short HEAD) && - echo "Fetching submodule submodule" > expect.out.sub && echo "From $pwd/." > expect.err.sub && echo " $head1..$head2 master -> origin/master" >>expect.err.sub && - head -2 expect.err >> expect.err.sub && + head -3 expect.err >> expect.err.sub && ( cd downstream && git fetch >../actual.out 2>../actual.err ) && test_i18ncmp expect.err.sub actual.err && - test_i18ncmp expect.out.sub actual.out + test_must_be_empty actual.out ' test_expect_success "Recursion doesn't happen when new superproject commits don't change any submodules" ' @@ -269,7 +298,7 @@ test_expect_success "Recursion picks up config in submodule" ' ) ) && test_i18ncmp expect.err.sub actual.err && - test_i18ncmp expect.out actual.out + test_must_be_empty actual.out ' test_expect_success "Recursion picks up all submodules when necessary" ' @@ -283,9 +312,10 @@ test_expect_success "Recursion picks up all submodules when necessary" ' ) && head1=$(git rev-parse --short HEAD^) && git add subdir/deepsubmodule && - git commit -m "new deepsubmodule" + git commit -m "new deepsubmodule" && head2=$(git rev-parse --short HEAD) && - echo "From $pwd/submodule" > ../expect.err.sub && + echo "Fetching submodule submodule" > ../expect.err.sub && + echo "From $pwd/submodule" >> ../expect.err.sub && echo " $head1..$head2 master -> origin/master" >> ../expect.err.sub ) && head1=$(git rev-parse --short HEAD) && @@ -295,13 +325,13 @@ test_expect_success "Recursion picks up all submodules when necessary" ' echo "From $pwd/." > expect.err.2 && echo " $head1..$head2 master -> origin/master" >> expect.err.2 && cat expect.err.sub >> expect.err.2 && - tail -2 expect.err >> expect.err.2 && + tail -3 expect.err >> expect.err.2 && ( cd downstream && git fetch >../actual.out 2>../actual.err ) && test_i18ncmp expect.err.2 actual.err && - test_i18ncmp expect.out actual.out + test_must_be_empty actual.out ' test_expect_success "'--recurse-submodules=on-demand' doesn't recurse when no new commits are fetched in the superproject (and ignores config)" ' @@ -317,7 +347,8 @@ test_expect_success "'--recurse-submodules=on-demand' doesn't recurse when no ne git add subdir/deepsubmodule && git commit -m "new deepsubmodule" && head2=$(git rev-parse --short HEAD) && - echo "From $pwd/submodule" > ../expect.err.sub && + echo Fetching submodule submodule > ../expect.err.sub && + echo "From $pwd/submodule" >> ../expect.err.sub && echo " $head1..$head2 master -> origin/master" >> ../expect.err.sub ) && ( @@ -335,7 +366,7 @@ test_expect_success "'--recurse-submodules=on-demand' recurses as deep as necess git add submodule && git commit -m "new submodule" && head2=$(git rev-parse --short HEAD) && - tail -2 expect.err > expect.err.deepsub && + tail -3 expect.err > expect.err.deepsub && echo "From $pwd/." > expect.err && echo " $head1..$head2 master -> origin/master" >>expect.err && cat expect.err.sub >> expect.err && @@ -354,7 +385,7 @@ test_expect_success "'--recurse-submodules=on-demand' recurses as deep as necess git config --unset -f .gitmodules submodule.subdir/deepsubmodule.fetchRecursive ) ) && - test_i18ncmp expect.out actual.out && + test_must_be_empty actual.out && test_i18ncmp expect.err actual.err ' @@ -388,7 +419,7 @@ test_expect_success "'fetch.recurseSubmodules=on-demand' overrides global config head2=$(git rev-parse --short HEAD) && echo "From $pwd/." > expect.err.2 && echo " $head1..$head2 master -> origin/master" >>expect.err.2 && - head -2 expect.err >> expect.err.2 && + head -3 expect.err >> expect.err.2 && ( cd downstream && git config fetch.recurseSubmodules on-demand && @@ -399,7 +430,7 @@ test_expect_success "'fetch.recurseSubmodules=on-demand' overrides global config cd downstream && git config --unset fetch.recurseSubmodules ) && - test_i18ncmp expect.out.sub actual.out && + test_must_be_empty actual.out && test_i18ncmp expect.err.2 actual.err ' @@ -416,7 +447,7 @@ test_expect_success "'submodule.<sub>.fetchRecurseSubmodules=on-demand' override head2=$(git rev-parse --short HEAD) && echo "From $pwd/." > expect.err.2 && echo " $head1..$head2 master -> origin/master" >>expect.err.2 && - head -2 expect.err >> expect.err.2 && + head -3 expect.err >> expect.err.2 && ( cd downstream && git config submodule.submodule.fetchRecurseSubmodules on-demand && @@ -427,7 +458,7 @@ test_expect_success "'submodule.<sub>.fetchRecurseSubmodules=on-demand' override cd downstream && git config --unset submodule.submodule.fetchRecurseSubmodules ) && - test_i18ncmp expect.out.sub actual.out && + test_must_be_empty actual.out && test_i18ncmp expect.err.2 actual.err ' @@ -447,7 +478,131 @@ test_expect_success "don't fetch submodule when newly recorded commits are alrea git fetch >../actual.out 2>../actual.err ) && ! test -s actual.out && - test_i18ncmp expect.err actual.err + test_i18ncmp expect.err actual.err && + ( + cd submodule && + git checkout -q master + ) +' + +test_expect_success "'fetch.recurseSubmodules=on-demand' works also without .gitmodules entry" ' + ( + cd downstream && + git fetch --recurse-submodules + ) && + add_upstream_commit && + head1=$(git rev-parse --short HEAD) && + git add submodule && + git rm .gitmodules && + git commit -m "new submodule without .gitmodules" && + printf "" >expect.out && + head2=$(git rev-parse --short HEAD) && + echo "From $pwd/." >expect.err.2 && + echo " $head1..$head2 master -> origin/master" >>expect.err.2 && + head -3 expect.err >>expect.err.2 && + ( + cd downstream && + rm .gitmodules && + git config fetch.recurseSubmodules on-demand && + # fake submodule configuration to avoid skipping submodule handling + git config -f .gitmodules submodule.fake.path fake && + git config -f .gitmodules submodule.fake.url fakeurl && + git add .gitmodules && + git config --unset submodule.submodule.url && + git fetch >../actual.out 2>../actual.err && + # cleanup + git config --unset fetch.recurseSubmodules && + git reset --hard + ) && + test_i18ncmp expect.out actual.out && + test_i18ncmp expect.err.2 actual.err && + git checkout HEAD^ -- .gitmodules && + git add .gitmodules && + git commit -m "new submodule restored .gitmodules" +' + +test_expect_success 'fetching submodules respects parallel settings' ' + git config fetch.recurseSubmodules true && + ( + cd downstream && + GIT_TRACE=$(pwd)/trace.out git fetch --jobs 7 && + grep "7 tasks" trace.out && + git config submodule.fetchJobs 8 && + GIT_TRACE=$(pwd)/trace.out git fetch && + grep "8 tasks" trace.out && + GIT_TRACE=$(pwd)/trace.out git fetch --jobs 9 && + grep "9 tasks" trace.out + ) +' + +test_expect_success 'fetching submodule into a broken repository' ' + # Prepare src and src/sub nested in it + git init src && + ( + cd src && + git init sub && + git -C sub commit --allow-empty -m "initial in sub" && + git submodule add -- ./sub sub && + git commit -m "initial in top" + ) && + + # Clone the old-fashoned way + git clone src dst && + git -C dst clone ../src/sub sub && + + # Make sure that old-fashoned layout is still supported + git -C dst status && + + # "diff" would find no change + git -C dst diff --exit-code && + + # Recursive-fetch works fine + git -C dst fetch --recurse-submodules && + + # Break the receiving submodule + rm -f dst/sub/.git/HEAD && + + # NOTE: without the fix the following tests will recurse forever! + # They should terminate with an error. + + test_must_fail git -C dst status && + test_must_fail git -C dst diff && + test_must_fail git -C dst fetch --recurse-submodules +' + +test_expect_success "fetch new commits when submodule got renamed" ' + git clone . downstream_rename && + ( + cd downstream_rename && + git submodule update --init && +# NEEDSWORK: we omitted --recursive for the submodule update here since +# that does not work. See test 7001 for mv "moving nested submodules" +# for details. Once that is fixed we should add the --recursive option +# here. + git checkout -b rename && + git mv submodule submodule_renamed && + ( + cd submodule_renamed && + git checkout -b rename_sub && + echo a >a && + git add a && + git commit -ma && + git push origin rename_sub && + git rev-parse HEAD >../../expect + ) && + git add submodule_renamed && + git commit -m "update renamed submodule" && + git push origin rename + ) && + ( + cd downstream && + git fetch --recurse-submodules=on-demand && + ( + cd submodule && + git rev-parse origin/rename_sub >../../actual + ) + ) && + test_cmp expect actual ' test_done diff --git a/t/t5528-push-default.sh b/t/t5528-push-default.sh index 73f4bb6346..44309566f1 100755 --- a/t/t5528-push-default.sh +++ b/t/t5528-push-default.sh @@ -98,6 +98,16 @@ test_expect_success 'push from/to new branch with upstream, matching and simple' test_push_failure upstream ' +test_expect_success 'push ambiguously named branch with upstream, matching and simple' ' + git checkout -b ambiguous && + test_config branch.ambiguous.remote parent1 && + test_config branch.ambiguous.merge refs/heads/ambiguous && + git tag ambiguous && + test_push_success simple ambiguous && + test_push_success matching ambiguous && + test_push_success upstream ambiguous +' + test_expect_success 'push from/to new branch with current creates remote branch' ' test_config branch.new-branch.remote repo1 && git checkout new-branch && diff --git a/t/t5530-upload-pack-error.sh b/t/t5530-upload-pack-error.sh index 3932e797f7..4f6e32b04c 100755 --- a/t/t5530-upload-pack-error.sh +++ b/t/t5530-upload-pack-error.sh @@ -4,7 +4,7 @@ test_description='errors in upload-pack' . ./test-lib.sh -D=`pwd` +D=$(pwd) corrupt_repo () { object_sha1=$(git rev-parse "$1") && diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh index 6507487c1a..39cb2c1c34 100755 --- a/t/t5531-deep-submodule-push.sh +++ b/t/t5531-deep-submodule-push.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='unpack-objects' +test_description='test push with submodules' . ./test-lib.sh @@ -27,7 +27,7 @@ test_expect_success setup ' ) ' -test_expect_success push ' +test_expect_success 'push works with recorded gitlink' ' ( cd work && git push ../pub.git master @@ -64,7 +64,12 @@ test_expect_success 'push fails if submodule commit not on remote' ' cd work && git add gar/bage && git commit -m "Third commit for gar/bage" && - test_must_fail git push --recurse-submodules=check ../pub.git master + # the push should fail with --recurse-submodules=check + # on the command line... + test_must_fail git push --recurse-submodules=check ../pub.git master && + + # ...or if specified in the configuration.. + test_must_fail git -c push.recurseSubmodules=check push ../pub.git master ) ' @@ -79,6 +84,247 @@ test_expect_success 'push succeeds after commit was pushed to remote' ' ) ' +test_expect_success 'push succeeds if submodule commit not on remote but using on-demand on command line' ' + ( + cd work/gar/bage && + >recurse-on-demand-on-command-line && + git add recurse-on-demand-on-command-line && + git commit -m "Recurse on-demand on command line junk" + ) && + ( + cd work && + git add gar/bage && + git commit -m "Recurse on-demand on command line for gar/bage" && + git push --recurse-submodules=on-demand ../pub.git master && + # Check that the supermodule commit got there + git fetch ../pub.git && + git diff --quiet FETCH_HEAD master && + # Check that the submodule commit got there too + cd gar/bage && + git diff --quiet origin/master master + ) +' + +test_expect_success 'push succeeds if submodule commit not on remote but using on-demand from config' ' + ( + cd work/gar/bage && + >recurse-on-demand-from-config && + git add recurse-on-demand-from-config && + git commit -m "Recurse on-demand from config junk" + ) && + ( + cd work && + git add gar/bage && + git commit -m "Recurse on-demand from config for gar/bage" && + git -c push.recurseSubmodules=on-demand push ../pub.git master && + # Check that the supermodule commit got there + git fetch ../pub.git && + git diff --quiet FETCH_HEAD master && + # Check that the submodule commit got there too + cd gar/bage && + git diff --quiet origin/master master + ) +' + +test_expect_success 'push succeeds if submodule commit not on remote but using auto-on-demand via submodule.recurse config' ' + ( + cd work/gar/bage && + >recurse-on-demand-from-submodule-recurse-config && + git add recurse-on-demand-from-submodule-recurse-config && + git commit -m "Recurse submodule.recurse from config junk" + ) && + ( + cd work && + git add gar/bage && + git commit -m "Recurse submodule.recurse from config for gar/bage" && + git -c submodule.recurse push ../pub.git master && + # Check that the supermodule commit got there + git fetch ../pub.git && + git diff --quiet FETCH_HEAD master && + # Check that the submodule commit got there too + cd gar/bage && + git diff --quiet origin/master master + ) +' + +test_expect_success 'push recurse-submodules on command line overrides config' ' + ( + cd work/gar/bage && + >recurse-check-on-command-line-overriding-config && + git add recurse-check-on-command-line-overriding-config && + git commit -m "Recurse on command-line overriding config junk" + ) && + ( + cd work && + git add gar/bage && + git commit -m "Recurse on command-line overriding config for gar/bage" && + + # Ensure that we can override on-demand in the config + # to just check submodules + test_must_fail git -c push.recurseSubmodules=on-demand push --recurse-submodules=check ../pub.git master && + # Check that the supermodule commit did not get there + git fetch ../pub.git && + git diff --quiet FETCH_HEAD master^ && + # Check that the submodule commit did not get there + (cd gar/bage && git diff --quiet origin/master master^) && + + # Ensure that we can override check in the config to + # disable submodule recursion entirely + (cd gar/bage && git diff --quiet origin/master master^) && + git -c push.recurseSubmodules=on-demand push --recurse-submodules=no ../pub.git master && + git fetch ../pub.git && + git diff --quiet FETCH_HEAD master && + (cd gar/bage && git diff --quiet origin/master master^) && + + # Ensure that we can override check in the config to + # disable submodule recursion entirely (alternative form) + git -c push.recurseSubmodules=on-demand push --no-recurse-submodules ../pub.git master && + git fetch ../pub.git && + git diff --quiet FETCH_HEAD master && + (cd gar/bage && git diff --quiet origin/master master^) && + + # Ensure that we can override check in the config to + # push the submodule too + git -c push.recurseSubmodules=check push --recurse-submodules=on-demand ../pub.git master && + git fetch ../pub.git && + git diff --quiet FETCH_HEAD master && + (cd gar/bage && git diff --quiet origin/master master) + ) +' + +test_expect_success 'push recurse-submodules last one wins on command line' ' + ( + cd work/gar/bage && + >recurse-check-on-command-line-overriding-earlier-command-line && + git add recurse-check-on-command-line-overriding-earlier-command-line && + git commit -m "Recurse on command-line overridiing earlier command-line junk" + ) && + ( + cd work && + git add gar/bage && + git commit -m "Recurse on command-line overriding earlier command-line for gar/bage" && + + # should result in "check" + test_must_fail git push --recurse-submodules=on-demand --recurse-submodules=check ../pub.git master && + # Check that the supermodule commit did not get there + git fetch ../pub.git && + git diff --quiet FETCH_HEAD master^ && + # Check that the submodule commit did not get there + (cd gar/bage && git diff --quiet origin/master master^) && + + # should result in "no" + git push --recurse-submodules=on-demand --recurse-submodules=no ../pub.git master && + # Check that the supermodule commit did get there + git fetch ../pub.git && + git diff --quiet FETCH_HEAD master && + # Check that the submodule commit did not get there + (cd gar/bage && git diff --quiet origin/master master^) && + + # should result in "no" + git push --recurse-submodules=on-demand --no-recurse-submodules ../pub.git master && + # Check that the submodule commit did not get there + (cd gar/bage && git diff --quiet origin/master master^) && + + # But the options in the other order should push the submodule + git push --recurse-submodules=check --recurse-submodules=on-demand ../pub.git master && + # Check that the submodule commit did get there + git fetch ../pub.git && + (cd gar/bage && git diff --quiet origin/master master) + ) +' + +test_expect_success 'push succeeds if submodule commit not on remote using on-demand from cmdline overriding config' ' + ( + cd work/gar/bage && + >recurse-on-demand-on-command-line-overriding-config && + git add recurse-on-demand-on-command-line-overriding-config && + git commit -m "Recurse on-demand on command-line overriding config junk" + ) && + ( + cd work && + git add gar/bage && + git commit -m "Recurse on-demand on command-line overriding config for gar/bage" && + git -c push.recurseSubmodules=check push --recurse-submodules=on-demand ../pub.git master && + # Check that the supermodule commit got there + git fetch ../pub.git && + git diff --quiet FETCH_HEAD master && + # Check that the submodule commit got there + cd gar/bage && + git diff --quiet origin/master master + ) +' + +test_expect_success 'push succeeds if submodule commit disabling recursion from cmdline overriding config' ' + ( + cd work/gar/bage && + >recurse-disable-on-command-line-overriding-config && + git add recurse-disable-on-command-line-overriding-config && + git commit -m "Recurse disable on command-line overriding config junk" + ) && + ( + cd work && + git add gar/bage && + git commit -m "Recurse disable on command-line overriding config for gar/bage" && + git -c push.recurseSubmodules=check push --recurse-submodules=no ../pub.git master && + # Check that the supermodule commit got there + git fetch ../pub.git && + git diff --quiet FETCH_HEAD master && + # But that the submodule commit did not + ( cd gar/bage && git diff --quiet origin/master master^ ) && + # Now push it to avoid confusing future tests + git push --recurse-submodules=on-demand ../pub.git master + ) +' + +test_expect_success 'push succeeds if submodule commit disabling recursion from cmdline (alternative form) overriding config' ' + ( + cd work/gar/bage && + >recurse-disable-on-command-line-alt-overriding-config && + git add recurse-disable-on-command-line-alt-overriding-config && + git commit -m "Recurse disable on command-line alternative overriding config junk" + ) && + ( + cd work && + git add gar/bage && + git commit -m "Recurse disable on command-line alternative overriding config for gar/bage" && + git -c push.recurseSubmodules=check push --no-recurse-submodules ../pub.git master && + # Check that the supermodule commit got there + git fetch ../pub.git && + git diff --quiet FETCH_HEAD master && + # But that the submodule commit did not + ( cd gar/bage && git diff --quiet origin/master master^ ) && + # Now push it to avoid confusing future tests + git push --recurse-submodules=on-demand ../pub.git master + ) +' + +test_expect_success 'submodule entry pointing at a tag is error' ' + git -C work/gar/bage tag -a test1 -m "tag" && + tag=$(git -C work/gar/bage rev-parse test1^{tag}) && + git -C work update-index --cacheinfo 160000 "$tag" gar/bage && + git -C work commit -m "bad commit" && + test_when_finished "git -C work reset --hard HEAD^" && + test_must_fail git -C work push --recurse-submodules=on-demand ../pub.git master 2>err && + test_i18ngrep "is a tag, not a commit" err +' + +test_expect_success 'push fails if recurse submodules option passed as yes' ' + ( + cd work/gar/bage && + >recurse-push-fails-if-recurse-submodules-passed-as-yes && + git add recurse-push-fails-if-recurse-submodules-passed-as-yes && + git commit -m "Recurse push fails if recurse submodules option passed as yes" + ) && + ( + cd work && + git add gar/bage && + git commit -m "Recurse push fails if recurse submodules option passed as yes for gar/bage" && + test_must_fail git push --recurse-submodules=yes ../pub.git master && + test_must_fail git -c push.recurseSubmodules=yes push ../pub.git master && + git push --recurse-submodules=on-demand ../pub.git master + ) +' + test_expect_success 'push fails when commit on multiple branches if one branch has no remote' ' ( cd work/gar/bage && @@ -212,7 +458,127 @@ test_expect_success 'push unpushable submodule recursively fails' ' cd submodule.git && git rev-parse master >../actual ) && + test_when_finished git -C work reset --hard master^ && test_cmp expected actual ' +test_expect_success 'push --dry-run does not recursively update submodules' ' + ( + cd work/gar/bage && + git checkout master && + git rev-parse master >../../../expected_submodule && + > junk9 && + git add junk9 && + git commit -m "Ninth junk" && + + # Go up to 'work' directory + cd ../.. && + git checkout master && + git rev-parse master >../expected_pub && + git add gar/bage && + git commit -m "Ninth commit for gar/bage" && + git push --dry-run --recurse-submodules=on-demand ../pub.git master + ) && + git -C submodule.git rev-parse master >actual_submodule && + git -C pub.git rev-parse master >actual_pub && + test_cmp expected_pub actual_pub && + test_cmp expected_submodule actual_submodule +' + +test_expect_success 'push --dry-run does not recursively update submodules' ' + git -C work push --dry-run --recurse-submodules=only ../pub.git master && + + git -C submodule.git rev-parse master >actual_submodule && + git -C pub.git rev-parse master >actual_pub && + test_cmp expected_pub actual_pub && + test_cmp expected_submodule actual_submodule +' + +test_expect_success 'push only unpushed submodules recursively' ' + git -C work/gar/bage rev-parse master >expected_submodule && + git -C pub.git rev-parse master >expected_pub && + + git -C work push --recurse-submodules=only ../pub.git master && + + git -C submodule.git rev-parse master >actual_submodule && + git -C pub.git rev-parse master >actual_pub && + test_cmp expected_submodule actual_submodule && + test_cmp expected_pub actual_pub +' + +test_expect_success 'push propagating the remotes name to a submodule' ' + git -C work remote add origin ../pub.git && + git -C work remote add pub ../pub.git && + + > work/gar/bage/junk10 && + git -C work/gar/bage add junk10 && + git -C work/gar/bage commit -m "Tenth junk" && + git -C work add gar/bage && + git -C work commit -m "Tenth junk added to gar/bage" && + + # Fails when submodule does not have a matching remote + test_must_fail git -C work push --recurse-submodules=on-demand pub master && + # Succeeds when submodules has matching remote and refspec + git -C work push --recurse-submodules=on-demand origin master && + + git -C submodule.git rev-parse master >actual_submodule && + git -C pub.git rev-parse master >actual_pub && + git -C work/gar/bage rev-parse master >expected_submodule && + git -C work rev-parse master >expected_pub && + test_cmp expected_submodule actual_submodule && + test_cmp expected_pub actual_pub +' + +test_expect_success 'push propagating refspec to a submodule' ' + > work/gar/bage/junk11 && + git -C work/gar/bage add junk11 && + git -C work/gar/bage commit -m "Eleventh junk" && + + git -C work checkout branch2 && + git -C work add gar/bage && + git -C work commit -m "updating gar/bage in branch2" && + + # Fails when submodule does not have a matching branch + test_must_fail git -C work push --recurse-submodules=on-demand origin branch2 && + # Fails when refspec includes an object id + test_must_fail git -C work push --recurse-submodules=on-demand origin \ + "$(git -C work rev-parse branch2):refs/heads/branch2" && + # Fails when refspec includes HEAD and parent and submodule do not + # have the same named branch checked out + test_must_fail git -C work push --recurse-submodules=on-demand origin \ + HEAD:refs/heads/branch2 && + + git -C work/gar/bage branch branch2 master && + git -C work push --recurse-submodules=on-demand origin branch2 && + + git -C submodule.git rev-parse branch2 >actual_submodule && + git -C pub.git rev-parse branch2 >actual_pub && + git -C work/gar/bage rev-parse branch2 >expected_submodule && + git -C work rev-parse branch2 >expected_pub && + test_cmp expected_submodule actual_submodule && + test_cmp expected_pub actual_pub +' + +test_expect_success 'push propagating HEAD refspec to a submodule' ' + git -C work/gar/bage checkout branch2 && + > work/gar/bage/junk12 && + git -C work/gar/bage add junk12 && + git -C work/gar/bage commit -m "Twelfth junk" && + + git -C work checkout branch2 && + git -C work add gar/bage && + git -C work commit -m "updating gar/bage in branch2" && + + # Passes since the superproject and submodules HEAD are both on branch2 + git -C work push --recurse-submodules=on-demand origin \ + HEAD:refs/heads/branch2 && + + git -C submodule.git rev-parse branch2 >actual_submodule && + git -C pub.git rev-parse branch2 >actual_pub && + git -C work/gar/bage rev-parse branch2 >expected_submodule && + git -C work rev-parse branch2 >expected_pub && + test_cmp expected_submodule actual_submodule && + test_cmp expected_pub actual_pub +' + test_done diff --git a/t/t5532-fetch-proxy.sh b/t/t5532-fetch-proxy.sh index 5531bd1af4..9c2798603b 100755 --- a/t/t5532-fetch-proxy.sh +++ b/t/t5532-fetch-proxy.sh @@ -12,10 +12,8 @@ test_expect_success 'setup remote repo' ' ) ' -cat >proxy <<'EOF' -#!/bin/sh -echo >&2 "proxying for $*" -cmd=`"$PERL_PATH" -e ' +test_expect_success 'setup proxy script' ' + write_script proxy-get-cmd "$PERL_PATH" <<-\EOF && read(STDIN, $buf, 4); my $n = hex($buf) - 4; read(STDIN, $buf, $n); @@ -23,11 +21,16 @@ cmd=`"$PERL_PATH" -e ' # drop absolute-path on repo name $cmd =~ s{ /}{ }; print $cmd; -'` -echo >&2 "Running '$cmd'" -exec $cmd -EOF -chmod +x proxy + EOF + + write_script proxy <<-\EOF + echo >&2 "proxying for $*" + cmd=$(./proxy-get-cmd) + echo >&2 "Running $cmd" + exec $cmd + EOF +' + test_expect_success 'setup local repo' ' git remote add fake git://example.com/remote && git config core.gitproxy ./proxy @@ -40,4 +43,9 @@ test_expect_success 'fetch through proxy works' ' test_cmp expect actual ' +test_expect_success 'funny hostnames are rejected before running proxy' ' + test_must_fail git fetch git://-remote/repo.git 2>stderr && + ! grep "proxying for" stderr +' + test_done diff --git a/t/t5533-push-cas.sh b/t/t5533-push-cas.sh index c402d8d3d7..d38ecee217 100755 --- a/t/t5533-push-cas.sh +++ b/t/t5533-push-cas.sh @@ -25,7 +25,8 @@ test_expect_success 'push to update (protected)' ' ( cd dst && test_commit D && - test_must_fail git push --force-with-lease=master:master origin master + test_must_fail git push --force-with-lease=master:master origin master 2>err && + grep "stale info" err ) && git ls-remote . refs/heads/master >expect && git ls-remote src refs/heads/master >actual && @@ -37,7 +38,8 @@ test_expect_success 'push to update (protected, forced)' ' ( cd dst && test_commit D && - git push --force --force-with-lease=master:master origin master + git push --force --force-with-lease=master:master origin master 2>err && + grep "forced update" err ) && git ls-remote dst refs/heads/master >expect && git ls-remote src refs/heads/master >actual && @@ -101,7 +103,8 @@ test_expect_success 'push to update (allowed, tracking)' ' ( cd dst && test_commit D && - git push --force-with-lease=master origin master + git push --force-with-lease=master origin master 2>err && + ! grep "forced update" err ) && git ls-remote dst refs/heads/master >expect && git ls-remote src refs/heads/master >actual && @@ -114,7 +117,8 @@ test_expect_success 'push to update (allowed even though no-ff)' ' cd dst && git reset --hard HEAD^ && test_commit D && - git push --force-with-lease=master origin master + git push --force-with-lease=master origin master 2>err && + grep "forced update" err ) && git ls-remote dst refs/heads/master >expect && git ls-remote src refs/heads/master >actual && @@ -147,7 +151,8 @@ test_expect_success 'push to delete (allowed)' ' setup_srcdst_basic && ( cd dst && - git push --force-with-lease=master origin :master + git push --force-with-lease=master origin :master 2>err && + grep deleted err ) && >expect && git ls-remote src refs/heads/master >actual && @@ -186,4 +191,71 @@ test_expect_success 'cover everything with default force-with-lease (allowed)' ' test_cmp expect actual ' +test_expect_success 'new branch covered by force-with-lease' ' + setup_srcdst_basic && + ( + cd dst && + git branch branch master && + git push --force-with-lease=branch origin branch + ) && + git ls-remote dst refs/heads/branch >expect && + git ls-remote src refs/heads/branch >actual && + test_cmp expect actual +' + +test_expect_success 'new branch covered by force-with-lease (explicit)' ' + setup_srcdst_basic && + ( + cd dst && + git branch branch master && + git push --force-with-lease=branch: origin branch + ) && + git ls-remote dst refs/heads/branch >expect && + git ls-remote src refs/heads/branch >actual && + test_cmp expect actual +' + +test_expect_success 'new branch already exists' ' + setup_srcdst_basic && + ( + cd src && + git checkout -b branch master && + test_commit F + ) && + ( + cd dst && + git branch branch master && + test_must_fail git push --force-with-lease=branch: origin branch + ) +' + +test_expect_success 'background updates of REMOTE can be mitigated with a non-updated REMOTE-push' ' + rm -rf src dst && + git init --bare src.bare && + test_when_finished "rm -rf src.bare" && + git clone --no-local src.bare dst && + test_when_finished "rm -rf dst" && + ( + cd dst && + test_commit G && + git remote add origin-push ../src.bare && + git push origin-push master:master + ) && + git clone --no-local src.bare dst2 && + test_when_finished "rm -rf dst2" && + ( + cd dst2 && + test_commit H && + git push + ) && + ( + cd dst && + test_commit I && + git fetch origin && + test_must_fail git push --force-with-lease origin-push && + git fetch origin-push && + git push --force-with-lease origin-push + ) +' + test_done diff --git a/t/t5534-push-signed.sh b/t/t5534-push-signed.sh index ecb8d446a5..1cea758f78 100755 --- a/t/t5534-push-signed.sh +++ b/t/t5534-push-signed.sh @@ -71,6 +71,13 @@ test_expect_success 'push --signed fails with a receiver without push certificat test_i18ngrep "the receiving end does not support" err ' +test_expect_success 'push --signed=1 is accepted' ' + prepare_dst && + mkdir -p dst/.git/hooks && + test_must_fail git push --signed=1 dst noop ff +noff 2>err && + test_i18ngrep "the receiving end does not support" err +' + test_expect_success GPG 'no certificate for a signed push with no update' ' prepare_dst && mkdir -p dst/.git/hooks && @@ -119,11 +126,51 @@ test_expect_success GPG 'signed push sends push certificate' ' sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" dst/push-cert ) >expect && - grep "$(git rev-parse noop ff) refs/heads/ff" dst/push-cert && - grep "$(git rev-parse noop noff) refs/heads/noff" dst/push-cert && + noop=$(git rev-parse noop) && + ff=$(git rev-parse ff) && + noff=$(git rev-parse noff) && + grep "$noop $ff refs/heads/ff" dst/push-cert && + grep "$noop $noff refs/heads/noff" dst/push-cert && test_cmp expect dst/push-cert-status ' +test_expect_success GPG 'inconsistent push options in signed push not allowed' ' + # First, invoke receive-pack with dummy input to obtain its preamble. + prepare_dst && + git -C dst config receive.certnonceseed sekrit && + git -C dst config receive.advertisepushoptions 1 && + printf xxxx | test_might_fail git receive-pack dst >preamble && + + # Then, invoke push. Simulate a receive-pack that sends the preamble we + # obtained, followed by a dummy packet. + write_script myscript <<-\EOF && + cat preamble && + printf xxxx && + cat >push + EOF + test_might_fail git push --push-option="foo" --push-option="bar" \ + --receive-pack="\"$(pwd)/myscript\"" --signed dst --delete ff && + + # Replay the push output on a fresh dst, checking that ff is truly + # deleted. + prepare_dst && + git -C dst config receive.certnonceseed sekrit && + git -C dst config receive.advertisepushoptions 1 && + git receive-pack dst <push && + test_must_fail git -C dst rev-parse ff && + + # Tweak the push output to make the push option outside the cert + # different, then replay it on a fresh dst, checking that ff is not + # deleted. + perl -pe "s/([^ ])bar/\$1baz/" push >push.tweak && + prepare_dst && + git -C dst config receive.certnonceseed sekrit && + git -C dst config receive.advertisepushoptions 1 && + git receive-pack dst <push.tweak >out && + git -C dst rev-parse ff && + grep "inconsistent push options" out +' + test_expect_success GPG 'fail without key and heed user.signingkey' ' prepare_dst && mkdir -p dst/.git/hooks && @@ -163,8 +210,11 @@ test_expect_success GPG 'fail without key and heed user.signingkey' ' sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" dst/push-cert ) >expect && - grep "$(git rev-parse noop ff) refs/heads/ff" dst/push-cert && - grep "$(git rev-parse noop noff) refs/heads/noff" dst/push-cert && + noop=$(git rev-parse noop) && + ff=$(git rev-parse ff) && + noff=$(git rev-parse noff) && + grep "$noop $ff refs/heads/ff" dst/push-cert && + grep "$noop $noff refs/heads/noff" dst/push-cert && test_cmp expect dst/push-cert-status ' diff --git a/t/t5536-fetch-conflicts.sh b/t/t5536-fetch-conflicts.sh index 6c5d3a4ce0..91f28c2f78 100755 --- a/t/t5536-fetch-conflicts.sh +++ b/t/t5536-fetch-conflicts.sh @@ -18,14 +18,6 @@ setup_repository () { ) } -verify_stderr () { - cat >expected && - # We're not interested in the error - # "fatal: The remote end hung up unexpectedly": - grep -E '^(fatal|warning):' <error | grep -v 'hung up' >actual | sort && - test_cmp expected actual -} - test_expect_success 'setup' ' git commit --allow-empty -m "Initial" && git branch branch1 && @@ -48,9 +40,7 @@ test_expect_success 'fetch conflict: config vs. config' ' "+refs/heads/branch2:refs/remotes/origin/branch1" && ( cd ccc && test_must_fail git fetch origin 2>error && - verify_stderr <<-\EOF - fatal: Cannot fetch both refs/heads/branch1 and refs/heads/branch2 to refs/remotes/origin/branch1 - EOF + test_i18ngrep "fatal: Cannot fetch both refs/heads/branch1 and refs/heads/branch2 to refs/remotes/origin/branch1" error ) ' @@ -77,9 +67,7 @@ test_expect_success 'fetch conflict: arg vs. arg' ' test_must_fail git fetch origin \ refs/heads/*:refs/remotes/origin/* \ refs/heads/branch2:refs/remotes/origin/branch1 2>error && - verify_stderr <<-\EOF - fatal: Cannot fetch both refs/heads/branch1 and refs/heads/branch2 to refs/remotes/origin/branch1 - EOF + test_i18ngrep "fatal: Cannot fetch both refs/heads/branch1 and refs/heads/branch2 to refs/remotes/origin/branch1" error ) ' @@ -90,10 +78,8 @@ test_expect_success 'fetch conflict: criss-cross args' ' git fetch origin \ refs/heads/branch1:refs/remotes/origin/branch2 \ refs/heads/branch2:refs/remotes/origin/branch1 2>error && - verify_stderr <<-\EOF - warning: refs/remotes/origin/branch1 usually tracks refs/heads/branch1, not refs/heads/branch2 - warning: refs/remotes/origin/branch2 usually tracks refs/heads/branch2, not refs/heads/branch1 - EOF + test_i18ngrep "warning: refs/remotes/origin/branch1 usually tracks refs/heads/branch1, not refs/heads/branch2" error && + test_i18ngrep "warning: refs/remotes/origin/branch2 usually tracks refs/heads/branch2, not refs/heads/branch1" error ) ' diff --git a/t/t5537-fetch-shallow.sh b/t/t5537-fetch-shallow.sh index a980574682..df8d2f095a 100755 --- a/t/t5537-fetch-shallow.sh +++ b/t/t5537-fetch-shallow.sh @@ -98,7 +98,7 @@ EOF test_expect_success 'fetch something upstream has but hidden by clients shallow boundaries' ' # the blob "1" is available in .git but hidden by the # shallow2/.git/shallow and it should be resent - ! git --git-dir=shallow2/.git cat-file blob `echo 1|git hash-object --stdin` >/dev/null && + ! git --git-dir=shallow2/.git cat-file blob $(echo 1|git hash-object --stdin) >/dev/null && echo 1 >1.t && git add 1.t && git commit -m add-1-back && @@ -114,7 +114,7 @@ add-1-back EOF test_cmp expect actual ) && - git --git-dir=shallow2/.git cat-file blob `echo 1|git hash-object --stdin` >/dev/null + git --git-dir=shallow2/.git cat-file blob $(echo 1|git hash-object --stdin) >/dev/null ' diff --git a/t/t5538-push-shallow.sh b/t/t5538-push-shallow.sh index ceee95b8a4..ecbf84d21c 100755 --- a/t/t5538-push-shallow.sh +++ b/t/t5538-push-shallow.sh @@ -104,7 +104,7 @@ EOF ' test_expect_success 'push from full to shallow' ' - ! git --git-dir=shallow2/.git cat-file blob `echo 1|git hash-object --stdin` && + ! git --git-dir=shallow2/.git cat-file blob $(echo 1|git hash-object --stdin) && commit 1 && git push shallow2/.git +master:refs/remotes/top/master && ( @@ -117,7 +117,7 @@ test_expect_success 'push from full to shallow' ' 3 EOF test_cmp expect actual && - git cat-file blob `echo 1|git hash-object --stdin` >/dev/null + git cat-file blob $(echo 1|git hash-object --stdin) >/dev/null ) ' test_done diff --git a/t/t5539-fetch-http-shallow.sh b/t/t5539-fetch-http-shallow.sh index 37a433504e..5fbf67c446 100755 --- a/t/t5539-fetch-http-shallow.sh +++ b/t/t5539-fetch-http-shallow.sh @@ -73,5 +73,78 @@ test_expect_success 'no shallow lines after receiving ACK ready' ' ) ' +test_expect_success 'clone shallow since ...' ' + test_create_repo shallow-since && + ( + cd shallow-since && + GIT_COMMITTER_DATE="100000000 +0700" git commit --allow-empty -m one && + GIT_COMMITTER_DATE="200000000 +0700" git commit --allow-empty -m two && + GIT_COMMITTER_DATE="300000000 +0700" git commit --allow-empty -m three && + mv .git "$HTTPD_DOCUMENT_ROOT_PATH/shallow-since.git" && + git clone --shallow-since "300000000 +0700" $HTTPD_URL/smart/shallow-since.git ../shallow11 && + git -C ../shallow11 log --pretty=tformat:%s HEAD >actual && + echo three >expected && + test_cmp expected actual + ) +' + +test_expect_success 'fetch shallow since ...' ' + git -C shallow11 fetch --shallow-since "200000000 +0700" origin && + git -C shallow11 log --pretty=tformat:%s origin/master >actual && + cat >expected <<-\EOF && + three + two + EOF + test_cmp expected actual +' + +test_expect_success 'shallow clone exclude tag two' ' + test_create_repo shallow-exclude && + ( + cd shallow-exclude && + test_commit one && + test_commit two && + test_commit three && + mv .git "$HTTPD_DOCUMENT_ROOT_PATH/shallow-exclude.git" && + git clone --shallow-exclude two $HTTPD_URL/smart/shallow-exclude.git ../shallow12 && + git -C ../shallow12 log --pretty=tformat:%s HEAD >actual && + echo three >expected && + test_cmp expected actual + ) +' + +test_expect_success 'fetch exclude tag one' ' + git -C shallow12 fetch --shallow-exclude one origin && + git -C shallow12 log --pretty=tformat:%s origin/master >actual && + test_write_lines three two >expected && + test_cmp expected actual +' + +test_expect_success 'fetching deepen' ' + test_create_repo shallow-deepen && + ( + cd shallow-deepen && + test_commit one && + test_commit two && + test_commit three && + mv .git "$HTTPD_DOCUMENT_ROOT_PATH/shallow-deepen.git" && + git clone --depth 1 $HTTPD_URL/smart/shallow-deepen.git deepen && + mv "$HTTPD_DOCUMENT_ROOT_PATH/shallow-deepen.git" .git && + test_commit four && + git -C deepen log --pretty=tformat:%s master >actual && + echo three >expected && + test_cmp expected actual && + mv .git "$HTTPD_DOCUMENT_ROOT_PATH/shallow-deepen.git" && + git -C deepen fetch --deepen=1 && + git -C deepen log --pretty=tformat:%s origin/master >actual && + cat >expected <<-\EOF && + four + three + two + EOF + test_cmp expected actual + ) +' + stop_httpd test_done diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh index fd7d06b9a2..21340e89c9 100755 --- a/t/t5541-http-push-smart.sh +++ b/t/t5541-http-push-smart.sh @@ -74,7 +74,7 @@ test_expect_success 'push to remote repository (standard)' ' test_tick && git commit -m path2 && HEAD=$(git rev-parse --verify HEAD) && - GIT_CURL_VERBOSE=1 git push -v -v 2>err && + GIT_TRACE_CURL=true git push -v -v 2>err && ! grep "Expect: 100-continue" err && grep "POST git-receive-pack ([0-9]* bytes)" err && (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && @@ -119,7 +119,7 @@ test_expect_success 'rejected update prints status' ' git commit -m dev2 && test_must_fail git push origin dev2 2>act && sed -e "/^remote: /s/ *$//" <act >cmp && - test_cmp exp cmp + test_i18ncmp exp cmp ' rm -f "$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git/hooks/update" @@ -219,7 +219,7 @@ test_expect_success TTY 'push shows progress when stderr is a tty' ' cd "$ROOT_PATH"/test_repo_clone && test_commit noisy && test_terminal git push >output 2>&1 && - grep "^Writing objects" output + test_i18ngrep "^Writing objects" output ' test_expect_success TTY 'push --quiet silences status and progress' ' @@ -233,16 +233,16 @@ test_expect_success TTY 'push --no-progress silences progress but not status' ' cd "$ROOT_PATH"/test_repo_clone && test_commit no-progress && test_terminal git push --no-progress >output 2>&1 && - grep "^To http" output && - ! grep "^Writing objects" + test_i18ngrep "^To http" output && + test_i18ngrep ! "^Writing objects" output ' test_expect_success 'push --progress shows progress to non-tty' ' cd "$ROOT_PATH"/test_repo_clone && test_commit progress && git push --progress >output 2>&1 && - grep "^To http" output && - grep "^Writing objects" output + test_i18ngrep "^To http" output && + test_i18ngrep "^Writing objects" output ' test_expect_success 'http push gives sane defaults to reflog' ' @@ -368,5 +368,14 @@ test_expect_success GPG 'push with post-receive to inspect certificate' ' test_cmp expect "$HTTPD_DOCUMENT_ROOT_PATH/push-cert-status" ' +test_expect_success 'push status output scrubs password' ' + cd "$ROOT_PATH/test_repo_clone" && + git push --porcelain \ + "$HTTPD_URL_USER_PASS/smart/test_repo.git" \ + +HEAD:scrub >status && + # should have been scrubbed down to vanilla URL + grep "^To $HTTPD_URL/smart/test_repo.git" status +' + stop_httpd test_done diff --git a/t/t5544-pack-objects-hook.sh b/t/t5544-pack-objects-hook.sh new file mode 100755 index 0000000000..4357af1525 --- /dev/null +++ b/t/t5544-pack-objects-hook.sh @@ -0,0 +1,62 @@ +#!/bin/sh + +test_description='test custom script in place of pack-objects' +. ./test-lib.sh + +test_expect_success 'create some history to fetch' ' + test_commit one && + test_commit two +' + +test_expect_success 'create debugging hook script' ' + write_script .git/hook <<-\EOF + echo >&2 "hook running" + echo "$*" >hook.args + cat >hook.stdin + "$@" <hook.stdin >hook.stdout + cat hook.stdout + EOF +' + +clear_hook_results () { + rm -rf .git/hook.* dst.git +} + +test_expect_success 'hook runs via global config' ' + clear_hook_results && + test_config_global uploadpack.packObjectsHook ./hook && + git clone --no-local . dst.git 2>stderr && + grep "hook running" stderr +' + +test_expect_success 'hook outputs are sane' ' + # check that we recorded a usable pack + git index-pack --stdin <.git/hook.stdout && + + # check that we recorded args and stdin. We do not check + # the full argument list or the exact pack contents, as it would make + # the test brittle. So just sanity check that we could replay + # the packing procedure. + grep "^git" .git/hook.args && + $(cat .git/hook.args) <.git/hook.stdin >replay +' + +test_expect_success 'hook runs from -c config' ' + clear_hook_results && + git clone --no-local \ + -u "git -c uploadpack.packObjectsHook=./hook upload-pack" \ + . dst.git 2>stderr && + grep "hook running" stderr +' + +test_expect_success 'hook does not run from repo config' ' + clear_hook_results && + test_config uploadpack.packObjectsHook "./hook" && + git clone --no-local . dst.git 2>stderr && + ! grep "hook running" stderr && + test_path_is_missing .git/hook.args && + test_path_is_missing .git/hook.stdin && + test_path_is_missing .git/hook.stdout +' + +test_done diff --git a/t/t5545-push-options.sh b/t/t5545-push-options.sh new file mode 100755 index 0000000000..b47a95871c --- /dev/null +++ b/t/t5545-push-options.sh @@ -0,0 +1,283 @@ +#!/bin/sh + +test_description='pushing to a repository using push options' + +. ./test-lib.sh + +mk_repo_pair () { + rm -rf workbench upstream && + test_create_repo upstream && + test_create_repo workbench && + ( + cd upstream && + git config receive.denyCurrentBranch warn && + mkdir -p .git/hooks && + cat >.git/hooks/pre-receive <<-'EOF' && + #!/bin/sh + if test -n "$GIT_PUSH_OPTION_COUNT"; then + i=0 + >hooks/pre-receive.push_options + while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"; do + eval "value=\$GIT_PUSH_OPTION_$i" + echo $value >>hooks/pre-receive.push_options + i=$((i + 1)) + done + fi + EOF + chmod u+x .git/hooks/pre-receive + + cat >.git/hooks/post-receive <<-'EOF' && + #!/bin/sh + if test -n "$GIT_PUSH_OPTION_COUNT"; then + i=0 + >hooks/post-receive.push_options + while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"; do + eval "value=\$GIT_PUSH_OPTION_$i" + echo $value >>hooks/post-receive.push_options + i=$((i + 1)) + done + fi + EOF + chmod u+x .git/hooks/post-receive + ) && + ( + cd workbench && + git remote add up ../upstream + ) +} + +# Compare the ref ($1) in upstream with a ref value from workbench ($2) +# i.e. test_refs second HEAD@{2} +test_refs () { + test $# = 2 && + git -C upstream rev-parse --verify "$1" >expect && + git -C workbench rev-parse --verify "$2" >actual && + test_cmp expect actual +} + +test_expect_success 'one push option works for a single branch' ' + mk_repo_pair && + git -C upstream config receive.advertisePushOptions true && + ( + cd workbench && + test_commit one && + git push --mirror up && + test_commit two && + git push --push-option=asdf up master + ) && + test_refs master master && + echo "asdf" >expect && + test_cmp expect upstream/.git/hooks/pre-receive.push_options && + test_cmp expect upstream/.git/hooks/post-receive.push_options +' + +test_expect_success 'push option denied by remote' ' + mk_repo_pair && + git -C upstream config receive.advertisePushOptions false && + ( + cd workbench && + test_commit one && + git push --mirror up && + test_commit two && + test_must_fail git push --push-option=asdf up master + ) && + test_refs master HEAD@{1} +' + +test_expect_success 'two push options work' ' + mk_repo_pair && + git -C upstream config receive.advertisePushOptions true && + ( + cd workbench && + test_commit one && + git push --mirror up && + test_commit two && + git push --push-option=asdf --push-option="more structured text" up master + ) && + test_refs master master && + printf "asdf\nmore structured text\n" >expect && + test_cmp expect upstream/.git/hooks/pre-receive.push_options && + test_cmp expect upstream/.git/hooks/post-receive.push_options +' + +test_expect_success 'push options and submodules' ' + test_when_finished "rm -rf parent" && + test_when_finished "rm -rf parent_upstream" && + mk_repo_pair && + git -C upstream config receive.advertisePushOptions true && + cp -r upstream parent_upstream && + test_commit -C upstream one && + + test_create_repo parent && + git -C parent remote add up ../parent_upstream && + test_commit -C parent one && + git -C parent push --mirror up && + + git -C parent submodule add ../upstream workbench && + git -C parent/workbench remote add up ../../upstream && + git -C parent commit -m "add submoule" && + + test_commit -C parent/workbench two && + git -C parent add workbench && + git -C parent commit -m "update workbench" && + + git -C parent push \ + --push-option=asdf --push-option="more structured text" \ + --recurse-submodules=on-demand up master && + + git -C upstream rev-parse --verify master >expect && + git -C parent/workbench rev-parse --verify master >actual && + test_cmp expect actual && + + git -C parent_upstream rev-parse --verify master >expect && + git -C parent rev-parse --verify master >actual && + test_cmp expect actual && + + printf "asdf\nmore structured text\n" >expect && + test_cmp expect upstream/.git/hooks/pre-receive.push_options && + test_cmp expect upstream/.git/hooks/post-receive.push_options && + test_cmp expect parent_upstream/.git/hooks/pre-receive.push_options && + test_cmp expect parent_upstream/.git/hooks/post-receive.push_options +' + +test_expect_success 'default push option' ' + mk_repo_pair && + git -C upstream config receive.advertisePushOptions true && + ( + cd workbench && + test_commit one && + git push --mirror up && + test_commit two && + git -c push.pushOption=default push up master + ) && + test_refs master master && + echo "default" >expect && + test_cmp expect upstream/.git/hooks/pre-receive.push_options && + test_cmp expect upstream/.git/hooks/post-receive.push_options +' + +test_expect_success 'two default push options' ' + mk_repo_pair && + git -C upstream config receive.advertisePushOptions true && + ( + cd workbench && + test_commit one && + git push --mirror up && + test_commit two && + git -c push.pushOption=default1 -c push.pushOption=default2 push up master + ) && + test_refs master master && + printf "default1\ndefault2\n" >expect && + test_cmp expect upstream/.git/hooks/pre-receive.push_options && + test_cmp expect upstream/.git/hooks/post-receive.push_options +' + +test_expect_success 'push option from command line overrides from-config push option' ' + mk_repo_pair && + git -C upstream config receive.advertisePushOptions true && + ( + cd workbench && + test_commit one && + git push --mirror up && + test_commit two && + git -c push.pushOption=default push --push-option=manual up master + ) && + test_refs master master && + echo "manual" >expect && + test_cmp expect upstream/.git/hooks/pre-receive.push_options && + test_cmp expect upstream/.git/hooks/post-receive.push_options +' + +test_expect_success 'empty value of push.pushOption in config clears the list' ' + mk_repo_pair && + git -C upstream config receive.advertisePushOptions true && + ( + cd workbench && + test_commit one && + git push --mirror up && + test_commit two && + git -c push.pushOption=default1 -c push.pushOption= -c push.pushOption=default2 push up master + ) && + test_refs master master && + echo "default2" >expect && + test_cmp expect upstream/.git/hooks/pre-receive.push_options && + test_cmp expect upstream/.git/hooks/post-receive.push_options +' + +test_expect_success 'invalid push option in config' ' + mk_repo_pair && + git -C upstream config receive.advertisePushOptions true && + ( + cd workbench && + test_commit one && + git push --mirror up && + test_commit two && + test_must_fail git -c push.pushOption push up master + ) && + test_refs master HEAD@{1} +' + +test_expect_success 'push options keep quoted characters intact (direct)' ' + mk_repo_pair && + git -C upstream config receive.advertisePushOptions true && + test_commit -C workbench one && + git -C workbench push --push-option="\"embedded quotes\"" up master && + echo "\"embedded quotes\"" >expect && + test_cmp expect upstream/.git/hooks/pre-receive.push_options +' + +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +# set up http repository for fetching/pushing, with push options config +# bool set to $1 +mk_http_pair () { + test_when_finished "rm -rf test_http_clone" && + test_when_finished 'rm -rf "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git' && + mk_repo_pair && + git -C upstream config receive.advertisePushOptions "$1" && + git -C upstream config http.receivepack true && + cp -R upstream/.git "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git && + git clone "$HTTPD_URL"/smart/upstream test_http_clone +} + +test_expect_success 'push option denied properly by http server' ' + mk_http_pair false && + test_commit -C test_http_clone one && + test_must_fail git -C test_http_clone push --push-option=asdf origin master 2>actual && + test_i18ngrep "the receiving end does not support push options" actual && + git -C test_http_clone push origin master +' + +test_expect_success 'push options work properly across http' ' + mk_http_pair true && + + test_commit -C test_http_clone one && + git -C test_http_clone push origin master && + git -C "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git rev-parse --verify master >expect && + git -C test_http_clone rev-parse --verify master >actual && + test_cmp expect actual && + + test_commit -C test_http_clone two && + git -C test_http_clone push --push-option=asdf --push-option="more structured text" origin master && + printf "asdf\nmore structured text\n" >expect && + test_cmp expect "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git/hooks/pre-receive.push_options && + test_cmp expect "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git/hooks/post-receive.push_options && + + git -C "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git rev-parse --verify master >expect && + git -C test_http_clone rev-parse --verify master >actual && + test_cmp expect actual +' + +test_expect_success 'push options keep quoted characters intact (http)' ' + mk_http_pair true && + + test_commit -C test_http_clone one && + git -C test_http_clone push --push-option="\"embedded quotes\"" origin master && + echo "\"embedded quotes\"" >expect && + test_cmp expect "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git/hooks/pre-receive.push_options +' + +stop_httpd + +test_done diff --git a/t/t5546-receive-limits.sh b/t/t5546-receive-limits.sh new file mode 100755 index 0000000000..0b0e987fdb --- /dev/null +++ b/t/t5546-receive-limits.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +test_description='check receive input limits' +. ./test-lib.sh + +# Let's run tests with different unpack limits: 1 and 10000 +# When the limit is 1, `git receive-pack` will call `git index-pack`. +# When the limit is 10000, `git receive-pack` will call `git unpack-objects`. + +test_pack_input_limit () { + case "$1" in + index) unpack_limit=1 ;; + unpack) unpack_limit=10000 ;; + esac + + test_expect_success 'prepare destination repository' ' + rm -fr dest && + git --bare init dest + ' + + test_expect_success "set unpacklimit to $unpack_limit" ' + git --git-dir=dest config receive.unpacklimit "$unpack_limit" + ' + + test_expect_success 'setting receive.maxInputSize to 512 rejects push' ' + git --git-dir=dest config receive.maxInputSize 512 && + test_must_fail git push dest HEAD + ' + + test_expect_success 'bumping limit to 4k allows push' ' + git --git-dir=dest config receive.maxInputSize 4k && + git push dest HEAD + ' + + test_expect_success 'prepare destination repository (again)' ' + rm -fr dest && + git --bare init dest + ' + + test_expect_success 'lifting the limit allows push' ' + git --git-dir=dest config receive.maxInputSize 0 && + git push dest HEAD + ' +} + +test_expect_success "create known-size (1024 bytes) commit" ' + test-tool genrandom foo 1024 >one-k && + git add one-k && + test_commit one-k +' + +test_pack_input_limit index +test_pack_input_limit unpack + +test_done diff --git a/t/t5547-push-quarantine.sh b/t/t5547-push-quarantine.sh new file mode 100755 index 0000000000..faaa51ccc5 --- /dev/null +++ b/t/t5547-push-quarantine.sh @@ -0,0 +1,72 @@ +#!/bin/sh + +test_description='check quarantine of objects during push' +. ./test-lib.sh + +test_expect_success 'create picky dest repo' ' + git init --bare dest.git && + write_script dest.git/hooks/pre-receive <<-\EOF + while read old new ref; do + test "$(git log -1 --format=%s $new)" = reject && exit 1 + done + exit 0 + EOF +' + +test_expect_success 'accepted objects work' ' + test_commit ok && + git push dest.git HEAD && + commit=$(git rev-parse HEAD) && + git --git-dir=dest.git cat-file commit $commit +' + +test_expect_success 'rejected objects are not installed' ' + test_commit reject && + commit=$(git rev-parse HEAD) && + test_must_fail git push dest.git reject && + test_must_fail git --git-dir=dest.git cat-file commit $commit +' + +test_expect_success 'rejected objects are removed' ' + echo "incoming-*" >expect && + (cd dest.git/objects && echo incoming-*) >actual && + test_cmp expect actual +' + +test_expect_success 'push to repo path with path separator (colon)' ' + # The interesting failure case here is when the + # receiving end cannot access its original object directory, + # so make it likely for us to generate a delta by having + # a non-trivial file with multiple versions. + + test-tool genrandom foo 4096 >file.bin && + git add file.bin && + git commit -m bin && + + if test_have_prereq MINGW + then + pathsep=";" + else + pathsep=":" + fi && + git clone --bare . "xxx${pathsep}yyy.git" && + + echo change >>file.bin && + git commit -am change && + # Note that we have to use the full path here, or it gets confused + # with the ssh host:path syntax. + git push "$(pwd)/xxx${pathsep}yyy.git" HEAD +' + +test_expect_success 'updating a ref from quarantine is forbidden' ' + git init --bare update.git && + write_script update.git/hooks/pre-receive <<-\EOF && + read old new refname + git update-ref refs/heads/unrelated $new + exit 1 + EOF + test_must_fail git push update.git HEAD && + git -C update.git fsck +' + +test_done diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index 87a7aa04ae..8552184e74 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -20,8 +20,9 @@ test_expect_success 'create http-accessible bare repository with loose objects' (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && git config core.bare true && mkdir -p hooks && - echo "exec git update-server-info" >hooks/post-update && - chmod +x hooks/post-update && + write_script "hooks/post-update" <<-\EOF && + exec git update-server-info + EOF hooks/post-update ) && git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && @@ -34,6 +35,15 @@ test_expect_success 'clone http repository' ' test_cmp file clone/file ' +test_expect_success 'list refs from outside any repository' ' + cat >expect <<-EOF && + $(git rev-parse master) HEAD + $(git rev-parse master) refs/heads/master + EOF + nongit git ls-remote "$HTTPD_URL/dumb/repo.git" >actual && + test_cmp expect actual +' + test_expect_success 'create password-protected repository' ' mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/" && cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \ @@ -91,6 +101,55 @@ test_expect_success 'configured username does not override URL' ' expect_askpass pass user@host ' +test_expect_success 'set up repo with http submodules' ' + git init super && + set_askpass user@host pass@host && + ( + cd super && + git submodule add "$HTTPD_URL/auth/dumb/repo.git" sub && + git commit -m "add submodule" + ) +' + +test_expect_success 'cmdline credential config passes to submodule via clone' ' + set_askpass wrong pass@host && + test_must_fail git clone --recursive super super-clone && + rm -rf super-clone && + + set_askpass wrong pass@host && + git -c "credential.$HTTPD_URL.username=user@host" \ + clone --recursive super super-clone && + expect_askpass pass user@host +' + +test_expect_success 'cmdline credential config passes submodule via fetch' ' + set_askpass wrong pass@host && + test_must_fail git -C super-clone fetch --recurse-submodules && + + set_askpass wrong pass@host && + git -C super-clone \ + -c "credential.$HTTPD_URL.username=user@host" \ + fetch --recurse-submodules && + expect_askpass pass user@host +' + +test_expect_success 'cmdline credential config passes submodule update' ' + # advance the submodule HEAD so that a fetch is required + git commit --allow-empty -m foo && + git push "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/repo.git" HEAD && + sha1=$(git rev-parse HEAD) && + git -C super-clone update-index --cacheinfo 160000,$sha1,sub && + + set_askpass wrong pass@host && + test_must_fail git -C super-clone submodule update && + + set_askpass wrong pass@host && + git -C super-clone \ + -c "credential.$HTTPD_URL.username=user@host" \ + submodule update && + expect_askpass pass user@host +' + test_expect_success 'fetch changes via http' ' echo content >>file && git commit -a -m two && @@ -132,7 +191,7 @@ test_expect_success 'fetch packed objects' ' test_expect_success 'fetch notices corrupt pack' ' cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git && (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git && - p=`ls objects/pack/pack-*.pack` && + p=$(ls objects/pack/pack-*.pack) && chmod u+w $p && printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc ) && @@ -140,14 +199,14 @@ test_expect_success 'fetch notices corrupt pack' ' (cd repo_bad1.git && git --bare init && test_must_fail git --bare fetch $HTTPD_URL/dumb/repo_bad1.git && - test 0 = `ls objects/pack/pack-*.pack | wc -l` + test 0 = $(ls objects/pack/pack-*.pack | wc -l) ) ' test_expect_success 'fetch notices corrupt idx' ' cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad2.git && (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad2.git && - p=`ls objects/pack/pack-*.idx` && + p=$(ls objects/pack/pack-*.idx) && chmod u+w $p && printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc ) && @@ -155,7 +214,7 @@ test_expect_success 'fetch notices corrupt idx' ' (cd repo_bad2.git && git --bare init && test_must_fail git --bare fetch $HTTPD_URL/dumb/repo_bad2.git && - test 0 = `ls objects/pack | wc -l` + test 0 = $(ls objects/pack | wc -l) ) ' @@ -214,15 +273,15 @@ check_language () { >expect ;; ?*) - echo "Accept-Language: $1" >expect + echo "=> Send header: Accept-Language: $1" >expect ;; esac && - GIT_CURL_VERBOSE=1 \ + GIT_TRACE_CURL=true \ LANGUAGE=$2 \ git ls-remote "$HTTPD_URL/dumb/repo.git" >output 2>&1 && tr -d '\015' <output | sort -u | - sed -ne '/^Accept-Language:/ p' >actual && + sed -ne '/^=> Send header: Accept-Language:/ p' >actual && test_cmp expect actual } @@ -246,8 +305,96 @@ ja;q=0.95, zh;q=0.94, sv;q=0.93, pt;q=0.92, nb;q=0.91, *;q=0.90" \ ' test_expect_success 'git client does not send an empty Accept-Language' ' - GIT_CURL_VERBOSE=1 LANGUAGE= git ls-remote "$HTTPD_URL/dumb/repo.git" 2>stderr && - ! grep "^Accept-Language:" stderr + GIT_TRACE_CURL=true LANGUAGE= git ls-remote "$HTTPD_URL/dumb/repo.git" 2>stderr && + ! grep "^=> Send header: Accept-Language:" stderr +' + +test_expect_success 'remote-http complains cleanly about malformed urls' ' + # do not actually issue "list" or other commands, as we do not + # want to rely on what curl would actually do with such a broken + # URL. This is just about making sure we do not segfault during + # initialization. + test_must_fail git remote-http http::/example.com/repo.git +' + +test_expect_success 'redirects can be forbidden/allowed' ' + test_must_fail git -c http.followRedirects=false \ + clone $HTTPD_URL/dumb-redir/repo.git dumb-redir && + git -c http.followRedirects=true \ + clone $HTTPD_URL/dumb-redir/repo.git dumb-redir 2>stderr +' + +test_expect_success 'redirects are reported to stderr' ' + # just look for a snippet of the redirected-to URL + test_i18ngrep /dumb/ stderr +' + +test_expect_success 'non-initial redirects can be forbidden' ' + test_must_fail git -c http.followRedirects=initial \ + clone $HTTPD_URL/redir-objects/repo.git redir-objects && + git -c http.followRedirects=true \ + clone $HTTPD_URL/redir-objects/repo.git redir-objects +' + +test_expect_success 'http.followRedirects defaults to "initial"' ' + test_must_fail git clone $HTTPD_URL/redir-objects/repo.git default +' + +# The goal is for a clone of the "evil" repository, which has no objects +# itself, to cause the client to fetch objects from the "victim" repository. +test_expect_success 'set up evil alternates scheme' ' + victim=$HTTPD_DOCUMENT_ROOT_PATH/victim.git && + git init --bare "$victim" && + git -C "$victim" --work-tree=. commit --allow-empty -m secret && + git -C "$victim" repack -ad && + git -C "$victim" update-server-info && + sha1=$(git -C "$victim" rev-parse HEAD) && + + evil=$HTTPD_DOCUMENT_ROOT_PATH/evil.git && + git init --bare "$evil" && + # do this by hand to avoid object existence check + printf "%s\\t%s\\n" $sha1 refs/heads/master >"$evil/info/refs" +' + +# Here we'll just redirect via HTTP. In a real-world attack these would be on +# different servers, but we should reject it either way. +test_expect_success 'http-alternates is a non-initial redirect' ' + echo "$HTTPD_URL/dumb/victim.git/objects" \ + >"$evil/objects/info/http-alternates" && + test_must_fail git -c http.followRedirects=initial \ + clone $HTTPD_URL/dumb/evil.git evil-initial && + git -c http.followRedirects=true \ + clone $HTTPD_URL/dumb/evil.git evil-initial +' + +# Curl supports a lot of protocols that we'd prefer not to allow +# http-alternates to use, but it's hard to test whether curl has +# accessed, say, the SMTP protocol, because we are not running an SMTP server. +# But we can check that it does not allow access to file://, which would +# otherwise allow this clone to complete. +test_expect_success 'http-alternates cannot point at funny protocols' ' + echo "file://$victim/objects" >"$evil/objects/info/http-alternates" && + test_must_fail git -c http.followRedirects=true \ + clone "$HTTPD_URL/dumb/evil.git" evil-file +' + +test_expect_success 'http-alternates triggers not-from-user protocol check' ' + echo "$HTTPD_URL/dumb/victim.git/objects" \ + >"$evil/objects/info/http-alternates" && + test_config_global http.followRedirects true && + test_must_fail git -c protocol.http.allow=user \ + clone $HTTPD_URL/dumb/evil.git evil-user && + git -c protocol.http.allow=always \ + clone $HTTPD_URL/dumb/evil.git evil-user +' + +test_expect_success 'can redirect through non-"info/refs?service=git-upload-pack" URL' ' + git clone "$HTTPD_URL/redir-to/dumb/repo.git" +' + +test_expect_success 'print HTTP error when any intermediate redirect throws error' ' + test_must_fail git clone "$HTTPD_URL/redir-to/502" 2> stderr && + test_i18ngrep "unable to access.*/redir-to/502" stderr ' stop_httpd diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index 58207d8825..f5721b4a59 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -43,12 +43,21 @@ cat >exp <<EOF < Content-Type: application/x-git-upload-pack-result EOF test_expect_success 'clone http repository' ' - GIT_CURL_VERBOSE=1 git clone --quiet $HTTPD_URL/smart/repo.git clone 2>err && + GIT_TRACE_CURL=true git clone --quiet $HTTPD_URL/smart/repo.git clone 2>err && test_cmp file clone/file && tr '\''\015'\'' Q <err | sed -e " s/Q\$// /^[*] /d + /^== Info:/d + /^=> Send header, /d + /^=> Send header:$/d + /^<= Recv header, /d + /^<= Recv header:$/d + s/=> Send header: // + s/= Recv header:// + /^<= Recv data/d + /^=> Send data/d /^$/d /^< $/d @@ -110,6 +119,10 @@ test_expect_success 'redirects re-root further requests' ' git clone $HTTPD_URL/smart-redir-limited/repo.git repo-redir-limited ' +test_expect_success 're-rooting dies on insane schemes' ' + test_must_fail git clone $HTTPD_URL/insane-redir/repo.git insane +' + test_expect_success 'clone from password-protected repository' ' echo two >expect && set_askpass user@host pass@host && @@ -261,12 +274,64 @@ test_expect_success CMDLINE_LIMIT \ ' test_expect_success 'large fetch-pack requests can be split across POSTs' ' - GIT_CURL_VERBOSE=1 git -c http.postbuffer=65536 \ + GIT_TRACE_CURL=true git -c http.postbuffer=65536 \ clone --bare "$HTTPD_URL/smart/repo.git" split.git 2>err && - grep "^> POST" err >posts && + grep "^=> Send header: POST" err >posts && test_line_count = 2 posts ' +test_expect_success 'test allowreachablesha1inwant' ' + test_when_finished "rm -rf test_reachable.git" && + server="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + master_sha=$(git -C "$server" rev-parse refs/heads/master) && + git -C "$server" config uploadpack.allowreachablesha1inwant 1 && + + git init --bare test_reachable.git && + git -C test_reachable.git remote add origin "$HTTPD_URL/smart/repo.git" && + git -C test_reachable.git fetch origin "$master_sha" +' + +test_expect_success 'test allowreachablesha1inwant with unreachable' ' + test_when_finished "rm -rf test_reachable.git; git reset --hard $(git rev-parse HEAD)" && + + #create unreachable sha + echo content >file2 && + git add file2 && + git commit -m two && + git push public HEAD:refs/heads/doomed && + git push public :refs/heads/doomed && + + server="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + master_sha=$(git -C "$server" rev-parse refs/heads/master) && + git -C "$server" config uploadpack.allowreachablesha1inwant 1 && + + git init --bare test_reachable.git && + git -C test_reachable.git remote add origin "$HTTPD_URL/smart/repo.git" && + test_must_fail git -C test_reachable.git fetch origin "$(git rev-parse HEAD)" +' + +test_expect_success 'test allowanysha1inwant with unreachable' ' + test_when_finished "rm -rf test_reachable.git; git reset --hard $(git rev-parse HEAD)" && + + #create unreachable sha + echo content >file2 && + git add file2 && + git commit -m two && + git push public HEAD:refs/heads/doomed && + git push public :refs/heads/doomed && + + server="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + master_sha=$(git -C "$server" rev-parse refs/heads/master) && + git -C "$server" config uploadpack.allowreachablesha1inwant 1 && + + git init --bare test_reachable.git && + git -C test_reachable.git remote add origin "$HTTPD_URL/smart/repo.git" && + test_must_fail git -C test_reachable.git fetch origin "$(git rev-parse HEAD)" && + + git -C "$server" config uploadpack.allowanysha1inwant 1 && + git -C test_reachable.git fetch origin "$(git rev-parse HEAD)" +' + test_expect_success EXPENSIVE 'http can handle enormous ref negotiation' ' ( cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && @@ -282,5 +347,55 @@ test_expect_success EXPENSIVE 'http can handle enormous ref negotiation' ' test_line_count = 100000 tags ' +test_expect_success 'custom http headers' ' + test_must_fail git -c http.extraheader="x-magic-two: cadabra" \ + fetch "$HTTPD_URL/smart_headers/repo.git" && + git -c http.extraheader="x-magic-one: abra" \ + -c http.extraheader="x-magic-two: cadabra" \ + fetch "$HTTPD_URL/smart_headers/repo.git" && + git update-index --add --cacheinfo 160000,$(git rev-parse HEAD),sub && + git config -f .gitmodules submodule.sub.path sub && + git config -f .gitmodules submodule.sub.url \ + "$HTTPD_URL/smart_headers/repo.git" && + git submodule init sub && + test_must_fail git submodule update sub && + git -c http.extraheader="x-magic-one: abra" \ + -c http.extraheader="x-magic-two: cadabra" \ + submodule update sub +' + +test_expect_success 'GIT_REDACT_COOKIES redacts cookies' ' + rm -rf clone && + echo "Set-Cookie: Foo=1" >cookies && + echo "Set-Cookie: Bar=2" >>cookies && + GIT_TRACE_CURL=true GIT_REDACT_COOKIES=Bar,Baz \ + git -c "http.cookieFile=$(pwd)/cookies" clone \ + $HTTPD_URL/smart/repo.git clone 2>err && + grep "Cookie:.*Foo=1" err && + grep "Cookie:.*Bar=<redacted>" err && + ! grep "Cookie:.*Bar=2" err +' + +test_expect_success 'GIT_REDACT_COOKIES handles empty values' ' + rm -rf clone && + echo "Set-Cookie: Foo=" >cookies && + GIT_TRACE_CURL=true GIT_REDACT_COOKIES=Foo \ + git -c "http.cookieFile=$(pwd)/cookies" clone \ + $HTTPD_URL/smart/repo.git clone 2>err && + grep "Cookie:.*Foo=<redacted>" err +' + +test_expect_success 'GIT_TRACE_CURL_NO_DATA prevents data from being traced' ' + rm -rf clone && + GIT_TRACE_CURL=true \ + git clone $HTTPD_URL/smart/repo.git clone 2>err && + grep "=> Send data" err && + + rm -rf clone && + GIT_TRACE_CURL=true GIT_TRACE_CURL_NO_DATA=1 \ + git clone $HTTPD_URL/smart/repo.git clone 2>err && + ! grep "=> Send data" err +' + stop_httpd test_done diff --git a/t/t5560-http-backend-noserver.sh b/t/t5560-http-backend-noserver.sh index aa73eeaef8..9fafcf1945 100755 --- a/t/t5560-http-backend-noserver.sh +++ b/t/t5560-http-backend-noserver.sh @@ -44,10 +44,6 @@ POST() { test_cmp exp act } -log_div() { - return 0 -} - . "$TEST_DIRECTORY"/t556x_common expect_aliased() { diff --git a/t/t5561-http-backend.sh b/t/t5561-http-backend.sh index 19afe96698..84a955770a 100755 --- a/t/t5561-http-backend.sh +++ b/t/t5561-http-backend.sh @@ -3,10 +3,16 @@ test_description='test git-http-backend' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh + +if ! test_have_prereq CURL; then + skip_all='skipping raw http-backend tests, curl not available' + test_done +fi + start_httpd GET() { - curl --include "$HTTPD_URL/$SMART/repo.git/$1" >out 2>/dev/null && + curl --include "$HTTPD_URL/$SMART/repo.git/$1" >out && tr '\015' Q <out | sed ' s/Q$// @@ -19,7 +25,7 @@ GET() { POST() { curl --include --data "$2" \ --header "Content-Type: application/x-$1-request" \ - "$HTTPD_URL/smart/repo.git/$1" >out 2>/dev/null && + "$HTTPD_URL/smart/repo.git/$1" >out && tr '\015' Q <out | sed ' s/Q$// @@ -29,15 +35,9 @@ POST() { test_cmp exp act } -log_div() { - echo >>"$HTTPD_ROOT_PATH"/access.log - echo "### $1" >>"$HTTPD_ROOT_PATH"/access.log - echo "###" >>"$HTTPD_ROOT_PATH"/access.log -} - . "$TEST_DIRECTORY"/t556x_common -cat >exp <<EOF +grep '^[^#]' >exp <<EOF ### refs/heads/master ### diff --git a/t/t556x_common b/t/t556x_common index 82926cfdb7..359fcfe32b 100755 --- a/t/t556x_common +++ b/t/t556x_common @@ -52,21 +52,17 @@ get_static_files() { SMART=smart GIT_HTTP_EXPORT_ALL=1 && export GIT_HTTP_EXPORT_ALL test_expect_success 'direct refs/heads/master not found' ' - log_div "refs/heads/master" && GET refs/heads/master "404 Not Found" ' test_expect_success 'static file is ok' ' - log_div "getanyfile default" && get_static_files "200 OK" ' SMART=smart_noexport unset GIT_HTTP_EXPORT_ALL test_expect_success 'no export by default' ' - log_div "no git-daemon-export-ok" && get_static_files "404 Not Found" ' test_expect_success 'export if git-daemon-export-ok' ' - log_div "git-daemon-export-ok" && (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && touch git-daemon-export-ok ) && @@ -75,47 +71,39 @@ test_expect_success 'export if git-daemon-export-ok' ' SMART=smart GIT_HTTP_EXPORT_ALL=1 && export GIT_HTTP_EXPORT_ALL test_expect_success 'static file if http.getanyfile true is ok' ' - log_div "getanyfile true" && config http.getanyfile true && get_static_files "200 OK" ' test_expect_success 'static file if http.getanyfile false fails' ' - log_div "getanyfile false" && config http.getanyfile false && get_static_files "403 Forbidden" ' test_expect_success 'http.uploadpack default enabled' ' - log_div "uploadpack default" && GET info/refs?service=git-upload-pack "200 OK" && POST git-upload-pack 0000 "200 OK" ' test_expect_success 'http.uploadpack true' ' - log_div "uploadpack true" && config http.uploadpack true && GET info/refs?service=git-upload-pack "200 OK" && POST git-upload-pack 0000 "200 OK" ' test_expect_success 'http.uploadpack false' ' - log_div "uploadpack false" && config http.uploadpack false && GET info/refs?service=git-upload-pack "403 Forbidden" && POST git-upload-pack 0000 "403 Forbidden" ' test_expect_success 'http.receivepack default disabled' ' - log_div "receivepack default" && GET info/refs?service=git-receive-pack "403 Forbidden" && POST git-receive-pack 0000 "403 Forbidden" ' test_expect_success 'http.receivepack true' ' - log_div "receivepack true" && config http.receivepack true && GET info/refs?service=git-receive-pack "200 OK" && POST git-receive-pack 0000 "200 OK" ' test_expect_success 'http.receivepack false' ' - log_div "receivepack false" && config http.receivepack false && GET info/refs?service=git-receive-pack "403 Forbidden" && POST git-receive-pack 0000 "403 Forbidden" diff --git a/t/t5570-git-daemon.sh b/t/t5570-git-daemon.sh index b7e283252d..0d4c52016b 100755 --- a/t/t5570-git-daemon.sh +++ b/t/t5570-git-daemon.sh @@ -6,6 +6,12 @@ test_description='test fetching over git protocol' . "$TEST_DIRECTORY"/lib-git-daemon.sh start_git_daemon +check_verbose_connect () { + grep -F "Looking up 127.0.0.1 ..." stderr && + grep -F "Connecting to 127.0.0.1 (port " stderr && + grep -F "done." stderr +} + test_expect_success 'setup repository' ' git config push.default matching && echo content >file && @@ -24,7 +30,8 @@ test_expect_success 'create git-accessible bare repository' ' ' test_expect_success 'clone git repository' ' - git clone "$GIT_DAEMON_URL/repo.git" clone && + git clone -v "$GIT_DAEMON_URL/repo.git" clone 2>stderr && + check_verbose_connect && test_cmp file clone/file ' @@ -32,10 +39,21 @@ test_expect_success 'fetch changes via git protocol' ' echo content >>file && git commit -a -m two && git push public && - (cd clone && git pull) && + (cd clone && git pull -v) 2>stderr && + check_verbose_connect && test_cmp file clone/file ' +test_expect_success 'no-op fetch -v stderr is as expected' ' + (cd clone && git fetch -v) 2>stderr && + check_verbose_connect +' + +test_expect_success 'no-op fetch without "-v" is quiet' ' + (cd clone && git fetch 2>../stderr) && + ! test -s stderr +' + test_expect_success 'remote detects correct HEAD' ' git push public master:other && (cd clone && @@ -57,7 +75,7 @@ test_expect_success 'prepare pack objects' ' test_expect_success 'fetch notices corrupt pack' ' cp -R "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_pack.git "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_bad1.git && (cd "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_bad1.git && - p=`ls objects/pack/pack-*.pack` && + p=$(ls objects/pack/pack-*.pack) && chmod u+w $p && printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc ) && @@ -65,14 +83,14 @@ test_expect_success 'fetch notices corrupt pack' ' (cd repo_bad1.git && git --bare init && test_must_fail git --bare fetch "$GIT_DAEMON_URL/repo_bad1.git" && - test 0 = `ls objects/pack/pack-*.pack | wc -l` + test 0 = $(ls objects/pack/pack-*.pack | wc -l) ) ' test_expect_success 'fetch notices corrupt idx' ' cp -R "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_pack.git "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_bad2.git && (cd "$GIT_DAEMON_DOCUMENT_ROOT_PATH"/repo_bad2.git && - p=`ls objects/pack/pack-*.idx` && + p=$(ls objects/pack/pack-*.idx) && chmod u+w $p && printf %0256d 0 | dd of=$p bs=256 count=1 seek=1 conv=notrunc ) && @@ -80,7 +98,7 @@ test_expect_success 'fetch notices corrupt idx' ' (cd repo_bad2.git && git --bare init && test_must_fail git --bare fetch "$GIT_DAEMON_URL/repo_bad2.git" && - test 0 = `ls objects/pack | wc -l` + test 0 = $(ls objects/pack | wc -l) ) ' @@ -149,23 +167,48 @@ test_expect_success 'access repo via interpolated hostname' ' git init --bare "$repo" && git push "$repo" HEAD && >"$repo"/git-daemon-export-ok && - rm -rf tmp.git && GIT_OVERRIDE_VIRTUAL_HOST=localhost \ - git clone --bare "$GIT_DAEMON_URL/interp.git" tmp.git && - rm -rf tmp.git && + git ls-remote "$GIT_DAEMON_URL/interp.git" && GIT_OVERRIDE_VIRTUAL_HOST=LOCALHOST \ - git clone --bare "$GIT_DAEMON_URL/interp.git" tmp.git + git ls-remote "$GIT_DAEMON_URL/interp.git" ' test_expect_success 'hostname cannot break out of directory' ' - rm -rf tmp.git && repo="$GIT_DAEMON_DOCUMENT_ROOT_PATH/../escape.git" && git init --bare "$repo" && git push "$repo" HEAD && >"$repo"/git-daemon-export-ok && test_must_fail \ env GIT_OVERRIDE_VIRTUAL_HOST=.. \ - git clone --bare "$GIT_DAEMON_URL/escape.git" tmp.git + git ls-remote "$GIT_DAEMON_URL/escape.git" +' + +test_expect_success 'daemon log records all attributes' ' + cat >expect <<-\EOF && + Extended attribute "host": localhost + Extended attribute "protocol": version=1 + EOF + >daemon.log && + GIT_OVERRIDE_VIRTUAL_HOST=localhost \ + git -c protocol.version=1 \ + ls-remote "$GIT_DAEMON_URL/interp.git" && + grep -i extended.attribute daemon.log | cut -d" " -f2- >actual && + test_cmp expect actual +' + +test_expect_success FAKENC 'hostname interpolation works after LF-stripping' ' + { + printf "git-upload-pack /interp.git\n\0host=localhost" | packetize + printf "0000" + } >input && + fake_nc "$GIT_DAEMON_HOST_PORT" <input >output && + depacketize <output >output.raw && + + # just pick out the value of master, which avoids any protocol + # particulars + perl -lne "print \$1 if m{^(\\S+) refs/heads/master}" <output.raw >actual && + git -C "$repo" rev-parse master >expect && + test_cmp expect actual ' stop_git_daemon diff --git a/t/t5571-pre-push-hook.sh b/t/t5571-pre-push-hook.sh index 6f9916a390..ba975bb355 100755 --- a/t/t5571-pre-push-hook.sh +++ b/t/t5571-pre-push-hook.sh @@ -109,23 +109,20 @@ test_expect_success 'push to URL' ' diff expected actual ' -# Test that filling pipe buffers doesn't cause failure -# Too slow to leave enabled for general use -if false -then - printf 'parent1\nrepo1\n' >expected - nr=1000 - while test $nr -lt 2000 - do - nr=$(( $nr + 1 )) - git branch b/$nr $COMMIT3 - echo "refs/heads/b/$nr $COMMIT3 refs/heads/b/$nr $_z40" >>expected - done - - test_expect_success 'push many refs' ' - git push parent1 "refs/heads/b/*:refs/heads/b/*" && - diff expected actual - ' -fi +test_expect_success 'set up many-ref tests' ' + { + nr=1000 + while test $nr -lt 2000 + do + nr=$(( $nr + 1 )) + echo "create refs/heads/b/$nr $COMMIT3" + done + } | git update-ref --stdin +' + +test_expect_success 'sigpipe does not cause pre-push hook failure' ' + echo "exit 0" | write_script "$HOOK" && + git push parent1 "refs/heads/b/*:refs/heads/b/*" +' test_done diff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh index accfa5cc0c..321bd37deb 100755 --- a/t/t5572-pull-submodule.sh +++ b/t/t5572-pull-submodule.sh @@ -42,4 +42,94 @@ KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1 KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1 test_submodule_switch "git_pull_noff" +test_expect_success 'pull --recurse-submodule setup' ' + test_create_repo child && + test_commit -C child bar && + + test_create_repo parent && + test_commit -C child foo && + + git -C parent submodule add ../child sub && + git -C parent commit -m "add submodule" && + + git clone --recurse-submodules parent super +' + +test_expect_success 'recursive pull updates working tree' ' + test_commit -C child merge_strategy && + git -C parent submodule update --remote && + git -C parent add sub && + git -C parent commit -m "update submodule" && + + git -C super pull --no-rebase --recurse-submodules && + test_path_is_file super/sub/merge_strategy.t +' + +test_expect_success "submodule.recurse option triggers recursive pull" ' + test_commit -C child merge_strategy_2 && + git -C parent submodule update --remote && + git -C parent add sub && + git -C parent commit -m "update submodule" && + + git -C super -c submodule.recurse pull --no-rebase && + test_path_is_file super/sub/merge_strategy_2.t +' + +test_expect_success " --[no-]recurse-submodule and submodule.recurse" ' + test_commit -C child merge_strategy_3 && + git -C parent submodule update --remote && + git -C parent add sub && + git -C parent commit -m "update submodule" && + + git -C super -c submodule.recurse pull --no-recurse-submodules --no-rebase && + test_path_is_missing super/sub/merge_strategy_3.t && + git -C super -c submodule.recurse=false pull --recurse-submodules --no-rebase && + test_path_is_file super/sub/merge_strategy_3.t && + + test_commit -C child merge_strategy_4 && + git -C parent submodule update --remote && + git -C parent add sub && + git -C parent commit -m "update submodule" && + + git -C super -c submodule.recurse=false pull --no-recurse-submodules --no-rebase && + test_path_is_missing super/sub/merge_strategy_4.t && + git -C super -c submodule.recurse=true pull --recurse-submodules --no-rebase && + test_path_is_file super/sub/merge_strategy_4.t +' + +test_expect_success 'recursive rebasing pull' ' + # change upstream + test_commit -C child rebase_strategy && + git -C parent submodule update --remote && + git -C parent add sub && + git -C parent commit -m "update submodule" && + + # also have local commits + test_commit -C super/sub local_stuff && + + git -C super pull --rebase --recurse-submodules && + test_path_is_file super/sub/rebase_strategy.t && + test_path_is_file super/sub/local_stuff.t +' + +test_expect_success 'pull rebase recursing fails with conflicts' ' + + # local changes in submodule recorded in superproject: + test_commit -C super/sub local_stuff_2 && + git -C super add sub && + git -C super commit -m "local update submodule" && + + # and in the remote as well: + test_commit -C child important_upstream_work && + git -C parent submodule update --remote && + git -C parent add sub && + git -C parent commit -m "remote update submodule" && + + # Unfortunately we fail here, despite no conflict in the + # submodule itself, but the merge strategy in submodules + # does not support rebase: + test_must_fail git -C super pull --rebase --recurse-submodules 2>err && + test_i18ngrep "locally recorded submodule modifications" err +' + test_done diff --git a/t/t5573-pull-verify-signatures.sh b/t/t5573-pull-verify-signatures.sh new file mode 100755 index 0000000000..9594e891f4 --- /dev/null +++ b/t/t5573-pull-verify-signatures.sh @@ -0,0 +1,81 @@ +#!/bin/sh + +test_description='pull signature verification tests' +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-gpg.sh" + +test_expect_success GPG 'create repositories with signed commits' ' + echo 1 >a && git add a && + test_tick && git commit -m initial && + git tag initial && + + git clone . signed && + ( + cd signed && + echo 2 >b && git add b && + test_tick && git commit -S -m "signed" + ) && + + git clone . unsigned && + ( + cd unsigned && + echo 3 >c && git add c && + test_tick && git commit -m "unsigned" + ) && + + git clone . bad && + ( + cd bad && + echo 4 >d && git add d && + test_tick && git commit -S -m "bad" && + git cat-file commit HEAD >raw && + sed -e "s/bad/forged bad/" raw >forged && + git hash-object -w -t commit forged >forged.commit && + git checkout $(cat forged.commit) + ) && + + git clone . untrusted && + ( + cd untrusted && + echo 5 >e && git add e && + test_tick && git commit -SB7227189 -m "untrusted" + ) +' + +test_expect_success GPG 'pull unsigned commit with --verify-signatures' ' + test_when_finished "git reset --hard && git checkout initial" && + test_must_fail git pull --ff-only --verify-signatures unsigned 2>pullerror && + test_i18ngrep "does not have a GPG signature" pullerror +' + +test_expect_success GPG 'pull commit with bad signature with --verify-signatures' ' + test_when_finished "git reset --hard && git checkout initial" && + test_must_fail git pull --ff-only --verify-signatures bad 2>pullerror && + test_i18ngrep "has a bad GPG signature" pullerror +' + +test_expect_success GPG 'pull commit with untrusted signature with --verify-signatures' ' + test_when_finished "git reset --hard && git checkout initial" && + test_must_fail git pull --ff-only --verify-signatures untrusted 2>pullerror && + test_i18ngrep "has an untrusted GPG signature" pullerror +' + +test_expect_success GPG 'pull signed commit with --verify-signatures' ' + test_when_finished "git reset --hard && git checkout initial" && + git pull --verify-signatures signed >pulloutput && + test_i18ngrep "has a good GPG signature" pulloutput +' + +test_expect_success GPG 'pull commit with bad signature without verification' ' + test_when_finished "git reset --hard && git checkout initial" && + git pull --ff-only bad 2>pullerror +' + +test_expect_success GPG 'pull commit with bad signature with --no-verify-signatures' ' + test_when_finished "git reset --hard && git checkout initial" && + test_config merge.verifySignatures true && + test_config pull.verifySignatures true && + git pull --ff-only --no-verify-signatures bad 2>pullerror +' + +test_done diff --git a/t/t5580-clone-push-unc.sh b/t/t5580-clone-push-unc.sh new file mode 100755 index 0000000000..ba548df4a9 --- /dev/null +++ b/t/t5580-clone-push-unc.sh @@ -0,0 +1,60 @@ +#!/bin/sh + +test_description='various Windows-only path tests' +. ./test-lib.sh + +if test_have_prereq CYGWIN +then + alias winpwd='cygpath -aw .' +elif test_have_prereq MINGW +then + alias winpwd=pwd +else + skip_all='skipping Windows-only path tests' + test_done +fi + +UNCPATH="$(winpwd)" +case "$UNCPATH" in +[A-Z]:*) + # Use administrative share e.g. \\localhost\C$\git-sdk-64\usr\src\git + # (we use forward slashes here because MSYS2 and Git accept them, and + # they are easier on the eyes) + UNCPATH="//localhost/${UNCPATH%%:*}\$/${UNCPATH#?:}" + test -d "$UNCPATH" || { + skip_all='could not access administrative share; skipping' + test_done + } + ;; +*) + skip_all='skipping UNC path tests, cannot determine current path as UNC' + test_done + ;; +esac + +test_expect_success setup ' + test_commit initial +' + +test_expect_success clone ' + git clone "file://$UNCPATH" clone +' + +test_expect_success push ' + ( + cd clone && + git checkout -b to-push && + test_commit to-push && + git push origin HEAD + ) && + rev="$(git -C clone rev-parse --verify refs/heads/to-push)" && + test "$rev" = "$(git rev-parse --verify refs/heads/to-push)" +' + +test_expect_success MINGW 'remote nick cannot contain backslashes' ' + BACKSLASHED="$(winpwd | tr / \\\\)" && + git ls-remote "$BACKSLASHED" >out 2>err && + test_i18ngrep ! "unable to access" err +' + +test_done diff --git a/t/t5600-clone-fail-cleanup.sh b/t/t5600-clone-fail-cleanup.sh index 4435693bb2..4a1a912e03 100755 --- a/t/t5600-clone-fail-cleanup.sh +++ b/t/t5600-clone-fail-cleanup.sh @@ -7,46 +7,94 @@ test_description='test git clone to cleanup after failure This test covers the fact that if git clone fails, it should remove the directory it created, to avoid the user having to manually -remove the directory before attempting a clone again.' +remove the directory before attempting a clone again. + +Unless the directory already exists, in which case we clean up only what we +wrote. +' . ./test-lib.sh -test_expect_success \ - 'clone of non-existent source should fail' \ - 'test_must_fail git clone foo bar' +corrupt_repo () { + test_when_finished "rmdir foo/.git/objects.bak" && + mkdir foo/.git/objects.bak/ && + test_when_finished "mv foo/.git/objects.bak/* foo/.git/objects/" && + mv foo/.git/objects/* foo/.git/objects.bak/ +} -test_expect_success \ - 'failed clone should not leave a directory' \ - '! test -d bar' +test_expect_success 'clone of non-existent source should fail' ' + test_must_fail git clone foo bar +' -# Need a repo to clone -test_create_repo foo +test_expect_success 'failed clone should not leave a directory' ' + test_path_is_missing bar +' -# clone doesn't like it if there is no HEAD. Is that a bug? -(cd foo && touch file && git add file && git commit -m 'add file' >/dev/null 2>&1) +test_expect_success 'create a repo to clone' ' + test_create_repo foo +' + +test_expect_success 'create objects in repo for later corruption' ' + test_commit -C foo file +' # source repository given to git clone should be relative to the # current path not to the target dir -test_expect_success \ - 'clone of non-existent (relative to $PWD) source should fail' \ - 'test_must_fail git clone ../foo baz' +test_expect_success 'clone of non-existent (relative to $PWD) source should fail' ' + test_must_fail git clone ../foo baz +' -test_expect_success \ - 'clone should work now that source exists' \ - 'git clone foo bar' +test_expect_success 'clone should work now that source exists' ' + git clone foo bar +' -test_expect_success \ - 'successful clone must leave the directory' \ - 'test -d bar' +test_expect_success 'successful clone must leave the directory' ' + test_path_is_dir bar +' test_expect_success 'failed clone --separate-git-dir should not leave any directories' ' - mkdir foo/.git/objects.bak/ && - mv foo/.git/objects/* foo/.git/objects.bak/ && + corrupt_repo && test_must_fail git clone --separate-git-dir gitdir foo worktree && - test_must_fail test -e gitdir && - test_must_fail test -e worktree && - mv foo/.git/objects.bak/* foo/.git/objects/ && - rmdir foo/.git/objects.bak + test_path_is_missing gitdir && + test_path_is_missing worktree +' + +test_expect_success 'failed clone into empty leaves directory (vanilla)' ' + mkdir -p empty && + corrupt_repo && + test_must_fail git clone foo empty && + test_dir_is_empty empty +' + +test_expect_success 'failed clone into empty leaves directory (bare)' ' + mkdir -p empty && + corrupt_repo && + test_must_fail git clone --bare foo empty && + test_dir_is_empty empty +' + +test_expect_success 'failed clone into empty leaves directory (separate)' ' + mkdir -p empty-git empty-wt && + corrupt_repo && + test_must_fail git clone --separate-git-dir empty-git foo empty-wt && + test_dir_is_empty empty-git && + test_dir_is_empty empty-wt +' + +test_expect_success 'failed clone into empty leaves directory (separate, git)' ' + mkdir -p empty-git && + corrupt_repo && + test_must_fail git clone --separate-git-dir empty-git foo no-wt && + test_dir_is_empty empty-git && + test_path_is_missing no-wt +' + +test_expect_success 'failed clone into empty leaves directory (separate, wt)' ' + mkdir -p empty-wt && + corrupt_repo && + test_must_fail git clone --separate-git-dir no-git foo empty-wt && + test_path_is_missing no-git && + test_dir_is_empty empty-wt ' test_done diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index bfdaf75966..0b62037744 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -4,6 +4,9 @@ test_description=clone . ./test-lib.sh +X= +test_have_prereq !MINGW || X=.exe + test_expect_success setup ' rm -fr .git && @@ -65,6 +68,29 @@ test_expect_success 'clone respects GIT_WORK_TREE' ' ' +test_expect_success 'clone from hooks' ' + + test_create_repo r0 && + cd r0 && + test_commit initial && + cd .. && + git init r1 && + cd r1 && + cat >.git/hooks/pre-commit <<-\EOF && + #!/bin/sh + git clone ../r0 ../r2 + exit 1 + EOF + chmod u+x .git/hooks/pre-commit && + : >file && + git add file && + test_must_fail git commit -m invoke-hook && + cd .. && + test_cmp r0/.git/HEAD r2/.git/HEAD && + test_cmp r0/initial.t r2/initial.t + +' + test_expect_success 'clone creates intermediate directories' ' git clone src long/path/to/dst && @@ -125,7 +151,7 @@ test_expect_success 'clone --mirror does not repeat tags' ' git clone --mirror src mirror2 && (cd mirror2 && git show-ref 2> clone.err > clone.out) && - test_must_fail grep Duplicate mirror2/clone.err && + ! grep Duplicate mirror2/clone.err && grep some-tag mirror2/clone.out ' @@ -221,7 +247,7 @@ test_expect_success 'clone separate gitdir' ' ' test_expect_success 'clone separate gitdir: output' ' - echo "gitdir: `pwd`/realgitdir" >expected && + echo "gitdir: $(pwd)/realgitdir" >expected && test_cmp expected dst/.git ' @@ -280,26 +306,21 @@ test_expect_success 'clone checking out a tag' ' test_cmp fetch.expected fetch.actual ' -setup_ssh_wrapper () { - test_expect_success 'setup ssh wrapper' ' - write_script "$TRASH_DIRECTORY/ssh-wrapper" <<-\EOF && - echo >>"$TRASH_DIRECTORY/ssh-output" "ssh: $*" && - # throw away all but the last argument, which should be the - # command - while test $# -gt 1; do shift; done - eval "$1" - EOF - GIT_SSH="$TRASH_DIRECTORY/ssh-wrapper" && - export GIT_SSH && - export TRASH_DIRECTORY && - >"$TRASH_DIRECTORY"/ssh-output - ' -} +test_expect_success 'set up ssh wrapper' ' + cp "$GIT_BUILD_DIR/t/helper/test-fake-ssh$X" \ + "$TRASH_DIRECTORY/ssh$X" && + GIT_SSH="$TRASH_DIRECTORY/ssh$X" && + export GIT_SSH && + export TRASH_DIRECTORY && + >"$TRASH_DIRECTORY"/ssh-output +' copy_ssh_wrapper_as () { - cp "$TRASH_DIRECTORY/ssh-wrapper" "$1" && - GIT_SSH="$1" && - export GIT_SSH + rm -f "${1%$X}$X" && + cp "$TRASH_DIRECTORY/ssh$X" "${1%$X}$X" && + test_when_finished "rm $(git rev-parse --sq-quote "${1%$X}$X")" && + GIT_SSH="${1%$X}$X" && + test_when_finished "GIT_SSH=\"\$TRASH_DIRECTORY/ssh\$X\"" } expect_ssh () { @@ -323,8 +344,6 @@ expect_ssh () { (cd "$TRASH_DIRECTORY" && test_cmp ssh-expect ssh-output) } -setup_ssh_wrapper - test_expect_success 'clone myhost:src uses ssh' ' git clone myhost:src ssh-clone && expect_ssh myhost src @@ -341,9 +360,52 @@ test_expect_success 'bracketed hostnames are still ssh' ' expect_ssh "-p 123" myhost src ' -test_expect_success 'uplink is not treated as putty' ' +test_expect_success 'OpenSSH variant passes -4' ' + git clone -4 "[myhost:123]:src" ssh-ipv4-clone && + expect_ssh "-4 -p 123" myhost src +' + +test_expect_success 'variant can be overridden' ' + copy_ssh_wrapper_as "$TRASH_DIRECTORY/putty" && + git -c ssh.variant=putty clone -4 "[myhost:123]:src" ssh-putty-clone && + expect_ssh "-4 -P 123" myhost src +' + +test_expect_success 'variant=auto picks based on basename' ' + copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" && + git -c ssh.variant=auto clone -4 "[myhost:123]:src" ssh-auto-clone && + expect_ssh "-4 -P 123" myhost src +' + +test_expect_success 'simple does not support -4/-6' ' + copy_ssh_wrapper_as "$TRASH_DIRECTORY/simple" && + test_must_fail git clone -4 "myhost:src" ssh-4-clone-simple +' + +test_expect_success 'simple does not support port' ' + copy_ssh_wrapper_as "$TRASH_DIRECTORY/simple" && + test_must_fail git clone "[myhost:123]:src" ssh-bracket-clone-simple +' + +test_expect_success 'uplink is treated as simple' ' copy_ssh_wrapper_as "$TRASH_DIRECTORY/uplink" && - git clone "[myhost:123]:src" ssh-bracket-clone-uplink && + test_must_fail git clone "[myhost:123]:src" ssh-bracket-clone-uplink && + git clone "myhost:src" ssh-clone-uplink && + expect_ssh myhost src +' + +test_expect_success 'OpenSSH-like uplink is treated as ssh' ' + write_script "$TRASH_DIRECTORY/uplink" <<-EOF && + if test "\$1" = "-G" + then + exit 0 + fi && + exec "\$TRASH_DIRECTORY/ssh$X" "\$@" + EOF + test_when_finished "rm -f \"\$TRASH_DIRECTORY/uplink\"" && + GIT_SSH="$TRASH_DIRECTORY/uplink" && + test_when_finished "GIT_SSH=\"\$TRASH_DIRECTORY/ssh\$X\"" && + git clone "[myhost:123]:src" ssh-bracket-clone-sshlike-uplink && expect_ssh "-p 123" myhost src ' @@ -365,8 +427,54 @@ test_expect_success 'tortoiseplink is like putty, with extra arguments' ' expect_ssh "-batch -P 123" myhost src ' -# Reset the GIT_SSH environment variable for clone tests. -setup_ssh_wrapper +test_expect_success 'double quoted plink.exe in GIT_SSH_COMMAND' ' + copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink.exe" && + GIT_SSH_COMMAND="\"$TRASH_DIRECTORY/plink.exe\" -v" \ + git clone "[myhost:123]:src" ssh-bracket-clone-plink-3 && + expect_ssh "-v -P 123" myhost src +' + +SQ="'" +test_expect_success 'single quoted plink.exe in GIT_SSH_COMMAND' ' + copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink.exe" && + GIT_SSH_COMMAND="$SQ$TRASH_DIRECTORY/plink.exe$SQ -v" \ + git clone "[myhost:123]:src" ssh-bracket-clone-plink-4 && + expect_ssh "-v -P 123" myhost src +' + +test_expect_success 'GIT_SSH_VARIANT overrides plink detection' ' + copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" && + GIT_SSH_VARIANT=ssh \ + git clone "[myhost:123]:src" ssh-bracket-clone-variant-1 && + expect_ssh "-p 123" myhost src +' + +test_expect_success 'ssh.variant overrides plink detection' ' + copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" && + git -c ssh.variant=ssh \ + clone "[myhost:123]:src" ssh-bracket-clone-variant-2 && + expect_ssh "-p 123" myhost src +' + +test_expect_success 'GIT_SSH_VARIANT overrides plink detection to plink' ' + copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" && + GIT_SSH_VARIANT=plink \ + git clone "[myhost:123]:src" ssh-bracket-clone-variant-3 && + expect_ssh "-P 123" myhost src +' + +test_expect_success 'GIT_SSH_VARIANT overrides plink to tortoiseplink' ' + copy_ssh_wrapper_as "$TRASH_DIRECTORY/plink" && + GIT_SSH_VARIANT=tortoiseplink \ + git clone "[myhost:123]:src" ssh-bracket-clone-variant-4 && + expect_ssh "-batch -P 123" myhost src +' + +test_expect_success 'clean failure on broken quoting' ' + test_must_fail \ + env GIT_SSH_COMMAND="${SQ}plink.exe -v" \ + git clone "[myhost:123]:src" sq-failure +' counter=0 # $1 url @@ -445,7 +553,7 @@ test_expect_success 'clone ssh://host.xz:22/~repo' ' #IPv6 for tuah in ::1 [::1] [::1]: user@::1 user@[::1] user@[::1]: [user@::1] [user@::1]: do - ehost=$(echo $tuah | sed -e "s/1]:/1]/ "| tr -d "[]") + ehost=$(echo $tuah | sed -e "s/1]:/1]/" | tr -d "[]") test_expect_success "clone ssh://$tuah/home/user/repo" " test_clone_url ssh://$tuah/home/user/repo $ehost /home/user/repo " @@ -496,4 +604,129 @@ test_expect_success 'shallow clone locally' ' ( cd ddsstt && git fsck ) ' +test_expect_success 'GIT_TRACE_PACKFILE produces a usable pack' ' + rm -rf dst.git && + GIT_TRACE_PACKFILE=$PWD/tmp.pack git clone --no-local --bare src dst.git && + git init --bare replay.git && + git -C replay.git index-pack -v --stdin <tmp.pack +' + +hex2oct () { + perl -ne 'printf "\\%03o", hex for /../g' +} + +test_expect_success 'clone on case-insensitive fs' ' + git init icasefs && + ( + cd icasefs + o=$(git hash-object -w --stdin </dev/null | hex2oct) && + t=$(printf "100644 X\0${o}100644 x\0${o}" | + git hash-object -w -t tree --stdin) && + c=$(git commit-tree -m bogus $t) && + git update-ref refs/heads/bogus $c && + git clone -b bogus . bogus + ) +' + +partial_clone () { + SERVER="$1" && + URL="$2" && + + rm -rf "$SERVER" client && + test_create_repo "$SERVER" && + test_commit -C "$SERVER" one && + HASH1=$(git hash-object "$SERVER/one.t") && + git -C "$SERVER" revert HEAD && + test_commit -C "$SERVER" two && + HASH2=$(git hash-object "$SERVER/two.t") && + test_config -C "$SERVER" uploadpack.allowfilter 1 && + test_config -C "$SERVER" uploadpack.allowanysha1inwant 1 && + + git clone --filter=blob:limit=0 "$URL" client && + + git -C client fsck && + + # Ensure that unneeded blobs are not inadvertently fetched. + test_config -C client extensions.partialclone "not a remote" && + test_must_fail git -C client cat-file -e "$HASH1" && + + # But this blob was fetched, because clone performs an initial checkout + git -C client cat-file -e "$HASH2" +} + +test_expect_success 'partial clone' ' + partial_clone server "file://$(pwd)/server" +' + +test_expect_success 'partial clone: warn if server does not support object filtering' ' + rm -rf server client && + test_create_repo server && + test_commit -C server one && + + git clone --filter=blob:limit=0 "file://$(pwd)/server" client 2> err && + + test_i18ngrep "filtering not recognized by server" err +' + +test_expect_success 'batch missing blob request during checkout' ' + rm -rf server client && + + test_create_repo server && + echo a >server/a && + echo b >server/b && + git -C server add a b && + + git -C server commit -m x && + echo aa >server/a && + echo bb >server/b && + git -C server add a b && + git -C server commit -m x && + + test_config -C server uploadpack.allowfilter 1 && + test_config -C server uploadpack.allowanysha1inwant 1 && + + git clone --filter=blob:limit=0 "file://$(pwd)/server" client && + + # Ensure that there is only one negotiation by checking that there is + # only "done" line sent. ("done" marks the end of negotiation.) + GIT_TRACE_PACKET="$(pwd)/trace" git -C client checkout HEAD^ && + grep "git> done" trace >done_lines && + test_line_count = 1 done_lines +' + +test_expect_success 'batch missing blob request does not inadvertently try to fetch gitlinks' ' + rm -rf server client && + + test_create_repo repo_for_submodule && + test_commit -C repo_for_submodule x && + + test_create_repo server && + echo a >server/a && + echo b >server/b && + git -C server add a b && + git -C server commit -m x && + + echo aa >server/a && + echo bb >server/b && + # Also add a gitlink pointing to an arbitrary repository + git -C server submodule add "$(pwd)/repo_for_submodule" c && + git -C server add a b c && + git -C server commit -m x && + + test_config -C server uploadpack.allowfilter 1 && + test_config -C server uploadpack.allowanysha1inwant 1 && + + # Make sure that it succeeds + git clone --filter=blob:limit=0 "file://$(pwd)/server" client +' + +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +test_expect_success 'partial clone using HTTP' ' + partial_clone "$HTTPD_DOCUMENT_ROOT_PATH/server" "$HTTPD_URL/smart/server" +' + +stop_httpd + test_done diff --git a/t/t5603-clone-dirname.sh b/t/t5603-clone-dirname.sh new file mode 100755 index 0000000000..13b5e5eb9b --- /dev/null +++ b/t/t5603-clone-dirname.sh @@ -0,0 +1,108 @@ +#!/bin/sh + +test_description='check output directory names used by git-clone' +. ./test-lib.sh + +# we use a fake ssh wrapper that ignores the arguments +# entirely; we really only care that we get _some_ repo, +# as the real test is what clone does on the local side +test_expect_success 'setup ssh wrapper' ' + write_script "$TRASH_DIRECTORY/ssh-wrapper" <<-\EOF && + git upload-pack "$TRASH_DIRECTORY" + EOF + GIT_SSH="$TRASH_DIRECTORY/ssh-wrapper" && + GIT_SSH_VARIANT=ssh && + export GIT_SSH && + export GIT_SSH_VARIANT && + export TRASH_DIRECTORY +' + +# make sure that cloning $1 results in local directory $2 +test_clone_dir () { + url=$1; shift + dir=$1; shift + expect=success + bare=non-bare + clone_opts= + for i in "$@" + do + case "$i" in + fail) + expect=failure + ;; + bare) + bare=bare + clone_opts=--bare + ;; + esac + done + test_expect_$expect "clone of $url goes to $dir ($bare)" " + rm -rf $dir && + git clone $clone_opts $url && + test_path_is_dir $dir + " +} + +# basic syntax with bare and non-bare variants +test_clone_dir host:foo foo +test_clone_dir host:foo foo.git bare +test_clone_dir host:foo.git foo +test_clone_dir host:foo.git foo.git bare +test_clone_dir host:foo/.git foo +test_clone_dir host:foo/.git foo.git bare + +# similar, but using ssh URL rather than host:path syntax +test_clone_dir ssh://host/foo foo +test_clone_dir ssh://host/foo foo.git bare +test_clone_dir ssh://host/foo.git foo +test_clone_dir ssh://host/foo.git foo.git bare +test_clone_dir ssh://host/foo/.git foo +test_clone_dir ssh://host/foo/.git foo.git bare + +# we should remove trailing slashes and .git suffixes +test_clone_dir ssh://host/foo/ foo +test_clone_dir ssh://host/foo/// foo +test_clone_dir ssh://host/foo/.git/ foo +test_clone_dir ssh://host/foo.git/ foo +test_clone_dir ssh://host/foo.git/// foo +test_clone_dir ssh://host/foo///.git/ foo +test_clone_dir ssh://host/foo/.git/// foo + +test_clone_dir host:foo/ foo +test_clone_dir host:foo/// foo +test_clone_dir host:foo.git/ foo +test_clone_dir host:foo/.git/ foo +test_clone_dir host:foo.git/// foo +test_clone_dir host:foo///.git/ foo +test_clone_dir host:foo/.git/// foo + +# omitting the path should default to the hostname +test_clone_dir ssh://host/ host +test_clone_dir ssh://host:1234/ host +test_clone_dir ssh://user@host/ host +test_clone_dir host:/ host + +# auth materials should be redacted +test_clone_dir ssh://user:password@host/ host +test_clone_dir ssh://user:password@host:1234/ host +test_clone_dir ssh://user:passw@rd@host:1234/ host +test_clone_dir user@host:/ host +test_clone_dir user:password@host:/ host +test_clone_dir user:passw@rd@host:/ host + +# auth-like material should not be dropped +test_clone_dir ssh://host/foo@bar foo@bar +test_clone_dir ssh://host/foo@bar.git foo@bar +test_clone_dir ssh://user:password@host/foo@bar foo@bar +test_clone_dir ssh://user:passw@rd@host/foo@bar.git foo@bar + +test_clone_dir host:/foo@bar foo@bar +test_clone_dir host:/foo@bar.git foo@bar +test_clone_dir user:password@host:/foo@bar foo@bar +test_clone_dir user:passw@rd@host:/foo@bar.git foo@bar + +# trailing port-like numbers should not be stripped for paths +test_clone_dir ssh://user:password@host/test:1234 1234 +test_clone_dir ssh://user:password@host/test:1234.git 1234 + +test_done diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh new file mode 100755 index 0000000000..4320082b1b --- /dev/null +++ b/t/t5604-clone-reference.sh @@ -0,0 +1,224 @@ +#!/bin/sh +# +# Copyright (C) 2006 Martin Waitz <tali@admingilde.org> +# + +test_description='test clone --reference' +. ./test-lib.sh + +base_dir=$(pwd) + +U=$base_dir/UPLOAD_LOG + +# create a commit in repo $1 with name $2 +commit_in () { + ( + cd "$1" && + echo "$2" >"$2" && + git add "$2" && + git commit -m "$2" + ) +} + +# check that there are $2 loose objects in repo $1 +test_objcount () { + echo "$2" >expect && + git -C "$1" count-objects >actual.raw && + cut -d' ' -f1 <actual.raw >actual && + test_cmp expect actual +} + +test_expect_success 'preparing first repository' ' + test_create_repo A && + commit_in A file1 +' + +test_expect_success 'preparing second repository' ' + git clone A B && + commit_in B file2 && + git -C B repack -ad && + git -C B prune +' + +test_expect_success 'cloning with reference (-l -s)' ' + git clone -l -s --reference B A C +' + +test_expect_success 'existence of info/alternates' ' + test_line_count = 2 C/.git/objects/info/alternates +' + +test_expect_success 'pulling from reference' ' + git -C C pull ../B master +' + +test_expect_success 'that reference gets used' ' + test_objcount C 0 +' + +test_expect_success 'cloning with reference (no -l -s)' ' + GIT_TRACE_PACKET=$U.D git clone --reference B "file://$(pwd)/A" D +' + +test_expect_success 'fetched no objects' ' + test -s "$U.D" && + ! grep " want" "$U.D" +' + +test_expect_success 'existence of info/alternates' ' + test_line_count = 1 D/.git/objects/info/alternates +' + +test_expect_success 'pulling from reference' ' + git -C D pull ../B master +' + +test_expect_success 'that reference gets used' ' + test_objcount D 0 +' + +test_expect_success 'updating origin' ' + commit_in A file3 && + git -C A repack -ad && + git -C A prune +' + +test_expect_success 'pulling changes from origin' ' + git -C C pull origin +' + +# the 2 local objects are commit and tree from the merge +test_expect_success 'that alternate to origin gets used' ' + test_objcount C 2 +' + +test_expect_success 'pulling changes from origin' ' + git -C D pull origin +' + +# the 5 local objects are expected; file3 blob, commit in A to add it +# and its tree, and 2 are our tree and the merge commit. +test_expect_success 'check objects expected to exist locally' ' + test_objcount D 5 +' + +test_expect_success 'preparing alternate repository #1' ' + test_create_repo F && + commit_in F file1 +' + +test_expect_success 'cloning alternate repo #2 and adding changes to repo #1' ' + git clone F G && + commit_in F file2 +' + +test_expect_success 'cloning alternate repo #1, using #2 as reference' ' + git clone --reference G F H +' + +test_expect_success 'cloning with reference being subset of source (-l -s)' ' + git clone -l -s --reference A B E +' + +test_expect_success 'cloning with multiple references drops duplicates' ' + git clone -s --reference B --reference A --reference B A dups && + test_line_count = 2 dups/.git/objects/info/alternates +' + +test_expect_success 'clone with reference from a tagged repository' ' + ( + cd A && git tag -a -m tagged HEAD + ) && + git clone --reference=A A I +' + +test_expect_success 'prepare branched repository' ' + git clone A J && + ( + cd J && + git checkout -b other master^ && + echo other >otherfile && + git add otherfile && + git commit -m other && + git checkout master + ) +' + +test_expect_success 'fetch with incomplete alternates' ' + git init K && + echo "$base_dir/A/.git/objects" >K/.git/objects/info/alternates && + ( + cd K && + git remote add J "file://$base_dir/J" && + GIT_TRACE_PACKET=$U.K git fetch J + ) && + master_object=$(cd A && git for-each-ref --format="%(objectname)" refs/heads/master) && + test -s "$U.K" && + ! grep " want $master_object" "$U.K" && + tag_object=$(cd A && git for-each-ref --format="%(objectname)" refs/tags/HEAD) && + ! grep " want $tag_object" "$U.K" +' + +test_expect_success 'clone using repo with gitfile as a reference' ' + git clone --separate-git-dir=L A M && + git clone --reference=M A N && + echo "$base_dir/L/objects" >expected && + test_cmp expected "$base_dir/N/.git/objects/info/alternates" +' + +test_expect_success 'clone using repo pointed at by gitfile as reference' ' + git clone --reference=M/.git A O && + echo "$base_dir/L/objects" >expected && + test_cmp expected "$base_dir/O/.git/objects/info/alternates" +' + +test_expect_success 'clone and dissociate from reference' ' + git init P && + ( + cd P && test_commit one + ) && + git clone P Q && + ( + cd Q && test_commit two + ) && + git clone --no-local --reference=P Q R && + git clone --no-local --reference=P --dissociate Q S && + # removing the reference P would corrupt R but not S + rm -fr P && + test_must_fail git -C R fsck && + git -C S fsck +' +test_expect_success 'clone, dissociate from partial reference and repack' ' + rm -fr P Q R && + git init P && + ( + cd P && + test_commit one && + git repack && + test_commit two && + git repack + ) && + git clone --bare P Q && + ( + cd P && + git checkout -b second && + test_commit three && + git repack + ) && + git clone --bare --dissociate --reference=P Q R && + ls R/objects/pack/*.pack >packs.txt && + test_line_count = 1 packs.txt +' + +test_expect_success 'clone, dissociate from alternates' ' + rm -fr A B C && + test_create_repo A && + commit_in A file1 && + git clone --reference=A A B && + test_line_count = 1 B/.git/objects/info/alternates && + git clone --local --dissociate B C && + ! test -f C/.git/objects/info/alternates && + ( cd C && git fsck ) +' + +test_done diff --git a/t/t5701-clone-local.sh b/t/t5605-clone-local.sh index 3c087e907c..3c087e907c 100755 --- a/t/t5701-clone-local.sh +++ b/t/t5605-clone-local.sh diff --git a/t/t5702-clone-options.sh b/t/t5606-clone-options.sh index 9e24ec88e6..9e24ec88e6 100755 --- a/t/t5702-clone-options.sh +++ b/t/t5606-clone-options.sh diff --git a/t/t5704-bundle.sh b/t/t5607-clone-bundle.sh index 348d9b3bc7..348d9b3bc7 100755 --- a/t/t5704-bundle.sh +++ b/t/t5607-clone-bundle.sh diff --git a/t/t5705-clone-2gb.sh b/t/t5608-clone-2gb.sh index 191d6d3a78..df822d9a3e 100755 --- a/t/t5705-clone-2gb.sh +++ b/t/t5608-clone-2gb.sh @@ -21,7 +21,7 @@ test_expect_success CLONE_2GB 'setup' ' do printf "Generating blob $i/$blobcount\r" >&2 && printf "blob\nmark :$i\ndata $blobsize\n" && - #test-genrandom $i $blobsize && + #test-tool genrandom $i $blobsize && printf "%-${blobsize}s" $i && echo "M 100644 :$i $i" >> commit i=$(($i+1)) || diff --git a/t/t5706-clone-branch.sh b/t/t5609-clone-branch.sh index 6e7a7be052..6e7a7be052 100755 --- a/t/t5706-clone-branch.sh +++ b/t/t5609-clone-branch.sh diff --git a/t/t5707-clone-detached.sh b/t/t5610-clone-detached.sh index 8b0d607df1..8b0d607df1 100755 --- a/t/t5707-clone-detached.sh +++ b/t/t5610-clone-detached.sh diff --git a/t/t5708-clone-config.sh b/t/t5611-clone-config.sh index 27d730c0a7..39329eb7a8 100755 --- a/t/t5708-clone-config.sh +++ b/t/t5611-clone-config.sh @@ -19,6 +19,14 @@ test_expect_success 'clone -c can set multi-keys' ' test_cmp expect actual ' +test_expect_success 'clone -c can set multi-keys, including some empty' ' + rm -rf child && + git clone -c credential.helper= -c credential.helper=hi . child && + printf "%s\n" "" hi >expect && + git --git-dir=child/.git config --get-all credential.helper >actual && + test_cmp expect actual +' + test_expect_success 'clone -c without a value is boolean true' ' rm -rf child && git clone -c core.foo . child && @@ -37,4 +45,24 @@ test_expect_success 'clone -c config is available during clone' ' test_cmp expect child/file ' +# Tests for the hidden file attribute on windows +is_hidden () { + # Use the output of `attrib`, ignore the absolute path + case "$(attrib "$1")" in *H*?:*) return 0;; esac + return 1 +} + +test_expect_success MINGW 'clone -c core.hideDotFiles' ' + test_commit attributes .gitattributes "" && + rm -rf child && + git clone -c core.hideDotFiles=false . child && + ! is_hidden child/.gitattributes && + rm -rf child && + git clone -c core.hideDotFiles=dotGitOnly . child && + ! is_hidden child/.gitattributes && + rm -rf child && + git clone -c core.hideDotFiles=true . child && + is_hidden child/.gitattributes +' + test_done diff --git a/t/t5709-clone-refspec.sh b/t/t5612-clone-refspec.sh index 7ace2535c8..fac5a73851 100755 --- a/t/t5709-clone-refspec.sh +++ b/t/t5612-clone-refspec.sh @@ -17,13 +17,20 @@ test_expect_success 'setup' ' echo four >file && git commit -a -m four && git checkout master && + git tag five && # default clone git clone . dir_all && + # default clone --no-tags + git clone --no-tags . dir_all_no_tags && + # default --single that follows HEAD=master git clone --single-branch . dir_master && + # default --single that follows HEAD=master with no tags + git clone --single-branch --no-tags . dir_master_no_tags && + # default --single that follows HEAD=side git checkout side && git clone --single-branch . dir_side && @@ -45,6 +52,9 @@ test_expect_success 'setup' ' # explicit --single with tag git clone --single-branch --branch two . dir_tag && + # explicit --single with tag and --no-tags + git clone --single-branch --no-tags --branch two . dir_tag_no_tags && + # advance both "master" and "side" branches git checkout side && echo five >file && @@ -59,7 +69,8 @@ test_expect_success 'setup' ' test_expect_success 'by default all branches will be kept updated' ' ( - cd dir_all && git fetch && + cd dir_all && + git fetch && git for-each-ref refs/remotes/origin | sed -e "/HEAD$/d" \ -e "s|/remotes/origin/|/heads/|" >../actual @@ -71,28 +82,82 @@ test_expect_success 'by default all branches will be kept updated' ' test_expect_success 'by default no tags will be kept updated' ' ( - cd dir_all && git fetch && + cd dir_all && + git fetch && git for-each-ref refs/tags >../actual ) && git for-each-ref refs/tags >expect && - test_must_fail test_cmp expect actual + test_must_fail test_cmp expect actual && + test_line_count = 2 actual +' + +test_expect_success 'clone with --no-tags' ' + ( + cd dir_all_no_tags && + git fetch && + git for-each-ref refs/tags >../actual + ) && + >expect && + test_cmp expect actual ' test_expect_success '--single-branch while HEAD pointing at master' ' ( - cd dir_master && git fetch && + cd dir_master && + git fetch && git for-each-ref refs/remotes/origin | sed -e "/HEAD$/d" \ -e "s|/remotes/origin/|/heads/|" >../actual ) && # only follow master git for-each-ref refs/heads/master >expect && - test_cmp expect actual + # get & check latest tags + test_cmp expect actual && + ( + cd dir_master && + git fetch --tags && + git for-each-ref refs/tags >../actual + ) && + git for-each-ref refs/tags >expect && + test_cmp expect actual && + test_line_count = 2 actual +' + +test_expect_success '--single-branch while HEAD pointing at master and --no-tags' ' + ( + cd dir_master_no_tags && + git fetch && + git for-each-ref refs/remotes/origin | + sed -e "/HEAD$/d" \ + -e "s|/remotes/origin/|/heads/|" >../actual + ) && + # only follow master + git for-each-ref refs/heads/master >expect && + test_cmp expect actual && + # get tags (noop) + ( + cd dir_master_no_tags && + git fetch && + git for-each-ref refs/tags >../actual + ) && + >expect && + test_cmp expect actual && + test_line_count = 0 actual && + # get tags with --tags overrides tagOpt + ( + cd dir_master_no_tags && + git fetch --tags && + git for-each-ref refs/tags >../actual + ) && + git for-each-ref refs/tags >expect && + test_cmp expect actual && + test_line_count = 2 actual ' test_expect_success '--single-branch while HEAD pointing at side' ' ( - cd dir_side && git fetch && + cd dir_side && + git fetch && git for-each-ref refs/remotes/origin | sed -e "/HEAD$/d" \ -e "s|/remotes/origin/|/heads/|" >../actual @@ -104,7 +169,8 @@ test_expect_success '--single-branch while HEAD pointing at side' ' test_expect_success '--single-branch with explicit --branch side' ' ( - cd dir_side2 && git fetch && + cd dir_side2 && + git fetch && git for-each-ref refs/remotes/origin | sed -e "/HEAD$/d" \ -e "s|/remotes/origin/|/heads/|" >../actual @@ -116,16 +182,29 @@ test_expect_success '--single-branch with explicit --branch side' ' test_expect_success '--single-branch with explicit --branch with tag fetches updated tag' ' ( - cd dir_tag && git fetch && + cd dir_tag && + git fetch && git for-each-ref refs/tags >../actual ) && git for-each-ref refs/tags >expect && test_cmp expect actual ' +test_expect_success '--single-branch with explicit --branch with tag fetches updated tag despite --no-tags' ' + ( + cd dir_tag_no_tags && + git fetch && + git for-each-ref refs/tags >../actual + ) && + git for-each-ref refs/tags/two >expect && + test_cmp expect actual && + test_line_count = 1 actual +' + test_expect_success '--single-branch with --mirror' ' ( - cd dir_mirror && git fetch && + cd dir_mirror && + git fetch && git for-each-ref refs > ../actual ) && git for-each-ref refs >expect && @@ -134,7 +213,8 @@ test_expect_success '--single-branch with --mirror' ' test_expect_success '--single-branch with explicit --branch and --mirror' ' ( - cd dir_mirror_side && git fetch && + cd dir_mirror_side && + git fetch && git for-each-ref refs > ../actual ) && git for-each-ref refs >expect && @@ -143,7 +223,8 @@ test_expect_success '--single-branch with explicit --branch and --mirror' ' test_expect_success '--single-branch with detached' ' ( - cd dir_detached && git fetch && + cd dir_detached && + git fetch && git for-each-ref refs/remotes/origin | sed -e "/HEAD$/d" \ -e "s|/remotes/origin/|/heads/|" >../actual diff --git a/t/t5613-info-alternate.sh b/t/t5613-info-alternate.sh new file mode 100755 index 0000000000..895f46bb91 --- /dev/null +++ b/t/t5613-info-alternate.sh @@ -0,0 +1,139 @@ +#!/bin/sh +# +# Copyright (C) 2006 Martin Waitz <tali@admingilde.org> +# + +test_description='test transitive info/alternate entries' +. ./test-lib.sh + +test_expect_success 'preparing first repository' ' + test_create_repo A && ( + cd A && + echo "Hello World" > file1 && + git add file1 && + git commit -m "Initial commit" file1 && + git repack -a -d && + git prune + ) +' + +test_expect_success 'preparing second repository' ' + git clone -l -s A B && ( + cd B && + echo "foo bar" > file2 && + git add file2 && + git commit -m "next commit" file2 && + git repack -a -d -l && + git prune + ) +' + +test_expect_success 'preparing third repository' ' + git clone -l -s B C && ( + cd C && + echo "Goodbye, cruel world" > file3 && + git add file3 && + git commit -m "one more" file3 && + git repack -a -d -l && + git prune + ) +' + +test_expect_success 'count-objects shows the alternates' ' + cat >expect <<-EOF && + alternate: $(pwd)/B/.git/objects + alternate: $(pwd)/A/.git/objects + EOF + git -C C count-objects -v >actual && + grep ^alternate: actual >actual.alternates && + test_cmp expect actual.alternates +' + +# Note: These tests depend on the hard-coded value of 5 as the maximum depth +# we will follow recursion. We start the depth at 0 and count links, not +# repositories. This means that in a chain like: +# +# A --> B --> C --> D --> E --> F --> G --> H +# 0 1 2 3 4 5 6 +# +# we are OK at "G", but break at "H", even though "H" is actually the 8th +# repository, not the 6th, which you might expect. Counting the links allows +# N+1 repositories, and counting from 0 to 5 inclusive allows 6 links. +# +# Note also that we must use "--bare -l" to make the link to H. The "-l" +# ensures we do not do a connectivity check, and the "--bare" makes sure +# we do not try to checkout the result (which needs objects), either of +# which would cause the clone to fail. +test_expect_success 'creating too deep nesting' ' + git clone -l -s C D && + git clone -l -s D E && + git clone -l -s E F && + git clone -l -s F G && + git clone --bare -l -s G H +' + +test_expect_success 'validity of seventh repository' ' + git -C G fsck +' + +test_expect_success 'invalidity of eighth repository' ' + test_must_fail git -C H fsck +' + +test_expect_success 'breaking of loops' ' + echo "$(pwd)"/B/.git/objects >>A/.git/objects/info/alternates && + git -C C fsck +' + +test_expect_success 'that info/alternates is necessary' ' + rm -f C/.git/objects/info/alternates && + test_must_fail git -C C fsck +' + +test_expect_success 'that relative alternate is possible for current dir' ' + echo "../../../B/.git/objects" >C/.git/objects/info/alternates && + git fsck +' + +test_expect_success 'that relative alternate is recursive' ' + git -C D fsck +' + +# we can reach "A" from our new repo both directly, and via "C". +# The deep/subdir is there to make sure we are not doing a stupid +# pure-text comparison of the alternate names. +test_expect_success 'relative duplicates are eliminated' ' + mkdir -p deep/subdir && + git init --bare deep/subdir/duplicate.git && + cat >deep/subdir/duplicate.git/objects/info/alternates <<-\EOF && + ../../../../C/.git/objects + ../../../../A/.git/objects + EOF + cat >expect <<-EOF && + alternate: $(pwd)/C/.git/objects + alternate: $(pwd)/B/.git/objects + alternate: $(pwd)/A/.git/objects + EOF + git -C deep/subdir/duplicate.git count-objects -v >actual && + grep ^alternate: actual >actual.alternates && + test_cmp expect actual.alternates +' + +test_expect_success CASE_INSENSITIVE_FS 'dup finding can be case-insensitive' ' + git init --bare insensitive.git && + # the previous entry for "A" will have used uppercase + cat >insensitive.git/objects/info/alternates <<-\EOF && + ../../C/.git/objects + ../../a/.git/objects + EOF + cat >expect <<-EOF && + alternate: $(pwd)/C/.git/objects + alternate: $(pwd)/B/.git/objects + alternate: $(pwd)/A/.git/objects + EOF + git -C insensitive.git count-objects -v >actual && + grep ^alternate: actual >actual.alternates && + test_cmp expect actual.alternates +' + +test_done diff --git a/t/t5614-clone-submodules-shallow.sh b/t/t5614-clone-submodules-shallow.sh new file mode 100755 index 0000000000..e4e6ea4d52 --- /dev/null +++ b/t/t5614-clone-submodules-shallow.sh @@ -0,0 +1,122 @@ +#!/bin/sh + +test_description='Test shallow cloning of repos with submodules' + +. ./test-lib.sh + +pwd=$(pwd) + +test_expect_success 'setup' ' + git checkout -b master && + test_commit commit1 && + test_commit commit2 && + mkdir sub && + ( + cd sub && + git init && + test_commit subcommit1 && + test_commit subcommit2 && + test_commit subcommit3 + ) && + git submodule add "file://$pwd/sub" sub && + git commit -m "add submodule" +' + +test_expect_success 'nonshallow clone implies nonshallow submodule' ' + test_when_finished "rm -rf super_clone" && + git clone --recurse-submodules "file://$pwd/." super_clone && + git -C super_clone log --oneline >lines && + test_line_count = 3 lines && + git -C super_clone/sub log --oneline >lines && + test_line_count = 3 lines +' + +test_expect_success 'shallow clone with shallow submodule' ' + test_when_finished "rm -rf super_clone" && + git clone --recurse-submodules --depth 2 --shallow-submodules "file://$pwd/." super_clone && + git -C super_clone log --oneline >lines && + test_line_count = 2 lines && + git -C super_clone/sub log --oneline >lines && + test_line_count = 1 lines +' + +test_expect_success 'shallow clone does not imply shallow submodule' ' + test_when_finished "rm -rf super_clone" && + git clone --recurse-submodules --depth 2 "file://$pwd/." super_clone && + git -C super_clone log --oneline >lines && + test_line_count = 2 lines && + git -C super_clone/sub log --oneline >lines && + test_line_count = 3 lines +' + +test_expect_success 'shallow clone with non shallow submodule' ' + test_when_finished "rm -rf super_clone" && + git clone --recurse-submodules --depth 2 --no-shallow-submodules "file://$pwd/." super_clone && + git -C super_clone log --oneline >lines && + test_line_count = 2 lines && + git -C super_clone/sub log --oneline >lines && + test_line_count = 3 lines +' + +test_expect_success 'non shallow clone with shallow submodule' ' + test_when_finished "rm -rf super_clone" && + git clone --recurse-submodules --no-local --shallow-submodules "file://$pwd/." super_clone && + git -C super_clone log --oneline >lines && + test_line_count = 3 lines && + git -C super_clone/sub log --oneline >lines && + test_line_count = 1 lines +' + +test_expect_success 'clone follows shallow recommendation' ' + test_when_finished "rm -rf super_clone" && + git config -f .gitmodules submodule.sub.shallow true && + git add .gitmodules && + git commit -m "recommend shallow for sub" && + git clone --recurse-submodules --no-local "file://$pwd/." super_clone && + ( + cd super_clone && + git log --oneline >lines && + test_line_count = 4 lines + ) && + ( + cd super_clone/sub && + git log --oneline >lines && + test_line_count = 1 lines + ) +' + +test_expect_success 'get unshallow recommended shallow submodule' ' + test_when_finished "rm -rf super_clone" && + git clone --no-local "file://$pwd/." super_clone && + ( + cd super_clone && + git submodule update --init --no-recommend-shallow && + git log --oneline >lines && + test_line_count = 4 lines + ) && + ( + cd super_clone/sub && + git log --oneline >lines && + test_line_count = 3 lines + ) +' + +test_expect_success 'clone follows non shallow recommendation' ' + test_when_finished "rm -rf super_clone" && + git config -f .gitmodules submodule.sub.shallow false && + git add .gitmodules && + git commit -m "recommend non shallow for sub" && + git clone --recurse-submodules --no-local "file://$pwd/." super_clone && + ( + cd super_clone && + git log --oneline >lines && + test_line_count = 5 lines + ) && + ( + cd super_clone/sub && + git log --oneline >lines && + test_line_count = 3 lines + ) +' + +test_done diff --git a/t/t5615-alternate-env.sh b/t/t5615-alternate-env.sh new file mode 100755 index 0000000000..b4905b822c --- /dev/null +++ b/t/t5615-alternate-env.sh @@ -0,0 +1,90 @@ +#!/bin/sh + +test_description='handling of alternates in environment variables' +. ./test-lib.sh + +check_obj () { + alt=$1; shift + while read obj expect + do + echo "$obj" >&5 && + echo "$obj $expect" >&6 + done 5>input 6>expect && + GIT_ALTERNATE_OBJECT_DIRECTORIES=$alt \ + git "$@" cat-file --batch-check='%(objectname) %(objecttype)' \ + <input >actual && + test_cmp expect actual +} + +test_expect_success 'create alternate repositories' ' + git init --bare one.git && + one=$(echo one | git -C one.git hash-object -w --stdin) && + git init --bare two.git && + two=$(echo two | git -C two.git hash-object -w --stdin) +' + +test_expect_success 'objects inaccessible without alternates' ' + check_obj "" <<-EOF + $one missing + $two missing + EOF +' + +test_expect_success 'access alternate via absolute path' ' + check_obj "$PWD/one.git/objects" <<-EOF + $one blob + $two missing + EOF +' + +test_expect_success 'access multiple alternates' ' + check_obj "$PWD/one.git/objects:$PWD/two.git/objects" <<-EOF + $one blob + $two blob + EOF +' + +# bare paths are relative from $GIT_DIR +test_expect_success 'access alternate via relative path (bare)' ' + git init --bare bare.git && + check_obj "../one.git/objects" -C bare.git <<-EOF + $one blob + EOF +' + +# non-bare paths are relative to top of worktree +test_expect_success 'access alternate via relative path (worktree)' ' + git init worktree && + check_obj "../one.git/objects" -C worktree <<-EOF + $one blob + EOF +' + +# path is computed after moving to top-level of worktree +test_expect_success 'access alternate via relative path (subdir)' ' + mkdir subdir && + check_obj "one.git/objects" -C subdir <<-EOF + $one blob + EOF +' + +# set variables outside test to avoid quote insanity; the \057 is '/', +# which doesn't need quoting, but just confirms that de-quoting +# is working. +quoted='"one.git\057objects"' +unquoted='two.git/objects' +test_expect_success 'mix of quoted and unquoted alternates' ' + check_obj "$quoted:$unquoted" <<-EOF + $one blob + $two blob + EOF +' + +test_expect_success !MINGW 'broken quoting falls back to interpreting raw' ' + mv one.git \"one.git && + check_obj \"one.git/objects <<-EOF + $one blob + EOF +' + +test_done diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh new file mode 100755 index 0000000000..cee5565367 --- /dev/null +++ b/t/t5616-partial-clone.sh @@ -0,0 +1,157 @@ +#!/bin/sh + +test_description='git partial clone' + +. ./test-lib.sh + +# create a normal "src" repo where we can later create new commits. +# expect_1.oids will contain a list of the OIDs of all blobs. +test_expect_success 'setup normal src repo' ' + echo "{print \$1}" >print_1.awk && + echo "{print \$2}" >print_2.awk && + + git init src && + for n in 1 2 3 4 + do + echo "This is file: $n" > src/file.$n.txt + git -C src add file.$n.txt + git -C src commit -m "file $n" + git -C src ls-files -s file.$n.txt >>temp + done && + awk -f print_2.awk <temp | sort >expect_1.oids && + test_line_count = 4 expect_1.oids +' + +# bare clone "src" giving "srv.bare" for use as our server. +test_expect_success 'setup bare clone for server' ' + git clone --bare "file://$(pwd)/src" srv.bare && + git -C srv.bare config --local uploadpack.allowfilter 1 && + git -C srv.bare config --local uploadpack.allowanysha1inwant 1 +' + +# do basic partial clone from "srv.bare" +# confirm we are missing all of the known blobs. +# confirm partial clone was registered in the local config. +test_expect_success 'do partial clone 1' ' + git clone --no-checkout --filter=blob:none "file://$(pwd)/srv.bare" pc1 && + git -C pc1 rev-list HEAD --quiet --objects --missing=print \ + | awk -f print_1.awk \ + | sed "s/?//" \ + | sort >observed.oids && + test_cmp expect_1.oids observed.oids && + test "$(git -C pc1 config --local core.repositoryformatversion)" = "1" && + test "$(git -C pc1 config --local extensions.partialclone)" = "origin" && + test "$(git -C pc1 config --local core.partialclonefilter)" = "blob:none" +' + +# checkout master to force dynamic object fetch of blobs at HEAD. +test_expect_success 'verify checkout with dynamic object fetch' ' + git -C pc1 rev-list HEAD --quiet --objects --missing=print >observed && + test_line_count = 4 observed && + git -C pc1 checkout master && + git -C pc1 rev-list HEAD --quiet --objects --missing=print >observed && + test_line_count = 0 observed +' + +# create new commits in "src" repo to establish a blame history on file.1.txt +# and push to "srv.bare". +test_expect_success 'push new commits to server' ' + git -C src remote add srv "file://$(pwd)/srv.bare" && + for x in a b c d e + do + echo "Mod file.1.txt $x" >>src/file.1.txt + git -C src add file.1.txt + git -C src commit -m "mod $x" + done && + git -C src blame master -- file.1.txt >expect.blame && + git -C src push -u srv master +' + +# (partial) fetch in the partial clone repo from the promisor remote. +# verify that fetch inherited the filter-spec from the config and DOES NOT +# have the new blobs. +test_expect_success 'partial fetch inherits filter settings' ' + git -C pc1 fetch origin && + git -C pc1 rev-list master..origin/master --quiet --objects --missing=print >observed && + test_line_count = 5 observed +' + +# force dynamic object fetch using diff. +# we should only get 1 new blob (for the file in origin/master). +test_expect_success 'verify diff causes dynamic object fetch' ' + git -C pc1 diff master..origin/master -- file.1.txt && + git -C pc1 rev-list master..origin/master --quiet --objects --missing=print >observed && + test_line_count = 4 observed +' + +# force full dynamic object fetch of the file's history using blame. +# we should get the intermediate blobs for the file. +test_expect_success 'verify blame causes dynamic object fetch' ' + git -C pc1 blame origin/master -- file.1.txt >observed.blame && + test_cmp expect.blame observed.blame && + git -C pc1 rev-list master..origin/master --quiet --objects --missing=print >observed && + test_line_count = 0 observed +' + +# create new commits in "src" repo to establish a history on file.2.txt +# and push to "srv.bare". +test_expect_success 'push new commits to server for file.2.txt' ' + for x in a b c d e f + do + echo "Mod file.2.txt $x" >>src/file.2.txt + git -C src add file.2.txt + git -C src commit -m "mod $x" + done && + git -C src push -u srv master +' + +# Do FULL fetch by disabling inherited filter-spec using --no-filter. +# Verify we have all the new blobs. +test_expect_success 'override inherited filter-spec using --no-filter' ' + git -C pc1 fetch --no-filter origin && + git -C pc1 rev-list master..origin/master --quiet --objects --missing=print >observed && + test_line_count = 0 observed +' + +# create new commits in "src" repo to establish a history on file.3.txt +# and push to "srv.bare". +test_expect_success 'push new commits to server for file.3.txt' ' + for x in a b c d e f + do + echo "Mod file.3.txt $x" >>src/file.3.txt + git -C src add file.3.txt + git -C src commit -m "mod $x" + done && + git -C src push -u srv master +' + +# Do a partial fetch and then try to manually fetch the missing objects. +# This can be used as the basis of a pre-command hook to bulk fetch objects +# perhaps combined with a command in dry-run mode. +test_expect_success 'manual prefetch of missing objects' ' + git -C pc1 fetch --filter=blob:none origin && + git -C pc1 rev-list master..origin/master --quiet --objects --missing=print \ + | awk -f print_1.awk \ + | sed "s/?//" \ + | sort >observed.oids && + test_line_count = 6 observed.oids && + git -C pc1 fetch-pack --stdin "file://$(pwd)/srv.bare" <observed.oids && + git -C pc1 rev-list master..origin/master --quiet --objects --missing=print \ + | awk -f print_1.awk \ + | sed "s/?//" \ + | sort >observed.oids && + test_line_count = 0 observed.oids +' + +test_expect_success 'partial clone with transfer.fsckobjects=1 uses index-pack --fsck-objects' ' + git init src && + test_commit -C src x && + test_config -C src uploadpack.allowfilter 1 && + test_config -C src uploadpack.allowanysha1inwant 1 && + + GIT_TRACE="$(pwd)/trace" git -c transfer.fsckobjects=1 \ + clone --filter="blob:none" "file://$(pwd)/src" dst && + grep "git index-pack.*--fsck-objects" trace +' + +test_done diff --git a/t/t5700-clone-reference.sh b/t/t5700-clone-reference.sh deleted file mode 100755 index 3e783fc450..0000000000 --- a/t/t5700-clone-reference.sh +++ /dev/null @@ -1,218 +0,0 @@ -#!/bin/sh -# -# Copyright (C) 2006 Martin Waitz <tali@admingilde.org> -# - -test_description='test clone --reference' -. ./test-lib.sh - -base_dir=`pwd` - -U=$base_dir/UPLOAD_LOG - -test_expect_success 'preparing first repository' \ -'test_create_repo A && cd A && -echo first > file1 && -git add file1 && -git commit -m initial' - -cd "$base_dir" - -test_expect_success 'preparing second repository' \ -'git clone A B && cd B && -echo second > file2 && -git add file2 && -git commit -m addition && -git repack -a -d && -git prune' - -cd "$base_dir" - -test_expect_success 'cloning with reference (-l -s)' \ -'git clone -l -s --reference B A C' - -cd "$base_dir" - -test_expect_success 'existence of info/alternates' \ -'test_line_count = 2 C/.git/objects/info/alternates' - -cd "$base_dir" - -test_expect_success 'pulling from reference' \ -'cd C && -git pull ../B master' - -cd "$base_dir" - -test_expect_success 'that reference gets used' \ -'cd C && -echo "0 objects, 0 kilobytes" > expected && -git count-objects > current && -test_cmp expected current' - -cd "$base_dir" - -rm -f "$U.D" - -test_expect_success 'cloning with reference (no -l -s)' ' - GIT_TRACE_PACKET=$U.D git clone --reference B "file://$(pwd)/A" D -' - -test_expect_success 'fetched no objects' ' - test -s "$U.D" && - ! grep " want" "$U.D" -' - -cd "$base_dir" - -test_expect_success 'existence of info/alternates' \ -'test_line_count = 1 D/.git/objects/info/alternates' - -cd "$base_dir" - -test_expect_success 'pulling from reference' \ -'cd D && git pull ../B master' - -cd "$base_dir" - -test_expect_success 'that reference gets used' \ -'cd D && echo "0 objects, 0 kilobytes" > expected && -git count-objects > current && -test_cmp expected current' - -cd "$base_dir" - -test_expect_success 'updating origin' \ -'cd A && -echo third > file3 && -git add file3 && -git commit -m update && -git repack -a -d && -git prune' - -cd "$base_dir" - -test_expect_success 'pulling changes from origin' \ -'cd C && -git pull origin' - -cd "$base_dir" - -# the 2 local objects are commit and tree from the merge -test_expect_success 'that alternate to origin gets used' \ -'cd C && -echo "2 objects" > expected && -git count-objects | cut -d, -f1 > current && -test_cmp expected current' - -cd "$base_dir" - -test_expect_success 'pulling changes from origin' \ -'cd D && -git pull origin' - -cd "$base_dir" - -# the 5 local objects are expected; file3 blob, commit in A to add it -# and its tree, and 2 are our tree and the merge commit. -test_expect_success 'check objects expected to exist locally' \ -'cd D && -echo "5 objects" > expected && -git count-objects | cut -d, -f1 > current && -test_cmp expected current' - -cd "$base_dir" - -test_expect_success 'preparing alternate repository #1' \ -'test_create_repo F && cd F && -echo first > file1 && -git add file1 && -git commit -m initial' - -cd "$base_dir" - -test_expect_success 'cloning alternate repo #2 and adding changes to repo #1' \ -'git clone F G && cd F && -echo second > file2 && -git add file2 && -git commit -m addition' - -cd "$base_dir" - -test_expect_success 'cloning alternate repo #1, using #2 as reference' \ -'git clone --reference G F H' - -cd "$base_dir" - -test_expect_success 'cloning with reference being subset of source (-l -s)' \ -'git clone -l -s --reference A B E' - -cd "$base_dir" - -test_expect_success 'clone with reference from a tagged repository' ' - ( - cd A && git tag -a -m 'tagged' HEAD - ) && - git clone --reference=A A I -' - -test_expect_success 'prepare branched repository' ' - git clone A J && - ( - cd J && - git checkout -b other master^ && - echo other >otherfile && - git add otherfile && - git commit -m other && - git checkout master - ) -' - -rm -f "$U.K" - -test_expect_success 'fetch with incomplete alternates' ' - git init K && - echo "$base_dir/A/.git/objects" >K/.git/objects/info/alternates && - ( - cd K && - git remote add J "file://$base_dir/J" && - GIT_TRACE_PACKET=$U.K git fetch J - ) && - master_object=$(cd A && git for-each-ref --format="%(objectname)" refs/heads/master) && - test -s "$U.K" && - ! grep " want $master_object" "$U.K" && - tag_object=$(cd A && git for-each-ref --format="%(objectname)" refs/tags/HEAD) && - ! grep " want $tag_object" "$U.K" -' - -test_expect_success 'clone using repo with gitfile as a reference' ' - git clone --separate-git-dir=L A M && - git clone --reference=M A N && - echo "$base_dir/L/objects" >expected && - test_cmp expected "$base_dir/N/.git/objects/info/alternates" -' - -test_expect_success 'clone using repo pointed at by gitfile as reference' ' - git clone --reference=M/.git A O && - echo "$base_dir/L/objects" >expected && - test_cmp expected "$base_dir/O/.git/objects/info/alternates" -' - -test_expect_success 'clone and dissociate from reference' ' - git init P && - ( - cd P && test_commit one - ) && - git clone P Q && - ( - cd Q && test_commit two - ) && - git clone --no-local --reference=P Q R && - git clone --no-local --reference=P --dissociate Q S && - # removing the reference P would corrupt R but not S - rm -fr P && - test_must_fail git -C R fsck && - git -C S fsck -' - -test_done diff --git a/t/t5700-protocol-v1.sh b/t/t5700-protocol-v1.sh new file mode 100755 index 0000000000..ba86a44eb1 --- /dev/null +++ b/t/t5700-protocol-v1.sh @@ -0,0 +1,294 @@ +#!/bin/sh + +test_description='test git wire-protocol transition' + +TEST_NO_CREATE_REPO=1 + +. ./test-lib.sh + +# Test protocol v1 with 'git://' transport +# +. "$TEST_DIRECTORY"/lib-git-daemon.sh +start_git_daemon --export-all --enable=receive-pack +daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent + +test_expect_success 'create repo to be served by git-daemon' ' + git init "$daemon_parent" && + test_commit -C "$daemon_parent" one +' + +test_expect_success 'clone with git:// using protocol v1' ' + GIT_TRACE_PACKET=1 git -c protocol.version=1 \ + clone "$GIT_DAEMON_URL/parent" daemon_child 2>log && + + git -C daemon_child log -1 --format=%s >actual && + git -C "$daemon_parent" log -1 --format=%s >expect && + test_cmp expect actual && + + # Client requested to use protocol v1 + grep "clone> .*\\\0\\\0version=1\\\0$" log && + # Server responded using protocol v1 + grep "clone< version 1" log +' + +test_expect_success 'fetch with git:// using protocol v1' ' + test_commit -C "$daemon_parent" two && + + GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \ + fetch 2>log && + + git -C daemon_child log -1 --format=%s origin/master >actual && + git -C "$daemon_parent" log -1 --format=%s >expect && + test_cmp expect actual && + + # Client requested to use protocol v1 + grep "fetch> .*\\\0\\\0version=1\\\0$" log && + # Server responded using protocol v1 + grep "fetch< version 1" log +' + +test_expect_success 'pull with git:// using protocol v1' ' + GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \ + pull 2>log && + + git -C daemon_child log -1 --format=%s >actual && + git -C "$daemon_parent" log -1 --format=%s >expect && + test_cmp expect actual && + + # Client requested to use protocol v1 + grep "fetch> .*\\\0\\\0version=1\\\0$" log && + # Server responded using protocol v1 + grep "fetch< version 1" log +' + +test_expect_success 'push with git:// using protocol v1' ' + test_commit -C daemon_child three && + + # Push to another branch, as the target repository has the + # master branch checked out and we cannot push into it. + GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \ + push origin HEAD:client_branch 2>log && + + git -C daemon_child log -1 --format=%s >actual && + git -C "$daemon_parent" log -1 --format=%s client_branch >expect && + test_cmp expect actual && + + # Client requested to use protocol v1 + grep "push> .*\\\0\\\0version=1\\\0$" log && + # Server responded using protocol v1 + grep "push< version 1" log +' + +stop_git_daemon + +# Test protocol v1 with 'file://' transport +# +test_expect_success 'create repo to be served by file:// transport' ' + git init file_parent && + test_commit -C file_parent one +' + +test_expect_success 'clone with file:// using protocol v1' ' + GIT_TRACE_PACKET=1 git -c protocol.version=1 \ + clone "file://$(pwd)/file_parent" file_child 2>log && + + git -C file_child log -1 --format=%s >actual && + git -C file_parent log -1 --format=%s >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "clone< version 1" log +' + +test_expect_success 'fetch with file:// using protocol v1' ' + test_commit -C file_parent two && + + GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \ + fetch 2>log && + + git -C file_child log -1 --format=%s origin/master >actual && + git -C file_parent log -1 --format=%s >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "fetch< version 1" log +' + +test_expect_success 'pull with file:// using protocol v1' ' + GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \ + pull 2>log && + + git -C file_child log -1 --format=%s >actual && + git -C file_parent log -1 --format=%s >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "fetch< version 1" log +' + +test_expect_success 'push with file:// using protocol v1' ' + test_commit -C file_child three && + + # Push to another branch, as the target repository has the + # master branch checked out and we cannot push into it. + GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \ + push origin HEAD:client_branch 2>log && + + git -C file_child log -1 --format=%s >actual && + git -C file_parent log -1 --format=%s client_branch >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "push< version 1" log +' + +# Test protocol v1 with 'ssh://' transport +# +test_expect_success 'setup ssh wrapper' ' + GIT_SSH="$GIT_BUILD_DIR/t/helper/test-fake-ssh" && + export GIT_SSH && + GIT_SSH_VARIANT=ssh && + export GIT_SSH_VARIANT && + export TRASH_DIRECTORY && + >"$TRASH_DIRECTORY"/ssh-output +' + +expect_ssh () { + test_when_finished '(cd "$TRASH_DIRECTORY" && rm -f ssh-expect && >ssh-output)' && + echo "ssh: -o SendEnv=GIT_PROTOCOL myhost $1 '$PWD/ssh_parent'" >"$TRASH_DIRECTORY/ssh-expect" && + (cd "$TRASH_DIRECTORY" && test_cmp ssh-expect ssh-output) +} + +test_expect_success 'create repo to be served by ssh:// transport' ' + git init ssh_parent && + test_commit -C ssh_parent one +' + +test_expect_success 'clone with ssh:// using protocol v1' ' + GIT_TRACE_PACKET=1 git -c protocol.version=1 \ + clone "ssh://myhost:$(pwd)/ssh_parent" ssh_child 2>log && + expect_ssh git-upload-pack && + + git -C ssh_child log -1 --format=%s >actual && + git -C ssh_parent log -1 --format=%s >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "clone< version 1" log +' + +test_expect_success 'fetch with ssh:// using protocol v1' ' + test_commit -C ssh_parent two && + + GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \ + fetch 2>log && + expect_ssh git-upload-pack && + + git -C ssh_child log -1 --format=%s origin/master >actual && + git -C ssh_parent log -1 --format=%s >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "fetch< version 1" log +' + +test_expect_success 'pull with ssh:// using protocol v1' ' + GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \ + pull 2>log && + expect_ssh git-upload-pack && + + git -C ssh_child log -1 --format=%s >actual && + git -C ssh_parent log -1 --format=%s >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "fetch< version 1" log +' + +test_expect_success 'push with ssh:// using protocol v1' ' + test_commit -C ssh_child three && + + # Push to another branch, as the target repository has the + # master branch checked out and we cannot push into it. + GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \ + push origin HEAD:client_branch 2>log && + expect_ssh git-receive-pack && + + git -C ssh_child log -1 --format=%s >actual && + git -C ssh_parent log -1 --format=%s client_branch >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "push< version 1" log +' + +# Test protocol v1 with 'http://' transport +# +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +test_expect_success 'create repo to be served by http:// transport' ' + git init "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config http.receivepack true && + test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" one +' + +test_expect_success 'clone with http:// using protocol v1' ' + GIT_TRACE_PACKET=1 GIT_TRACE_CURL=1 git -c protocol.version=1 \ + clone "$HTTPD_URL/smart/http_parent" http_child 2>log && + + git -C http_child log -1 --format=%s >actual && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect && + test_cmp expect actual && + + # Client requested to use protocol v1 + grep "Git-Protocol: version=1" log && + # Server responded using protocol v1 + grep "git< version 1" log +' + +test_expect_success 'fetch with http:// using protocol v1' ' + test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" two && + + GIT_TRACE_PACKET=1 git -C http_child -c protocol.version=1 \ + fetch 2>log && + + git -C http_child log -1 --format=%s origin/master >actual && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "git< version 1" log +' + +test_expect_success 'pull with http:// using protocol v1' ' + GIT_TRACE_PACKET=1 git -C http_child -c protocol.version=1 \ + pull 2>log && + + git -C http_child log -1 --format=%s >actual && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "git< version 1" log +' + +test_expect_success 'push with http:// using protocol v1' ' + test_commit -C http_child three && + + # Push to another branch, as the target repository has the + # master branch checked out and we cannot push into it. + GIT_TRACE_PACKET=1 git -C http_child -c protocol.version=1 \ + push origin HEAD:client_branch && #2>log && + + git -C http_child log -1 --format=%s >actual && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s client_branch >expect && + test_cmp expect actual && + + # Server responded using protocol v1 + grep "git< version 1" log +' + +stop_httpd + +test_done diff --git a/t/t5710-info-alternate.sh b/t/t5710-info-alternate.sh deleted file mode 100755 index 5a6e49d18d..0000000000 --- a/t/t5710-info-alternate.sh +++ /dev/null @@ -1,112 +0,0 @@ -#!/bin/sh -# -# Copyright (C) 2006 Martin Waitz <tali@admingilde.org> -# - -test_description='test transitive info/alternate entries' -. ./test-lib.sh - -# test that a file is not reachable in the current repository -# but that it is after creating a info/alternate entry -reachable_via() { - alternate="$1" - file="$2" - if git cat-file -e "HEAD:$file"; then return 1; fi - echo "$alternate" >> .git/objects/info/alternate - git cat-file -e "HEAD:$file" -} - -test_valid_repo() { - git fsck --full > fsck.log && - test_line_count = 0 fsck.log -} - -base_dir=`pwd` - -test_expect_success 'preparing first repository' \ -'test_create_repo A && cd A && -echo "Hello World" > file1 && -git add file1 && -git commit -m "Initial commit" file1 && -git repack -a -d && -git prune' - -cd "$base_dir" - -test_expect_success 'preparing second repository' \ -'git clone -l -s A B && cd B && -echo "foo bar" > file2 && -git add file2 && -git commit -m "next commit" file2 && -git repack -a -d -l && -git prune' - -cd "$base_dir" - -test_expect_success 'preparing third repository' \ -'git clone -l -s B C && cd C && -echo "Goodbye, cruel world" > file3 && -git add file3 && -git commit -m "one more" file3 && -git repack -a -d -l && -git prune' - -cd "$base_dir" - -test_expect_success 'creating too deep nesting' \ -'git clone -l -s C D && -git clone -l -s D E && -git clone -l -s E F && -git clone -l -s F G && -git clone --bare -l -s G H' - -test_expect_success 'invalidity of deepest repository' \ -'cd H && { - test_valid_repo - test $? -ne 0 -}' - -cd "$base_dir" - -test_expect_success 'validity of third repository' \ -'cd C && -test_valid_repo' - -cd "$base_dir" - -test_expect_success 'validity of fourth repository' \ -'cd D && -test_valid_repo' - -cd "$base_dir" - -test_expect_success 'breaking of loops' \ -'echo "$base_dir"/B/.git/objects >> "$base_dir"/A/.git/objects/info/alternates&& -cd C && -test_valid_repo' - -cd "$base_dir" - -test_expect_success 'that info/alternates is necessary' \ -'cd C && -rm -f .git/objects/info/alternates && -! (test_valid_repo)' - -cd "$base_dir" - -test_expect_success 'that relative alternate is possible for current dir' \ -'cd C && -echo "../../../B/.git/objects" > .git/objects/info/alternates && -test_valid_repo' - -cd "$base_dir" - -test_expect_success \ - 'that relative alternate is only possible for current dir' ' - cd D && - ! (test_valid_repo) -' - -cd "$base_dir" - -test_done diff --git a/t/t5801-remote-helpers.sh b/t/t5801-remote-helpers.sh index c9d3ed14c3..362b1581e0 100755 --- a/t/t5801-remote-helpers.sh +++ b/t/t5801-remote-helpers.sh @@ -242,13 +242,6 @@ clean_mark () { sort >$(basename "$1") } -cmp_marks () { - test_when_finished "rm -rf git.marks testgit.marks" && - clean_mark ".git/testgit/$1/git.marks" && - clean_mark ".git/testgit/$1/testgit.marks" && - test_cmp git.marks testgit.marks -} - test_expect_success 'proper failure checks for fetching' ' (cd local && test_must_fail env GIT_REMOTE_TESTGIT_FAILURE=1 git fetch 2>error && @@ -258,12 +251,15 @@ test_expect_success 'proper failure checks for fetching' ' ' test_expect_success 'proper failure checks for pushing' ' + test_when_finished "rm -rf local/git.marks local/testgit.marks" && (cd local && git checkout -b crash master && echo crash >>file && git commit -a -m crash && test_must_fail env GIT_REMOTE_TESTGIT_FAILURE=1 git push --all && - cmp_marks origin + clean_mark ".git/testgit/origin/git.marks" && + clean_mark ".git/testgit/origin/testgit.marks" && + test_cmp git.marks testgit.marks ) ' diff --git a/t/t5802-connect-helper.sh b/t/t5802-connect-helper.sh index 878faf2b63..c6c2661878 100755 --- a/t/t5802-connect-helper.sh +++ b/t/t5802-connect-helper.sh @@ -4,6 +4,7 @@ test_description='ext::cmd remote "connect" helper' . ./test-lib.sh test_expect_success setup ' + git config --global protocol.ext.allow user && test_tick && git commit --allow-empty -m initial && test_tick && @@ -69,4 +70,32 @@ test_expect_success 'update backfilled tag without primary transfer' ' test_cmp expect actual ' + +test_expect_success 'set up fake git-daemon' ' + mkdir remote && + git init --bare remote/one.git && + mkdir remote/host && + git init --bare remote/host/two.git && + write_script fake-daemon <<-\EOF && + git daemon --inetd \ + --informative-errors \ + --export-all \ + --base-path="$TRASH_DIRECTORY/remote" \ + --interpolated-path="$TRASH_DIRECTORY/remote/%H%D" \ + "$TRASH_DIRECTORY/remote" + EOF + export TRASH_DIRECTORY && + PATH=$TRASH_DIRECTORY:$PATH +' + +test_expect_success 'ext command can connect to git daemon (no vhost)' ' + rm -rf dst && + git clone "ext::fake-daemon %G/one.git" dst +' + +test_expect_success 'ext command can connect to git daemon (vhost)' ' + rm -rf dst && + git clone "ext::fake-daemon %G/two.git %Vhost" dst +' + test_done diff --git a/t/t5810-proto-disable-local.sh b/t/t5810-proto-disable-local.sh new file mode 100755 index 0000000000..c1ef99b85c --- /dev/null +++ b/t/t5810-proto-disable-local.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +test_description='test disabling of local paths in clone/fetch' +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-proto-disable.sh" + +test_expect_success 'setup repository to clone' ' + test_commit one +' + +test_proto "file://" file "file://$PWD" +test_proto "path" file . + +test_expect_success 'setup repo with dash' ' + git init --bare repo.git && + git push repo.git HEAD && + mv repo.git "$PWD/-repo.git" +' + +# This will fail even without our rejection because upload-pack will +# complain about the bogus option. So let's make sure that GIT_TRACE +# doesn't show us even running upload-pack. +# +# We must also be sure to use "fetch" and not "clone" here, as the latter +# actually canonicalizes our input into an absolute path (which is fine +# to allow). +test_expect_success 'repo names starting with dash are rejected' ' + rm -f trace.out && + test_must_fail env GIT_TRACE="$PWD/trace.out" git fetch -- -repo.git && + ! grep upload-pack trace.out +' + +test_expect_success 'full paths still work' ' + git fetch "$PWD/-repo.git" +' + +test_done diff --git a/t/t5811-proto-disable-git.sh b/t/t5811-proto-disable-git.sh new file mode 100755 index 0000000000..8ac6b2a1d0 --- /dev/null +++ b/t/t5811-proto-disable-git.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +test_description='test disabling of git-over-tcp in clone/fetch' +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-proto-disable.sh" +. "$TEST_DIRECTORY/lib-git-daemon.sh" +start_git_daemon + +test_expect_success 'create git-accessible repo' ' + bare="$GIT_DAEMON_DOCUMENT_ROOT_PATH/repo.git" && + test_commit one && + git --bare init "$bare" && + git push "$bare" HEAD && + >"$bare/git-daemon-export-ok" && + git -C "$bare" config daemon.receivepack true +' + +test_proto "git://" git "$GIT_DAEMON_URL/repo.git" + +test_done diff --git a/t/t5812-proto-disable-http.sh b/t/t5812-proto-disable-http.sh new file mode 100755 index 0000000000..872788ac8c --- /dev/null +++ b/t/t5812-proto-disable-http.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +test_description='test disabling of git-over-http in clone/fetch' +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-proto-disable.sh" +. "$TEST_DIRECTORY/lib-httpd.sh" +start_httpd + +test_expect_success 'create git-accessible repo' ' + bare="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + test_commit one && + git --bare init "$bare" && + git push "$bare" HEAD && + git -C "$bare" config http.receivepack true +' + +test_proto "smart http" http "$HTTPD_URL/smart/repo.git" + +test_expect_success 'curl redirects respect whitelist' ' + test_must_fail env GIT_ALLOW_PROTOCOL=http:https \ + GIT_SMART_HTTP=0 \ + git clone "$HTTPD_URL/ftp-redir/repo.git" 2>stderr && + test_i18ngrep -E "(ftp.*disabled|your curl version is too old)" stderr +' + +test_expect_success 'curl limits redirects' ' + test_must_fail git clone "$HTTPD_URL/loop-redir/smart/repo.git" +' + +test_expect_success 'http can be limited to from-user' ' + git -c protocol.http.allow=user \ + clone "$HTTPD_URL/smart/repo.git" plain.git && + test_must_fail git -c protocol.http.allow=user \ + clone "$HTTPD_URL/smart-redir-perm/repo.git" redir.git +' + +stop_httpd +test_done diff --git a/t/t5813-proto-disable-ssh.sh b/t/t5813-proto-disable-ssh.sh new file mode 100755 index 0000000000..3f084ee306 --- /dev/null +++ b/t/t5813-proto-disable-ssh.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +test_description='test disabling of git-over-ssh in clone/fetch' +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-proto-disable.sh" + +setup_ssh_wrapper + +test_expect_success 'setup repository to clone' ' + test_commit one && + mkdir remote && + git init --bare remote/repo.git && + git push remote/repo.git HEAD +' + +test_proto "host:path" ssh "remote:repo.git" +test_proto "ssh://" ssh "ssh://remote$PWD/remote/repo.git" +test_proto "git+ssh://" ssh "git+ssh://remote$PWD/remote/repo.git" + +# Don't even bother setting up a "-remote" directory, as ssh would generally +# complain about the bogus option rather than completing our request. Our +# fake wrapper actually _can_ handle this case, but it's more robust to +# simply confirm from its output that it did not run at all. +test_expect_success 'hostnames starting with dash are rejected' ' + test_must_fail git clone ssh://-remote/repo.git dash-host 2>stderr && + ! grep ^ssh: stderr +' + +test_expect_success 'setup repo with dash' ' + git init --bare remote/-repo.git && + git push remote/-repo.git HEAD +' + +test_expect_success 'repo names starting with dash are rejected' ' + test_must_fail git clone remote:-repo.git dash-path 2>stderr && + ! grep ^ssh: stderr +' + +test_expect_success 'full paths still work' ' + git clone "remote:$PWD/remote/-repo.git" dash-path +' + +test_done diff --git a/t/t5814-proto-disable-ext.sh b/t/t5814-proto-disable-ext.sh new file mode 100755 index 0000000000..9d6f7dfa2c --- /dev/null +++ b/t/t5814-proto-disable-ext.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +test_description='test disabling of remote-helper paths in clone/fetch' +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-proto-disable.sh" + +setup_ext_wrapper + +test_expect_success 'setup repository to clone' ' + test_commit one && + mkdir remote && + git init --bare remote/repo.git && + git push remote/repo.git HEAD +' + +test_proto "remote-helper" ext "ext::fake-remote %S repo.git" + +test_done diff --git a/t/t5815-submodule-protos.sh b/t/t5815-submodule-protos.sh new file mode 100755 index 0000000000..06f55a1b8a --- /dev/null +++ b/t/t5815-submodule-protos.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +test_description='test protocol whitelisting with submodules' +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-proto-disable.sh + +setup_ext_wrapper +setup_ssh_wrapper + +test_expect_success 'setup repository with submodules' ' + mkdir remote && + git init remote/repo.git && + (cd remote/repo.git && test_commit one) && + # submodule-add should probably trust what we feed it on the cmdline, + # but its implementation is overly conservative. + GIT_ALLOW_PROTOCOL=ssh git submodule add remote:repo.git ssh-module && + GIT_ALLOW_PROTOCOL=ext git submodule add "ext::fake-remote %S repo.git" ext-module && + git commit -m "add submodules" +' + +test_expect_success 'clone with recurse-submodules fails' ' + test_must_fail git clone --recurse-submodules . dst +' + +test_expect_success 'setup individual updates' ' + rm -rf dst && + git clone . dst && + git -C dst submodule init +' + +test_expect_success 'update of ssh allowed' ' + git -C dst submodule update ssh-module +' + +test_expect_success 'update of ext not allowed' ' + test_must_fail git -C dst submodule update ext-module +' + +test_expect_success 'user can override whitelist' ' + GIT_ALLOW_PROTOCOL=ext git -C dst submodule update ext-module +' + +test_done diff --git a/t/t5900-repo-selection.sh b/t/t5900-repo-selection.sh index 3d5b418bb4..14e59c5b3e 100755 --- a/t/t5900-repo-selection.sh +++ b/t/t5900-repo-selection.sh @@ -15,7 +15,7 @@ make_tree() { make_bare() { git init --bare "$1" && (cd "$1" && - tree=`git hash-object -w -t tree /dev/null` && + tree=$(git hash-object -w -t tree /dev/null) && commit=$(echo "$1" | git commit-tree $tree) && git update-ref HEAD $commit ) diff --git a/t/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh index 3e752ce032..969e4e9e52 100755 --- a/t/t6000-rev-list-misc.sh +++ b/t/t6000-rev-list-misc.sh @@ -100,4 +100,18 @@ test_expect_success '--bisect and --first-parent can not be combined' ' test_must_fail git rev-list --bisect --first-parent HEAD ' +test_expect_success '--header shows a NUL after each commit' ' + # We know that there is no Q in the true payload; names and + # addresses of the authors and the committers do not have + # any, and object names or header names do not, either. + git rev-list --header --max-count=2 HEAD | + nul_to_q | + grep "^Q" >actual && + cat >expect <<-EOF && + Q$(git rev-parse HEAD~1) + Q + EOF + test_cmp expect actual +' + test_done diff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh index 8efcd13079..05ddc69cf2 100755 --- a/t/t6001-rev-list-graft.sh +++ b/t/t6001-rev-list-graft.sh @@ -10,15 +10,15 @@ test_expect_success setup ' echo >subdir/fileB fileB && git add fileA subdir/fileB && git commit -a -m "Initial in one history." && - A0=`git rev-parse --verify HEAD` && + A0=$(git rev-parse --verify HEAD) && echo >fileA fileA modified && git commit -a -m "Second in one history." && - A1=`git rev-parse --verify HEAD` && + A1=$(git rev-parse --verify HEAD) && echo >subdir/fileB fileB modified && git commit -a -m "Third in one history." && - A2=`git rev-parse --verify HEAD` && + A2=$(git rev-parse --verify HEAD) && rm -f .git/refs/heads/master .git/index && @@ -26,15 +26,15 @@ test_expect_success setup ' echo >subdir/fileB fileB again && git add fileA subdir/fileB && git commit -a -m "Initial in alternate history." && - B0=`git rev-parse --verify HEAD` && + B0=$(git rev-parse --verify HEAD) && echo >fileA fileA modified in alternate history && git commit -a -m "Second in alternate history." && - B1=`git rev-parse --verify HEAD` && + B1=$(git rev-parse --verify HEAD) && echo >subdir/fileB fileB modified in alternate history && git commit -a -m "Third in alternate history." && - B2=`git rev-parse --verify HEAD` && + B2=$(git rev-parse --verify HEAD) && : done ' diff --git a/t/t6002-rev-list-bisect.sh b/t/t6002-rev-list-bisect.sh index 43ad772484..a661408038 100755 --- a/t/t6002-rev-list-bisect.sh +++ b/t/t6002-rev-list-bisect.sh @@ -27,9 +27,9 @@ test_bisection_diff() # Test if bisection size is close to half of list size within # tolerance. # - _bisect_err=`expr $_list_size - $_bisection_size \* 2` - test "$_bisect_err" -lt 0 && _bisect_err=`expr 0 - $_bisect_err` - _bisect_err=`expr $_bisect_err / 2` ; # floor + _bisect_err=$(expr $_list_size - $_bisection_size \* 2) + test "$_bisect_err" -lt 0 && _bisect_err=$(expr 0 - $_bisect_err) + _bisect_err=$(expr $_bisect_err / 2) ; # floor test_expect_success \ "bisection diff $_bisect_option $_head $* <= $_max_diff" \ @@ -235,4 +235,32 @@ test_sequence "--bisect" # # + +test_expect_success 'set up fake --bisect refs' ' + git update-ref refs/bisect/bad c3 && + good=$(git rev-parse b1) && + git update-ref refs/bisect/good-$good $good && + good=$(git rev-parse c1) && + git update-ref refs/bisect/good-$good $good +' + +test_expect_success 'rev-list --bisect can default to good/bad refs' ' + # the only thing between c3 and c1 is c2 + git rev-parse c2 >expect && + git rev-list --bisect >actual && + test_cmp expect actual +' + +test_expect_success 'rev-parse --bisect can default to good/bad refs' ' + git rev-parse c3 ^b1 ^c1 >expect && + git rev-parse --bisect >actual && + + # output order depends on the refnames, which in turn depends on + # the exact sha1s. We just want to make sure we have the same set + # of lines in any order. + sort <expect >expect.sorted && + sort <actual >actual.sorted && + test_cmp expect.sorted actual.sorted +' + test_done diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index b77d4c97c1..98be78b4a2 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -59,10 +59,14 @@ test_format () { } # Feed to --format to provide predictable colored sequences. +BASIC_COLOR='%Credfoo%Creset' +COLOR='%C(red)foo%C(reset)' AUTO_COLOR='%C(auto,red)foo%C(auto,reset)' +ALWAYS_COLOR='%C(always,red)foo%C(always,reset)' has_color () { - printf '\033[31mfoo\033[m\n' >expect && - test_cmp expect "$1" + test_decode_color <"$1" >decoded && + echo "<RED>foo<RESET>" >expect && + test_cmp expect decoded } has_no_color () { @@ -170,57 +174,101 @@ $added EOF -test_format colors %Credfoo%Cgreenbar%Cbluebaz%Cresetxyzzy <<EOF -commit $head2 -[31mfoo[32mbar[34mbaz[mxyzzy -commit $head1 -[31mfoo[32mbar[34mbaz[mxyzzy -EOF - -test_format advanced-colors '%C(red yellow bold)foo%C(reset)' <<EOF -commit $head2 -[1;31;43mfoo[m -commit $head1 -[1;31;43mfoo[m -EOF - -test_expect_success '%C(auto) does not enable color by default' ' - git log --format=$AUTO_COLOR -1 >actual && - has_no_color actual +test_expect_success 'basic colors' ' + cat >expect <<-EOF && + commit $head2 + <RED>foo<GREEN>bar<BLUE>baz<RESET>xyzzy + EOF + format="%Credfoo%Cgreenbar%Cbluebaz%Cresetxyzzy" && + git rev-list --color --format="$format" -1 master >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expect actual ' -test_expect_success '%C(auto) enables colors for color.diff' ' - git -c color.diff=always log --format=$AUTO_COLOR -1 >actual && - has_color actual +test_expect_success 'advanced colors' ' + cat >expect <<-EOF && + commit $head2 + <BOLD;RED;BYELLOW>foo<RESET> + EOF + format="%C(red yellow bold)foo%C(reset)" && + git rev-list --color --format="$format" -1 master >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expect actual ' -test_expect_success '%C(auto) enables colors for color.ui' ' - git -c color.ui=always log --format=$AUTO_COLOR -1 >actual && +for spec in \ + "%Cred:$BASIC_COLOR" \ + "%C(...):$COLOR" \ + "%C(auto,...):$AUTO_COLOR" +do + desc=${spec%%:*} + color=${spec#*:} + test_expect_success "$desc does not enable color by default" ' + git log --format=$color -1 >actual && + has_no_color actual + ' + + test_expect_success "$desc enables colors for color.diff" ' + git -c color.diff=always log --format=$color -1 >actual && + has_color actual + ' + + test_expect_success "$desc enables colors for color.ui" ' + git -c color.ui=always log --format=$color -1 >actual && + has_color actual + ' + + test_expect_success "$desc respects --color" ' + git log --format=$color -1 --color >actual && + has_color actual + ' + + test_expect_success "$desc respects --no-color" ' + git -c color.ui=always log --format=$color -1 --no-color >actual && + has_no_color actual + ' + + test_expect_success TTY "$desc respects --color=auto (stdout is tty)" ' + test_terminal git log --format=$color -1 --color=auto >actual && + has_color actual + ' + + test_expect_success "$desc respects --color=auto (stdout not tty)" ' + ( + TERM=vt100 && export TERM && + git log --format=$color -1 --color=auto >actual && + has_no_color actual + ) + ' +done + +test_expect_success '%C(always,...) enables color even without tty' ' + git log --format=$ALWAYS_COLOR -1 >actual && has_color actual ' test_expect_success '%C(auto) respects --color' ' - git log --format=$AUTO_COLOR -1 --color >actual && - has_color actual + git log --color --format="%C(auto)%H" -1 >actual.raw && + test_decode_color <actual.raw >actual && + echo "<YELLOW>$(git rev-parse HEAD)<RESET>" >expect && + test_cmp expect actual ' test_expect_success '%C(auto) respects --no-color' ' - git -c color.ui=always log --format=$AUTO_COLOR -1 --no-color >actual && - has_no_color actual -' - -test_expect_success TTY '%C(auto) respects --color=auto (stdout is tty)' ' - test_terminal env TERM=vt100 \ - git log --format=$AUTO_COLOR -1 --color=auto >actual && - has_color actual + git log --no-color --format="%C(auto)%H" -1 >actual && + git rev-parse HEAD >expect && + test_cmp expect actual ' -test_expect_success '%C(auto) respects --color=auto (stdout not tty)' ' - ( - TERM=vt100 && export TERM && - git log --format=$AUTO_COLOR -1 --color=auto >actual && - has_no_color actual - ) +test_expect_success 'rev-list %C(auto,...) respects --color' ' + git rev-list --color --format="%C(auto,green)foo%C(auto,reset)" \ + -1 HEAD >actual.raw && + test_decode_color <actual.raw >actual && + cat >expect <<-EOF && + commit $(git rev-parse HEAD) + <GREEN>foo<RESET> + EOF + test_cmp expect actual ' iconv -f utf-8 -t $test_encoding > commit-msg <<EOF diff --git a/t/t6007-rev-list-cherry-pick-file.sh b/t/t6007-rev-list-cherry-pick-file.sh index 28d4f6b259..f0268372d2 100755 --- a/t/t6007-rev-list-cherry-pick-file.sh +++ b/t/t6007-rev-list-cherry-pick-file.sh @@ -57,7 +57,7 @@ test_expect_success '--left-right' ' git rev-list --left-right B...C > actual && git name-rev --stdin --name-only --refs="*tags/*" \ < actual > actual.named && - test_cmp actual.named expect + test_cmp expect actual.named ' test_expect_success '--count' ' @@ -77,14 +77,14 @@ test_expect_success '--cherry-pick bar does not come up empty' ' git rev-list --left-right --cherry-pick B...C -- bar > actual && git name-rev --stdin --name-only --refs="*tags/*" \ < actual > actual.named && - test_cmp actual.named expect + test_cmp expect actual.named ' test_expect_success 'bar does not come up empty' ' git rev-list --left-right B...C -- bar > actual && git name-rev --stdin --name-only --refs="*tags/*" \ < actual > actual.named && - test_cmp actual.named expect + test_cmp expect actual.named ' cat >expect <<EOF @@ -96,7 +96,45 @@ test_expect_success '--cherry-pick bar does not come up empty (II)' ' git rev-list --left-right --cherry-pick F...E -- bar > actual && git name-rev --stdin --name-only --refs="*tags/*" \ < actual > actual.named && - test_cmp actual.named expect + test_cmp expect actual.named +' + +test_expect_success 'name-rev multiple --refs combine inclusive' ' + git rev-list --left-right --cherry-pick F...E -- bar >actual && + git name-rev --stdin --name-only --refs="*tags/F" --refs="*tags/E" \ + <actual >actual.named && + test_cmp expect actual.named +' + +cat >expect <<EOF +<tags/F +EOF + +test_expect_success 'name-rev --refs excludes non-matched patterns' ' + git rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect && + git rev-list --left-right --cherry-pick F...E -- bar >actual && + git name-rev --stdin --name-only --refs="*tags/F" \ + <actual >actual.named && + test_cmp expect actual.named +' + +cat >expect <<EOF +<tags/F +EOF + +test_expect_success 'name-rev --exclude excludes matched patterns' ' + git rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect && + git rev-list --left-right --cherry-pick F...E -- bar >actual && + git name-rev --stdin --name-only --refs="*tags/*" --exclude="*E" \ + <actual >actual.named && + test_cmp expect actual.named +' + +test_expect_success 'name-rev --no-refs clears the refs list' ' + git rev-list --left-right --cherry-pick F...E -- bar >expect && + git name-rev --stdin --name-only --refs="*tags/F" --refs="*tags/E" --no-refs --refs="*tags/G" \ + <expect >actual && + test_cmp expect actual ' cat >expect <<EOF @@ -110,7 +148,7 @@ test_expect_success '--cherry-mark' ' git rev-list --cherry-mark F...E -- bar > actual && git name-rev --stdin --name-only --refs="*tags/*" \ < actual > actual.named && - test_cmp actual.named expect + test_cmp expect actual.named ' cat >expect <<EOF @@ -124,7 +162,7 @@ test_expect_success '--cherry-mark --left-right' ' git rev-list --cherry-mark --left-right F...E -- bar > actual && git name-rev --stdin --name-only --refs="*tags/*" \ < actual > actual.named && - test_cmp actual.named expect + test_cmp expect actual.named ' cat >expect <<EOF @@ -135,14 +173,14 @@ test_expect_success '--cherry-pick --right-only' ' git rev-list --cherry-pick --right-only F...E -- bar > actual && git name-rev --stdin --name-only --refs="*tags/*" \ < actual > actual.named && - test_cmp actual.named expect + test_cmp expect actual.named ' test_expect_success '--cherry-pick --left-only' ' git rev-list --cherry-pick --left-only E...F -- bar > actual && git name-rev --stdin --name-only --refs="*tags/*" \ < actual > actual.named && - test_cmp actual.named expect + test_cmp expect actual.named ' cat >expect <<EOF @@ -154,7 +192,7 @@ test_expect_success '--cherry' ' git rev-list --cherry F...E -- bar > actual && git name-rev --stdin --name-only --refs="*tags/*" \ < actual > actual.named && - test_cmp actual.named expect + test_cmp expect actual.named ' cat >expect <<EOF @@ -163,7 +201,7 @@ EOF test_expect_success '--cherry --count' ' git rev-list --cherry --count F...E -- bar > actual && - test_cmp actual expect + test_cmp expect actual ' cat >expect <<EOF @@ -172,7 +210,7 @@ EOF test_expect_success '--cherry-mark --count' ' git rev-list --cherry-mark --count F...E -- bar > actual && - test_cmp actual expect + test_cmp expect actual ' cat >expect <<EOF @@ -181,7 +219,7 @@ EOF test_expect_success '--cherry-mark --left-right --count' ' git rev-list --cherry-mark --left-right --count F...E -- bar > actual && - test_cmp actual expect + test_cmp expect actual ' test_expect_success '--cherry-pick with independent, but identical branches' ' @@ -207,4 +245,25 @@ test_expect_success '--count --left-right' ' test_cmp expect actual ' +# Corrupt the object store deliberately to make sure +# the object is not even checked for its existence. +remove_loose_object () { + sha1="$(git rev-parse "$1")" && + remainder=${sha1#??} && + firsttwo=${sha1%$remainder} && + rm .git/objects/$firsttwo/$remainder +} + +test_expect_success '--cherry-pick avoids looking at full diffs' ' + git checkout -b shy-diff && + test_commit dont-look-at-me && + echo Hello >dont-look-at-me.t && + test_tick && + git commit -m tip dont-look-at-me.t && + git checkout -b mainline HEAD^ && + test_commit to-cherry-pick && + remove_loose_object shy-diff^:dont-look-at-me.t && + git rev-list --cherry-pick ...shy-diff +' + test_done diff --git a/t/t6009-rev-list-parent.sh b/t/t6009-rev-list-parent.sh index 66cda17ef3..20e3e2554a 100755 --- a/t/t6009-rev-list-parent.sh +++ b/t/t6009-rev-list-parent.sh @@ -47,7 +47,9 @@ test_expect_success 'setup roots, merges and octopuses' ' git checkout -b yetanotherbranch four && test_commit eight && git checkout master && - test_merge normalmerge newroot && + test_tick && + git merge --allow-unrelated-histories -m normalmerge newroot && + git tag normalmerge && test_tick && git merge -m tripus sidebranch anotherbranch && git tag tripus && diff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh index 39b3238da2..31db7b5f91 100755 --- a/t/t6010-merge-base.sh +++ b/t/t6010-merge-base.sh @@ -215,11 +215,13 @@ test_expect_success 'criss-cross merge-base for octopus-step' ' git reset --hard E && test_commit CC2 && test_tick && - git merge -s ours CC1 && + # E is a root commit unrelated to MMR root on which CC1 is based + git merge -s ours --allow-unrelated-histories CC1 && test_commit CC-o && test_commit CCB && git reset --hard CC1 && - git merge -s ours CC2 && + # E is a root commit unrelated to MMR root on which CC1 is based + git merge -s ours --allow-unrelated-histories CC2 && test_commit CCA && git rev-parse CC1 CC2 >expected && @@ -258,6 +260,12 @@ test_expect_success 'using reflog to find the fork point' ' test_cmp expect3 actual ' +test_expect_success '--fork-point works with empty reflog' ' + git -c core.logallrefupdates=false branch no-reflog base && + git merge-base --fork-point no-reflog derived && + test_cmp expect3 actual +' + test_expect_success 'merge-base --octopus --all for complex tree' ' # Best common ancestor for JE, JAA and JDD is JC # JE diff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh index b89cd6b07a..2a0fbb87b1 100755 --- a/t/t6012-rev-list-simplify.sh +++ b/t/t6012-rev-list-simplify.sh @@ -71,7 +71,7 @@ test_expect_success setup ' note J && git checkout master && - test_tick && git merge -m "Coolest" unrelated && + test_tick && git merge --allow-unrelated-histories -m "Coolest" unrelated && note K && echo "Immaterial" >elif && diff --git a/t/t6013-rev-list-reverse-parents.sh b/t/t6013-rev-list-reverse-parents.sh index 59fc2f06e0..89458d370f 100755 --- a/t/t6013-rev-list-reverse-parents.sh +++ b/t/t6013-rev-list-reverse-parents.sh @@ -28,7 +28,7 @@ test_expect_success '--reverse --parents --full-history combines correctly' ' perl -e "print reverse <>" > expected && git rev-list --reverse --parents --full-history master -- foo \ > actual && - test_cmp actual expected + test_cmp expected actual ' test_expect_success '--boundary does too' ' @@ -36,7 +36,7 @@ test_expect_success '--boundary does too' ' perl -e "print reverse <>" > expected && git rev-list --boundary --reverse --parents --full-history \ master ^root -- foo > actual && - test_cmp actual expected + test_cmp expected actual ' test_done diff --git a/t/t6015-rev-list-show-all-parents.sh b/t/t6015-rev-list-show-all-parents.sh deleted file mode 100755 index 8b146fb432..0000000000 --- a/t/t6015-rev-list-show-all-parents.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/sh - -test_description='--show-all --parents does not rewrite TREESAME commits' - -. ./test-lib.sh - -test_expect_success 'set up --show-all --parents test' ' - test_commit one foo.txt && - commit1=`git rev-list -1 HEAD` && - test_commit two bar.txt && - commit2=`git rev-list -1 HEAD` && - test_commit three foo.txt && - commit3=`git rev-list -1 HEAD` - ' - -test_expect_success '--parents rewrites TREESAME parents correctly' ' - echo $commit3 $commit1 > expected && - echo $commit1 >> expected && - git rev-list --parents HEAD -- foo.txt > actual && - test_cmp expected actual - ' - -test_expect_success '--parents --show-all does not rewrites TREESAME parents' ' - echo $commit3 $commit2 > expected && - echo $commit2 $commit1 >> expected && - echo $commit1 >> expected && - git rev-list --parents --show-all HEAD -- foo.txt > actual && - test_cmp expected actual - ' - -test_done diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh index d00f7db868..d3453c583c 100755 --- a/t/t6018-rev-list-glob.sh +++ b/t/t6018-rev-list-glob.sh @@ -255,27 +255,19 @@ test_expect_success 'rev-list accumulates multiple --exclude' ' compare rev-list "--exclude=refs/remotes/* --exclude=refs/tags/* --all" --branches ' - -# "git rev-list<ENTER>" is likely to be a bug in the calling script and may -# deserve an error message, but do cases where set of refs programatically -# given using globbing and/or --stdin need to fail with the same error, or -# are we better off reporting a success with no output? The following few -# tests document the current behaviour to remind us that we might want to -# think about this issue. - -test_expect_failure 'rev-list may want to succeed with empty output on no input (1)' ' +test_expect_failure 'rev-list should succeed with empty output on empty stdin' ' >expect && git rev-list --stdin <expect >actual && test_cmp expect actual ' -test_expect_failure 'rev-list may want to succeed with empty output on no input (2)' ' +test_expect_success 'rev-list should succeed with empty output with all refs excluded' ' >expect && git rev-list --exclude=* --all >actual && test_cmp expect actual ' -test_expect_failure 'rev-list may want to succeed with empty output on no input (3)' ' +test_expect_success 'rev-list should succeed with empty output with empty --all' ' ( test_create_repo empty && cd empty && @@ -285,6 +277,12 @@ test_expect_failure 'rev-list may want to succeed with empty output on no input ) ' +test_expect_success 'rev-list should succeed with empty output with empty glob' ' + >expect && + git rev-list --glob=does-not-match-anything >actual && + test_cmp expect actual +' + test_expect_success 'shortlog accepts --glob/--tags/--remotes' ' compare shortlog "subspace/one subspace/two" --branches=subspace && diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh index 05ebba7afa..a1fad6980b 100755 --- a/t/t6022-merge-rename.sh +++ b/t/t6022-merge-rename.sh @@ -242,10 +242,12 @@ test_expect_success 'merge of identical changes in a renamed file' ' rm -f A M N && git reset --hard && git checkout change+rename && - GIT_MERGE_VERBOSITY=3 git merge change | test_i18ngrep "^Skipped B" && + GIT_MERGE_VERBOSITY=3 git merge change >out && + test_i18ngrep "^Skipped B" out && git reset --hard HEAD^ && git checkout change && - GIT_MERGE_VERBOSITY=3 git merge change+rename | test_i18ngrep "^Skipped B" + GIT_MERGE_VERBOSITY=3 git merge change+rename >out && + test_i18ngrep "^Skipped B" out ' test_expect_success 'setup for rename + d/f conflicts' ' @@ -633,10 +635,10 @@ test_expect_success 'setup avoid unnecessary update, normal rename' ' test_expect_success 'avoid unnecessary update, normal rename' ' git checkout -q avoid-unnecessary-update-1^0 && - test-chmtime =1000000000 rename && - test-chmtime -v +0 rename >expect && + test-tool chmtime =1000000000 rename && + test-tool chmtime -v +0 rename >expect && git merge merge-branch-1 && - test-chmtime -v +0 rename >actual && + test-tool chmtime -v +0 rename >actual && test_cmp expect actual # "rename" should have stayed intact ' @@ -666,10 +668,10 @@ test_expect_success 'setup to test avoiding unnecessary update, with D/F conflic test_expect_success 'avoid unnecessary update, with D/F conflict' ' git checkout -q avoid-unnecessary-update-2^0 && - test-chmtime =1000000000 df && - test-chmtime -v +0 df >expect && + test-tool chmtime =1000000000 df && + test-tool chmtime -v +0 df >expect && git merge merge-branch-2 && - test-chmtime -v +0 df >actual && + test-tool chmtime -v +0 df >actual && test_cmp expect actual # "df" should have stayed intact ' @@ -698,10 +700,10 @@ test_expect_success 'setup avoid unnecessary update, dir->(file,nothing)' ' test_expect_success 'avoid unnecessary update, dir->(file,nothing)' ' git checkout -q master^0 && - test-chmtime =1000000000 df && - test-chmtime -v +0 df >expect && + test-tool chmtime =1000000000 df && + test-tool chmtime -v +0 df >expect && git merge side && - test-chmtime -v +0 df >actual && + test-tool chmtime -v +0 df >actual && test_cmp expect actual # "df" should have stayed intact ' @@ -728,10 +730,10 @@ test_expect_success 'setup avoid unnecessary update, modify/delete' ' test_expect_success 'avoid unnecessary update, modify/delete' ' git checkout -q master^0 && - test-chmtime =1000000000 file && - test-chmtime -v +0 file >expect && + test-tool chmtime =1000000000 file && + test-tool chmtime -v +0 file >expect && test_must_fail git merge side && - test-chmtime -v +0 file >actual && + test-tool chmtime -v +0 file >actual && test_cmp expect actual # "file" should have stayed intact ' @@ -757,10 +759,10 @@ test_expect_success 'setup avoid unnecessary update, rename/add-dest' ' test_expect_success 'avoid unnecessary update, rename/add-dest' ' git checkout -q master^0 && - test-chmtime =1000000000 newfile && - test-chmtime -v +0 newfile >expect && + test-tool chmtime =1000000000 newfile && + test-tool chmtime -v +0 newfile >expect && git merge side && - test-chmtime -v +0 newfile >actual && + test-tool chmtime -v +0 newfile >actual && test_cmp expect actual # "file" should have stayed intact ' diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh index 190ee903cf..20aee43f95 100755 --- a/t/t6023-merge-file.sh +++ b/t/t6023-merge-file.sh @@ -346,4 +346,17 @@ test_expect_success 'conflict at EOF without LF resolved by --union' \ printf "line1\nline2\nline3x\nline3y" >expect.txt && test_cmp expect.txt output.txt' +test_expect_success 'conflict sections match existing line endings' ' + printf "1\\r\\n2\\r\\n3" >crlf-orig.txt && + printf "1\\r\\n2\\r\\n4" >crlf-diff1.txt && + printf "1\\r\\n2\\r\\n5" >crlf-diff2.txt && + test_must_fail git -c core.eol=crlf merge-file -p \ + crlf-diff1.txt crlf-orig.txt crlf-diff2.txt >crlf.txt && + test $(tr "\015" Q <crlf.txt | grep "^[<=>].*Q$" | wc -l) = 3 && + test $(tr "\015" Q <crlf.txt | grep "[345]Q$" | wc -l) = 3 && + test_must_fail git -c core.eol=crlf merge-file -p \ + nolf-diff1.txt nolf-orig.txt nolf-diff2.txt >nolf.txt && + test $(tr "\015" Q <nolf.txt | grep "^[<=>].*Q$" | wc -l) = 0 +' + test_done diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh index 755d30ce2a..3f59e58dfb 100755 --- a/t/t6024-recursive-merge.sh +++ b/t/t6024-recursive-merge.sh @@ -76,7 +76,7 @@ test_expect_success "result contains a conflict" "test_cmp expect a1" git ls-files --stage > out cat > expect << EOF -100644 439cc46de773d8a83c77799b7cc9191c128bfcff 1 a1 +100644 ec3fe2a791706733f2d8fa7ad45d9a9672031f5e 1 a1 100644 cf84443e49e1b366fac938711ddf4be2d4d1d9e9 2 a1 100644 fd7923529855d0b274795ae3349c5e0438333979 3 a1 EOF diff --git a/t/t6026-merge-attr.sh b/t/t6026-merge-attr.sh index 3c21938a68..8f9b48a493 100755 --- a/t/t6026-merge-attr.sh +++ b/t/t6026-merge-attr.sh @@ -85,11 +85,12 @@ test_expect_success 'retry the merge with longer context' ' cat >./custom-merge <<\EOF #!/bin/sh -orig="$1" ours="$2" theirs="$3" exit="$4" +orig="$1" ours="$2" theirs="$3" exit="$4" path=$5 ( echo "orig is $orig" echo "ours is $ours" echo "theirs is $theirs" + echo "path is $path" echo "=== orig ===" cat "$orig" echo "=== ours ===" @@ -110,7 +111,7 @@ test_expect_success 'custom merge backend' ' git reset --hard anchor && git config --replace-all \ - merge.custom.driver "./custom-merge %O %A %B 0" && + merge.custom.driver "./custom-merge %O %A %B 0 %P" && git config --replace-all \ merge.custom.name "custom merge driver for testing" && @@ -121,7 +122,7 @@ test_expect_success 'custom merge backend' ' o=$(git unpack-file master^:text) && a=$(git unpack-file side^:text) && b=$(git unpack-file master:text) && - sh -c "./custom-merge $o $a $b 0" && + sh -c "./custom-merge $o $a $b 0 'text'" && sed -e 1,3d $a >check-2 && cmp check-1 check-2 && rm -f $o $a $b @@ -131,7 +132,7 @@ test_expect_success 'custom merge backend' ' git reset --hard anchor && git config --replace-all \ - merge.custom.driver "./custom-merge %O %A %B 1" && + merge.custom.driver "./custom-merge %O %A %B 1 %P" && git config --replace-all \ merge.custom.name "custom merge driver for testing" && @@ -148,9 +149,12 @@ test_expect_success 'custom merge backend' ' o=$(git unpack-file master^:text) && a=$(git unpack-file anchor:text) && b=$(git unpack-file master:text) && - sh -c "./custom-merge $o $a $b 0" && + sh -c "./custom-merge $o $a $b 0 'text'" && sed -e 1,3d $a >check-2 && cmp check-1 check-2 && + sed -e 1,3d -e 4q $a >check-3 && + echo "path is text" >expect && + cmp expect check-3 && rm -f $o $a $b ' @@ -172,8 +176,32 @@ test_expect_success 'up-to-date merge without common ancestor' ' test_tick && ( cd repo1 && - git pull ../repo2 master + git fetch ../repo2 master && + git merge --allow-unrelated-histories FETCH_HEAD ) ' +test_expect_success 'custom merge does not lock index' ' + git reset --hard anchor && + write_script sleep-an-hour.sh <<-\EOF && + sleep 3600 & + echo $! >sleep.pid + EOF + + test_write_lines >.gitattributes \ + "* merge=ours" "text merge=sleep-an-hour" && + test_config merge.ours.driver true && + test_config merge.sleep-an-hour.driver ./sleep-an-hour.sh && + + # We are testing that the custom merge driver does not block + # index.lock on Windows due to an inherited file handle. + # To ensure that the backgrounded process ran sufficiently + # long (and has been started in the first place), we do not + # ignore the result of the kill command. + # By packaging the command in test_when_finished, we get both + # the correctness check and the clean-up. + test_when_finished "kill \$(cat sleep.pid)" && + git merge master +' + test_done diff --git a/t/t6029-merge-subtree.sh b/t/t6029-merge-subtree.sh index 73fc240e85..3e692454a7 100755 --- a/t/t6029-merge-subtree.sh +++ b/t/t6029-merge-subtree.sh @@ -49,7 +49,7 @@ test_expect_success 'setup' ' test_expect_success 'initial merge' ' git remote add -f gui ../git-gui && - git merge -s ours --no-commit gui/master && + git merge -s ours --no-commit --allow-unrelated-histories gui/master && git read-tree --prefix=git-gui/ -u gui/master && git commit -m "Merge git-gui as our subdirectory" && git checkout -b work && diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 9393488650..f84ff941c3 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -362,7 +362,7 @@ test_expect_success 'bisect starting with a detached HEAD' ' test_expect_success 'bisect errors out if bad and good are mistaken' ' git bisect reset && test_must_fail git bisect start $HASH2 $HASH4 2> rev_list_error && - grep "mistook good and bad" rev_list_error && + test_i18ngrep "mistook good and bad" rev_list_error && git bisect reset ' @@ -404,16 +404,16 @@ test_expect_success 'side branch creation' ' test_expect_success 'good merge base when good and bad are siblings' ' git bisect start "$HASH7" "$SIDE_HASH7" > my_bisect_log.txt && - grep "merge base must be tested" my_bisect_log.txt && + test_i18ngrep "merge base must be tested" my_bisect_log.txt && grep $HASH4 my_bisect_log.txt && git bisect good > my_bisect_log.txt && - test_must_fail grep "merge base must be tested" my_bisect_log.txt && + ! grep "merge base must be tested" my_bisect_log.txt && grep $HASH6 my_bisect_log.txt && git bisect reset ' test_expect_success 'skipped merge base when good and bad are siblings' ' git bisect start "$SIDE_HASH7" "$HASH7" > my_bisect_log.txt && - grep "merge base must be tested" my_bisect_log.txt && + test_i18ngrep "merge base must be tested" my_bisect_log.txt && grep $HASH4 my_bisect_log.txt && git bisect skip > my_bisect_log.txt 2>&1 && grep "warning" my_bisect_log.txt && @@ -423,11 +423,11 @@ test_expect_success 'skipped merge base when good and bad are siblings' ' test_expect_success 'bad merge base when good and bad are siblings' ' git bisect start "$HASH7" HEAD > my_bisect_log.txt && - grep "merge base must be tested" my_bisect_log.txt && + test_i18ngrep "merge base must be tested" my_bisect_log.txt && grep $HASH4 my_bisect_log.txt && test_must_fail git bisect bad > my_bisect_log.txt 2>&1 && - grep "merge base $HASH4 is bad" my_bisect_log.txt && - grep "fixed between $HASH4 and \[$SIDE_HASH7\]" my_bisect_log.txt && + test_i18ngrep "merge base $HASH4 is bad" my_bisect_log.txt && + test_i18ngrep "fixed between $HASH4 and \[$SIDE_HASH7\]" my_bisect_log.txt && git bisect reset ' @@ -460,9 +460,9 @@ test_expect_success 'many merge bases creation' ' test_expect_success 'good merge bases when good and bad are siblings' ' git bisect start "$B_HASH" "$A_HASH" > my_bisect_log.txt && - grep "merge base must be tested" my_bisect_log.txt && + test_i18ngrep "merge base must be tested" my_bisect_log.txt && git bisect good > my_bisect_log2.txt && - grep "merge base must be tested" my_bisect_log2.txt && + test_i18ngrep "merge base must be tested" my_bisect_log2.txt && { { grep "$SIDE_HASH5" my_bisect_log.txt && @@ -477,14 +477,14 @@ test_expect_success 'good merge bases when good and bad are siblings' ' test_expect_success 'optimized merge base checks' ' git bisect start "$HASH7" "$SIDE_HASH7" > my_bisect_log.txt && - grep "merge base must be tested" my_bisect_log.txt && + test_i18ngrep "merge base must be tested" my_bisect_log.txt && grep "$HASH4" my_bisect_log.txt && git bisect good > my_bisect_log2.txt && test -f ".git/BISECT_ANCESTORS_OK" && test "$HASH6" = $(git rev-parse --verify HEAD) && git bisect bad > my_bisect_log3.txt && git bisect good "$A_HASH" > my_bisect_log4.txt && - grep "merge base must be tested" my_bisect_log4.txt && + test_i18ngrep "merge base must be tested" my_bisect_log4.txt && test_must_fail test -f ".git/BISECT_ANCESTORS_OK" ' @@ -562,7 +562,7 @@ test_expect_success 'skipping away from skipped commit' ' test_expect_success 'erroring out when using bad path parameters' ' test_must_fail git bisect start $PARA_HASH7 $HASH1 -- foobar 2> error.txt && - grep "bad path parameters" error.txt + test_i18ngrep "bad path parameters" error.txt ' test_expect_success 'test bisection on bare repo - --no-checkout specified' ' @@ -721,7 +721,7 @@ git bisect good 3de952f2416b6084f557ec417709eac740c6818c # first bad commit: [32a594a3fdac2d57cf6d02987e30eec68511498c] Add <4: Ciao for now> into <hello>. EOF -test_expect_success 'bisect log: successfull result' ' +test_expect_success 'bisect log: successful result' ' git bisect reset && git bisect start $HASH4 $HASH2 && git bisect good && @@ -803,7 +803,7 @@ test_expect_success 'bisect terms needs 0 or 1 argument' ' test_must_fail git bisect terms 1 2 && test_must_fail git bisect terms 2>actual && echo "no terms defined" >expected && - test_cmp expected actual + test_i18ncmp expected actual ' test_expect_success 'bisect terms shows good/bad after start' ' @@ -817,4 +817,98 @@ test_expect_success 'bisect terms shows good/bad after start' ' test_cmp expected actual ' +test_expect_success 'bisect start with one term1 and term2' ' + git bisect reset && + git bisect start --term-old term2 --term-new term1 && + git bisect term2 $HASH1 && + git bisect term1 $HASH4 && + git bisect term1 && + git bisect term1 >bisect_result && + grep "$HASH2 is the first term1 commit" bisect_result && + git bisect log >log_to_replay.txt && + git bisect reset +' + +test_expect_success 'bisect replay with term1 and term2' ' + git bisect replay log_to_replay.txt >bisect_result && + grep "$HASH2 is the first term1 commit" bisect_result && + git bisect reset +' + +test_expect_success 'bisect start term1 term2' ' + git bisect reset && + git bisect start --term-new term1 --term-old term2 $HASH4 $HASH1 && + git bisect term1 && + git bisect term1 >bisect_result && + grep "$HASH2 is the first term1 commit" bisect_result && + git bisect log >log_to_replay.txt && + git bisect reset +' + +test_expect_success 'bisect cannot mix terms' ' + git bisect reset && + git bisect start --term-good term1 --term-bad term2 $HASH4 $HASH1 && + test_must_fail git bisect a && + test_must_fail git bisect b && + test_must_fail git bisect bad && + test_must_fail git bisect good && + test_must_fail git bisect new && + test_must_fail git bisect old +' + +test_expect_success 'bisect terms rejects invalid terms' ' + git bisect reset && + test_must_fail git bisect start --term-good invalid..term && + test_must_fail git bisect terms --term-bad invalid..term && + test_must_fail git bisect terms --term-good bad && + test_must_fail git bisect terms --term-good old && + test_must_fail git bisect terms --term-good skip && + test_must_fail git bisect terms --term-good reset && + test_path_is_missing .git/BISECT_TERMS +' + +test_expect_success 'bisect start --term-* does store terms' ' + git bisect reset && + git bisect start --term-bad=one --term-good=two && + git bisect terms >actual && + cat <<-EOF >expected && + Your current terms are two for the old state + and one for the new state. + EOF + test_i18ncmp expected actual && + git bisect terms --term-bad >actual && + echo one >expected && + test_cmp expected actual && + git bisect terms --term-good >actual && + echo two >expected && + test_cmp expected actual +' + +test_expect_success 'bisect start takes options and revs in any order' ' + git bisect reset && + git bisect start --term-good one $HASH4 \ + --term-good two --term-bad bad-term \ + $HASH1 --term-good three -- && + (git bisect terms --term-bad && git bisect terms --term-good) >actual && + printf "%s\n%s\n" bad-term three >expected && + test_cmp expected actual +' + +test_expect_success 'git bisect reset cleans bisection state properly' ' + git bisect reset && + git bisect start && + git bisect good $HASH1 && + git bisect bad $HASH4 && + git bisect reset && + test -z "$(git for-each-ref "refs/bisect/*")" && + test_path_is_missing "$GIT_DIR/BISECT_EXPECTED_REV" && + test_path_is_missing "$GIT_DIR/BISECT_ANCESTORS_OK" && + test_path_is_missing "$GIT_DIR/BISECT_LOG" && + test_path_is_missing "$GIT_DIR/BISECT_RUN" && + test_path_is_missing "$GIT_DIR/BISECT_TERMS" && + test_path_is_missing "$GIT_DIR/head-name" && + test_path_is_missing "$GIT_DIR/BISECT_HEAD" && + test_path_is_missing "$GIT_DIR/BISECT_START" +' + test_done diff --git a/t/t6031-merge-filemode.sh b/t/t6031-merge-filemode.sh new file mode 100755 index 0000000000..7d06461f13 --- /dev/null +++ b/t/t6031-merge-filemode.sh @@ -0,0 +1,100 @@ +#!/bin/sh + +test_description='merge: handle file mode' +. ./test-lib.sh + +test_expect_success 'set up mode change in one branch' ' + : >file1 && + git add file1 && + git commit -m initial && + git checkout -b a1 master && + : >dummy && + git add dummy && + git commit -m a && + git checkout -b b1 master && + test_chmod +x file1 && + git add file1 && + git commit -m b1 +' + +do_one_mode () { + strategy=$1 + us=$2 + them=$3 + test_expect_success "resolve single mode change ($strategy, $us)" ' + git checkout -f $us && + git merge -s $strategy $them && + git ls-files -s file1 | grep ^100755 + ' + + test_expect_success FILEMODE "verify executable bit on file ($strategy, $us)" ' + test -x file1 + ' +} + +do_one_mode recursive a1 b1 +do_one_mode recursive b1 a1 +do_one_mode resolve a1 b1 +do_one_mode resolve b1 a1 + +test_expect_success 'set up mode change in both branches' ' + git reset --hard HEAD && + git checkout -b a2 master && + : >file2 && + H=$(git hash-object file2) && + test_chmod +x file2 && + git commit -m a2 && + git checkout -b b2 master && + : >file2 && + git add file2 && + git commit -m b2 && + { + echo "100755 $H 2 file2" + echo "100644 $H 3 file2" + } >expect +' + +do_both_modes () { + strategy=$1 + test_expect_success "detect conflict on double mode change ($strategy)" ' + git reset --hard && + git checkout -f a2 && + test_must_fail git merge -s $strategy b2 && + git ls-files -u >actual && + test_cmp actual expect && + git ls-files -s file2 | grep ^100755 + ' + + test_expect_success FILEMODE "verify executable bit on file ($strategy)" ' + test -x file2 + ' +} + +# both sides are equivalent, so no need to run both ways +do_both_modes recursive +do_both_modes resolve + +test_expect_success 'set up delete/modechange scenario' ' + git reset --hard && + git checkout -b deletion master && + git rm file1 && + git commit -m deletion +' + +do_delete_modechange () { + strategy=$1 + us=$2 + them=$3 + test_expect_success "detect delete/modechange conflict ($strategy, $us)" ' + git reset --hard && + git checkout $us && + test_must_fail git merge -s $strategy $them + ' +} + +do_delete_modechange recursive b1 deletion +do_delete_modechange recursive deletion b1 +do_delete_modechange resolve b1 deletion +do_delete_modechange resolve deletion b1 + +test_done diff --git a/t/t6031-merge-recursive.sh b/t/t6031-merge-recursive.sh deleted file mode 100755 index 6464a16a19..0000000000 --- a/t/t6031-merge-recursive.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/bin/sh - -test_description='merge-recursive: handle file mode' -. ./test-lib.sh - -test_expect_success 'mode change in one branch: keep changed version' ' - : >file1 && - git add file1 && - git commit -m initial && - git checkout -b a1 master && - : >dummy && - git add dummy && - git commit -m a && - git checkout -b b1 master && - test_chmod +x file1 && - git add file1 && - git commit -m b1 && - git checkout a1 && - git merge-recursive master -- a1 b1 && - git ls-files -s file1 | grep ^100755 -' - -test_expect_success FILEMODE 'verify executable bit on file' ' - test -x file1 -' - -test_expect_success 'mode change in both branches: expect conflict' ' - git reset --hard HEAD && - git checkout -b a2 master && - : >file2 && - H=$(git hash-object file2) && - test_chmod +x file2 && - git commit -m a2 && - git checkout -b b2 master && - : >file2 && - git add file2 && - git commit -m b2 && - git checkout a2 && - ( - git merge-recursive master -- a2 b2 - test $? = 1 - ) && - git ls-files -u >actual && - ( - echo "100755 $H 2 file2" - echo "100644 $H 3 file2" - ) >expect && - test_cmp actual expect && - git ls-files -s file2 | grep ^100755 -' - -test_expect_success FILEMODE 'verify executable bit on file' ' - test -x file2 -' - -test_expect_success 'merging with triple rename across D/F conflict' ' - git reset --hard HEAD && - git checkout -b main && - git rm -rf . && - - echo "just a file" >sub1 && - mkdir -p sub2 && - echo content1 >sub2/file1 && - echo content2 >sub2/file2 && - echo content3 >sub2/file3 && - mkdir simple && - echo base >simple/bar && - git add -A && - test_tick && - git commit -m base && - - git checkout -b other && - echo more >>simple/bar && - test_tick && - git commit -a -m changesimplefile && - - git checkout main && - git rm sub1 && - git mv sub2 sub1 && - test_tick && - git commit -m changefiletodir && - - test_tick && - git merge other -' - -test_done diff --git a/t/t6032-merge-large-rename.sh b/t/t6032-merge-large-rename.sh index 0f79268917..80777386dc 100755 --- a/t/t6032-merge-large-rename.sh +++ b/t/t6032-merge-large-rename.sh @@ -20,7 +20,7 @@ test_expect_success 'setup (initial)' ' make_text() { echo $1: $2 - for i in `count 20`; do + for i in $(count 20); do echo $1: $i done echo $1: $3 diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh index 9d6621c056..18aa88b5c0 100755 --- a/t/t6036-recursive-corner-cases.sh +++ b/t/t6036-recursive-corner-cases.sh @@ -212,7 +212,8 @@ test_expect_success 'git detects differently handled merges conflict' ' -L "" \ -L "Temporary merge branch 1" \ merged empty merge-me && - test $(git rev-parse :1:new_a) = $(git hash-object merged) + sed -e "s/^\([<=>]\)/\1\1\1/" merged >merged-internal && + test $(git rev-parse :1:new_a) = $(git hash-object merged-internal) ' # @@ -299,89 +300,6 @@ test_expect_success 'git detects conflict merging criss-cross+modify/delete, rev ' # -# criss-cross + modify/modify with very contrived file contents: -# -# B D -# o---o -# / \ / \ -# A o X ? F -# \ / \ / -# o---o -# C E -# -# Commit A: file with contents 'A\n' -# Commit B: file with contents 'B\n' -# Commit C: file with contents 'C\n' -# Commit D: file with contents 'D\n' -# Commit E: file with contents: -# <<<<<<< Temporary merge branch 1 -# C -# ======= -# B -# >>>>>>> Temporary merge branch 2 -# -# Now, when we merge commits D & E, does git detect the conflict? - -test_expect_success 'setup differently handled merges of content conflict' ' - git clean -fdqx && - rm -rf .git && - git init && - - echo A >file && - git add file && - test_tick && - git commit -m A && - - git branch B && - git checkout -b C && - echo C >file && - git add file && - test_tick && - git commit -m C && - - git checkout B && - echo B >file && - git add file && - test_tick && - git commit -m B && - - git checkout B^0 && - test_must_fail git merge C && - echo D >file && - git add file && - test_tick && - git commit -m D && - git tag D && - - git checkout C^0 && - test_must_fail git merge B && - cat <<EOF >file && -<<<<<<< Temporary merge branch 1 -C -======= -B ->>>>>>> Temporary merge branch 2 -EOF - git add file && - test_tick && - git commit -m E && - git tag E -' - -test_expect_failure 'git detects conflict w/ criss-cross+contrived resolution' ' - git checkout D^0 && - - test_must_fail git merge -s recursive E^0 && - - test 3 -eq $(git ls-files -s | wc -l) && - test 3 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse :2:file) = $(git rev-parse D:file) && - test $(git rev-parse :3:file) = $(git rev-parse E:file) -' - -# # criss-cross + d/f conflict via add/add: # Commit A: Neither file 'a' nor directory 'a/' exists. # Commit B: Introduce 'a' diff --git a/t/t6037-merge-ours-theirs.sh b/t/t6037-merge-ours-theirs.sh index 3889eca4ae..0aebc6c028 100755 --- a/t/t6037-merge-ours-theirs.sh +++ b/t/t6037-merge-ours-theirs.sh @@ -73,4 +73,36 @@ test_expect_success 'pull passes -X to underlying merge' ' git reset --hard master && test_must_fail git pull -s recursive -X bork . side ' +test_expect_success SYMLINKS 'symlink with -Xours/-Xtheirs' ' + git reset --hard master && + git checkout -b two master && + ln -s target-zero link && + git add link && + git commit -m "add link pointing to zero" && + + ln -f -s target-two link && + git commit -m "add link pointing to two" link && + + git checkout -b one HEAD^ && + ln -f -s target-one link && + git commit -m "add link pointing to one" link && + + # we expect symbolic links not to resolve automatically, of course + git checkout one^0 && + test_must_fail git merge -s recursive two && + + # favor theirs to resolve to target-two? + git reset --hard && + git checkout one^0 && + git merge -s recursive -X theirs two && + git diff --exit-code two HEAD link && + + # favor ours to resolve to target-one? + git reset --hard && + git checkout one^0 && + git merge -s recursive -X ours two && + git diff --exit-code one HEAD link + +' + test_done diff --git a/t/t6038-merge-text-auto.sh b/t/t6038-merge-text-auto.sh index 85c10b0940..5e8d5fa50c 100755 --- a/t/t6038-merge-text-auto.sh +++ b/t/t6038-merge-text-auto.sh @@ -16,6 +16,13 @@ test_description='CRLF merge conflict across text=auto change test_have_prereq SED_STRIPS_CR && SED_OPTIONS=-b +compare_files () { + tr '\015\000' QN <"$1" >"$1".expect && + tr '\015\000' QN <"$2" >"$2".actual && + test_cmp "$1".expect "$2".actual && + rm "$1".expect "$2".actual +} + test_expect_success setup ' git config core.autocrlf false && @@ -30,7 +37,7 @@ test_expect_success setup ' git branch side && echo "* text=auto" >.gitattributes && - touch file && + echo first line >file && git add .gitattributes file && test_tick && git commit -m "normalize file" && @@ -81,38 +88,49 @@ test_expect_success 'Merge after setting text=auto' ' rm -f .gitattributes && git reset --hard a && git merge b && - test_cmp expected file + compare_files expected file ' -test_expect_success 'Merge addition of text=auto' ' +test_expect_success 'Merge addition of text=auto eol=LF' ' + git config core.eol lf && cat <<-\EOF >expected && first line same line EOF - if test_have_prereq NATIVE_CRLF; then - append_cr <expected >expected.temp && - mv expected.temp expected - fi && git config merge.renormalize true && git rm -fr . && rm -f .gitattributes && git reset --hard b && git merge a && - test_cmp expected file + compare_files expected file +' + +test_expect_success 'Merge addition of text=auto eol=CRLF' ' + git config core.eol crlf && + cat <<-\EOF >expected && + first line + same line + EOF + + append_cr <expected >expected.temp && + mv expected.temp expected && + git config merge.renormalize true && + git rm -fr . && + rm -f .gitattributes && + git reset --hard b && + echo >&2 "After git reset --hard b" && + git ls-files -s --eol >&2 && + git merge a && + compare_files expected file ' test_expect_success 'Detect CRLF/LF conflict after setting text=auto' ' + git config core.eol native && echo "<<<<<<<" >expected && - if test_have_prereq NATIVE_CRLF; then - echo first line | append_cr >>expected && - echo same line | append_cr >>expected && - echo ======= | append_cr >>expected - else - echo first line >>expected && - echo same line >>expected && - echo ======= >>expected - fi && + echo first line >>expected && + echo same line >>expected && + echo ======= >>expected && echo first line | append_cr >>expected && echo same line | append_cr >>expected && echo ">>>>>>>" >>expected && @@ -121,29 +139,23 @@ test_expect_success 'Detect CRLF/LF conflict after setting text=auto' ' git reset --hard a && test_must_fail git merge b && fuzz_conflict file >file.fuzzy && - test_cmp expected file.fuzzy + compare_files expected file.fuzzy ' test_expect_success 'Detect LF/CRLF conflict from addition of text=auto' ' echo "<<<<<<<" >expected && echo first line | append_cr >>expected && echo same line | append_cr >>expected && - if test_have_prereq NATIVE_CRLF; then - echo ======= | append_cr >>expected && - echo first line | append_cr >>expected && - echo same line | append_cr >>expected - else - echo ======= >>expected && - echo first line >>expected && - echo same line >>expected - fi && + echo ======= >>expected && + echo first line >>expected && + echo same line >>expected && echo ">>>>>>>" >>expected && git config merge.renormalize false && rm -f .gitattributes && git reset --hard b && test_must_fail git merge a && fuzz_conflict file >file.fuzzy && - test_cmp expected file.fuzzy + compare_files expected file.fuzzy ' test_expect_failure 'checkout -m after setting text=auto' ' @@ -158,7 +170,7 @@ test_expect_failure 'checkout -m after setting text=auto' ' git reset --hard initial && git checkout a -- . && git checkout -m b && - test_cmp expected file + compare_files expected file ' test_expect_failure 'checkout -m addition of text=auto' ' @@ -173,7 +185,7 @@ test_expect_failure 'checkout -m addition of text=auto' ' git reset --hard initial && git checkout b -- . && git checkout -m a && - test_cmp expected file + compare_files expected file ' test_expect_failure 'cherry-pick patch from after text=auto was added' ' @@ -187,7 +199,7 @@ test_expect_failure 'cherry-pick patch from after text=auto was added' ' git reset --hard b && test_must_fail git cherry-pick a >err 2>&1 && grep "[Nn]othing added" err && - test_cmp expected file + compare_files expected file ' test_expect_success 'Test delete/normalize conflict' ' diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh index 3d5c238c81..716283b274 100755 --- a/t/t6040-tracking-info.sh +++ b/t/t6040-tracking-info.sh @@ -44,7 +44,7 @@ b1 [ahead 1, behind 1] d b2 [ahead 1, behind 1] d b3 [behind 1] b b4 [ahead 2] f -b5 g +b5 [gone] g b6 c EOF @@ -100,7 +100,7 @@ test_expect_success 'checkout (up-to-date with upstream)' ' ( cd test && git checkout b6 ) >actual && - test_i18ngrep "Your branch is up-to-date with .origin/master" actual + test_i18ngrep "Your branch is up to date with .origin/master" actual ' test_expect_success 'status (diverged from upstream)' ' @@ -130,7 +130,7 @@ test_expect_success 'status (up-to-date with upstream)' ' # reports nothing to commit test_must_fail git commit --dry-run ) >actual && - test_i18ngrep "Your branch is up-to-date with .origin/master" actual + test_i18ngrep "Your branch is up to date with .origin/master" actual ' cat >expect <<\EOF @@ -147,6 +147,48 @@ test_expect_success 'status -s -b (diverged from upstream)' ' ' cat >expect <<\EOF +## b1...origin/master [different] +EOF + +test_expect_success 'status -s -b --no-ahead-behind (diverged from upstream)' ' + ( + cd test && + git checkout b1 >/dev/null && + git status -s -b --no-ahead-behind | head -1 + ) >actual && + test_i18ncmp expect actual +' + +cat >expect <<\EOF +On branch b1 +Your branch and 'origin/master' have diverged, +and have 1 and 1 different commits each, respectively. +EOF + +test_expect_success 'status --long --branch' ' + ( + cd test && + git checkout b1 >/dev/null && + git status --long -b | head -3 + ) >actual && + test_i18ncmp expect actual +' + +cat >expect <<\EOF +On branch b1 +Your branch and 'origin/master' refer to different commits. +EOF + +test_expect_success 'status --long --branch --no-ahead-behind' ' + ( + cd test && + git checkout b1 >/dev/null && + git status --long -b --no-ahead-behind | head -2 + ) >actual && + test_i18ncmp expect actual +' + +cat >expect <<\EOF ## b5...brokenbase [gone] EOF @@ -188,35 +230,29 @@ test_expect_success 'fail to track annotated tags' ' test_must_fail git checkout heavytrack ' -test_expect_success 'setup tracking with branch --set-upstream on existing branch' ' +test_expect_success '--set-upstream-to does not change branch' ' git branch from-master master && - test_must_fail git config branch.from-master.merge > actual && - git branch --set-upstream from-master master && - git config branch.from-master.merge > actual && - grep -q "^refs/heads/master$" actual -' - -test_expect_success '--set-upstream does not change branch' ' + git branch --set-upstream-to master from-master && git branch from-master2 master && test_must_fail git config branch.from-master2.merge > actual && git rev-list from-master2 && git update-ref refs/heads/from-master2 from-master2^ && git rev-parse from-master2 >expect2 && - git branch --set-upstream from-master2 master && + git branch --set-upstream-to master from-master2 && git config branch.from-master.merge > actual && git rev-parse from-master2 >actual2 && grep -q "^refs/heads/master$" actual && cmp expect2 actual2 ' -test_expect_success '--set-upstream @{-1}' ' - git checkout from-master && +test_expect_success '--set-upstream-to @{-1}' ' + git checkout follower && git checkout from-master2 && git config branch.from-master2.merge > expect2 && - git branch --set-upstream @{-1} follower && + git branch --set-upstream-to @{-1} from-master && git config branch.from-master.merge > actual && git config branch.from-master2.merge > actual2 && - git branch --set-upstream from-master follower && + git branch --set-upstream-to follower from-master && git config branch.from-master.merge > expect && test_cmp expect2 actual2 && test_cmp expect actual diff --git a/t/t6041-bisect-submodule.sh b/t/t6041-bisect-submodule.sh index c6b7aa6977..62b8a2e7bb 100755 --- a/t/t6041-bisect-submodule.sh +++ b/t/t6041-bisect-submodule.sh @@ -8,7 +8,7 @@ test_description='bisect can handle submodules' git_bisect () { git status -su >expect && ls -1pR * >>expect && - tar czf "$TRASH_DIRECTORY/tmp.tgz" * && + tar cf "$TRASH_DIRECTORY/tmp.tar" * && GOOD=$(git rev-parse --verify HEAD) && git checkout "$1" && echo "foo" >bar && @@ -20,7 +20,7 @@ git_bisect () { git bisect start && git bisect good $GOOD && rm -rf * && - tar xzf "$TRASH_DIRECTORY/tmp.tgz" && + tar xf "$TRASH_DIRECTORY/tmp.tar" && git status -su >actual && ls -1pR * >>actual && test_cmp expect actual && diff --git a/t/t6044-merge-unrelated-index-changes.sh b/t/t6044-merge-unrelated-index-changes.sh new file mode 100755 index 0000000000..23b86fb977 --- /dev/null +++ b/t/t6044-merge-unrelated-index-changes.sh @@ -0,0 +1,169 @@ +#!/bin/sh + +test_description="merges with unrelated index changes" + +. ./test-lib.sh + +# Testcase for some simple merges +# A +# o-------o B +# \ +# \-----o C +# \ +# \---o D +# \ +# \-o E +# \ +# o F +# Commit A: some file a +# Commit B: adds file b, modifies end of a +# Commit C: adds file c +# Commit D: adds file d, modifies beginning of a +# Commit E: renames a->subdir/a, adds subdir/e +# Commit F: empty commit + +test_expect_success 'setup trivial merges' ' + test_seq 1 10 >a && + git add a && + test_tick && git commit -m A && + + git branch A && + git branch B && + git branch C && + git branch D && + git branch E && + git branch F && + + git checkout B && + echo b >b && + echo 11 >>a && + git add a b && + test_tick && git commit -m B && + + git checkout C && + echo c >c && + git add c && + test_tick && git commit -m C && + + git checkout D && + test_seq 2 10 >a && + echo d >d && + git add a d && + test_tick && git commit -m D && + + git checkout E && + mkdir subdir && + git mv a subdir/a && + echo e >subdir/e && + git add subdir && + test_tick && git commit -m E && + + git checkout F && + test_tick && git commit --allow-empty -m F +' + +test_expect_success 'ff update' ' + git reset --hard && + git checkout A^0 && + + touch random_file && git add random_file && + + git merge E^0 && + + test_must_fail git rev-parse HEAD:random_file && + test "$(git diff --name-only --cached E)" = "random_file" +' + +test_expect_success 'ff update, important file modified' ' + git reset --hard && + git checkout A^0 && + + mkdir subdir && + touch subdir/e && + git add subdir/e && + + test_must_fail git merge E^0 +' + +test_expect_success 'resolve, trivial' ' + git reset --hard && + git checkout B^0 && + + touch random_file && git add random_file && + + test_must_fail git merge -s resolve C^0 +' + +test_expect_success 'resolve, non-trivial' ' + git reset --hard && + git checkout B^0 && + + touch random_file && git add random_file && + + test_must_fail git merge -s resolve D^0 +' + +test_expect_success 'recursive' ' + git reset --hard && + git checkout B^0 && + + touch random_file && git add random_file && + + test_must_fail git merge -s recursive C^0 +' + +test_expect_success 'recursive, when merge branch matches merge base' ' + git reset --hard && + git checkout B^0 && + + touch random_file && git add random_file && + + test_must_fail git merge -s recursive F^0 +' + +test_expect_success 'octopus, unrelated file touched' ' + git reset --hard && + git checkout B^0 && + + touch random_file && git add random_file && + + test_must_fail git merge C^0 D^0 +' + +test_expect_success 'octopus, related file removed' ' + git reset --hard && + git checkout B^0 && + + git rm b && + + test_must_fail git merge C^0 D^0 +' + +test_expect_success 'octopus, related file modified' ' + git reset --hard && + git checkout B^0 && + + echo 12 >>a && git add a && + + test_must_fail git merge C^0 D^0 +' + +test_expect_success 'ours' ' + git reset --hard && + git checkout B^0 && + + touch random_file && git add random_file && + + test_must_fail git merge -s ours C^0 +' + +test_expect_success 'subtree' ' + git reset --hard && + git checkout B^0 && + + touch random_file && git add random_file && + + test_must_fail git merge -s subtree E^0 +' + +test_done diff --git a/t/t6045-merge-rename-delete.sh b/t/t6045-merge-rename-delete.sh new file mode 100755 index 0000000000..5d33577d2f --- /dev/null +++ b/t/t6045-merge-rename-delete.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +test_description='Merge-recursive rename/delete conflict message' +. ./test-lib.sh + +test_expect_success 'rename/delete' ' + echo foo >A && + git add A && + git commit -m "initial" && + + git checkout -b rename && + git mv A B && + git commit -m "rename" && + + git checkout master && + git rm A && + git commit -m "delete" && + + test_must_fail git merge --strategy=recursive rename >output && + test_i18ngrep "CONFLICT (rename/delete): A deleted in HEAD and renamed to B in rename. Version rename of B left in tree." output +' + +test_done diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh index 4d5a25eedf..c630aba657 100755 --- a/t/t6050-replace.sh +++ b/t/t6050-replace.sh @@ -351,11 +351,15 @@ test_expect_success 'test --format long' ' test_cmp expected actual ' -test_expect_success 'setup a fake editor' ' - write_script fakeeditor <<-\EOF +test_expect_success 'setup fake editors' ' + write_script fakeeditor <<-\EOF && sed -e "s/A U Thor/A fake Thor/" "$1" >"$1.new" mv "$1.new" "$1" EOF + write_script failingfakeeditor <<-\EOF + ./fakeeditor "$@" + false + EOF ' test_expect_success '--edit with and without already replaced object' ' @@ -372,7 +376,7 @@ test_expect_success '--edit with and without already replaced object' ' test_expect_success '--edit and change nothing or command failed' ' git replace -d "$PARA3" && test_must_fail env GIT_EDITOR=true git replace --edit "$PARA3" && - test_must_fail env GIT_EDITOR="./fakeeditor;false" git replace --edit "$PARA3" && + test_must_fail env GIT_EDITOR="./failingfakeeditor" git replace --edit "$PARA3" && GIT_EDITOR=./fakeeditor git replace --edit "$PARA3" && git replace -l | grep "$PARA3" && git cat-file commit "$PARA3" | grep "A fake Thor" diff --git a/t/t6100-rev-list-in-order.sh b/t/t6100-rev-list-in-order.sh new file mode 100755 index 0000000000..b2bb0a7f61 --- /dev/null +++ b/t/t6100-rev-list-in-order.sh @@ -0,0 +1,77 @@ +#!/bin/sh + +test_description='rev-list testing in-commit-order' + +. ./test-lib.sh + +test_expect_success 'setup a commit history with trees, blobs' ' + for x in one two three four + do + echo $x >$x && + git add $x && + git commit -m "add file $x" || + return 1 + done && + for x in four three + do + git rm $x && + git commit -m "remove $x" || + return 1 + done +' + +test_expect_success 'rev-list --in-commit-order' ' + git rev-list --in-commit-order --objects HEAD >actual.raw && + cut -c 1-40 >actual <actual.raw && + + git cat-file --batch-check="%(objectname)" >expect.raw <<-\EOF && + HEAD^{commit} + HEAD^{tree} + HEAD^{tree}:one + HEAD^{tree}:two + HEAD~1^{commit} + HEAD~1^{tree} + HEAD~1^{tree}:three + HEAD~2^{commit} + HEAD~2^{tree} + HEAD~2^{tree}:four + HEAD~3^{commit} + # HEAD~3^{tree} skipped, same as HEAD~1^{tree} + HEAD~4^{commit} + # HEAD~4^{tree} skipped, same as HEAD^{tree} + HEAD~5^{commit} + HEAD~5^{tree} + EOF + grep -v "#" >expect <expect.raw && + + test_cmp expect actual +' + +test_expect_success 'rev-list lists blobs and trees after commits' ' + git rev-list --objects HEAD >actual.raw && + cut -c 1-40 >actual <actual.raw && + + git cat-file --batch-check="%(objectname)" >expect.raw <<-\EOF && + HEAD^{commit} + HEAD~1^{commit} + HEAD~2^{commit} + HEAD~3^{commit} + HEAD~4^{commit} + HEAD~5^{commit} + HEAD^{tree} + HEAD^{tree}:one + HEAD^{tree}:two + HEAD~1^{tree} + HEAD~1^{tree}:three + HEAD~2^{tree} + HEAD~2^{tree}:four + # HEAD~3^{tree} skipped, same as HEAD~1^{tree} + # HEAD~4^{tree} skipped, same as HEAD^{tree} + HEAD~5^{tree} + EOF + grep -v "#" >expect <expect.raw && + + test_cmp expect actual +' + +test_done diff --git a/t/t6101-rev-parse-parents.sh b/t/t6101-rev-parse-parents.sh index 10b1452766..8c617981a3 100755 --- a/t/t6101-rev-parse-parents.sh +++ b/t/t6101-rev-parse-parents.sh @@ -19,7 +19,7 @@ test_expect_success 'setup' ' git checkout --orphan tmp && test_commit start2 && git checkout master && - git merge -m next start2 && + git merge -m next --allow-unrelated-histories start2 && test_commit final && test_seq 40 | @@ -83,12 +83,24 @@ test_expect_success 'final^1^@ = final^1^1 final^1^2' ' test_cmp expect actual ' +test_expect_success 'symbolic final^1^@ = final^1^1 final^1^2' ' + git rev-parse --symbolic final^1^1 final^1^2 >expect && + git rev-parse --symbolic final^1^@ >actual && + test_cmp expect actual +' + test_expect_success 'final^1^! = final^1 ^final^1^1 ^final^1^2' ' git rev-parse final^1 ^final^1^1 ^final^1^2 >expect && git rev-parse final^1^! >actual && test_cmp expect actual ' +test_expect_success 'symbolic final^1^! = final^1 ^final^1^1 ^final^1^2' ' + git rev-parse --symbolic final^1 ^final^1^1 ^final^1^2 >expect && + git rev-parse --symbolic final^1^! >actual && + test_cmp expect actual +' + test_expect_success 'large graft octopus' ' test_cmp_rev_output b31 "git rev-parse --verify b1^30" ' @@ -102,4 +114,104 @@ test_expect_success 'short SHA-1 works' ' test_cmp_rev_output start "git rev-parse ${start%?}" ' +# rev^- tests; we can use a simpler setup for these + +test_expect_success 'setup for rev^- tests' ' + test_commit one && + test_commit two && + test_commit three && + + # Merge in a branch for testing rev^- + git checkout -b branch && + git checkout HEAD^^ && + git merge -m merge --no-edit --no-ff branch && + git checkout -b merge +' + +# The merged branch has 2 commits + the merge +test_expect_success 'rev-list --count merge^- = merge^..merge' ' + git rev-list --count merge^..merge >expect && + echo 3 >actual && + test_cmp expect actual +' + +# All rev^- rev-parse tests + +test_expect_success 'rev-parse merge^- = merge^..merge' ' + git rev-parse merge^..merge >expect && + git rev-parse merge^- >actual && + test_cmp expect actual +' + +test_expect_success 'rev-parse merge^-1 = merge^..merge' ' + git rev-parse merge^1..merge >expect && + git rev-parse merge^-1 >actual && + test_cmp expect actual +' + +test_expect_success 'rev-parse merge^-2 = merge^2..merge' ' + git rev-parse merge^2..merge >expect && + git rev-parse merge^-2 >actual && + test_cmp expect actual +' + +test_expect_success 'symbolic merge^-1 = merge^1..merge' ' + git rev-parse --symbolic merge^1..merge >expect && + git rev-parse --symbolic merge^-1 >actual && + test_cmp expect actual +' + +test_expect_success 'rev-parse merge^-0 (invalid parent)' ' + test_must_fail git rev-parse merge^-0 +' + +test_expect_success 'rev-parse merge^-3 (invalid parent)' ' + test_must_fail git rev-parse merge^-3 +' + +test_expect_success 'rev-parse merge^-^ (garbage after ^-)' ' + test_must_fail git rev-parse merge^-^ +' + +test_expect_success 'rev-parse merge^-1x (garbage after ^-1)' ' + test_must_fail git rev-parse merge^-1x +' + +# All rev^- rev-list tests (should be mostly the same as rev-parse; the reason +# for the duplication is that rev-parse and rev-list use different parsers). + +test_expect_success 'rev-list merge^- = merge^..merge' ' + git rev-list merge^..merge >expect && + git rev-list merge^- >actual && + test_cmp expect actual +' + +test_expect_success 'rev-list merge^-1 = merge^1..merge' ' + git rev-list merge^1..merge >expect && + git rev-list merge^-1 >actual && + test_cmp expect actual +' + +test_expect_success 'rev-list merge^-2 = merge^2..merge' ' + git rev-list merge^2..merge >expect && + git rev-list merge^-2 >actual && + test_cmp expect actual +' + +test_expect_success 'rev-list merge^-0 (invalid parent)' ' + test_must_fail git rev-list merge^-0 +' + +test_expect_success 'rev-list merge^-3 (invalid parent)' ' + test_must_fail git rev-list merge^-3 +' + +test_expect_success 'rev-list merge^-^ (garbage after ^-)' ' + test_must_fail git rev-list merge^-^ +' + +test_expect_success 'rev-list merge^-1x (garbage after ^-1)' ' + test_must_fail git rev-list merge^-1x +' + test_done diff --git a/t/t6112-rev-list-filters-objects.sh b/t/t6112-rev-list-filters-objects.sh new file mode 100755 index 0000000000..0a37dd5f97 --- /dev/null +++ b/t/t6112-rev-list-filters-objects.sh @@ -0,0 +1,225 @@ +#!/bin/sh + +test_description='git rev-list using object filtering' + +. ./test-lib.sh + +# Test the blob:none filter. + +test_expect_success 'setup r1' ' + echo "{print \$1}" >print_1.awk && + echo "{print \$2}" >print_2.awk && + + git init r1 && + for n in 1 2 3 4 5 + do + echo "This is file: $n" > r1/file.$n + git -C r1 add file.$n + git -C r1 commit -m "$n" + done +' + +test_expect_success 'verify blob:none omits all 5 blobs' ' + git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r1 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:none \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify emitted+omitted == all' ' + git -C r1 rev-list HEAD --objects \ + | awk -f print_1.awk \ + | sort >expected && + git -C r1 rev-list HEAD --objects --filter-print-omitted --filter=blob:none \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + + +# Test blob:limit=<n>[kmg] filter. +# We boundary test around the size parameter. The filter is strictly less than +# the value, so size 500 and 1000 should have the same results, but 1001 should +# filter more. + +test_expect_success 'setup r2' ' + git init r2 && + for n in 1000 10000 + do + printf "%"$n"s" X > r2/large.$n + git -C r2 add large.$n + git -C r2 commit -m "$n" + done +' + +test_expect_success 'verify blob:limit=500 omits all blobs' ' + git -C r2 ls-files -s large.1000 large.10000 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=500 \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify emitted+omitted == all' ' + git -C r2 rev-list HEAD --objects \ + | awk -f print_1.awk \ + | sort >expected && + git -C r2 rev-list HEAD --objects --filter-print-omitted --filter=blob:limit=500 \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify blob:limit=1000' ' + git -C r2 ls-files -s large.1000 large.10000 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1000 \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify blob:limit=1001' ' + git -C r2 ls-files -s large.10000 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1001 \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify blob:limit=1k' ' + git -C r2 ls-files -s large.10000 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1k \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify blob:limit=1m' ' + cat </dev/null >expected && + git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1m \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +# Test sparse:path=<path> filter. +# Use a local file containing a sparse-checkout specification to filter +# out blobs not required for the corresponding sparse-checkout. We do not +# require sparse-checkout to actually be enabled. + +test_expect_success 'setup r3' ' + git init r3 && + mkdir r3/dir1 && + for n in sparse1 sparse2 + do + echo "This is file: $n" > r3/$n + git -C r3 add $n + echo "This is file: dir1/$n" > r3/dir1/$n + git -C r3 add dir1/$n + done && + git -C r3 commit -m "sparse" && + echo dir1/ >pattern1 && + echo sparse1 >pattern2 +' + +test_expect_success 'verify sparse:path=pattern1 omits top-level files' ' + git -C r3 ls-files -s sparse1 sparse2 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:path=../pattern1 \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify sparse:path=pattern2 omits both sparse2 files' ' + git -C r3 ls-files -s sparse2 dir1/sparse2 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:path=../pattern2 \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +# Test sparse:oid=<oid-ish> filter. +# Like sparse:path, but we get the sparse-checkout specification from +# a blob rather than a file on disk. + +test_expect_success 'setup r3 part 2' ' + echo dir1/ >r3/pattern && + git -C r3 add pattern && + git -C r3 commit -m "pattern" +' + +test_expect_success 'verify sparse:oid=OID omits top-level files' ' + git -C r3 ls-files -s pattern sparse1 sparse2 \ + | awk -f print_2.awk \ + | sort >expected && + oid=$(git -C r3 ls-files -s pattern | awk -f print_2.awk) && + git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:oid=$oid \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'verify sparse:oid=oid-ish omits top-level files' ' + git -C r3 ls-files -s pattern sparse1 sparse2 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r3 rev-list HEAD --quiet --objects --filter-print-omitted --filter=sparse:oid=master:pattern \ + | awk -f print_1.awk \ + | sed "s/~//" \ + | sort >observed && + test_cmp observed expected +' + +# Delete some loose objects and use rev-list, but WITHOUT any filtering. +# This models previously omitted objects that we did not receive. + +test_expect_success 'rev-list W/ --missing=print' ' + git -C r1 ls-files -s file.1 file.2 file.3 file.4 file.5 \ + | awk -f print_2.awk \ + | sort >expected && + for id in `cat expected | sed "s|..|&/|"` + do + rm r1/.git/objects/$id + done && + git -C r1 rev-list --quiet HEAD --missing=print --objects \ + | awk -f print_1.awk \ + | sed "s/?//" \ + | sort >observed && + test_cmp observed expected +' + +test_expect_success 'rev-list W/O --missing fails' ' + test_must_fail git -C r1 rev-list --quiet --objects HEAD +' + +test_expect_success 'rev-list W/ missing=allow-any' ' + git -C r1 rev-list --quiet --missing=allow-any --objects HEAD +' + +test_done diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index c0e5b2a627..bae78c4e89 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -113,8 +113,16 @@ check_describe A-3-* --long HEAD^^2 check_describe c-7-* --tags check_describe e-3-* --first-parent --tags +test_expect_success 'describe --contains defaults to HEAD without commit-ish' ' + echo "A^0" >expect && + git checkout A && + test_when_finished "git checkout -" && + git describe --contains >actual && + test_cmp expect actual +' + : >err.expect -check_describe A --all A^0 +check_describe tags/A --all A^0 test_expect_success 'no warning was displayed for A' ' test_cmp err.expect err.actual ' @@ -174,6 +182,41 @@ check_describe "test2-lightweight-*" --tags --match="test2-*" check_describe "test2-lightweight-*" --long --tags --match="test2-*" HEAD^ +check_describe "test2-lightweight-*" --long --tags --match="test1-*" --match="test2-*" HEAD^ + +check_describe "test2-lightweight-*" --long --tags --match="test1-*" --no-match --match="test2-*" HEAD^ + +check_describe "test1-lightweight-*" --long --tags --match="test1-*" --match="test3-*" HEAD + +check_describe "test1-lightweight-*" --long --tags --match="test3-*" --match="test1-*" HEAD + +test_expect_success 'set-up branches' ' + git branch branch_A A && + git branch branch_C c && + git update-ref refs/remotes/origin/remote_branch_A "A^{commit}" && + git update-ref refs/remotes/origin/remote_branch_C "c^{commit}" && + git update-ref refs/original/original_branch_A test-annotated~2 +' + +check_describe "heads/branch_A*" --all --match="branch_*" --exclude="branch_C" HEAD + +check_describe "remotes/origin/remote_branch_A*" --all --match="origin/remote_branch_*" --exclude="origin/remote_branch_C" HEAD + +check_describe "original/original_branch_A*" --all test-annotated~1 + +test_expect_success '--match does not work for other types' ' + test_must_fail git describe --all --match="*original_branch_*" test-annotated~1 +' + +test_expect_success '--exclude does not work for other types' ' + R=$(git describe --all --exclude="any_pattern_even_not_matching" test-annotated~1) && + case "$R" in + *original_branch_A*) echo "fail: Found unknown reference $R with --exclude" + false;; + *) echo ok: Found some known type;; + esac +' + test_expect_success 'name-rev with exact tags' ' echo A >expect && tag_object=$(git rev-parse refs/tags/A) && @@ -186,6 +229,31 @@ test_expect_success 'name-rev with exact tags' ' test_cmp expect actual ' +test_expect_success 'name-rev --all' ' + >expect.unsorted && + for rev in $(git rev-list --all) + do + git name-rev $rev >>expect.unsorted + done && + sort <expect.unsorted >expect && + git name-rev --all >actual.unsorted && + sort <actual.unsorted >actual && + test_cmp expect actual +' + +test_expect_success 'name-rev --stdin' ' + >expect.unsorted && + for rev in $(git rev-list --all) + do + name=$(git name-rev --name-only $rev) && + echo "$rev ($name)" >>expect.unsorted + done && + sort <expect.unsorted >expect && + git rev-list --all | git name-rev --stdin >actual.unsorted && + sort <actual.unsorted >actual && + test_cmp expect actual +' + test_expect_success 'describe --contains with the exact tags' ' echo "A^0" >expect && tag_object=$(git rev-parse refs/tags/A) && @@ -198,4 +266,124 @@ test_expect_success 'describe --contains with the exact tags' ' test_cmp expect actual ' +test_expect_success 'describe --contains and --match' ' + echo "A^0" >expect && + tagged_commit=$(git rev-parse "refs/tags/A^0") && + test_must_fail git describe --contains --match="B" $tagged_commit && + git describe --contains --match="B" --match="A" $tagged_commit >actual && + test_cmp expect actual +' + +test_expect_success 'describe --exclude' ' + echo "c~1" >expect && + tagged_commit=$(git rev-parse "refs/tags/A^0") && + test_must_fail git describe --contains --match="B" $tagged_commit && + git describe --contains --match="?" --exclude="A" $tagged_commit >actual && + test_cmp expect actual +' + +test_expect_success 'describe --contains and --no-match' ' + echo "A^0" >expect && + tagged_commit=$(git rev-parse "refs/tags/A^0") && + git describe --contains --match="B" --no-match $tagged_commit >actual && + test_cmp expect actual +' + +test_expect_success 'setup and absorb a submodule' ' + test_create_repo sub1 && + test_commit -C sub1 initial && + git submodule add ./sub1 && + git submodule absorbgitdirs && + git commit -a -m "add submodule" && + git describe --dirty >expect && + git describe --broken >out && + test_cmp expect out +' + +test_expect_success 'describe chokes on severely broken submodules' ' + mv .git/modules/sub1/ .git/modules/sub_moved && + test_must_fail git describe --dirty +' +test_expect_success 'describe ignoring a broken submodule' ' + git describe --broken >out && + test_when_finished "mv .git/modules/sub_moved .git/modules/sub1" && + grep broken out +' + +test_expect_success 'describe a blob at a directly tagged commit' ' + echo "make it a unique blob" >file && + git add file && git commit -m "content in file" && + git tag -a -m "latest annotated tag" unique-file && + git describe HEAD:file >actual && + echo "unique-file:file" >expect && + test_cmp expect actual +' + +test_expect_success 'describe a blob with its first introduction' ' + git commit --allow-empty -m "empty commit" && + git rm file && + git commit -m "delete blob" && + git revert HEAD && + git commit --allow-empty -m "empty commit" && + git describe HEAD:file >actual && + echo "unique-file:file" >expect && + test_cmp expect actual +' + +test_expect_success 'describe directly tagged blob' ' + git tag test-blob unique-file:file && + git describe test-blob >actual && + echo "unique-file:file" >expect && + # suboptimal: we rather want to see "test-blob" + test_cmp expect actual +' + +test_expect_success 'describe tag object' ' + git tag test-blob-1 -a -m msg unique-file:file && + test_must_fail git describe test-blob-1 2>actual && + test_i18ngrep "fatal: test-blob-1 is neither a commit nor blob" actual +' + +test_expect_failure ULIMIT_STACK_SIZE 'name-rev works in a deep repo' ' + i=1 && + while test $i -lt 8000 + do + echo "commit refs/heads/master +committer A U Thor <author@example.com> $((1000000000 + $i * 100)) +0200 +data <<EOF +commit #$i +EOF" + test $i = 1 && echo "from refs/heads/master^0" + i=$(($i + 1)) + done | git fast-import && + git checkout master && + git tag far-far-away HEAD^ && + echo "HEAD~4000 tags/far-far-away~3999" >expect && + git name-rev HEAD~4000 >actual && + test_cmp expect actual && + run_with_limited_stack git name-rev HEAD~4000 >actual && + test_cmp expect actual +' + +test_expect_success ULIMIT_STACK_SIZE 'describe works in a deep repo' ' + git tag -f far-far-away HEAD~7999 && + echo "far-far-away" >expect && + git describe --tags --abbrev=0 HEAD~4000 >actual && + test_cmp expect actual && + run_with_limited_stack git describe --tags --abbrev=0 HEAD~4000 >actual && + test_cmp expect actual +' + +check_describe tags/A --all A +check_describe tags/c --all c +check_describe heads/branch_A --all --match='branch_*' branch_A + +test_expect_success 'describe complains about tree object' ' + test_must_fail git describe HEAD^{tree} +' + +test_expect_success 'describe complains about missing object' ' + test_must_fail git describe $_z40 +' + test_done diff --git a/t/t6132-pathspec-exclude.sh b/t/t6132-pathspec-exclude.sh index e1e1b1fa38..eb829fce97 100755 --- a/t/t6132-pathspec-exclude.sh +++ b/t/t6132-pathspec-exclude.sh @@ -7,7 +7,7 @@ test_description='test case exclude pathspec' test_expect_success 'setup' ' for p in file sub/file sub/sub/file sub/file2 sub/sub/sub/file sub2/file; do if echo $p | grep /; then - mkdir -p `dirname $p` + mkdir -p $(dirname $p) fi && : >$p && git add $p && @@ -25,8 +25,10 @@ EOF test_cmp expect actual ' -test_expect_success 'exclude only should error out' ' - test_must_fail git log --oneline --format=%s -- ":(exclude)sub" +test_expect_success 'exclude only pathspec uses default implicit pathspec' ' + git log --oneline --format=%s -- . ":(exclude)sub" >expect && + git log --oneline --format=%s -- ":(exclude)sub" >actual && + test_cmp expect actual ' test_expect_success 't_e_i() exclude sub' ' @@ -181,4 +183,15 @@ EOF test_cmp expect actual ' +test_expect_success 'multiple exclusions' ' + git ls-files -- ":^*/file2" ":^sub2" >actual && + cat <<-\EOF >expect && + file + sub/file + sub/sub/file + sub/sub/sub/file + EOF + test_cmp expect actual +' + test_done diff --git a/t/t6133-pathspec-rev-dwim.sh b/t/t6133-pathspec-rev-dwim.sh new file mode 100755 index 0000000000..a290ffca0d --- /dev/null +++ b/t/t6133-pathspec-rev-dwim.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +test_description='test dwim of revs versus pathspecs in revision parser' +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit base && + echo content >"br[ack]ets" && + git add . && + test_tick && + git commit -m brackets +' + +test_expect_success 'non-rev wildcard dwims to pathspec' ' + git log -- "*.t" >expect && + git log "*.t" >actual && + test_cmp expect actual +' + +test_expect_success 'tree:path with metacharacters dwims to rev' ' + git show "HEAD:br[ack]ets" -- >expect && + git show "HEAD:br[ack]ets" >actual && + test_cmp expect actual +' + +test_expect_success '^{foo} with metacharacters dwims to rev' ' + git log "HEAD^{/b.*}" -- >expect && + git log "HEAD^{/b.*}" >actual && + test_cmp expect actual +' + +test_expect_success '@{foo} with metacharacters dwims to rev' ' + git log "HEAD@{now [or thereabouts]}" -- >expect && + git log "HEAD@{now [or thereabouts]}" >actual && + test_cmp expect actual +' + +test_expect_success ':/*.t from a subdir dwims to a pathspec' ' + mkdir subdir && + ( + cd subdir && + git log -- ":/*.t" >expect && + git log ":/*.t" >actual && + test_cmp expect actual + ) +' + +test_done diff --git a/t/t6134-pathspec-in-submodule.sh b/t/t6134-pathspec-in-submodule.sh new file mode 100755 index 0000000000..c670668409 --- /dev/null +++ b/t/t6134-pathspec-in-submodule.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +test_description='test case exclude pathspec' + +. ./test-lib.sh + +test_expect_success 'setup a submodule' ' + test_create_repo pretzel && + : >pretzel/a && + git -C pretzel add a && + git -C pretzel commit -m "add a file" -- a && + git submodule add ./pretzel sub && + git commit -a -m "add submodule" && + git submodule deinit --all +' + +cat <<EOF >expect +fatal: Pathspec 'sub/a' is in submodule 'sub' +EOF + +test_expect_success 'error message for path inside submodule' ' + echo a >sub/a && + test_must_fail git add sub/a 2>actual && + test_i18ncmp expect actual +' + +test_expect_success 'error message for path inside submodule from within submodule' ' + test_must_fail git -C sub add . 2>actual && + test_i18ngrep "in unpopulated submodule" actual +' + +test_done diff --git a/t/t6135-pathspec-with-attrs.sh b/t/t6135-pathspec-with-attrs.sh new file mode 100755 index 0000000000..77b8cef661 --- /dev/null +++ b/t/t6135-pathspec-with-attrs.sh @@ -0,0 +1,200 @@ +#!/bin/sh + +test_description='test labels in pathspecs' +. ./test-lib.sh + +test_expect_success 'setup a tree' ' + cat <<-\EOF >expect && + fileA + fileAB + fileAC + fileB + fileBC + fileC + fileNoLabel + fileSetLabel + fileUnsetLabel + fileValue + fileWrongLabel + sub/fileA + sub/fileAB + sub/fileAC + sub/fileB + sub/fileBC + sub/fileC + sub/fileNoLabel + sub/fileSetLabel + sub/fileUnsetLabel + sub/fileValue + sub/fileWrongLabel + EOF + mkdir sub && + while read path + do + : >$path && + git add $path || return 1 + done <expect && + git commit -m "initial commit" && + git ls-files >actual && + test_cmp expect actual +' + +test_expect_success 'pathspec with no attr' ' + test_must_fail git ls-files ":(attr:)" +' + +test_expect_success 'pathspec with labels and non existent .gitattributes' ' + git ls-files ":(attr:label)" >actual && + test_must_be_empty actual +' + +test_expect_success 'setup .gitattributes' ' + cat <<-\EOF >.gitattributes && + fileA labelA + fileB labelB + fileC labelC + fileAB labelA labelB + fileAC labelA labelC + fileBC labelB labelC + fileUnsetLabel -label + fileSetLabel label + fileValue label=foo + fileWrongLabel label☺ + EOF + git add .gitattributes && + git commit -m "add attributes" +' + +test_expect_success 'check specific set attr' ' + cat <<-\EOF >expect && + fileSetLabel + sub/fileSetLabel + EOF + git ls-files ":(attr:label)" >actual && + test_cmp expect actual +' + +test_expect_success 'check specific unset attr' ' + cat <<-\EOF >expect && + fileUnsetLabel + sub/fileUnsetLabel + EOF + git ls-files ":(attr:-label)" >actual && + test_cmp expect actual +' + +test_expect_success 'check specific value attr' ' + cat <<-\EOF >expect && + fileValue + sub/fileValue + EOF + git ls-files ":(attr:label=foo)" >actual && + test_cmp expect actual && + git ls-files ":(attr:label=bar)" >actual && + test_must_be_empty actual +' + +test_expect_success 'check unspecified attr' ' + cat <<-\EOF >expect && + .gitattributes + fileA + fileAB + fileAC + fileB + fileBC + fileC + fileNoLabel + fileWrongLabel + sub/fileA + sub/fileAB + sub/fileAC + sub/fileB + sub/fileBC + sub/fileC + sub/fileNoLabel + sub/fileWrongLabel + EOF + git ls-files ":(attr:!label)" >actual && + test_cmp expect actual +' + +test_expect_success 'check multiple unspecified attr' ' + cat <<-\EOF >expect && + .gitattributes + fileC + fileNoLabel + fileWrongLabel + sub/fileC + sub/fileNoLabel + sub/fileWrongLabel + EOF + git ls-files ":(attr:!labelB !labelA !label)" >actual && + test_cmp expect actual +' + +test_expect_success 'check label with more labels but excluded path' ' + cat <<-\EOF >expect && + fileAB + fileB + fileBC + EOF + git ls-files ":(attr:labelB)" ":(exclude)sub/" >actual && + test_cmp expect actual +' + +test_expect_success 'check label excluding other labels' ' + cat <<-\EOF >expect && + fileAB + fileB + fileBC + sub/fileAB + sub/fileB + EOF + git ls-files ":(attr:labelB)" ":(exclude,attr:labelC)sub/" >actual && + test_cmp expect actual +' + +test_expect_success 'fail on multiple attr specifiers in one pathspec item' ' + test_must_fail git ls-files . ":(attr:labelB,attr:labelC)" 2>actual && + test_i18ngrep "Only one" actual +' + +test_expect_success 'fail if attr magic is used places not implemented' ' + # The main purpose of this test is to check that we actually fail + # when you attempt to use attr magic in commands that do not implement + # attr magic. This test does not advocate git-add to stay that way, + # though, but git-add is convenient as it has its own internal pathspec + # parsing. + test_must_fail git add ":(attr:labelB)" 2>actual && + test_i18ngrep "unsupported magic" actual +' + +test_expect_success 'abort on giving invalid label on the command line' ' + test_must_fail git ls-files . ":(attr:☺)" +' + +test_expect_success 'abort on asking for wrong magic' ' + test_must_fail git ls-files . ":(attr:-label=foo)" && + test_must_fail git ls-files . ":(attr:!label=foo)" +' + +test_expect_success 'check attribute list' ' + cat <<-EOF >>.gitattributes && + * whitespace=indent,trail,space + EOF + git ls-files ":(attr:whitespace=indent\,trail\,space)" >actual && + git ls-files >expect && + test_cmp expect actual +' + +test_expect_success 'backslash cannot be the last character' ' + test_must_fail git ls-files ":(attr:label=foo\\ labelA=bar)" 2>actual && + test_i18ngrep "not allowed as last character in attr value" actual +' + +test_expect_success 'backslash cannot be used as a value' ' + test_must_fail git ls-files ":(attr:label=f\\\oo)" 2>actual && + test_i18ngrep "for value matching" actual +' + +test_done diff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh index 2e2fb0e957..a54a52aaa4 100755 --- a/t/t6200-fmt-merge-msg.sh +++ b/t/t6200-fmt-merge-msg.sh @@ -512,7 +512,7 @@ test_expect_success 'merge-msg with "merging" an annotated tag' ' test_when_finished "git reset --hard" && annote=$(git rev-parse annote) && - git merge --no-commit $annote && + git merge --no-commit --no-ff $annote && { cat <<-EOF Merge tag '\''$annote'\'' diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 24fc2ba55d..295d1475bd 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -7,9 +7,10 @@ test_description='for-each-ref test' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-gpg.sh +. "$TEST_DIRECTORY"/lib-terminal.sh -# Mon Jul 3 15:18:43 2006 +0000 -datestamp=1151939923 +# Mon Jul 3 23:18:43 2006 +0000 +datestamp=1151968723 setdate_and_increment () { GIT_COMMITTER_DATE="$datestamp +0200" datestamp=$(expr "$datestamp" + 1) @@ -38,6 +39,7 @@ test_atom() { case "$1" in head) ref=refs/heads/master ;; tag) ref=refs/tags/testtag ;; + sym) ref=refs/heads/sym ;; *) ref=$1 ;; esac printf '%s\n' "$3" >expected @@ -49,11 +51,42 @@ test_atom() { } test_atom head refname refs/heads/master +test_atom head refname: refs/heads/master +test_atom head refname:short master +test_atom head refname:lstrip=1 heads/master +test_atom head refname:lstrip=2 master +test_atom head refname:lstrip=-1 master +test_atom head refname:lstrip=-2 heads/master +test_atom head refname:rstrip=1 refs/heads +test_atom head refname:rstrip=2 refs +test_atom head refname:rstrip=-1 refs +test_atom head refname:rstrip=-2 refs/heads +test_atom head refname:strip=1 heads/master +test_atom head refname:strip=2 master +test_atom head refname:strip=-1 master +test_atom head refname:strip=-2 heads/master test_atom head upstream refs/remotes/origin/master +test_atom head upstream:short origin/master +test_atom head upstream:lstrip=2 origin/master +test_atom head upstream:lstrip=-2 origin/master +test_atom head upstream:rstrip=2 refs/remotes +test_atom head upstream:rstrip=-2 refs/remotes +test_atom head upstream:strip=2 origin/master +test_atom head upstream:strip=-2 origin/master test_atom head push refs/remotes/myfork/master +test_atom head push:short myfork/master +test_atom head push:lstrip=1 remotes/myfork/master +test_atom head push:lstrip=-1 master +test_atom head push:rstrip=1 refs/remotes/myfork +test_atom head push:rstrip=-1 refs +test_atom head push:strip=1 remotes/myfork/master +test_atom head push:strip=-1 master test_atom head objecttype commit test_atom head objectsize 171 test_atom head objectname $(git rev-parse refs/heads/master) +test_atom head objectname:short $(git rev-parse --short refs/heads/master) +test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/master) +test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/master) test_atom head tree $(git rev-parse refs/heads/master^{tree}) test_atom head parent '' test_atom head numparent 0 @@ -61,21 +94,21 @@ test_atom head object '' test_atom head type '' test_atom head '*objectname' '' test_atom head '*objecttype' '' -test_atom head author 'A U Thor <author@example.com> 1151939924 +0200' +test_atom head author 'A U Thor <author@example.com> 1151968724 +0200' test_atom head authorname 'A U Thor' test_atom head authoremail '<author@example.com>' -test_atom head authordate 'Mon Jul 3 17:18:44 2006 +0200' -test_atom head committer 'C O Mitter <committer@example.com> 1151939923 +0200' +test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200' +test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200' test_atom head committername 'C O Mitter' test_atom head committeremail '<committer@example.com>' -test_atom head committerdate 'Mon Jul 3 17:18:43 2006 +0200' +test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200' test_atom head tag '' test_atom head tagger '' test_atom head taggername '' test_atom head taggeremail '' test_atom head taggerdate '' -test_atom head creator 'C O Mitter <committer@example.com> 1151939923 +0200' -test_atom head creatordate 'Mon Jul 3 17:18:43 2006 +0200' +test_atom head creator 'C O Mitter <committer@example.com> 1151968723 +0200' +test_atom head creatordate 'Tue Jul 4 01:18:43 2006 +0200' test_atom head subject 'Initial' test_atom head contents:subject 'Initial' test_atom head body '' @@ -86,17 +119,21 @@ test_atom head contents 'Initial test_atom head HEAD '*' test_atom tag refname refs/tags/testtag +test_atom tag refname:short testtag test_atom tag upstream '' test_atom tag push '' test_atom tag objecttype tag test_atom tag objectsize 154 test_atom tag objectname $(git rev-parse refs/tags/testtag) +test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag) +test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/master) +test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/master) test_atom tag tree '' test_atom tag parent '' test_atom tag numparent '' test_atom tag object $(git rev-parse refs/tags/testtag^0) test_atom tag type 'commit' -test_atom tag '*objectname' '67a36f10722846e891fbada1ba48ed035de75581' +test_atom tag '*objectname' 'ea122842f48be4afb2d1fc6a4b96c05885ab7463' test_atom tag '*objecttype' 'commit' test_atom tag author '' test_atom tag authorname '' @@ -107,18 +144,18 @@ test_atom tag committername '' test_atom tag committeremail '' test_atom tag committerdate '' test_atom tag tag 'testtag' -test_atom tag tagger 'C O Mitter <committer@example.com> 1151939925 +0200' +test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200' test_atom tag taggername 'C O Mitter' test_atom tag taggeremail '<committer@example.com>' -test_atom tag taggerdate 'Mon Jul 3 17:18:45 2006 +0200' -test_atom tag creator 'C O Mitter <committer@example.com> 1151939925 +0200' -test_atom tag creatordate 'Mon Jul 3 17:18:45 2006 +0200' -test_atom tag subject 'Tagging at 1151939927' -test_atom tag contents:subject 'Tagging at 1151939927' +test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200' +test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200' +test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200' +test_atom tag subject 'Tagging at 1151968727' +test_atom tag contents:subject 'Tagging at 1151968727' test_atom tag body '' test_atom tag contents:body '' test_atom tag contents:signature '' -test_atom tag contents 'Tagging at 1151939927 +test_atom tag contents 'Tagging at 1151968727 ' test_atom tag HEAD ' ' @@ -146,84 +183,136 @@ test_expect_success 'Check invalid format specifiers are errors' ' test_must_fail git for-each-ref --format="%(authordate:INVALID)" refs/heads ' -cat >expected <<\EOF -'refs/heads/master' 'Mon Jul 3 17:18:43 2006 +0200' 'Mon Jul 3 17:18:44 2006 +0200' -'refs/tags/testtag' 'Mon Jul 3 17:18:45 2006 +0200' -EOF +test_expect_success 'arguments to %(objectname:short=) must be positive integers' ' + test_must_fail git for-each-ref --format="%(objectname:short=0)" && + test_must_fail git for-each-ref --format="%(objectname:short=-1)" && + test_must_fail git for-each-ref --format="%(objectname:short=foo)" +' -test_expect_success 'Check unformatted date fields output' ' - (git for-each-ref --shell --format="%(refname) %(committerdate) %(authordate)" refs/heads && - git for-each-ref --shell --format="%(refname) %(taggerdate)" refs/tags) >actual && +test_date () { + f=$1 && + committer_date=$2 && + author_date=$3 && + tagger_date=$4 && + cat >expected <<-EOF && + 'refs/heads/master' '$committer_date' '$author_date' + 'refs/tags/testtag' '$tagger_date' + EOF + ( + git for-each-ref --shell \ + --format="%(refname) %(committerdate${f:+:$f}) %(authordate${f:+:$f})" \ + refs/heads && + git for-each-ref --shell \ + --format="%(refname) %(taggerdate${f:+:$f})" \ + refs/tags + ) >actual && test_cmp expected actual +} + +test_expect_success 'Check unformatted date fields output' ' + test_date "" \ + "Tue Jul 4 01:18:43 2006 +0200" \ + "Tue Jul 4 01:18:44 2006 +0200" \ + "Tue Jul 4 01:18:45 2006 +0200" ' test_expect_success 'Check format "default" formatted date fields output' ' - f=default && - (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && - git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual && - test_cmp expected actual + test_date default \ + "Tue Jul 4 01:18:43 2006 +0200" \ + "Tue Jul 4 01:18:44 2006 +0200" \ + "Tue Jul 4 01:18:45 2006 +0200" +' + +test_expect_success 'Check format "default-local" date fields output' ' + test_date default-local "Mon Jul 3 23:18:43 2006" "Mon Jul 3 23:18:44 2006" "Mon Jul 3 23:18:45 2006" ' # Don't know how to do relative check because I can't know when this script # is going to be run and can't fake the current time to git, and hence can't # provide expected output. Instead, I'll just make sure that "relative" # doesn't exit in error -# -#cat >expected <<\EOF -# -#EOF -# test_expect_success 'Check format "relative" date fields output' ' f=relative && (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual ' -cat >expected <<\EOF -'refs/heads/master' '2006-07-03' '2006-07-03' -'refs/tags/testtag' '2006-07-03' -EOF +# We just check that this is the same as "relative" for now. +test_expect_success 'Check format "relative-local" date fields output' ' + test_date relative-local \ + "$(git for-each-ref --format="%(committerdate:relative)" refs/heads)" \ + "$(git for-each-ref --format="%(authordate:relative)" refs/heads)" \ + "$(git for-each-ref --format="%(taggerdate:relative)" refs/tags)" +' test_expect_success 'Check format "short" date fields output' ' - f=short && - (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && - git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual && - test_cmp expected actual + test_date short 2006-07-04 2006-07-04 2006-07-04 ' -cat >expected <<\EOF -'refs/heads/master' 'Mon Jul 3 15:18:43 2006' 'Mon Jul 3 15:18:44 2006' -'refs/tags/testtag' 'Mon Jul 3 15:18:45 2006' -EOF +test_expect_success 'Check format "short-local" date fields output' ' + test_date short-local 2006-07-03 2006-07-03 2006-07-03 +' test_expect_success 'Check format "local" date fields output' ' - f=local && - (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && - git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual && - test_cmp expected actual + test_date local \ + "Mon Jul 3 23:18:43 2006" \ + "Mon Jul 3 23:18:44 2006" \ + "Mon Jul 3 23:18:45 2006" ' -cat >expected <<\EOF -'refs/heads/master' '2006-07-03 17:18:43 +0200' '2006-07-03 17:18:44 +0200' -'refs/tags/testtag' '2006-07-03 17:18:45 +0200' -EOF - test_expect_success 'Check format "iso8601" date fields output' ' - f=iso8601 && - (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && - git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual && - test_cmp expected actual + test_date iso8601 \ + "2006-07-04 01:18:43 +0200" \ + "2006-07-04 01:18:44 +0200" \ + "2006-07-04 01:18:45 +0200" ' -cat >expected <<\EOF -'refs/heads/master' 'Mon, 3 Jul 2006 17:18:43 +0200' 'Mon, 3 Jul 2006 17:18:44 +0200' -'refs/tags/testtag' 'Mon, 3 Jul 2006 17:18:45 +0200' -EOF +test_expect_success 'Check format "iso8601-local" date fields output' ' + test_date iso8601-local "2006-07-03 23:18:43 +0000" "2006-07-03 23:18:44 +0000" "2006-07-03 23:18:45 +0000" +' test_expect_success 'Check format "rfc2822" date fields output' ' - f=rfc2822 && - (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && - git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual && + test_date rfc2822 \ + "Tue, 4 Jul 2006 01:18:43 +0200" \ + "Tue, 4 Jul 2006 01:18:44 +0200" \ + "Tue, 4 Jul 2006 01:18:45 +0200" +' + +test_expect_success 'Check format "rfc2822-local" date fields output' ' + test_date rfc2822-local "Mon, 3 Jul 2006 23:18:43 +0000" "Mon, 3 Jul 2006 23:18:44 +0000" "Mon, 3 Jul 2006 23:18:45 +0000" +' + +test_expect_success 'Check format "raw" date fields output' ' + test_date raw "1151968723 +0200" "1151968724 +0200" "1151968725 +0200" +' + +test_expect_success 'Check format "raw-local" date fields output' ' + test_date raw-local "1151968723 +0000" "1151968724 +0000" "1151968725 +0000" +' + +test_expect_success 'Check format of strftime date fields' ' + echo "my date is 2006-07-04" >expected && + git for-each-ref \ + --format="%(authordate:format:my date is %Y-%m-%d)" \ + refs/heads >actual && + test_cmp expected actual +' + +test_expect_success 'Check format of strftime-local date fields' ' + echo "my date is 2006-07-03" >expected && + git for-each-ref \ + --format="%(authordate:format-local:my date is %Y-%m-%d)" \ + refs/heads >actual && + test_cmp expected actual +' + +test_expect_success 'exercise strftime with odd fields' ' + echo >expected && + git for-each-ref --format="%(authordate:format:)" refs/heads >actual && + test_cmp expected actual && + long="long format -- $_z40$_z40$_z40$_z40$_z40$_z40$_z40" && + echo $long >expected && + git for-each-ref --format="%(authordate:format:$long)" refs/heads >actual && test_cmp expected actual ' @@ -284,55 +373,21 @@ test_expect_success 'Quoting style: tcl' ' for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do test_expect_success "more than one quoting style: $i" " - git for-each-ref $i 2>&1 | (read line && - case \$line in - \"error: more than one quoting style\"*) : happy;; - *) false - esac) + test_must_fail git for-each-ref $i 2>err && + grep '^error: more than one quoting style' err " done -cat >expected <<\EOF -master -testtag -EOF - -test_expect_success 'Check short refname format' ' - (git for-each-ref --format="%(refname:short)" refs/heads && - git for-each-ref --format="%(refname:short)" refs/tags) >actual && - test_cmp expected actual -' - -cat >expected <<EOF -origin/master -EOF - -test_expect_success 'Check short upstream format' ' - git for-each-ref --format="%(upstream:short)" refs/heads >actual && - test_cmp expected actual -' - test_expect_success 'setup for upstream:track[short]' ' test_commit two ' -cat >expected <<EOF -[ahead 1] -EOF - -test_expect_success 'Check upstream:track format' ' - git for-each-ref --format="%(upstream:track)" refs/heads >actual && - test_cmp expected actual -' - -cat >expected <<EOF -> -EOF - -test_expect_success 'Check upstream:trackshort format' ' - git for-each-ref --format="%(upstream:trackshort)" refs/heads >actual && - test_cmp expected actual -' +test_atom head upstream:track '[ahead 1]' +test_atom head upstream:trackshort '>' +test_atom head upstream:track,nobracket 'ahead 1' +test_atom head upstream:nobracket,track 'ahead 1' +test_atom head push:track '[ahead 1]' +test_atom head push:trackshort '>' test_expect_success 'Check that :track[short] cannot be used with other atoms' ' test_must_fail git for-each-ref --format="%(refname:track)" 2>/dev/null && @@ -341,7 +396,7 @@ test_expect_success 'Check that :track[short] cannot be used with other atoms' ' test_expect_success 'Check that :track[short] works when upstream is invalid' ' cat >expected <<-\EOF && - + [gone] EOF test_when_finished "git config branch.master.merge refs/heads/master" && @@ -352,40 +407,41 @@ test_expect_success 'Check that :track[short] works when upstream is invalid' ' test_cmp expected actual ' -test_expect_success '%(push) supports tracking specifiers, too' ' - echo "[ahead 1]" >expected && - git for-each-ref --format="%(push:track)" refs/heads >actual && - test_cmp expected actual +test_expect_success 'Check for invalid refname format' ' + test_must_fail git for-each-ref --format="%(refname:INVALID)" ' -cat >expected <<EOF -$(git rev-parse --short HEAD) -EOF - -test_expect_success 'Check short objectname format' ' - git for-each-ref --format="%(objectname:short)" refs/heads >actual && - test_cmp expected actual +test_expect_success 'set up color tests' ' + cat >expected.color <<-EOF && + $(git rev-parse --short refs/heads/master) <GREEN>master<RESET> + $(git rev-parse --short refs/remotes/origin/master) <GREEN>origin/master<RESET> + $(git rev-parse --short refs/tags/testtag) <GREEN>testtag<RESET> + $(git rev-parse --short refs/tags/two) <GREEN>two<RESET> + EOF + sed "s/<[^>]*>//g" <expected.color >expected.bare && + color_format="%(objectname:short) %(color:green)%(refname:short)" ' -test_expect_success 'Check for invalid refname format' ' - test_must_fail git for-each-ref --format="%(refname:INVALID)" +test_expect_success TTY '%(color) shows color with a tty' ' + test_terminal git for-each-ref --format="$color_format" >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expected.color actual ' -get_color () -{ - git config --get-color no.such.slot "$1" -} +test_expect_success '%(color) does not show color without tty' ' + TERM=vt100 git for-each-ref --format="$color_format" >actual && + test_cmp expected.bare actual +' -cat >expected <<EOF -$(git rev-parse --short refs/heads/master) $(get_color green)master$(get_color reset) -$(git rev-parse --short refs/remotes/origin/master) $(get_color green)origin/master$(get_color reset) -$(git rev-parse --short refs/tags/testtag) $(get_color green)testtag$(get_color reset) -$(git rev-parse --short refs/tags/two) $(get_color green)two$(get_color reset) -EOF +test_expect_success '--color can override tty check' ' + git for-each-ref --color --format="$color_format" >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expected.color actual +' -test_expect_success 'Check %(color:...) ' ' - git for-each-ref --format="%(objectname:short) %(color:green)%(refname:short)" >actual && - test_cmp expected actual +test_expect_success 'color.ui=always does not override tty check' ' + git -c color.ui=always for-each-ref --format="$color_format" >actual && + test_cmp expected.bare actual ' cat >expected <<\EOF @@ -528,8 +584,8 @@ body contents $sig" cat >expected <<EOF -$(git rev-parse refs/tags/master) <committer@example.com> refs/tags/master $(git rev-parse refs/tags/bogo) <committer@example.com> refs/tags/bogo +$(git rev-parse refs/tags/master) <committer@example.com> refs/tags/master EOF test_expect_success 'Verify sort with multiple keys' ' @@ -537,4 +593,206 @@ test_expect_success 'Verify sort with multiple keys' ' refs/tags/bogo refs/tags/master > actual && test_cmp expected actual ' + + +test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' ' + test_when_finished "git checkout master" && + git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual && + sed -e "s/^\* / /" actual >expect && + git checkout --orphan orphaned-branch && + git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual && + test_cmp expect actual +' + +cat >trailers <<EOF +Reviewed-by: A U Thor <author@example.com> +Signed-off-by: A U Thor <author@example.com> +[ v2 updated patch description ] +Acked-by: A U Thor + <author@example.com> +EOF + +unfold () { + perl -0pe 's/\n\s+/ /g' +} + +test_expect_success 'set up trailers for next test' ' + echo "Some contents" > two && + git add two && + git commit -F - <<-EOF + trailers: this commit message has trailers + + Some message contents + + $(cat trailers) + EOF +' + +test_expect_success '%(trailers:unfold) unfolds trailers' ' + git for-each-ref --format="%(trailers:unfold)" refs/heads/master >actual && + { + unfold <trailers + echo + } >expect && + test_cmp expect actual +' + +test_expect_success '%(trailers:only) shows only "key: value" trailers' ' + git for-each-ref --format="%(trailers:only)" refs/heads/master >actual && + { + grep -v patch.description <trailers && + echo + } >expect && + test_cmp expect actual +' + +test_expect_success '%(trailers:only) and %(trailers:unfold) work together' ' + git for-each-ref --format="%(trailers:only,unfold)" refs/heads/master >actual && + git for-each-ref --format="%(trailers:unfold,only)" refs/heads/master >reverse && + test_cmp actual reverse && + { + grep -v patch.description <trailers | unfold && + echo + } >expect && + test_cmp expect actual +' + +test_expect_success '%(contents:trailers:unfold) unfolds trailers' ' + git for-each-ref --format="%(contents:trailers:unfold)" refs/heads/master >actual && + { + unfold <trailers + echo + } >expect && + test_cmp expect actual +' + +test_expect_success '%(contents:trailers:only) shows only "key: value" trailers' ' + git for-each-ref --format="%(contents:trailers:only)" refs/heads/master >actual && + { + grep -v patch.description <trailers && + echo + } >expect && + test_cmp expect actual +' + +test_expect_success '%(contents:trailers:only) and %(contents:trailers:unfold) work together' ' + git for-each-ref --format="%(contents:trailers:only,unfold)" refs/heads/master >actual && + git for-each-ref --format="%(contents:trailers:unfold,only)" refs/heads/master >reverse && + test_cmp actual reverse && + { + grep -v patch.description <trailers | unfold && + echo + } >expect && + test_cmp expect actual +' + +test_expect_success '%(trailers) rejects unknown trailers arguments' ' + # error message cannot be checked under i18n + cat >expect <<-EOF && + fatal: unknown %(trailers) argument: unsupported + EOF + test_must_fail git for-each-ref --format="%(trailers:unsupported)" 2>actual && + test_i18ncmp expect actual +' + +test_expect_success '%(contents:trailers) rejects unknown trailers arguments' ' + # error message cannot be checked under i18n + cat >expect <<-EOF && + fatal: unknown %(trailers) argument: unsupported + EOF + test_must_fail git for-each-ref --format="%(contents:trailers:unsupported)" 2>actual && + test_i18ncmp expect actual +' + +test_expect_success 'basic atom: head contents:trailers' ' + git for-each-ref --format="%(contents:trailers)" refs/heads/master >actual && + sanitize_pgp <actual >actual.clean && + # git for-each-ref ends with a blank line + cat >expect <<-EOF && + $(cat trailers) + + EOF + test_cmp expect actual.clean +' + +test_expect_success 'Add symbolic ref for the following tests' ' + git symbolic-ref refs/heads/sym refs/heads/master +' + +cat >expected <<EOF +refs/heads/master +EOF + +test_expect_success 'Verify usage of %(symref) atom' ' + git for-each-ref --format="%(symref)" refs/heads/sym >actual && + test_cmp expected actual +' + +cat >expected <<EOF +heads/master +EOF + +test_expect_success 'Verify usage of %(symref:short) atom' ' + git for-each-ref --format="%(symref:short)" refs/heads/sym >actual && + test_cmp expected actual +' + +cat >expected <<EOF +master +heads/master +EOF + +test_expect_success 'Verify usage of %(symref:lstrip) atom' ' + git for-each-ref --format="%(symref:lstrip=2)" refs/heads/sym > actual && + git for-each-ref --format="%(symref:lstrip=-2)" refs/heads/sym >> actual && + test_cmp expected actual && + + git for-each-ref --format="%(symref:strip=2)" refs/heads/sym > actual && + git for-each-ref --format="%(symref:strip=-2)" refs/heads/sym >> actual && + test_cmp expected actual +' + +cat >expected <<EOF +refs +refs/heads +EOF + +test_expect_success 'Verify usage of %(symref:rstrip) atom' ' + git for-each-ref --format="%(symref:rstrip=2)" refs/heads/sym > actual && + git for-each-ref --format="%(symref:rstrip=-2)" refs/heads/sym >> actual && + test_cmp expected actual +' + +test_expect_success ':remotename and :remoteref' ' + git init remote-tests && + ( + cd remote-tests && + test_commit initial && + git remote add from fifth.coffee:blub && + git config branch.master.remote from && + git config branch.master.merge refs/heads/stable && + git remote add to southridge.audio:repo && + git config remote.to.push "refs/heads/*:refs/heads/pushed/*" && + git config branch.master.pushRemote to && + for pair in "%(upstream)=refs/remotes/from/stable" \ + "%(upstream:remotename)=from" \ + "%(upstream:remoteref)=refs/heads/stable" \ + "%(push)=refs/remotes/to/pushed/master" \ + "%(push:remotename)=to" \ + "%(push:remoteref)=refs/heads/pushed/master" + do + echo "${pair#*=}" >expect && + git for-each-ref --format="${pair%=*}" \ + refs/heads/master >actual && + test_cmp expect actual + done && + git branch push-simple && + git config branch.push-simple.pushRemote from && + actual="$(git for-each-ref \ + --format="%(push:remotename),%(push:remoteref)" \ + refs/heads/push-simple)" && + test from, = "$actual" + ) +' + test_done diff --git a/t/t6301-for-each-ref-errors.sh b/t/t6301-for-each-ref-errors.sh new file mode 100755 index 0000000000..c734ce2388 --- /dev/null +++ b/t/t6301-for-each-ref-errors.sh @@ -0,0 +1,56 @@ +#!/bin/sh + +test_description='for-each-ref errors for broken refs' + +. ./test-lib.sh + +ZEROS=$_z40 +MISSING=abababababababababababababababababababab + +test_expect_success setup ' + git commit --allow-empty -m "Initial" && + git tag testtag && + git for-each-ref >full-list && + git for-each-ref --format="%(objectname) %(refname)" >brief-list +' + +test_expect_success 'Broken refs are reported correctly' ' + r=refs/heads/bogus && + : >.git/$r && + test_when_finished "rm -f .git/$r" && + echo "warning: ignoring broken ref $r" >broken-err && + git for-each-ref >out 2>err && + test_i18ncmp full-list out && + test_i18ncmp broken-err err +' + +test_expect_success 'NULL_SHA1 refs are reported correctly' ' + r=refs/heads/zeros && + echo $ZEROS >.git/$r && + test_when_finished "rm -f .git/$r" && + echo "warning: ignoring broken ref $r" >zeros-err && + git for-each-ref >out 2>err && + test_cmp full-list out && + test_i18ncmp zeros-err err && + git for-each-ref --format="%(objectname) %(refname)" >brief-out 2>brief-err && + test_cmp brief-list brief-out && + test_i18ncmp zeros-err brief-err +' + +test_expect_success 'Missing objects are reported correctly' ' + r=refs/heads/missing && + echo $MISSING >.git/$r && + test_when_finished "rm -f .git/$r" && + echo "fatal: missing object $MISSING for $r" >missing-err && + test_must_fail git for-each-ref 2>err && + test_i18ncmp missing-err err && + ( + cat brief-list && + echo "$MISSING $r" + ) | sort -k 2 >missing-brief-expected && + git for-each-ref --format="%(objectname) %(refname)" >brief-out 2>brief-err && + test_cmp missing-brief-expected brief-out && + test_must_be_empty brief-err +' + +test_done diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh new file mode 100755 index 0000000000..fc067ed672 --- /dev/null +++ b/t/t6302-for-each-ref-filter.sh @@ -0,0 +1,444 @@ +#!/bin/sh + +test_description='test for-each-refs usage of ref-filter APIs' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-gpg.sh + +test_expect_success 'setup some history and refs' ' + test_commit one && + test_commit two && + test_commit three && + git checkout -b side && + test_commit four && + git tag -m "An annotated tag" annotated-tag && + git tag -m "Annonated doubly" doubly-annotated-tag annotated-tag && + + # Note that these "signed" tags might not actually be signed. + # Tests which care about the distinction should be marked + # with the GPG prereq. + if test_have_prereq GPG + then + sign=-s + else + sign= + fi && + git tag $sign -m "A signed tag" signed-tag && + git tag $sign -m "Signed doubly" doubly-signed-tag signed-tag && + + git checkout master && + git update-ref refs/odd/spot master +' + +test_expect_success 'filtering with --points-at' ' + cat >expect <<-\EOF && + refs/heads/master + refs/odd/spot + refs/tags/three + EOF + git for-each-ref --format="%(refname)" --points-at=master >actual && + test_cmp expect actual +' + +test_expect_success 'check signed tags with --points-at' ' + sed -e "s/Z$//" >expect <<-\EOF && + refs/heads/side Z + refs/tags/annotated-tag four + refs/tags/four Z + refs/tags/signed-tag four + EOF + git for-each-ref --format="%(refname) %(*subject)" --points-at=side >actual && + test_cmp expect actual +' + +test_expect_success 'filtering with --merged' ' + cat >expect <<-\EOF && + refs/heads/master + refs/odd/spot + refs/tags/one + refs/tags/three + refs/tags/two + EOF + git for-each-ref --format="%(refname)" --merged=master >actual && + test_cmp expect actual +' + +test_expect_success 'filtering with --no-merged' ' + cat >expect <<-\EOF && + refs/heads/side + refs/tags/annotated-tag + refs/tags/doubly-annotated-tag + refs/tags/doubly-signed-tag + refs/tags/four + refs/tags/signed-tag + EOF + git for-each-ref --format="%(refname)" --no-merged=master >actual && + test_cmp expect actual +' + +test_expect_success 'filtering with --contains' ' + cat >expect <<-\EOF && + refs/heads/master + refs/heads/side + refs/odd/spot + refs/tags/annotated-tag + refs/tags/doubly-annotated-tag + refs/tags/doubly-signed-tag + refs/tags/four + refs/tags/signed-tag + refs/tags/three + refs/tags/two + EOF + git for-each-ref --format="%(refname)" --contains=two >actual && + test_cmp expect actual +' + +test_expect_success 'filtering with --no-contains' ' + cat >expect <<-\EOF && + refs/tags/one + EOF + git for-each-ref --format="%(refname)" --no-contains=two >actual && + test_cmp expect actual +' + +test_expect_success 'filtering with --contains and --no-contains' ' + cat >expect <<-\EOF && + refs/tags/two + EOF + git for-each-ref --format="%(refname)" --contains=two --no-contains=three >actual && + test_cmp expect actual +' + +test_expect_success '%(color) must fail' ' + test_must_fail git for-each-ref --format="%(color)%(refname)" +' + +test_expect_success 'left alignment is default' ' + cat >expect <<-\EOF && + refname is refs/heads/master |refs/heads/master + refname is refs/heads/side |refs/heads/side + refname is refs/odd/spot |refs/odd/spot + refname is refs/tags/annotated-tag|refs/tags/annotated-tag + refname is refs/tags/doubly-annotated-tag|refs/tags/doubly-annotated-tag + refname is refs/tags/doubly-signed-tag|refs/tags/doubly-signed-tag + refname is refs/tags/four |refs/tags/four + refname is refs/tags/one |refs/tags/one + refname is refs/tags/signed-tag|refs/tags/signed-tag + refname is refs/tags/three |refs/tags/three + refname is refs/tags/two |refs/tags/two + EOF + git for-each-ref --format="%(align:30)refname is %(refname)%(end)|%(refname)" >actual && + test_cmp expect actual +' + +test_expect_success 'middle alignment' ' + cat >expect <<-\EOF && + | refname is refs/heads/master |refs/heads/master + | refname is refs/heads/side |refs/heads/side + | refname is refs/odd/spot |refs/odd/spot + |refname is refs/tags/annotated-tag|refs/tags/annotated-tag + |refname is refs/tags/doubly-annotated-tag|refs/tags/doubly-annotated-tag + |refname is refs/tags/doubly-signed-tag|refs/tags/doubly-signed-tag + | refname is refs/tags/four |refs/tags/four + | refname is refs/tags/one |refs/tags/one + |refname is refs/tags/signed-tag|refs/tags/signed-tag + | refname is refs/tags/three |refs/tags/three + | refname is refs/tags/two |refs/tags/two + EOF + git for-each-ref --format="|%(align:middle,30)refname is %(refname)%(end)|%(refname)" >actual && + test_cmp expect actual +' + +test_expect_success 'right alignment' ' + cat >expect <<-\EOF && + | refname is refs/heads/master|refs/heads/master + | refname is refs/heads/side|refs/heads/side + | refname is refs/odd/spot|refs/odd/spot + |refname is refs/tags/annotated-tag|refs/tags/annotated-tag + |refname is refs/tags/doubly-annotated-tag|refs/tags/doubly-annotated-tag + |refname is refs/tags/doubly-signed-tag|refs/tags/doubly-signed-tag + | refname is refs/tags/four|refs/tags/four + | refname is refs/tags/one|refs/tags/one + |refname is refs/tags/signed-tag|refs/tags/signed-tag + | refname is refs/tags/three|refs/tags/three + | refname is refs/tags/two|refs/tags/two + EOF + git for-each-ref --format="|%(align:30,right)refname is %(refname)%(end)|%(refname)" >actual && + test_cmp expect actual +' + +cat >expect <<-\EOF +| refname is refs/heads/master |refs/heads/master +| refname is refs/heads/side |refs/heads/side +| refname is refs/odd/spot |refs/odd/spot +| refname is refs/tags/annotated-tag |refs/tags/annotated-tag +|refname is refs/tags/doubly-annotated-tag |refs/tags/doubly-annotated-tag +| refname is refs/tags/doubly-signed-tag |refs/tags/doubly-signed-tag +| refname is refs/tags/four |refs/tags/four +| refname is refs/tags/one |refs/tags/one +| refname is refs/tags/signed-tag |refs/tags/signed-tag +| refname is refs/tags/three |refs/tags/three +| refname is refs/tags/two |refs/tags/two +EOF + +test_align_permutations() { + while read -r option + do + test_expect_success "align:$option" ' + git for-each-ref --format="|%(align:$option)refname is %(refname)%(end)|%(refname)" >actual && + test_cmp expect actual + ' + done +} + +test_align_permutations <<-\EOF + middle,42 + 42,middle + position=middle,42 + 42,position=middle + middle,width=42 + width=42,middle + position=middle,width=42 + width=42,position=middle +EOF + +# Last one wins (silently) when multiple arguments of the same type are given + +test_align_permutations <<-\EOF + 32,width=42,middle + width=30,42,middle + width=42,position=right,middle + 42,right,position=middle +EOF + +# Individual atoms inside %(align:...) and %(end) must not be quoted. + +test_expect_success 'alignment with format quote' " + cat >expect <<-\EOF && + |' '\''master| A U Thor'\'' '| + |' '\''side| A U Thor'\'' '| + |' '\''odd/spot| A U Thor'\'' '| + |' '\''annotated-tag| '\'' '| + |' '\''doubly-annotated-tag| '\'' '| + |' '\''doubly-signed-tag| '\'' '| + |' '\''four| A U Thor'\'' '| + |' '\''one| A U Thor'\'' '| + |' '\''signed-tag| '\'' '| + |' '\''three| A U Thor'\'' '| + |' '\''two| A U Thor'\'' '| + EOF + git for-each-ref --shell --format=\"|%(align:30,middle)'%(refname:short)| %(authorname)'%(end)|\" >actual && + test_cmp expect actual +" + +test_expect_success 'nested alignment with quote formatting' " + cat >expect <<-\EOF && + |' master '| + |' side '| + |' odd/spot '| + |' annotated-tag '| + |'doubly-annotated-tag '| + |'doubly-signed-tag '| + |' four '| + |' one '| + |' signed-tag '| + |' three '| + |' two '| + EOF + git for-each-ref --shell --format='|%(align:30,left)%(align:15,right)%(refname:short)%(end)%(end)|' >actual && + test_cmp expect actual +" + +test_expect_success 'check `%(contents:lines=1)`' ' + cat >expect <<-\EOF && + master |three + side |four + odd/spot |three + annotated-tag |An annotated tag + doubly-annotated-tag |Annonated doubly + doubly-signed-tag |Signed doubly + four |four + one |one + signed-tag |A signed tag + three |three + two |two + EOF + git for-each-ref --format="%(refname:short) |%(contents:lines=1)" >actual && + test_cmp expect actual +' + +test_expect_success 'check `%(contents:lines=0)`' ' + cat >expect <<-\EOF && + master | + side | + odd/spot | + annotated-tag | + doubly-annotated-tag | + doubly-signed-tag | + four | + one | + signed-tag | + three | + two | + EOF + git for-each-ref --format="%(refname:short) |%(contents:lines=0)" >actual && + test_cmp expect actual +' + +test_expect_success 'check `%(contents:lines=99999)`' ' + cat >expect <<-\EOF && + master |three + side |four + odd/spot |three + annotated-tag |An annotated tag + doubly-annotated-tag |Annonated doubly + doubly-signed-tag |Signed doubly + four |four + one |one + signed-tag |A signed tag + three |three + two |two + EOF + git for-each-ref --format="%(refname:short) |%(contents:lines=99999)" >actual && + test_cmp expect actual +' + +test_expect_success '`%(contents:lines=-1)` should fail' ' + test_must_fail git for-each-ref --format="%(refname:short) |%(contents:lines=-1)" +' + +test_expect_success 'setup for version sort' ' + test_commit foo1.3 && + test_commit foo1.6 && + test_commit foo1.10 +' + +test_expect_success 'version sort' ' + git for-each-ref --sort=version:refname --format="%(refname:short)" refs/tags/ | grep "foo" >actual && + cat >expect <<-\EOF && + foo1.3 + foo1.6 + foo1.10 + EOF + test_cmp expect actual +' + +test_expect_success 'version sort (shortened)' ' + git for-each-ref --sort=v:refname --format="%(refname:short)" refs/tags/ | grep "foo" >actual && + cat >expect <<-\EOF && + foo1.3 + foo1.6 + foo1.10 + EOF + test_cmp expect actual +' + +test_expect_success 'reverse version sort' ' + git for-each-ref --sort=-version:refname --format="%(refname:short)" refs/tags/ | grep "foo" >actual && + cat >expect <<-\EOF && + foo1.10 + foo1.6 + foo1.3 + EOF + test_cmp expect actual +' + +test_expect_success 'improper usage of %(if), %(then), %(else) and %(end) atoms' ' + test_must_fail git for-each-ref --format="%(if)" && + test_must_fail git for-each-ref --format="%(then) %(end)" && + test_must_fail git for-each-ref --format="%(else) %(end)" && + test_must_fail git for-each-ref --format="%(if) %(else) %(end)" && + test_must_fail git for-each-ref --format="%(if) %(then) %(then) %(end)" && + test_must_fail git for-each-ref --format="%(then) %(else) %(end)" && + test_must_fail git for-each-ref --format="%(if) %(else) %(end)" && + test_must_fail git for-each-ref --format="%(if) %(then) %(else)" && + test_must_fail git for-each-ref --format="%(if) %(else) %(then) %(end)" && + test_must_fail git for-each-ref --format="%(if) %(then) %(else) %(else) %(end)" && + test_must_fail git for-each-ref --format="%(if) %(end)" +' + +test_expect_success 'check %(if)...%(then)...%(end) atoms' ' + git for-each-ref --format="%(refname)%(if)%(authorname)%(then) Author: %(authorname)%(end)" >actual && + cat >expect <<-\EOF && + refs/heads/master Author: A U Thor + refs/heads/side Author: A U Thor + refs/odd/spot Author: A U Thor + refs/tags/annotated-tag + refs/tags/doubly-annotated-tag + refs/tags/doubly-signed-tag + refs/tags/foo1.10 Author: A U Thor + refs/tags/foo1.3 Author: A U Thor + refs/tags/foo1.6 Author: A U Thor + refs/tags/four Author: A U Thor + refs/tags/one Author: A U Thor + refs/tags/signed-tag + refs/tags/three Author: A U Thor + refs/tags/two Author: A U Thor + EOF + test_cmp expect actual +' + +test_expect_success 'check %(if)...%(then)...%(else)...%(end) atoms' ' + git for-each-ref --format="%(if)%(authorname)%(then)%(authorname)%(else)No author%(end): %(refname)" >actual && + cat >expect <<-\EOF && + A U Thor: refs/heads/master + A U Thor: refs/heads/side + A U Thor: refs/odd/spot + No author: refs/tags/annotated-tag + No author: refs/tags/doubly-annotated-tag + No author: refs/tags/doubly-signed-tag + A U Thor: refs/tags/foo1.10 + A U Thor: refs/tags/foo1.3 + A U Thor: refs/tags/foo1.6 + A U Thor: refs/tags/four + A U Thor: refs/tags/one + No author: refs/tags/signed-tag + A U Thor: refs/tags/three + A U Thor: refs/tags/two + EOF + test_cmp expect actual +' +test_expect_success 'ignore spaces in %(if) atom usage' ' + git for-each-ref --format="%(refname:short): %(if)%(HEAD)%(then)Head ref%(else)Not Head ref%(end)" >actual && + cat >expect <<-\EOF && + master: Head ref + side: Not Head ref + odd/spot: Not Head ref + annotated-tag: Not Head ref + doubly-annotated-tag: Not Head ref + doubly-signed-tag: Not Head ref + foo1.10: Not Head ref + foo1.3: Not Head ref + foo1.6: Not Head ref + four: Not Head ref + one: Not Head ref + signed-tag: Not Head ref + three: Not Head ref + two: Not Head ref + EOF + test_cmp expect actual +' + +test_expect_success 'check %(if:equals=<string>)' ' + git for-each-ref --format="%(if:equals=master)%(refname:short)%(then)Found master%(else)Not master%(end)" refs/heads/ >actual && + cat >expect <<-\EOF && + Found master + Not master + EOF + test_cmp expect actual +' + +test_expect_success 'check %(if:notequals=<string>)' ' + git for-each-ref --format="%(if:notequals=master)%(refname:short)%(then)Not master%(else)Found master%(end)" refs/heads/ >actual && + cat >expect <<-\EOF && + Found master + Not master + EOF + test_cmp expect actual +' + +test_expect_success '--merged is incompatible with --no-merged' ' + test_must_fail git for-each-ref --merged HEAD --no-merged HEAD +' + +test_done diff --git a/t/t6500-gc.sh b/t/t6500-gc.sh index 63194d819e..d5255dd576 100755 --- a/t/t6500-gc.sh +++ b/t/t6500-gc.sh @@ -30,4 +30,94 @@ test_expect_success 'gc -h with invalid configuration' ' test_i18ngrep "[Uu]sage" broken/usage ' +test_expect_success 'gc is not aborted due to a stale symref' ' + git init remote && + ( + cd remote && + test_commit initial && + git clone . ../client && + git branch -m develop && + cd ../client && + git fetch --prune && + git gc + ) +' + +test_expect_success 'auto gc with too many loose objects does not attempt to create bitmaps' ' + test_config gc.auto 3 && + test_config gc.autodetach false && + test_config pack.writebitmaps true && + # We need to create two object whose sha1s start with 17 + # since this is what git gc counts. As it happens, these + # two blobs will do so. + test_commit 263 && + test_commit 410 && + # Our first gc will create a pack; our second will create a second pack + git gc --auto && + ls .git/objects/pack | sort >existing_packs && + test_commit 523 && + test_commit 790 && + + git gc --auto 2>err && + test_i18ngrep ! "^warning:" err && + ls .git/objects/pack/ | sort >post_packs && + comm -1 -3 existing_packs post_packs >new && + comm -2 -3 existing_packs post_packs >del && + test_line_count = 0 del && # No packs are deleted + test_line_count = 2 new # There is one new pack and its .idx +' + +run_and_wait_for_auto_gc () { + # We read stdout from gc for the side effect of waiting until the + # background gc process exits, closing its fd 9. Furthermore, the + # variable assignment from a command substitution preserves the + # exit status of the main gc process. + # Note: this fd trickery doesn't work on Windows, but there is no + # need to, because on Win the auto gc always runs in the foreground. + doesnt_matter=$(git gc --auto 9>&1) +} + +test_expect_success 'background auto gc does not run if gc.log is present and recent but does if it is old' ' + test_commit foo && + test_commit bar && + git repack && + test_config gc.autopacklimit 1 && + test_config gc.autodetach true && + echo fleem >.git/gc.log && + test_must_fail git gc --auto 2>err && + test_i18ngrep "^error:" err && + test_config gc.logexpiry 5.days && + test-tool chmtime =-345600 .git/gc.log && + test_must_fail git gc --auto && + test_config gc.logexpiry 2.days && + run_and_wait_for_auto_gc && + ls .git/objects/pack/pack-*.pack >packs && + test_line_count = 1 packs +' + +test_expect_success 'background auto gc respects lock for all operations' ' + # make sure we run a background auto-gc + test_commit make-pack && + git repack && + test_config gc.autopacklimit 1 && + test_config gc.autodetach true && + + # create a ref whose loose presence we can use to detect a pack-refs run + git update-ref refs/heads/should-be-loose HEAD && + test_path_is_file .git/refs/heads/should-be-loose && + + # now fake a concurrent gc that holds the lock; we can use our + # shell pid so that it looks valid. + hostname=$(hostname || echo unknown) && + printf "$$ %s" "$hostname" >.git/gc.pid && + + # our gc should exit zero without doing anything + run_and_wait_for_auto_gc && + test_path_is_file .git/refs/heads/should-be-loose +' + +# DO NOT leave a detached auto gc process running near the end of the +# test script: it can run long enough in the background to racily +# interfere with the cleanup in 'test_done'. + test_done diff --git a/t/t6501-freshen-objects.sh b/t/t6501-freshen-objects.sh index 2adf825076..765cced60b 100755 --- a/t/t6501-freshen-objects.sh +++ b/t/t6501-freshen-objects.sh @@ -56,7 +56,7 @@ for repack in '' true; do test_expect_success "disable reflogs ($title)" ' git config core.logallrefupdates false && - rm -rf .git/logs + git reflog expire --expire=all --all ' test_expect_success "setup basic history ($title)" ' @@ -73,7 +73,7 @@ for repack in '' true; do test_expect_success "simulate time passing ($title)" ' find .git/objects -type f | - xargs test-chmtime -v -86400 + xargs test-tool chmtime -v -86400 ' test_expect_success "start writing new commit with old blob ($title)" ' @@ -104,7 +104,7 @@ for repack in '' true; do test_expect_success "abandon objects again ($title)" ' git reset --hard HEAD^ && find .git/objects -type f | - xargs test-chmtime -v -86400 + xargs test-tool chmtime -v -86400 ' test_expect_success "start writing new commit with same tree ($title)" ' @@ -129,7 +129,7 @@ for repack in '' true; do ' done -test_expect_success 'do not complain about existing broken links' ' +test_expect_success 'do not complain about existing broken links (commit)' ' cat >broken-commit <<-\EOF && tree 0000000000000000000000000000000000000001 parent 0000000000000000000000000000000000000002 @@ -144,4 +144,29 @@ test_expect_success 'do not complain about existing broken links' ' test_must_be_empty stderr ' +test_expect_success 'do not complain about existing broken links (tree)' ' + cat >broken-tree <<-\EOF && + 100644 blob 0000000000000000000000000000000000000003 foo + EOF + tree=$(git mktree --missing <broken-tree) && + git gc 2>stderr && + git cat-file -e $tree && + test_must_be_empty stderr +' + +test_expect_success 'do not complain about existing broken links (tag)' ' + cat >broken-tag <<-\EOF && + object 0000000000000000000000000000000000000004 + type commit + tag broken + tagger whatever <whatever@example.com> 1234 -0000 + + this is a broken tag + EOF + tag=$(git hash-object -t tag -w broken-tag) && + git gc 2>stderr && + git cat-file -e $tag && + test_must_be_empty stderr +' + test_done diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index 7b56081137..e96cbdb105 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -21,8 +21,8 @@ test_expect_success \ test_expect_success \ 'checking the commit' \ - 'git diff-tree -r -M --name-status HEAD^ HEAD | \ - grep "^R100..*path0/COPYING..*path1/COPYING"' + 'git diff-tree -r -M --name-status HEAD^ HEAD >actual && + grep "^R100..*path0/COPYING..*path1/COPYING" actual' test_expect_success \ 'moving the file back into subdirectory' \ @@ -35,8 +35,14 @@ test_expect_success \ test_expect_success \ 'checking the commit' \ - 'git diff-tree -r -M --name-status HEAD^ HEAD | \ - grep "^R100..*path1/COPYING..*path0/COPYING"' + 'git diff-tree -r -M --name-status HEAD^ HEAD >actual && + grep "^R100..*path1/COPYING..*path0/COPYING" actual' + +test_expect_success \ + 'mv --dry-run does not move file' \ + 'git mv -n path0/COPYING MOVED && + test -f path0/COPYING && + test ! -f MOVED' test_expect_success \ 'checking -k on non-existing file' \ @@ -102,7 +108,7 @@ test_expect_success \ test_expect_success \ 'adding another file' \ - 'cp "$TEST_DIRECTORY"/../README path0/README && + 'cp "$TEST_DIRECTORY"/../README.md path0/README && git add path0/README && git commit -m add2 -a' @@ -116,10 +122,9 @@ test_expect_success \ test_expect_success \ 'checking the commit' \ - 'git diff-tree -r -M --name-status HEAD^ HEAD | \ - grep "^R100..*path0/COPYING..*path2/COPYING" && - git diff-tree -r -M --name-status HEAD^ HEAD | \ - grep "^R100..*path0/README..*path2/README"' + 'git diff-tree -r -M --name-status HEAD^ HEAD >actual && + grep "^R100..*path0/COPYING..*path2/COPYING" actual && + grep "^R100..*path0/README..*path2/README" actual' test_expect_success \ 'succeed when source is a prefix of destination' \ @@ -135,10 +140,9 @@ test_expect_success \ test_expect_success \ 'checking the commit' \ - 'git diff-tree -r -M --name-status HEAD^ HEAD | \ - grep "^R100..*path2/COPYING..*path1/path2/COPYING" && - git diff-tree -r -M --name-status HEAD^ HEAD | \ - grep "^R100..*path2/README..*path1/path2/README"' + 'git diff-tree -r -M --name-status HEAD^ HEAD >actual && + grep "^R100..*path2/COPYING..*path1/path2/COPYING" actual && + grep "^R100..*path2/README..*path1/path2/README" actual' test_expect_success \ 'do not move directory over existing directory' \ @@ -156,11 +160,11 @@ test_expect_success "Michael Cassar's test case" ' echo b > partA/outline.txt && echo c > papers/unsorted/_another && git add papers partA && - T1=`git write-tree` && + T1=$(git write-tree) && git mv papers/unsorted/Thesis.pdf papers/all-papers/moo-blah.pdf && - T=`git write-tree` && + T=$(git write-tree) && git ls-tree -r $T | verbose grep partA/outline.txt ' @@ -292,6 +296,9 @@ test_expect_success 'setup submodule' ' echo content >file && git add file && git commit -m "added sub and file" && + mkdir -p deep/directory/hierarchy && + git submodule add ./. deep/directory/hierarchy/sub && + git commit -m "added another submodule" && git branch submodule ' @@ -449,7 +456,7 @@ test_expect_success 'checking out a commit before submodule moved needs manual u git mv sub sub2 && git commit -m "moved sub to sub2" && git checkout -q HEAD^ 2>actual && - test_i18ngrep "^warning: unable to rmdir sub2:" actual && + test_i18ngrep "^warning: unable to rmdir '\''sub2'\'':" actual && git status -s sub2 >actual && echo "?? sub2/" >expected && test_cmp expected actual && @@ -475,4 +482,42 @@ test_expect_success 'mv -k does not accidentally destroy submodules' ' git checkout . ' +test_expect_success 'moving a submodule in nested directories' ' + ( + cd deep && + git mv directory ../ && + # git status would fail if the update of linking git dir to + # work dir of the submodule failed. + git status && + git config -f ../.gitmodules submodule.deep/directory/hierarchy/sub.path >../actual && + echo "directory/hierarchy/sub" >../expect + ) && + test_cmp expect actual +' + +test_expect_failure 'moving nested submodules' ' + git commit -am "cleanup commit" && + mkdir sub_nested_nested && + (cd sub_nested_nested && + touch nested_level2 && + git init && + git add . && + git commit -m "nested level 2" + ) && + mkdir sub_nested && + (cd sub_nested && + touch nested_level1 && + git init && + git add . && + git commit -m "nested level 1" + git submodule add ../sub_nested_nested && + git commit -m "add nested level 2" + ) && + git submodule add ./sub_nested nested_move && + git commit -m "add nested_move" && + git submodule update --init --recursive && + git mv nested_move sub_nested_moved && + git status +' + test_done diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index 855afda80a..ec4b160ddb 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -2,6 +2,7 @@ test_description='git filter-branch' . ./test-lib.sh +. "$TEST_DIRECTORY/lib-gpg.sh" test_expect_success 'setup' ' test_commit A && @@ -186,7 +187,8 @@ test_expect_success 'author information is preserved' ' test \$GIT_COMMIT != $(git rev-parse master) || \ echo Hallo" \ preserved-author) && - test 1 = $(git rev-list --author="B V Uips" preserved-author | wc -l) + git rev-list --author="B V Uips" preserved-author >actual && + test_line_count = 1 actual ' test_expect_success "remove a certain author's commits" ' @@ -204,7 +206,8 @@ test_expect_success "remove a certain author's commits" ' cnt1=$(git rev-list master | wc -l) && cnt2=$(git rev-list removed-author | wc -l) && test $cnt1 -eq $(($cnt2 + 1)) && - test 0 = $(git rev-list --author="B V Uips" removed-author | wc -l) + git rev-list --author="B V Uips" removed-author >actual && + test_line_count = 0 actual ' test_expect_success 'barf on invalid name' ' @@ -257,7 +260,8 @@ test_expect_success 'Subdirectory filter with disappearing trees' ' git commit -m "Re-adding foo" && git filter-branch -f --subdirectory-filter foo && - test $(git rev-list master | wc -l) = 3 + git rev-list master >actual && + test_line_count = 3 actual ' test_expect_success 'Tag name filtering retains tag message' ' @@ -292,6 +296,19 @@ test_expect_success 'Tag name filtering strips gpg signature' ' test_cmp expect actual ' +test_expect_success GPG 'Filtering retains message of gpg signed commit' ' + mkdir gpg && + touch gpg/foo && + git add gpg && + test_tick && + git commit -S -m "Adding gpg" && + + git log -1 --format="%s" > expect && + git filter-branch -f --msg-filter "cat" && + git log -1 --format="%s" > actual && + test_cmp expect actual +' + test_expect_success 'Tag name filtering allows slashes in tag names' ' git tag -m tag-with-slash X/1 && git cat-file tag X/1 | sed -e s,X/1,X/2, > expect && @@ -299,6 +316,27 @@ test_expect_success 'Tag name filtering allows slashes in tag names' ' git cat-file tag X/2 > actual && test_cmp expect actual ' +test_expect_success 'setup --prune-empty comparisons' ' + git checkout --orphan master-no-a && + git rm -rf . && + unset test_tick && + test_tick && + GIT_COMMITTER_DATE="@0 +0000" GIT_AUTHOR_DATE="@0 +0000" && + test_commit --notick B B.t B Bx && + git checkout -b branch-no-a Bx && + test_commit D D.t D Dx && + mkdir dir && + test_commit dir/D dir/D.t dir/D dir/Dx && + test_commit E E.t E Ex && + git checkout master-no-a && + test_commit C C.t C Cx && + git checkout branch-no-a && + git merge Cx -m "Merge tag '\''C'\'' into branch" && + git tag Fx && + test_commit G G.t G Gx && + test_commit H H.t H Hx && + git checkout branch +' test_expect_success 'Prune empty commits' ' git rev-list HEAD > expect && @@ -319,6 +357,30 @@ test_expect_success 'prune empty collapsed merges' ' test_cmp expect actual ' +test_expect_success 'prune empty works even without index/tree filters' ' + git rev-list HEAD >expect && + git commit --allow-empty -m empty && + git filter-branch -f --prune-empty HEAD && + git rev-list HEAD >actual && + test_cmp expect actual +' + +test_expect_success '--prune-empty is able to prune root commit' ' + git rev-list branch-no-a >expect && + git branch testing H && + git filter-branch -f --prune-empty --index-filter "git update-index --remove A.t" testing && + git rev-list testing >actual && + git branch -D testing && + test_cmp expect actual +' + +test_expect_success '--prune-empty is able to prune entire branch' ' + git branch prune-entire B && + git filter-branch -f --prune-empty --index-filter "git update-index --remove A.t B.t" prune-entire && + test_path_is_missing .git/refs/heads/prune-entire && + test_must_fail git reflog exists refs/heads/prune-entire +' + test_expect_success '--remap-to-ancestor with filename filters' ' git checkout master && git reset --hard A && @@ -373,7 +435,7 @@ test_expect_success 'setup submodule' ' git branch original HEAD ' -orig_head=`git show-ref --hash --head HEAD` +orig_head=$(git show-ref --hash --head HEAD) test_expect_success 'rewrite submodule with another content' ' git filter-branch --tree-filter "test -d submod && { @@ -382,7 +444,7 @@ test_expect_success 'rewrite submodule with another content' ' mkdir submod && : > submod/file } || :" HEAD && - test $orig_head != `git show-ref --hash --head HEAD` + test $orig_head != $(git show-ref --hash --head HEAD) ' test_expect_success 'replace submodule revision' ' @@ -391,7 +453,7 @@ test_expect_success 'replace submodule revision' ' "if git ls-files --error-unmatch -- submod > /dev/null 2>&1 then git update-index --cacheinfo 160000 0123456789012345678901234567890123456789 submod fi" HEAD && - test $orig_head != `git show-ref --hash --head HEAD` + test $orig_head != $(git show-ref --hash --head HEAD) ' test_expect_success 'filter commit message without trailing newline' ' @@ -404,4 +466,25 @@ test_expect_success 'filter commit message without trailing newline' ' test_cmp expect actual ' +test_expect_success 'tree-filter deals with object name vs pathname ambiguity' ' + test_when_finished "git reset --hard original" && + ambiguous=$(git rev-list -1 HEAD) && + git filter-branch --tree-filter "mv file.t $ambiguous" HEAD^.. && + git show HEAD:$ambiguous +' + +test_expect_success 'rewrite repository including refs that point at non-commit object' ' + test_when_finished "git reset --hard original" && + tree=$(git rev-parse HEAD^{tree}) && + test_when_finished "git replace -d $tree" && + echo A >new && + git add new && + new_tree=$(git write-tree) && + git replace $tree $new_tree && + git tag -a -m "tag to a tree" treetag $new_tree && + git reset --hard HEAD && + git filter-branch -f -- --all >filter-output 2>&1 && + ! fgrep fatal filter-output +' + test_done diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index d1ff5c94f2..2aac77af70 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -9,6 +9,7 @@ Tests for operations with tags.' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-gpg.sh +. "$TEST_DIRECTORY"/lib-terminal.sh # creating and listing lightweight tags: @@ -16,15 +17,38 @@ tag_exists () { git show-ref --quiet --verify refs/tags/"$1" } -# todo: git tag -l now returns always zero, when fixed, change this test test_expect_success 'listing all tags in an empty tree should succeed' ' git tag -l && git tag ' test_expect_success 'listing all tags in an empty tree should output nothing' ' - test `git tag -l | wc -l` -eq 0 && - test `git tag | wc -l` -eq 0 + test $(git tag -l | wc -l) -eq 0 && + test $(git tag | wc -l) -eq 0 +' + +test_expect_success 'sort tags, ignore case' ' + ( + git init sort && + cd sort && + test_commit initial && + git tag tag-one && + git tag TAG-two && + git tag -l >actual && + cat >expected <<-\EOF && + TAG-two + initial + tag-one + EOF + test_cmp expected actual && + git tag -l -i >actual && + cat >expected <<-\EOF && + initial + tag-one + TAG-two + EOF + test_cmp expected actual + ) ' test_expect_success 'looking for a tag in an empty tree should fail' \ @@ -47,11 +71,47 @@ test_expect_success 'creating a tag for an unknown revision should fail' ' # commit used in the tests, test_tick is also called here to freeze the date: test_expect_success 'creating a tag using default HEAD should succeed' ' + test_config core.logAllRefUpdates true && test_tick && echo foo >foo && git add foo && git commit -m Foo && - git tag mytag + git tag mytag && + test_must_fail git reflog exists refs/tags/mytag +' + +test_expect_success 'creating a tag with --create-reflog should create reflog' ' + git log -1 \ + --format="format:tag: tagging %h (%s, %cd)%n" \ + --date=format:%Y-%m-%d >expected && + test_when_finished "git tag -d tag_with_reflog" && + git tag --create-reflog tag_with_reflog && + git reflog exists refs/tags/tag_with_reflog && + sed -e "s/^.* //" .git/logs/refs/tags/tag_with_reflog >actual && + test_i18ncmp expected actual +' + +test_expect_success 'annotated tag with --create-reflog has correct message' ' + git log -1 \ + --format="format:tag: tagging %h (%s, %cd)%n" \ + --date=format:%Y-%m-%d >expected && + test_when_finished "git tag -d tag_with_reflog" && + git tag -m "annotated tag" --create-reflog tag_with_reflog && + git reflog exists refs/tags/tag_with_reflog && + sed -e "s/^.* //" .git/logs/refs/tags/tag_with_reflog >actual && + test_i18ncmp expected actual +' + +test_expect_success '--create-reflog does not create reflog on failure' ' + test_must_fail git tag --create-reflog mytag && + test_must_fail git reflog exists refs/tags/mytag +' + +test_expect_success 'option core.logAllRefUpdates=always creates reflog' ' + test_when_finished "git tag -d tag_with_reflog" && + test_config core.logAllRefUpdates always && + git tag tag_with_reflog && + git reflog exists refs/tags/tag_with_reflog ' test_expect_success 'listing all tags if one exists should succeed' ' @@ -59,9 +119,21 @@ test_expect_success 'listing all tags if one exists should succeed' ' git tag ' +cat >expect <<EOF +mytag +EOF +test_expect_success 'Multiple -l or --list options are equivalent to one -l option' ' + git tag -l -l >actual && + test_cmp expect actual && + git tag --list --list >actual && + test_cmp expect actual && + git tag --list -l --list >actual && + test_cmp expect actual +' + test_expect_success 'listing all tags if one exists should output that tag' ' - test `git tag -l` = mytag && - test `git tag` = mytag + test $(git tag -l) = mytag && + test $(git tag) = mytag ' # pattern matching: @@ -69,18 +141,20 @@ test_expect_success 'listing all tags if one exists should output that tag' ' test_expect_success 'listing a tag using a matching pattern should succeed' \ 'git tag -l mytag' +test_expect_success 'listing a tag with --ignore-case' \ + 'test $(git tag -l --ignore-case MYTAG) = mytag' + test_expect_success \ 'listing a tag using a matching pattern should output that tag' \ - 'test `git tag -l mytag` = mytag' + 'test $(git tag -l mytag) = mytag' -# todo: git tag -l now returns always zero, when fixed, change this test test_expect_success \ - 'listing tags using a non-matching pattern should suceed' \ + 'listing tags using a non-matching pattern should succeed' \ 'git tag -l xxx' test_expect_success \ 'listing tags using a non-matching pattern should output nothing' \ - 'test `git tag -l xxx | wc -l` -eq 0' + 'test $(git tag -l xxx | wc -l) -eq 0' # special cases for creating tags: @@ -90,13 +164,13 @@ test_expect_success \ test_expect_success \ 'trying to create a tag with a non-valid name should fail' ' - test `git tag -l | wc -l` -eq 1 && + test $(git tag -l | wc -l) -eq 1 && 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 $(git tag -l | wc -l) -eq 1 ' test_expect_success 'creating a tag using HEAD directly should succeed' ' @@ -110,11 +184,11 @@ test_expect_success '--force can create a tag with the name of one existing' ' tag_exists mytag' test_expect_success '--force is moot with a non-existing tag name' ' + test_when_finished git tag -d newtag forcetag && git tag newtag >expect && git tag --force forcetag >actual && test_cmp expect actual ' -git tag -d newtag forcetag # deleting tags: @@ -275,6 +349,19 @@ test_expect_success 'tag -l can accept multiple patterns' ' test_cmp expect actual ' +# Between v1.7.7 & v2.13.0 a fair reading of the git-tag documentation +# could leave you with the impression that "-l <pattern> -l <pattern>" +# was how we wanted to accept multiple patterns. +# +# This test should not imply that this is a sane thing to support. but +# since the documentation was worded like it was let's at least find +# out if we're going to break this long-documented form of taking +# multiple patterns. +test_expect_success 'tag -l <pattern> -l <pattern> works, as our buggy documentation previously suggested' ' + git tag -l "v1*" -l "v0*" >actual && + test_cmp expect actual +' + test_expect_success 'listing tags in column' ' COLUMNS=40 git tag -l --column=row >actual && cat >expected <<\EOF && @@ -285,11 +372,9 @@ EOF ' test_expect_success 'listing tags in column with column.*' ' - git config column.tag row && - git config column.ui dense && + test_config column.tag row && + test_config column.ui dense && COLUMNS=40 git tag -l >actual && - git config --unset column.ui && - git config --unset column.tag && cat >expected <<\EOF && a1 aa1 cba t210 t211 v0.2.1 v1.0 v1.0.1 v1.1.3 @@ -302,9 +387,8 @@ test_expect_success 'listing tag with -n --column should fail' ' ' test_expect_success 'listing tags -n in column with column.ui ignored' ' - git config column.ui "row dense" && + test_config column.ui "row dense" && COLUMNS=40 git tag -l -n >actual && - git config --unset column.ui && cat >expected <<\EOF && a1 Foo aa1 Foo @@ -368,6 +452,21 @@ test_expect_success \ test_cmp expect actual ' +get_tag_header annotated-tag-edit $commit commit $time >expect +echo "An edited message" >>expect +test_expect_success 'set up editor' ' + write_script fakeeditor <<-\EOF + sed -e "s/A message/An edited message/g" <"$1" >"$1-" + mv "$1-" "$1" + EOF +' +test_expect_success \ + 'creating an annotated tag with -m message --edit should succeed' ' + GIT_EDITOR=./fakeeditor git tag -m "A message" --edit annotated-tag-edit && + get_tag_msg annotated-tag-edit >actual && + test_cmp expect actual +' + cat >msgfile <<EOF Another message in a file. @@ -381,6 +480,21 @@ test_expect_success \ test_cmp expect actual ' +get_tag_header file-annotated-tag-edit $commit commit $time >expect +sed -e "s/Another message/Another edited message/g" msgfile >>expect +test_expect_success 'set up editor' ' + write_script fakeeditor <<-\EOF + sed -e "s/Another message/Another edited message/g" <"$1" >"$1-" + mv "$1-" "$1" + EOF +' +test_expect_success \ + 'creating an annotated tag with -F messagefile --edit should succeed' ' + GIT_EDITOR=./fakeeditor git tag -F msgfile --edit file-annotated-tag-edit && + get_tag_msg file-annotated-tag-edit >actual && + test_cmp expect actual +' + cat >inputmsg <<EOF A message from the standard input @@ -560,6 +674,11 @@ test_expect_success \ git tag -n0 -l tag-one-line >actual && test_cmp expect actual && + git tag -n0 | grep "^tag-one-line" >actual && + test_cmp expect actual && + git tag -n0 tag-one-line >actual && + test_cmp expect actual && + echo "tag-one-line A msg" >expect && git tag -n1 -l | grep "^tag-one-line" >actual && test_cmp expect actual && @@ -573,6 +692,17 @@ test_expect_success \ test_cmp expect actual ' +test_expect_success 'The -n 100 invocation means -n --list 100, not -n100' ' + >expect && + git tag -n 100 >actual && + test_cmp expect actual && + + git tag -m "A msg" 100 && + echo "100 A msg" >expect && + git tag -n 100 >actual && + test_cmp expect actual +' + test_expect_success \ 'listing the zero-lines message of a non-signed tag should succeed' ' git tag -m "" tag-zero-lines && @@ -763,6 +893,47 @@ test_expect_success GPG '-s implies annotated tag' ' test_cmp expect actual ' +get_tag_header forcesignannotated-implied-sign $commit commit $time >expect +echo "A message" >>expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success GPG \ + 'git tag -s implied if configured with tag.forcesignannotated' \ + 'test_config tag.forcesignannotated true && + git tag -m "A message" forcesignannotated-implied-sign && + get_tag_msg forcesignannotated-implied-sign >actual && + test_cmp expect actual +' + +test_expect_success GPG \ + 'lightweight with no message when configured with tag.forcesignannotated' \ + 'test_config tag.forcesignannotated true && + git tag forcesignannotated-lightweight && + tag_exists forcesignannotated-lightweight && + test_must_fail git tag -v forcesignannotated-no-message +' + +get_tag_header forcesignannotated-annotate $commit commit $time >expect +echo "A message" >>expect +test_expect_success GPG \ + 'git tag -a disable configured tag.forcesignannotated' \ + 'test_config tag.forcesignannotated true && + git tag -a -m "A message" forcesignannotated-annotate && + get_tag_msg forcesignannotated-annotate >actual && + test_cmp expect actual && + test_must_fail git tag -v forcesignannotated-annotate +' + +get_tag_header forcesignannotated-disabled $commit commit $time >expect +echo "A message" >>expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success GPG \ + 'git tag --sign enable GPG sign' \ + 'test_config tag.forcesignannotated false && + git tag --sign -m "A message" forcesignannotated-disabled && + get_tag_msg forcesignannotated-disabled >actual && + test_cmp expect actual +' + test_expect_success GPG \ 'trying to create a signed tag with non-existing -F file should fail' ' ! test -f nonexistingfile && @@ -794,6 +965,20 @@ test_expect_success GPG 'verifying a forged tag should fail' ' test_must_fail git tag -v forged-tag ' +test_expect_success GPG 'verifying a proper tag with --format pass and format accordingly' ' + cat >expect <<-\EOF && + tagname : signed-tag + EOF + git tag -v --format="tagname : %(tag)" "signed-tag" >actual && + test_cmp expect actual +' + +test_expect_success GPG 'verifying a forged tag with --format should fail silently' ' + >expect && + test_must_fail git tag -v --format="tagname : %(tag)" "forged-tag" >actual && + test_cmp expect actual +' + # blank and empty messages for signed tags: get_tag_header empty-signed-tag $commit commit $time >expect @@ -1147,11 +1332,17 @@ test_expect_success GPG,RFC1991 \ ' # try to sign with bad user.signingkey -git config user.signingkey BobTheMouse test_expect_success GPG \ - 'git tag -s fails if gpg is misconfigured' \ - 'test_must_fail git tag -s -m tail tag-gpg-failure' -git config --unset user.signingkey + 'git tag -s fails if gpg is misconfigured (bad key)' \ + 'test_config user.signingkey BobTheMouse && + test_must_fail git tag -s -m tail tag-gpg-failure' + +# try to produce invalid signature +test_expect_success GPG \ + 'git tag -s fails if gpg is misconfigured (bad signature format)' \ + 'test_config gpg.program echo && + test_must_fail git tag -s -m tail tag-gpg-failure' + # try to verify without gpg: @@ -1262,6 +1453,23 @@ test_expect_success 'checking that first commit is in all tags (relative)' " test_cmp expected actual " +# All the --contains tests above, but with --no-contains +test_expect_success 'checking that first commit is not listed in any tag with --no-contains (hash)' " + >expected && + git tag -l --no-contains $hash1 v* >actual && + test_cmp expected actual +" + +test_expect_success 'checking that first commit is in all tags (tag)' " + git tag -l --no-contains v1.0 v* >actual && + test_cmp expected actual +" + +test_expect_success 'checking that first commit is in all tags (relative)' " + git tag -l --no-contains HEAD~2 v* >actual && + test_cmp expected actual +" + cat > expected <<EOF v2.0 EOF @@ -1271,6 +1479,17 @@ test_expect_success 'checking that second commit only has one tag' " test_cmp expected actual " +cat > expected <<EOF +v0.2.1 +v1.0 +v1.0.1 +v1.1.3 +EOF + +test_expect_success 'inverse of the last test, with --no-contains' " + git tag -l --no-contains $hash2 v* >actual && + test_cmp expected actual +" cat > expected <<EOF EOF @@ -1280,6 +1499,19 @@ test_expect_success 'checking that third commit has no tags' " test_cmp expected actual " +cat > expected <<EOF +v0.2.1 +v1.0 +v1.0.1 +v1.1.3 +v2.0 +EOF + +test_expect_success 'conversely --no-contains on the third commit lists all tags' " + git tag -l --no-contains $hash3 v* >actual && + test_cmp expected actual +" + # how about a simple merge? test_expect_success 'creating simple branch' ' @@ -1301,6 +1533,19 @@ test_expect_success 'checking that branch head only has one tag' " test_cmp expected actual " +cat > expected <<EOF +v0.2.1 +v1.0 +v1.0.1 +v1.1.3 +v2.0 +EOF + +test_expect_success 'checking that branch head with --no-contains lists all but one tag' " + git tag -l --no-contains $hash4 v* >actual && + test_cmp expected actual +" + test_expect_success 'merging original branch into this branch' ' git merge --strategy=ours master && git tag v4.0 @@ -1322,6 +1567,20 @@ v1.0.1 v1.1.3 v2.0 v3.0 +EOF + +test_expect_success 'checking that original branch head with --no-contains lists all but one tag now' " + git tag -l --no-contains $hash3 v* >actual && + test_cmp expected actual +" + +cat > expected <<EOF +v0.2.1 +v1.0 +v1.0.1 +v1.1.3 +v2.0 +v3.0 v4.0 EOF @@ -1330,21 +1589,76 @@ test_expect_success 'checking that initial commit is in all tags' " test_cmp expected actual " +test_expect_success 'checking that --contains can be used in non-list mode' ' + git tag --contains $hash1 v* >actual && + test_cmp expected actual +' + +test_expect_success 'checking that initial commit is in all tags with --no-contains' " + >expected && + git tag -l --no-contains $hash1 v* >actual && + test_cmp expected actual +" + # mixing modes and options: test_expect_success 'mixing incompatibles modes and options is forbidden' ' test_must_fail git tag -a && + test_must_fail git tag -a -l && + test_must_fail git tag -s && + test_must_fail git tag -s -l && + test_must_fail git tag -m && + test_must_fail git tag -m -l && + test_must_fail git tag -m "hlagh" && + test_must_fail git tag -m "hlagh" -l && + test_must_fail git tag -F && + test_must_fail git tag -F -l && + test_must_fail git tag -f && + test_must_fail git tag -f -l && + test_must_fail git tag -a -s -m -F && + test_must_fail git tag -a -s -m -F -l && test_must_fail git tag -l -v && - test_must_fail git tag -n 100 && + test_must_fail git tag -l -d && + test_must_fail git tag -l -v -d && + test_must_fail git tag -n 100 -v && test_must_fail git tag -l -m msg && test_must_fail git tag -l -F some file && - test_must_fail git tag -v -s -' + test_must_fail git tag -v -s && + test_must_fail git tag --contains tag-tree && + test_must_fail git tag --contains tag-blob && + test_must_fail git tag --no-contains tag-tree && + test_must_fail git tag --no-contains tag-blob && + test_must_fail git tag --contains --no-contains && + test_must_fail git tag --no-with HEAD && + test_must_fail git tag --no-without HEAD +' + +for option in --contains --with --no-contains --without --merged --no-merged --points-at +do + test_expect_success "mixing incompatible modes with $option is forbidden" " + test_must_fail git tag -d $option HEAD && + test_must_fail git tag -d $option HEAD some-tag && + test_must_fail git tag -v $option HEAD + " + test_expect_success "Doing 'git tag --list-like $option <commit> <pattern> is permitted" " + git tag -n $option HEAD HEAD && + git tag $option HEAD HEAD && + git tag $option + " +done # check points-at -test_expect_success '--points-at cannot be used in non-list mode' ' - test_must_fail git tag --points-at=v4.0 foo +test_expect_success '--points-at can be used in non-list mode' ' + echo v4.0 >expect && + git tag --points-at=v4.0 "v*" >actual && + test_cmp expect actual +' + +test_expect_success '--points-at is a synonym for --points-at HEAD' ' + echo v4.0 >expect && + git tag --points-at >actual && + test_cmp expect actual ' test_expect_success '--points-at finds lightweight tags' ' @@ -1424,7 +1738,7 @@ test_expect_success 'reverse lexical sort' ' ' test_expect_success 'configured lexical sort' ' - git config tag.sort "v:refname" && + test_config tag.sort "v:refname" && git tag -l "foo*" >actual && cat >expect <<-\EOF && foo1.3 @@ -1435,6 +1749,7 @@ test_expect_success 'configured lexical sort' ' ' test_expect_success 'option override configured sort' ' + test_config tag.sort "v:refname" && git tag -l --sort=-refname "foo*" >actual && cat >expect <<-\EOF && foo1.6 @@ -1449,19 +1764,12 @@ test_expect_success 'invalid sort parameter on command line' ' ' test_expect_success 'invalid sort parameter in configuratoin' ' - git config tag.sort "v:notvalid" && - git tag -l "foo*" >actual && - cat >expect <<-\EOF && - foo1.10 - foo1.3 - foo1.6 - EOF - test_cmp expect actual + test_config tag.sort "v:notvalid" && + test_must_fail git tag -l "foo*" ' test_expect_success 'version sort with prerelease reordering' ' - git config --unset tag.sort && - git config versionsort.prereleaseSuffix -rc && + test_config versionsort.prereleaseSuffix -rc && git tag foo1.6-rc1 && git tag foo1.6-rc2 && git tag -l --sort=version:refname "foo*" >actual && @@ -1476,6 +1784,7 @@ test_expect_success 'version sort with prerelease reordering' ' ' test_expect_success 'reverse version sort with prerelease reordering' ' + test_config versionsort.prereleaseSuffix -rc && git tag -l --sort=-version:refname "foo*" >actual && cat >expect <<-\EOF && foo1.10 @@ -1487,14 +1796,104 @@ test_expect_success 'reverse version sort with prerelease reordering' ' test_cmp expect actual ' -run_with_limited_stack () { - (ulimit -s 128 && "$@") -} +test_expect_success 'version sort with prerelease reordering and common leading character' ' + test_config versionsort.prereleaseSuffix -before && + git tag foo1.7-before1 && + git tag foo1.7 && + git tag foo1.7-after1 && + git tag -l --sort=version:refname "foo1.7*" >actual && + cat >expect <<-\EOF && + foo1.7-before1 + foo1.7 + foo1.7-after1 + EOF + test_cmp expect actual +' -test_lazy_prereq ULIMIT_STACK_SIZE 'run_with_limited_stack true' +test_expect_success 'version sort with prerelease reordering, multiple suffixes and common leading character' ' + test_config versionsort.prereleaseSuffix -before && + git config --add versionsort.prereleaseSuffix -after && + git tag -l --sort=version:refname "foo1.7*" >actual && + cat >expect <<-\EOF && + foo1.7-before1 + foo1.7-after1 + foo1.7 + EOF + test_cmp expect actual +' + +test_expect_success 'version sort with prerelease reordering, multiple suffixes match the same tag' ' + test_config versionsort.prereleaseSuffix -bar && + git config --add versionsort.prereleaseSuffix -foo-baz && + git config --add versionsort.prereleaseSuffix -foo-bar && + git tag foo1.8-foo-bar && + git tag foo1.8-foo-baz && + git tag foo1.8 && + git tag -l --sort=version:refname "foo1.8*" >actual && + cat >expect <<-\EOF && + foo1.8-foo-baz + foo1.8-foo-bar + foo1.8 + EOF + test_cmp expect actual +' + +test_expect_success 'version sort with prerelease reordering, multiple suffixes match starting at the same position' ' + test_config versionsort.prereleaseSuffix -pre && + git config --add versionsort.prereleaseSuffix -prerelease && + git tag foo1.9-pre1 && + git tag foo1.9-pre2 && + git tag foo1.9-prerelease1 && + git tag -l --sort=version:refname "foo1.9*" >actual && + cat >expect <<-\EOF && + foo1.9-pre1 + foo1.9-pre2 + foo1.9-prerelease1 + EOF + test_cmp expect actual +' -# we require ulimit, this excludes Windows -test_expect_success ULIMIT_STACK_SIZE '--contains works in a deep repo' ' +test_expect_success 'version sort with general suffix reordering' ' + test_config versionsort.suffix -alpha && + git config --add versionsort.suffix -beta && + git config --add versionsort.suffix "" && + git config --add versionsort.suffix -gamma && + git config --add versionsort.suffix -delta && + git tag foo1.10-alpha && + git tag foo1.10-beta && + git tag foo1.10-gamma && + git tag foo1.10-delta && + git tag foo1.10-unlisted-suffix && + git tag -l --sort=version:refname "foo1.10*" >actual && + cat >expect <<-\EOF && + foo1.10-alpha + foo1.10-beta + foo1.10 + foo1.10-unlisted-suffix + foo1.10-gamma + foo1.10-delta + EOF + test_cmp expect actual +' + +test_expect_success 'versionsort.suffix overrides versionsort.prereleaseSuffix' ' + test_config versionsort.suffix -before && + test_config versionsort.prereleaseSuffix -after && + git tag -l --sort=version:refname "foo1.7*" >actual && + cat >expect <<-\EOF && + foo1.7-before1 + foo1.7 + foo1.7-after1 + EOF + test_cmp expect actual +' + +test_expect_success 'version sort with very long prerelease suffix' ' + test_config versionsort.prereleaseSuffix -very-looooooooooooooooooooooooong-prerelease-suffix && + git tag -l --sort=version:refname +' + +test_expect_success ULIMIT_STACK_SIZE '--contains and --no-contains work in a deep repo' ' >expect && i=1 && while test $i -lt 8000 @@ -1510,7 +1909,141 @@ EOF" git checkout master && git tag far-far-away HEAD^ && run_with_limited_stack git tag --contains HEAD >actual && + test_cmp expect actual && + run_with_limited_stack git tag --no-contains HEAD >actual && + test_line_count "-gt" 10 actual +' + +test_expect_success '--format should list tags as per format given' ' + cat >expect <<-\EOF && + refname : refs/tags/v1.0 + refname : refs/tags/v1.0.1 + refname : refs/tags/v1.1.3 + EOF + git tag -l --format="refname : %(refname)" "v1*" >actual && test_cmp expect actual ' +test_expect_success "set up color tests" ' + echo "<RED>v1.0<RESET>" >expect.color && + echo "v1.0" >expect.bare && + color_args="--format=%(color:red)%(refname:short) --list v1.0" +' + +test_expect_success '%(color) omitted without tty' ' + TERM=vt100 git tag $color_args >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expect.bare actual +' + +test_expect_success TTY '%(color) present with tty' ' + test_terminal git tag $color_args >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expect.color actual +' + +test_expect_success '--color overrides auto-color' ' + git tag --color $color_args >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expect.color actual +' + +test_expect_success 'color.ui=always overrides auto-color' ' + git -c color.ui=always tag $color_args >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expect.color actual +' + +test_expect_success 'setup --merged test tags' ' + git tag mergetest-1 HEAD~2 && + git tag mergetest-2 HEAD~1 && + git tag mergetest-3 HEAD +' + +test_expect_success '--merged can be used in non-list mode' ' + cat >expect <<-\EOF && + mergetest-1 + mergetest-2 + EOF + git tag --merged=mergetest-2 "mergetest*" >actual && + test_cmp expect actual +' + +test_expect_success '--merged is incompatible with --no-merged' ' + test_must_fail git tag --merged HEAD --no-merged HEAD +' + +test_expect_success '--merged shows merged tags' ' + cat >expect <<-\EOF && + mergetest-1 + mergetest-2 + EOF + git tag -l --merged=mergetest-2 mergetest-* >actual && + test_cmp expect actual +' + +test_expect_success '--no-merged show unmerged tags' ' + cat >expect <<-\EOF && + mergetest-3 + EOF + git tag -l --no-merged=mergetest-2 mergetest-* >actual && + test_cmp expect actual +' + +test_expect_success '--no-merged can be used in non-list mode' ' + git tag --no-merged=mergetest-2 mergetest-* >actual && + test_cmp expect actual +' + +test_expect_success 'ambiguous branch/tags not marked' ' + git tag ambiguous && + git branch ambiguous && + echo ambiguous >expect && + git tag -l ambiguous >actual && + test_cmp expect actual +' + +test_expect_success '--contains combined with --no-contains' ' + ( + git init no-contains && + cd no-contains && + test_commit v0.1 && + test_commit v0.2 && + test_commit v0.3 && + test_commit v0.4 && + test_commit v0.5 && + cat >expected <<-\EOF && + v0.2 + v0.3 + v0.4 + EOF + git tag --contains v0.2 --no-contains v0.5 >actual && + test_cmp expected actual + ) +' + +# As the docs say, list tags which contain a specified *commit*. We +# don't recurse down to tags for trees or blobs pointed to by *those* +# commits. +test_expect_success 'Does --[no-]contains stop at commits? Yes!' ' + cd no-contains && + blob=$(git rev-parse v0.3:v0.3.t) && + tree=$(git rev-parse v0.3^{tree}) && + git tag tag-blob $blob && + git tag tag-tree $tree && + git tag --contains v0.3 >actual && + cat >expected <<-\EOF && + v0.3 + v0.4 + v0.5 + EOF + test_cmp expected actual && + git tag --no-contains v0.3 >actual && + cat >expected <<-\EOF && + v0.1 + v0.2 + EOF + test_cmp expected actual +' + test_done diff --git a/t/t7005-editor.sh b/t/t7005-editor.sh index 1b530b5022..29e5043b94 100755 --- a/t/t7005-editor.sh +++ b/t/t7005-editor.sh @@ -38,7 +38,7 @@ test_expect_success setup ' test_commit "$msg" && echo "$msg" >expect && git show -s --format=%s > actual && - test_cmp actual expect + test_cmp expect actual ' @@ -85,7 +85,7 @@ do git --exec-path=. commit --amend && git show -s --pretty=oneline | sed -e "s/^[0-9a-f]* //" >actual && - test_cmp actual expect + test_cmp expect actual ' done @@ -107,7 +107,7 @@ do git --exec-path=. commit --amend && git show -s --pretty=oneline | sed -e "s/^[0-9a-f]* //" >actual && - test_cmp actual expect + test_cmp expect actual ' done diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh index 947b690fd7..7541ba5edb 100755 --- a/t/t7006-pager.sh +++ b/t/t7006-pager.sh @@ -49,6 +49,19 @@ test_expect_success TTY 'LESS and LV envvars are set for pagination' ' grep ^LV= pager-env.out ' +test_expect_success !MINGW,TTY 'LESS and LV envvars set by git-sh-setup' ' + ( + sane_unset LESS LV && + PAGER="env >pager-env.out; wc" && + export PAGER && + PATH="$(git --exec-path):$PATH" && + export PATH && + test_terminal sh -c ". git-sh-setup && git_pager" + ) && + grep ^LESS= pager-env.out && + grep ^LV= pager-env.out +' + test_expect_success TTY 'some commands do not use a pager' ' rm -f paginated.out && test_terminal git rev-list HEAD && @@ -97,13 +110,6 @@ test_expect_success TTY 'configuration can disable pager' ' ! test -e paginated.out ' -test_expect_success TTY 'git config uses a pager if configured to' ' - rm -f paginated.out && - test_config pager.config true && - test_terminal git config --list && - test -e paginated.out -' - test_expect_success TTY 'configuration can enable pager (from subdir)' ' rm -f paginated.out && mkdir -p subdir && @@ -121,6 +127,166 @@ test_expect_success TTY 'configuration can enable pager (from subdir)' ' } ' +test_expect_success TTY 'git tag -l defaults to paging' ' + rm -f paginated.out && + test_terminal git tag -l && + test -e paginated.out +' + +test_expect_success TTY 'git tag -l respects pager.tag' ' + rm -f paginated.out && + test_terminal git -c pager.tag=false tag -l && + ! test -e paginated.out +' + +test_expect_success TTY 'git tag -l respects --no-pager' ' + rm -f paginated.out && + test_terminal git -c pager.tag --no-pager tag -l && + ! test -e paginated.out +' + +test_expect_success TTY 'git tag with no args defaults to paging' ' + # no args implies -l so this should page like -l + rm -f paginated.out && + test_terminal git tag && + test -e paginated.out +' + +test_expect_success TTY 'git tag with no args respects pager.tag' ' + # no args implies -l so this should page like -l + rm -f paginated.out && + test_terminal git -c pager.tag=false tag && + ! test -e paginated.out +' + +test_expect_success TTY 'git tag --contains defaults to paging' ' + # --contains implies -l so this should page like -l + rm -f paginated.out && + test_terminal git tag --contains && + test -e paginated.out +' + +test_expect_success TTY 'git tag --contains respects pager.tag' ' + # --contains implies -l so this should page like -l + rm -f paginated.out && + test_terminal git -c pager.tag=false tag --contains && + ! test -e paginated.out +' + +test_expect_success TTY 'git tag -a defaults to not paging' ' + test_when_finished "git tag -d newtag" && + rm -f paginated.out && + test_terminal git tag -am message newtag && + ! test -e paginated.out +' + +test_expect_success TTY 'git tag -a ignores pager.tag' ' + test_when_finished "git tag -d newtag" && + rm -f paginated.out && + test_terminal git -c pager.tag tag -am message newtag && + ! test -e paginated.out +' + +test_expect_success TTY 'git tag -a respects --paginate' ' + test_when_finished "git tag -d newtag" && + rm -f paginated.out && + test_terminal git --paginate tag -am message newtag && + test -e paginated.out +' + +test_expect_success TTY 'git tag as alias ignores pager.tag with -a' ' + test_when_finished "git tag -d newtag" && + rm -f paginated.out && + test_terminal git -c pager.tag -c alias.t=tag t -am message newtag && + ! test -e paginated.out +' + +test_expect_success TTY 'git tag as alias respects pager.tag with -l' ' + rm -f paginated.out && + test_terminal git -c pager.tag=false -c alias.t=tag t -l && + ! test -e paginated.out +' + +test_expect_success TTY 'git branch defaults to paging' ' + rm -f paginated.out && + test_terminal git branch && + test -e paginated.out +' + +test_expect_success TTY 'git branch respects pager.branch' ' + rm -f paginated.out && + test_terminal git -c pager.branch=false branch && + ! test -e paginated.out +' + +test_expect_success TTY 'git branch respects --no-pager' ' + rm -f paginated.out && + test_terminal git --no-pager branch && + ! test -e paginated.out +' + +test_expect_success TTY 'git branch --edit-description ignores pager.branch' ' + rm -f paginated.out editor.used && + write_script editor <<-\EOF && + echo "New description" >"$1" + touch editor.used + EOF + EDITOR=./editor test_terminal git -c pager.branch branch --edit-description && + ! test -e paginated.out && + test -e editor.used +' + +test_expect_success TTY 'git branch --set-upstream-to ignores pager.branch' ' + rm -f paginated.out && + git branch other && + test_when_finished "git branch -D other" && + test_terminal git -c pager.branch branch --set-upstream-to=other && + test_when_finished "git branch --unset-upstream" && + ! test -e paginated.out +' + +test_expect_success TTY 'git config ignores pager.config when setting' ' + rm -f paginated.out && + test_terminal git -c pager.config config foo.bar bar && + ! test -e paginated.out +' + +test_expect_success TTY 'git config --edit ignores pager.config' ' + rm -f paginated.out editor.used && + write_script editor <<-\EOF && + touch editor.used + EOF + EDITOR=./editor test_terminal git -c pager.config config --edit && + ! test -e paginated.out && + test -e editor.used +' + +test_expect_success TTY 'git config --get ignores pager.config' ' + rm -f paginated.out && + test_terminal git -c pager.config config --get foo.bar && + ! test -e paginated.out +' + +test_expect_success TTY 'git config --get-urlmatch defaults to paging' ' + rm -f paginated.out && + test_terminal git -c http."https://foo.com/".bar=foo \ + config --get-urlmatch http https://foo.com && + test -e paginated.out +' + +test_expect_success TTY 'git config --get-all respects pager.config' ' + rm -f paginated.out && + test_terminal git -c pager.config=false config --get-all foo.bar && + ! test -e paginated.out +' + +test_expect_success TTY 'git config --list defaults to paging' ' + rm -f paginated.out && + test_terminal git config --list && + test -e paginated.out +' + + # A colored commit log will begin with an appropriate ANSI escape # for the first color; the text "commit" comes later. colorful() { @@ -146,7 +312,7 @@ test_expect_success 'no color when stdout is a regular file' ' test_expect_success TTY 'color when writing to a pager' ' rm -f paginated.out && test_config color.ui auto && - test_terminal env TERM=vt100 git log && + test_terminal git log && colorful paginated.out ' @@ -154,7 +320,7 @@ test_expect_success TTY 'colors are suppressed by color.pager' ' rm -f paginated.out && test_config color.ui auto && test_config color.pager false && - test_terminal env TERM=vt100 git log && + test_terminal git log && ! colorful paginated.out ' @@ -173,7 +339,7 @@ test_expect_success 'color when writing to a file intended for a pager' ' test_expect_success TTY 'colors are sent to pager for external commands' ' test_config alias.externallog "!git log" && test_config color.ui auto && - test_terminal env TERM=vt100 git -p externallog && + test_terminal git -p externallog && colorful paginated.out ' @@ -347,27 +513,48 @@ test_pager_choices 'git aliasedlog' test_default_pager expect_success 'git -p aliasedlog' test_PAGER_overrides expect_success 'git -p aliasedlog' test_core_pager_overrides expect_success 'git -p aliasedlog' -test_core_pager_subdir expect_failure 'git -p aliasedlog' +test_core_pager_subdir expect_success 'git -p aliasedlog' test_GIT_PAGER_overrides expect_success 'git -p aliasedlog' test_default_pager expect_success 'git -p true' test_PAGER_overrides expect_success 'git -p true' test_core_pager_overrides expect_success 'git -p true' -test_core_pager_subdir expect_failure 'git -p true' +test_core_pager_subdir expect_success 'git -p true' test_GIT_PAGER_overrides expect_success 'git -p true' test_default_pager expect_success test_must_fail 'git -p request-pull' test_PAGER_overrides expect_success test_must_fail 'git -p request-pull' test_core_pager_overrides expect_success test_must_fail 'git -p request-pull' -test_core_pager_subdir expect_failure test_must_fail 'git -p request-pull' +test_core_pager_subdir expect_success test_must_fail 'git -p request-pull' test_GIT_PAGER_overrides expect_success test_must_fail 'git -p request-pull' test_default_pager expect_success test_must_fail 'git -p' test_PAGER_overrides expect_success test_must_fail 'git -p' test_local_config_ignored expect_failure test_must_fail 'git -p' -test_no_local_config_subdir expect_success test_must_fail 'git -p' test_GIT_PAGER_overrides expect_success test_must_fail 'git -p' +test_expect_success TTY 'core.pager in repo config works and retains cwd' ' + sane_unset GIT_PAGER && + test_config core.pager "cat >cwd-retained" && + ( + cd sub && + rm -f cwd-retained && + test_terminal git -p rev-parse HEAD && + test_path_is_file cwd-retained + ) +' + +test_expect_success TTY 'core.pager is found via alias in subdirectory' ' + sane_unset GIT_PAGER && + test_config core.pager "cat >via-alias" && + ( + cd sub && + rm -f via-alias && + test_terminal git -c alias.r="-p rev-parse" r HEAD && + test_path_is_file via-alias + ) +' + test_doesnt_paginate expect_failure test_must_fail 'git -p nonsense' test_pager_choices 'git shortlog' @@ -424,7 +611,7 @@ test_expect_success TTY 'command-specific pager works for external commands' ' echo "foo:initial" >expect && >actual && test_config pager.external "sed s/^/foo:/ >actual" && - test_terminal git --exec-path="`pwd`" external log --format=%s -1 && + test_terminal git --exec-path="$(pwd)" external log --format=%s -1 && test_cmp expect actual ' @@ -447,4 +634,27 @@ test_expect_success TTY 'external command pagers override sub-commands' ' test_cmp expect actual ' +test_expect_success 'command with underscores does not complain' ' + write_script git-under_score <<-\EOF && + echo ok + EOF + git --exec-path=. under_score >actual 2>&1 && + echo ok >expect && + test_cmp expect actual +' + +test_expect_success TTY 'git tag with auto-columns ' ' + test_commit one && + test_commit two && + test_commit three && + test_commit four && + test_commit five && + cat >expect <<-\EOF && + initial one two three four five + EOF + test_terminal env PAGER="cat >actual" COLUMNS=80 \ + git -c column.ui=auto tag --sort=authordate && + test_cmp expect actual +' + test_done diff --git a/t/t7008-grep-binary.sh b/t/t7008-grep-binary.sh index b146406e9c..615e7e0162 100755 --- a/t/t7008-grep-binary.sh +++ b/t/t7008-grep-binary.sh @@ -4,8 +4,43 @@ test_description='git grep in binary files' . ./test-lib.sh +nul_match () { + matches=$1 + flags=$2 + pattern=$3 + pattern_human=$(echo "$pattern" | sed 's/Q/<NUL>/g') + + if test "$matches" = 1 + then + test_expect_success "git grep -f f $flags '$pattern_human' a" " + printf '$pattern' | q_to_nul >f && + git grep -f f $flags a + " + elif test "$matches" = 0 + then + test_expect_success "git grep -f f $flags '$pattern_human' a" " + printf '$pattern' | q_to_nul >f && + test_must_fail git grep -f f $flags a + " + elif test "$matches" = T1 + then + test_expect_failure "git grep -f f $flags '$pattern_human' a" " + printf '$pattern' | q_to_nul >f && + git grep -f f $flags a + " + elif test "$matches" = T0 + then + test_expect_failure "git grep -f f $flags '$pattern_human' a" " + printf '$pattern' | q_to_nul >f && + test_must_fail git grep -f f $flags a + " + else + test_expect_success "PANIC: Test framework error. Unknown matches value $matches" 'false' + fi +} + test_expect_success 'setup' " - echo 'binaryQfile' | q_to_nul >a && + echo 'binaryQfileQm[*]cQ*æQð' | q_to_nul >a && git add a && git commit -m. " @@ -69,35 +104,71 @@ test_expect_failure 'git grep .fi a' ' git grep .fi a ' -test_expect_success 'git grep -F y<NUL>f a' " - printf 'yQf' | q_to_nul >f && - git grep -f f -F a -" - -test_expect_success 'git grep -F y<NUL>x a' " - printf 'yQx' | q_to_nul >f && - test_must_fail git grep -f f -F a -" - -test_expect_success 'git grep -Fi Y<NUL>f a' " - printf 'YQf' | q_to_nul >f && - git grep -f f -Fi a -" - -test_expect_success 'git grep -Fi Y<NUL>x a' " - printf 'YQx' | q_to_nul >f && - test_must_fail git grep -f f -Fi a -" - -test_expect_success 'git grep y<NUL>f a' " - printf 'yQf' | q_to_nul >f && - git grep -f f a -" - -test_expect_success 'git grep y<NUL>x a' " - printf 'yQx' | q_to_nul >f && - test_must_fail git grep -f f a -" +nul_match 1 '-F' 'yQf' +nul_match 0 '-F' 'yQx' +nul_match 1 '-Fi' 'YQf' +nul_match 0 '-Fi' 'YQx' +nul_match 1 '' 'yQf' +nul_match 0 '' 'yQx' +nul_match 1 '' 'æQð' +nul_match 1 '-F' 'eQm[*]c' +nul_match 1 '-Fi' 'EQM[*]C' + +# Regex patterns that would match but shouldn't with -F +nul_match 0 '-F' 'yQ[f]' +nul_match 0 '-F' '[y]Qf' +nul_match 0 '-Fi' 'YQ[F]' +nul_match 0 '-Fi' '[Y]QF' +nul_match 0 '-F' 'æQ[ð]' +nul_match 0 '-F' '[æ]Qð' +nul_match 0 '-Fi' 'ÆQ[Ã]' +nul_match 0 '-Fi' '[Æ]QÃ' + +# kwset is disabled on -i & non-ASCII. No way to match non-ASCII \0 +# patterns case-insensitively. +nul_match T1 '-i' 'ÆQÃ' + +# \0 implicitly disables regexes. This is an undocumented internal +# limitation. +nul_match T1 '' 'yQ[f]' +nul_match T1 '' '[y]Qf' +nul_match T1 '-i' 'YQ[F]' +nul_match T1 '-i' '[Y]Qf' +nul_match T1 '' 'æQ[ð]' +nul_match T1 '' '[æ]Qð' +nul_match T1 '-i' 'ÆQ[Ã]' + +# ... because of \0 implicitly disabling regexes regexes that +# should/shouldn't match don't do the right thing. +nul_match T1 '' 'eQm.*cQ' +nul_match T1 '-i' 'EQM.*cQ' +nul_match T0 '' 'eQm[*]c' +nul_match T0 '-i' 'EQM[*]C' + +# Due to the REG_STARTEND extension when kwset() is disabled on -i & +# non-ASCII the string will be matched in its entirety, but the +# pattern will be cut off at the first \0. +nul_match 0 '-i' 'NOMATCHQð' +nul_match T0 '-i' '[Æ]QNOMATCH' +nul_match T0 '-i' '[æ]QNOMATCH' +# Matches, but for the wrong reasons, just stops at [æ] +nul_match 1 '-i' '[Æ]Qð' +nul_match 1 '-i' '[æ]Qð' + +# Ensure that the matcher doesn't regress to something that stops at +# \0 +nul_match 0 '-F' 'yQ[f]' +nul_match 0 '-Fi' 'YQ[F]' +nul_match 0 '' 'yQNOMATCH' +nul_match 0 '' 'QNOMATCH' +nul_match 0 '-i' 'YQNOMATCH' +nul_match 0 '-i' 'QNOMATCH' +nul_match 0 '-F' 'æQ[ð]' +nul_match 0 '-Fi' 'ÆQ[Ã]' +nul_match 0 '' 'yQNÓMATCH' +nul_match 0 '' 'QNÓMATCH' +nul_match 0 '-i' 'YQNÓMATCH' +nul_match 0 '-i' 'QNÓMATCH' test_expect_success 'grep respects binary diff attribute' ' echo text >t && @@ -141,7 +212,8 @@ test_expect_success 'grep respects not-binary diff attribute' ' test_cmp expect actual && echo "b diff" >.gitattributes && echo "b:binQary" >expect && - git grep bin b | nul_to_q >actual && + git grep bin b >actual.raw && + nul_to_q <actual.raw >actual && test_cmp expect actual ' @@ -161,7 +233,7 @@ test_expect_success 'grep does not honor textconv' ' ' test_expect_success 'grep --textconv honors textconv' ' - echo "a:binaryQfile" >expect && + echo "a:binaryQfileQm[*]cQ*æQð" >expect && git grep --textconv Qfile >actual && test_cmp expect actual ' @@ -171,7 +243,7 @@ test_expect_success 'grep --no-textconv does not honor textconv' ' ' test_expect_success 'grep --textconv blob honors textconv' ' - echo "HEAD:a:binaryQfile" >expect && + echo "HEAD:a:binaryQfileQm[*]cQ*æQð" >expect && git grep --textconv Qfile HEAD:a >actual && test_cmp expect actual ' diff --git a/t/t7009-filter-branch-null-sha1.sh b/t/t7009-filter-branch-null-sha1.sh index c27f90f285..a8d9ec4987 100755 --- a/t/t7009-filter-branch-null-sha1.sh +++ b/t/t7009-filter-branch-null-sha1.sh @@ -31,6 +31,12 @@ test_expect_success 'setup: bring HEAD and index in sync' ' git commit -a -m "back to normal" ' +test_expect_success 'noop filter-branch complains' ' + test_must_fail git filter-branch \ + --force --prune-empty \ + --index-filter "true" +' + test_expect_success 'filter commands are still checked' ' test_must_fail git filter-branch \ --force --prune-empty \ diff --git a/t/t7011-skip-worktree-reading.sh b/t/t7011-skip-worktree-reading.sh index 88d60c1ce2..84f41451ec 100755 --- a/t/t7011-skip-worktree-reading.sh +++ b/t/t7011-skip-worktree-reading.sh @@ -23,17 +23,15 @@ S sub/1 H sub/2 EOF -NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 - setup_absent() { test -f 1 && rm 1 git update-index --remove 1 && - git update-index --add --cacheinfo 100644 $NULL_SHA1 1 && + git update-index --add --cacheinfo 100644 $EMPTY_BLOB 1 && git update-index --skip-worktree 1 } test_absent() { - echo "100644 $NULL_SHA1 0 1" > expected && + echo "100644 $EMPTY_BLOB 0 1" > expected && git ls-files --stage 1 > result && test_cmp expected result && test ! -f 1 @@ -42,12 +40,12 @@ test_absent() { setup_dirty() { git update-index --force-remove 1 && echo dirty > 1 && - git update-index --add --cacheinfo 100644 $NULL_SHA1 1 && + git update-index --add --cacheinfo 100644 $EMPTY_BLOB 1 && git update-index --skip-worktree 1 } test_dirty() { - echo "100644 $NULL_SHA1 0 1" > expected && + echo "100644 $EMPTY_BLOB 0 1" > expected && git ls-files --stage 1 > result && test_cmp expected result && echo dirty > expected @@ -120,7 +118,7 @@ test_expect_success 'grep with skip-worktree file' ' test "$(git grep --no-ext-grep test)" = "1:test" ' -echo ":000000 100644 $_z40 $NULL_SHA1 A 1" > expected +echo ":000000 100644 $_z40 $EMPTY_BLOB A 1" > expected test_expect_success 'diff-index does not examine skip-worktree absent entries' ' setup_absent && git diff-index HEAD -- 1 > result && diff --git a/t/t7012-skip-worktree-writing.sh b/t/t7012-skip-worktree-writing.sh index 9ceaa4049f..9d1abe50ef 100755 --- a/t/t7012-skip-worktree-writing.sh +++ b/t/t7012-skip-worktree-writing.sh @@ -53,17 +53,15 @@ test_expect_success 'read-tree removes worktree, dirty case' ' git update-index --no-skip-worktree added ' -NULL_SHA1=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 - setup_absent() { test -f 1 && rm 1 git update-index --remove 1 && - git update-index --add --cacheinfo 100644 $NULL_SHA1 1 && + git update-index --add --cacheinfo 100644 $EMPTY_BLOB 1 && git update-index --skip-worktree 1 } test_absent() { - echo "100644 $NULL_SHA1 0 1" > expected && + echo "100644 $EMPTY_BLOB 0 1" > expected && git ls-files --stage 1 > result && test_cmp expected result && test ! -f 1 @@ -72,12 +70,12 @@ test_absent() { setup_dirty() { git update-index --force-remove 1 && echo dirty > 1 && - git update-index --add --cacheinfo 100644 $NULL_SHA1 1 && + git update-index --add --cacheinfo 100644 $EMPTY_BLOB 1 && git update-index --skip-worktree 1 } test_dirty() { - echo "100644 $NULL_SHA1 0 1" > expected && + echo "100644 $EMPTY_BLOB 0 1" > expected && git ls-files --stage 1 > result && test_cmp expected result && echo dirty > expected diff --git a/t/t7030-verify-tag.sh b/t/t7030-verify-tag.sh new file mode 100755 index 0000000000..b4b49eeb08 --- /dev/null +++ b/t/t7030-verify-tag.sh @@ -0,0 +1,142 @@ +#!/bin/sh + +test_description='signed tag tests' +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-gpg.sh" + +test_expect_success GPG 'create signed tags' ' + echo 1 >file && git add file && + test_tick && git commit -m initial && + git tag -s -m initial initial && + git branch side && + + echo 2 >file && test_tick && git commit -a -m second && + git tag -s -m second second && + + git checkout side && + echo 3 >elif && git add elif && + test_tick && git commit -m "third on side" && + + git checkout master && + test_tick && git merge -S side && + git tag -s -m merge merge && + + echo 4 >file && test_tick && git commit -a -S -m "fourth unsigned" && + git tag -a -m fourth-unsigned fourth-unsigned && + + test_tick && git commit --amend -S -m "fourth signed" && + git tag -s -m fourth fourth-signed && + + echo 5 >file && test_tick && git commit -a -m "fifth" && + git tag fifth-unsigned && + + git config commit.gpgsign true && + echo 6 >file && test_tick && git commit -a -m "sixth" && + git tag -a -m sixth sixth-unsigned && + + test_tick && git rebase -f HEAD^^ && git tag -s -m 6th sixth-signed HEAD^ && + git tag -m seventh -s seventh-signed && + + echo 8 >file && test_tick && git commit -a -m eighth && + git tag -uB7227189 -m eighth eighth-signed-alt +' + +test_expect_success GPG 'verify and show signatures' ' + ( + for tag in initial second merge fourth-signed sixth-signed seventh-signed + do + git verify-tag $tag 2>actual && + grep "Good signature from" actual && + ! grep "BAD signature from" actual && + echo $tag OK || exit 1 + done + ) && + ( + for tag in fourth-unsigned fifth-unsigned sixth-unsigned + do + test_must_fail git verify-tag $tag 2>actual && + ! grep "Good signature from" actual && + ! grep "BAD signature from" actual && + echo $tag OK || exit 1 + done + ) && + ( + for tag in eighth-signed-alt + do + git verify-tag $tag 2>actual && + grep "Good signature from" actual && + ! grep "BAD signature from" actual && + grep "not certified" actual && + echo $tag OK || exit 1 + done + ) +' + +test_expect_success GPG 'detect fudged signature' ' + git cat-file tag seventh-signed >raw && + sed -e "s/seventh/7th forged/" raw >forged1 && + git hash-object -w -t tag forged1 >forged1.tag && + test_must_fail git verify-tag $(cat forged1.tag) 2>actual1 && + grep "BAD signature from" actual1 && + ! grep "Good signature from" actual1 +' + +test_expect_success GPG 'verify signatures with --raw' ' + ( + for tag in initial second merge fourth-signed sixth-signed seventh-signed + do + git verify-tag --raw $tag 2>actual && + grep "GOODSIG" actual && + ! grep "BADSIG" actual && + echo $tag OK || exit 1 + done + ) && + ( + for tag in fourth-unsigned fifth-unsigned sixth-unsigned + do + test_must_fail git verify-tag --raw $tag 2>actual && + ! grep "GOODSIG" actual && + ! grep "BADSIG" actual && + echo $tag OK || exit 1 + done + ) && + ( + for tag in eighth-signed-alt + do + git verify-tag --raw $tag 2>actual && + grep "GOODSIG" actual && + ! grep "BADSIG" actual && + grep "TRUST_UNDEFINED" actual && + echo $tag OK || exit 1 + done + ) +' + +test_expect_success GPG 'verify multiple tags' ' + tags="fourth-signed sixth-signed seventh-signed" && + for i in $tags + do + git verify-tag -v --raw $i || return 1 + done >expect.stdout 2>expect.stderr.1 && + grep "^.GNUPG:." <expect.stderr.1 >expect.stderr && + git verify-tag -v --raw $tags >actual.stdout 2>actual.stderr.1 && + grep "^.GNUPG:." <actual.stderr.1 >actual.stderr && + test_cmp expect.stdout actual.stdout && + test_cmp expect.stderr actual.stderr +' + +test_expect_success GPG 'verifying tag with --format' ' + cat >expect <<-\EOF && + tagname : fourth-signed + EOF + git verify-tag --format="tagname : %(tag)" "fourth-signed" >actual && + test_cmp expect actual +' + +test_expect_success GPG 'verifying a forged tag with --format should fail silently' ' + >expect && + test_must_fail git verify-tag --format="tagname : %(tag)" $(cat forged1.tag) >actual-forged && + test_cmp expect actual-forged +' + +test_done diff --git a/t/t7060-wtstatus.sh b/t/t7060-wtstatus.sh index 741ec08576..53cf42fac1 100755 --- a/t/t7060-wtstatus.sh +++ b/t/t7060-wtstatus.sh @@ -34,6 +34,7 @@ test_expect_success 'M/D conflict does not segfault' ' On branch side You have unmerged paths. (fix conflicts and run "git commit") + (use "git merge --abort" to abort the merge) Unmerged paths: (use "git add/rm <file>..." as appropriate to mark resolution) @@ -106,7 +107,7 @@ test_expect_success 'git diff-index --cached -M shows 2 added + 1 unmerged' ' A THREE A TWO EOF - git diff-index --cached --name-status HEAD >actual && + git diff-index --cached -M --name-status HEAD >actual && test_cmp expected actual ' @@ -138,6 +139,7 @@ test_expect_success 'status when conflicts with add and rm advice (deleted by th On branch master You have unmerged paths. (fix conflicts and run "git commit") + (use "git merge --abort" to abort the merge) Unmerged paths: (use "git add/rm <file>..." as appropriate to mark resolution) @@ -171,6 +173,7 @@ test_expect_success 'status when conflicts with add and rm advice (both deleted) On branch conflict_second You have unmerged paths. (fix conflicts and run "git commit") + (use "git merge --abort" to abort the merge) Unmerged paths: (use "git add/rm <file>..." as appropriate to mark resolution) @@ -195,6 +198,7 @@ test_expect_success 'status when conflicts with only rm advice (both deleted)' ' On branch conflict_second You have unmerged paths. (fix conflicts and run "git commit") + (use "git merge --abort" to abort the merge) Changes to be committed: @@ -213,5 +217,40 @@ EOF git checkout master ' +test_expect_success 'status --branch with detached HEAD' ' + git reset --hard && + git checkout master^0 && + git status --branch --porcelain >actual && + cat >expected <<-EOF && + ## HEAD (no branch) + ?? .gitconfig + ?? actual + ?? expect + ?? expected + ?? mdconflict/ + EOF + test_i18ncmp expected actual +' + +## Duplicate the above test and verify --porcelain=v1 arg parsing. +test_expect_success 'status --porcelain=v1 --branch with detached HEAD' ' + git reset --hard && + git checkout master^0 && + git status --branch --porcelain=v1 >actual && + cat >expected <<-EOF && + ## HEAD (no branch) + ?? .gitconfig + ?? actual + ?? expect + ?? expected + ?? mdconflict/ + EOF + test_i18ncmp expected actual +' + +## Verify parser error on invalid --porcelain argument. +test_expect_success 'status --porcelain=bogus' ' + test_must_fail git status --porcelain=bogus +' test_done diff --git a/t/t7061-wtstatus-ignore.sh b/t/t7061-wtstatus-ignore.sh index cdc0747bf0..0c394cf995 100755 --- a/t/t7061-wtstatus-ignore.sh +++ b/t/t7061-wtstatus-ignore.sh @@ -9,6 +9,7 @@ cat >expected <<\EOF ?? actual ?? expected ?? untracked/ +!! untracked/ignored EOF test_expect_success 'status untracked directory with --ignored' ' @@ -271,4 +272,15 @@ test_expect_success 'status ignored tracked directory with uncommitted file in t test_cmp expected actual ' +cat >expected <<\EOF +!! tracked/submodule/ +EOF + +test_expect_success 'status ignores submodule in excluded directory' ' + git init tracked/submodule && + test_commit -C tracked/submodule initial && + git status --porcelain --ignored -u tracked/submodule >actual && + test_cmp expected actual +' + test_done diff --git a/t/t7063-status-untracked-cache.sh b/t/t7063-status-untracked-cache.sh index bd4806c12a..c61e304e97 100755 --- a/t/t7063-status-untracked-cache.sh +++ b/t/t7063-status-untracked-cache.sh @@ -4,14 +4,35 @@ test_description='test untracked cache' . ./test-lib.sh +# On some filesystems (e.g. FreeBSD's ext2 and ufs) directory mtime +# is updated lazily after contents in the directory changes, which +# forces the untracked cache code to take the slow path. A test +# that wants to make sure that the fast path works correctly should +# call this helper to make mtime of the containing directory in sync +# with the reality before checking the fast path behaviour. +# +# See <20160803174522.5571-1-pclouds@gmail.com> if you want to know +# more. + +GIT_FORCE_UNTRACKED_CACHE=true +export GIT_FORCE_UNTRACKED_CACHE + +sync_mtime () { + find . -type d -ls >/dev/null +} + avoid_racy() { sleep 1 } -# It's fine if git update-index returns an error code other than one, -# it'll be caught in the first test. +status_is_clean() { + >../status.expect && + git status --porcelain >../status.actual && + test_cmp ../status.expect ../status.actual +} + test_lazy_prereq UNTRACKED_CACHE ' - { git update-index --untracked-cache; ret=$?; } && + { git update-index --test-untracked-cache; ret=$?; } && test $ret -ne 1 ' @@ -20,6 +41,10 @@ if ! test_have_prereq UNTRACKED_CACHE; then test_done fi +test_expect_success 'core.untrackedCache is unset' ' + test_must_fail git config --get core.untrackedCache +' + test_expect_success 'setup' ' git init worktree && cd worktree && @@ -32,13 +57,13 @@ test_expect_success 'setup' ' test_expect_success 'untracked cache is empty' ' test-dump-untracked-cache >../actual && - cat >../expect <<EOF && + cat >../expect-empty <<EOF && info/exclude 0000000000000000000000000000000000000000 core.excludesfile 0000000000000000000000000000000000000000 exclude_per_dir .gitignore flags 00000006 EOF - test_cmp ../expect ../actual + test_cmp ../expect-empty ../actual ' cat >../status.expect <<EOF && @@ -51,7 +76,7 @@ A two EOF cat >../dump.expect <<EOF && -info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 +info/exclude $EMPTY_BLOB core.excludesfile 0000000000000000000000000000000000000000 exclude_per_dir .gitignore flags 00000006 @@ -135,7 +160,7 @@ EOF test_expect_success 'verify untracked cache dump' ' test-dump-untracked-cache >../actual && cat >../expect <<EOF && -info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 +info/exclude $EMPTY_BLOB core.excludesfile 0000000000000000000000000000000000000000 exclude_per_dir .gitignore flags 00000006 @@ -182,7 +207,7 @@ EOF test_expect_success 'verify untracked cache dump' ' test-dump-untracked-cache >../actual && cat >../expect <<EOF && -info/exclude e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 +info/exclude $EMPTY_BLOB core.excludesfile 0000000000000000000000000000000000000000 exclude_per_dir .gitignore flags 00000006 @@ -354,4 +379,398 @@ EOF test_cmp ../expect ../actual ' +test_expect_success 'set up for sparse checkout testing' ' + echo two >done/.gitignore && + echo three >>done/.gitignore && + echo two >done/two && + git add -f done/two done/.gitignore && + git commit -m "first commit" +' + +test_expect_success 'status after commit' ' + : >../trace && + GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \ + git status --porcelain >../actual && + cat >../status.expect <<EOF && +?? .gitignore +?? dtwo/ +EOF + test_cmp ../status.expect ../actual && + cat >../trace.expect <<EOF && +node creation: 0 +gitignore invalidation: 0 +directory invalidation: 0 +opendir: 2 +EOF + test_cmp ../trace.expect ../trace +' + +test_expect_success 'untracked cache correct after commit' ' + test-dump-untracked-cache >../actual && + cat >../expect <<EOF && +info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0 +core.excludesfile 0000000000000000000000000000000000000000 +exclude_per_dir .gitignore +flags 00000006 +/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid +.gitignore +dtwo/ +/done/ 0000000000000000000000000000000000000000 recurse valid +/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid +/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid +two +EOF + test_cmp ../expect ../actual +' + +test_expect_success 'set up sparse checkout' ' + echo "done/[a-z]*" >.git/info/sparse-checkout && + test_config core.sparsecheckout true && + git checkout master && + git update-index --force-untracked-cache && + git status --porcelain >/dev/null && # prime the cache + test_path_is_missing done/.gitignore && + test_path_is_file done/one +' + +test_expect_success 'create/modify files, some of which are gitignored' ' + echo two bis >done/two && + echo three >done/three && # three is gitignored + echo four >done/four && # four is gitignored at a higher level + echo five >done/five && # five is not gitignored + echo test >base && #we need to ensure that the root dir is touched + rm base && + sync_mtime +' + +test_expect_success 'test sparse status with untracked cache' ' + : >../trace && + avoid_racy && + GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \ + git status --porcelain >../status.actual && + cat >../status.expect <<EOF && + M done/two +?? .gitignore +?? done/five +?? dtwo/ +EOF + test_cmp ../status.expect ../status.actual && + cat >../trace.expect <<EOF && +node creation: 0 +gitignore invalidation: 1 +directory invalidation: 2 +opendir: 2 +EOF + test_cmp ../trace.expect ../trace +' + +test_expect_success 'untracked cache correct after status' ' + test-dump-untracked-cache >../actual && + cat >../expect <<EOF && +info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0 +core.excludesfile 0000000000000000000000000000000000000000 +exclude_per_dir .gitignore +flags 00000006 +/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid +.gitignore +dtwo/ +/done/ 1946f0437f90c5005533cbe1736a6451ca301714 recurse valid +five +/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid +/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid +two +EOF + test_cmp ../expect ../actual +' + +test_expect_success 'test sparse status again with untracked cache' ' + avoid_racy && + : >../trace && + GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \ + git status --porcelain >../status.actual && + cat >../status.expect <<EOF && + M done/two +?? .gitignore +?? done/five +?? dtwo/ +EOF + test_cmp ../status.expect ../status.actual && + cat >../trace.expect <<EOF && +node creation: 0 +gitignore invalidation: 0 +directory invalidation: 0 +opendir: 0 +EOF + test_cmp ../trace.expect ../trace +' + +test_expect_success 'set up for test of subdir and sparse checkouts' ' + mkdir done/sub && + mkdir done/sub/sub && + echo "sub" > done/sub/sub/file +' + +test_expect_success 'test sparse status with untracked cache and subdir' ' + avoid_racy && + : >../trace && + GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \ + git status --porcelain >../status.actual && + cat >../status.expect <<EOF && + M done/two +?? .gitignore +?? done/five +?? done/sub/ +?? dtwo/ +EOF + test_cmp ../status.expect ../status.actual && + cat >../trace.expect <<EOF && +node creation: 2 +gitignore invalidation: 0 +directory invalidation: 1 +opendir: 3 +EOF + test_cmp ../trace.expect ../trace +' + +test_expect_success 'verify untracked cache dump (sparse/subdirs)' ' + test-dump-untracked-cache >../actual && + cat >../expect-from-test-dump <<EOF && +info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0 +core.excludesfile 0000000000000000000000000000000000000000 +exclude_per_dir .gitignore +flags 00000006 +/ e6fcc8f2ee31bae321d66afd183fcb7237afae6e recurse valid +.gitignore +dtwo/ +/done/ 1946f0437f90c5005533cbe1736a6451ca301714 recurse valid +five +sub/ +/done/sub/ 0000000000000000000000000000000000000000 recurse check_only valid +sub/ +/done/sub/sub/ 0000000000000000000000000000000000000000 recurse check_only valid +file +/dthree/ 0000000000000000000000000000000000000000 recurse check_only valid +/dtwo/ 0000000000000000000000000000000000000000 recurse check_only valid +two +EOF + test_cmp ../expect-from-test-dump ../actual +' + +test_expect_success 'test sparse status again with untracked cache and subdir' ' + avoid_racy && + : >../trace && + GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace" \ + git status --porcelain >../status.actual && + test_cmp ../status.expect ../status.actual && + cat >../trace.expect <<EOF && +node creation: 0 +gitignore invalidation: 0 +directory invalidation: 0 +opendir: 0 +EOF + test_cmp ../trace.expect ../trace +' + +test_expect_success 'move entry in subdir from untracked to cached' ' + git add dtwo/two && + git status --porcelain >../status.actual && + cat >../status.expect <<EOF && + M done/two +A dtwo/two +?? .gitignore +?? done/five +?? done/sub/ +EOF + test_cmp ../status.expect ../status.actual +' + +test_expect_success 'move entry in subdir from cached to untracked' ' + git rm --cached dtwo/two && + git status --porcelain >../status.actual && + cat >../status.expect <<EOF && + M done/two +?? .gitignore +?? done/five +?? done/sub/ +?? dtwo/ +EOF + test_cmp ../status.expect ../status.actual +' + +test_expect_success '--no-untracked-cache removes the cache' ' + git update-index --no-untracked-cache && + test-dump-untracked-cache >../actual && + echo "no untracked cache" >../expect-no-uc && + test_cmp ../expect-no-uc ../actual +' + +test_expect_success 'git status does not change anything' ' + git status && + test-dump-untracked-cache >../actual && + test_cmp ../expect-no-uc ../actual +' + +test_expect_success 'setting core.untrackedCache to true and using git status creates the cache' ' + git config core.untrackedCache true && + test-dump-untracked-cache >../actual && + test_cmp ../expect-no-uc ../actual && + git status && + test-dump-untracked-cache >../actual && + test_cmp ../expect-from-test-dump ../actual +' + +test_expect_success 'using --no-untracked-cache does not fail when core.untrackedCache is true' ' + git update-index --no-untracked-cache && + test-dump-untracked-cache >../actual && + test_cmp ../expect-no-uc ../actual && + git update-index --untracked-cache && + test-dump-untracked-cache >../actual && + test_cmp ../expect-empty ../actual +' + +test_expect_success 'setting core.untrackedCache to false and using git status removes the cache' ' + git config core.untrackedCache false && + test-dump-untracked-cache >../actual && + test_cmp ../expect-empty ../actual && + git status && + test-dump-untracked-cache >../actual && + test_cmp ../expect-no-uc ../actual +' + +test_expect_success 'using --untracked-cache does not fail when core.untrackedCache is false' ' + git update-index --untracked-cache && + test-dump-untracked-cache >../actual && + test_cmp ../expect-empty ../actual +' + +test_expect_success 'setting core.untrackedCache to keep' ' + git config core.untrackedCache keep && + git update-index --untracked-cache && + test-dump-untracked-cache >../actual && + test_cmp ../expect-empty ../actual && + git status && + test-dump-untracked-cache >../actual && + test_cmp ../expect-from-test-dump ../actual && + git update-index --no-untracked-cache && + test-dump-untracked-cache >../actual && + test_cmp ../expect-no-uc ../actual && + git update-index --force-untracked-cache && + test-dump-untracked-cache >../actual && + test_cmp ../expect-empty ../actual && + git status && + test-dump-untracked-cache >../actual && + test_cmp ../expect-from-test-dump ../actual +' + +test_expect_success 'test ident field is working' ' + mkdir ../other_worktree && + cp -R done dthree dtwo four three ../other_worktree && + GIT_WORK_TREE=../other_worktree git status 2>../err && + echo "warning: Untracked cache is disabled on this system or location." >../expect && + test_i18ncmp ../expect ../err +' + +test_expect_success 'untracked cache survives a checkout' ' + git commit --allow-empty -m empty && + test-dump-untracked-cache >../before && + test_when_finished "git checkout master" && + git checkout -b other_branch && + test-dump-untracked-cache >../after && + test_cmp ../before ../after && + test_commit test && + test-dump-untracked-cache >../before && + git checkout master && + test-dump-untracked-cache >../after && + test_cmp ../before ../after +' + +test_expect_success 'untracked cache survives a commit' ' + test-dump-untracked-cache >../before && + git add done/two && + git commit -m commit && + test-dump-untracked-cache >../after && + test_cmp ../before ../after +' + +test_expect_success 'teardown worktree' ' + cd .. +' + +test_expect_success SYMLINKS 'setup worktree for symlink test' ' + git init worktree-symlink && + cd worktree-symlink && + git config core.untrackedCache true && + mkdir one two && + touch one/file two/file && + git add one/file two/file && + git commit -m"first commit" && + git rm -rf one && + ln -s two one && + git add one && + git commit -m"second commit" +' + +test_expect_success SYMLINKS '"status" after symlink replacement should be clean with UC=true' ' + git checkout HEAD~ && + status_is_clean && + status_is_clean && + git checkout master && + avoid_racy && + status_is_clean && + status_is_clean +' + +test_expect_success SYMLINKS '"status" after symlink replacement should be clean with UC=false' ' + git config core.untrackedCache false && + git checkout HEAD~ && + status_is_clean && + status_is_clean && + git checkout master && + avoid_racy && + status_is_clean && + status_is_clean +' + +test_expect_success 'setup worktree for non-symlink test' ' + git init worktree-non-symlink && + cd worktree-non-symlink && + git config core.untrackedCache true && + mkdir one two && + touch one/file two/file && + git add one/file two/file && + git commit -m"first commit" && + git rm -rf one && + cp two/file one && + git add one && + git commit -m"second commit" +' + +test_expect_success '"status" after file replacement should be clean with UC=true' ' + git checkout HEAD~ && + status_is_clean && + status_is_clean && + git checkout master && + avoid_racy && + status_is_clean && + test-dump-untracked-cache >../actual && + grep -F "recurse valid" ../actual >../actual.grep && + cat >../expect.grep <<EOF && +/ 0000000000000000000000000000000000000000 recurse valid +/two/ 0000000000000000000000000000000000000000 recurse valid +EOF + status_is_clean && + test_cmp ../expect.grep ../actual.grep +' + +test_expect_success '"status" after file replacement should be clean with UC=false' ' + git config core.untrackedCache false && + git checkout HEAD~ && + status_is_clean && + status_is_clean && + git checkout master && + avoid_racy && + status_is_clean && + status_is_clean +' + test_done diff --git a/t/t7064-wtstatus-pv2.sh b/t/t7064-wtstatus-pv2.sh new file mode 100755 index 0000000000..8f795327a0 --- /dev/null +++ b/t/t7064-wtstatus-pv2.sh @@ -0,0 +1,655 @@ +#!/bin/sh + +test_description='git status --porcelain=v2 + +This test exercises porcelain V2 output for git status.' + + +. ./test-lib.sh + + +test_expect_success setup ' + test_tick && + git config core.autocrlf false && + echo x >file_x && + echo y >file_y && + echo z >file_z && + mkdir dir1 && + echo a >dir1/file_a && + echo b >dir1/file_b +' + +test_expect_success 'before initial commit, nothing added, only untracked' ' + cat >expect <<-EOF && + # branch.oid (initial) + # branch.head master + ? actual + ? dir1/ + ? expect + ? file_x + ? file_y + ? file_z + EOF + + git status --porcelain=v2 --branch --untracked-files=normal >actual && + test_cmp expect actual +' + +test_expect_success 'before initial commit, things added' ' + git add file_x file_y file_z dir1 && + OID_A=$(git hash-object -t blob -- dir1/file_a) && + OID_B=$(git hash-object -t blob -- dir1/file_b) && + OID_X=$(git hash-object -t blob -- file_x) && + OID_Y=$(git hash-object -t blob -- file_y) && + OID_Z=$(git hash-object -t blob -- file_z) && + + cat >expect <<-EOF && + # branch.oid (initial) + # branch.head master + 1 A. N... 000000 100644 100644 $_z40 $OID_A dir1/file_a + 1 A. N... 000000 100644 100644 $_z40 $OID_B dir1/file_b + 1 A. N... 000000 100644 100644 $_z40 $OID_X file_x + 1 A. N... 000000 100644 100644 $_z40 $OID_Y file_y + 1 A. N... 000000 100644 100644 $_z40 $OID_Z file_z + ? actual + ? expect + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'before initial commit, things added (-z)' ' + lf_to_nul >expect <<-EOF && + # branch.oid (initial) + # branch.head master + 1 A. N... 000000 100644 100644 $_z40 $OID_A dir1/file_a + 1 A. N... 000000 100644 100644 $_z40 $OID_B dir1/file_b + 1 A. N... 000000 100644 100644 $_z40 $OID_X file_x + 1 A. N... 000000 100644 100644 $_z40 $OID_Y file_y + 1 A. N... 000000 100644 100644 $_z40 $OID_Z file_z + ? actual + ? expect + EOF + + git status -z --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'make first commit, comfirm HEAD oid and branch' ' + git commit -m initial && + H0=$(git rev-parse HEAD) && + cat >expect <<-EOF && + # branch.oid $H0 + # branch.head master + ? actual + ? expect + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'after first commit, create unstaged changes' ' + echo x >>file_x && + OID_X1=$(git hash-object -t blob -- file_x) && + rm file_z && + H0=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $H0 + # branch.head master + 1 .M N... 100644 100644 100644 $OID_X $OID_X file_x + 1 .D N... 100644 100644 000000 $OID_Z $OID_Z file_z + ? actual + ? expect + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'after first commit but omit untracked files and branch' ' + cat >expect <<-EOF && + 1 .M N... 100644 100644 100644 $OID_X $OID_X file_x + 1 .D N... 100644 100644 000000 $OID_Z $OID_Z file_z + EOF + + git status --porcelain=v2 --untracked-files=no >actual && + test_cmp expect actual +' + +test_expect_success 'after first commit, stage existing changes' ' + git add file_x && + git rm file_z && + H0=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $H0 + # branch.head master + 1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x + 1 D. N... 100644 000000 000000 $OID_Z $_z40 file_z + ? actual + ? expect + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'rename causes 2 path lines' ' + git mv file_y renamed_y && + H0=$(git rev-parse HEAD) && + + q_to_tab >expect <<-EOF && + # branch.oid $H0 + # branch.head master + 1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x + 1 D. N... 100644 000000 000000 $OID_Z $_z40 file_z + 2 R. N... 100644 100644 100644 $OID_Y $OID_Y R100 renamed_yQfile_y + ? actual + ? expect + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'rename causes 2 path lines (-z)' ' + H0=$(git rev-parse HEAD) && + + ## Lines use NUL path separator and line terminator, so double transform here. + q_to_nul <<-EOF | lf_to_nul >expect && + # branch.oid $H0 + # branch.head master + 1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x + 1 D. N... 100644 000000 000000 $OID_Z $_z40 file_z + 2 R. N... 100644 100644 100644 $OID_Y $OID_Y R100 renamed_yQfile_y + ? actual + ? expect + EOF + + git status --porcelain=v2 --branch --untracked-files=all -z >actual && + test_cmp expect actual +' + +test_expect_success 'make second commit, confirm clean and new HEAD oid' ' + git commit -m second && + H1=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $H1 + # branch.head master + ? actual + ? expect + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'confirm ignored files are not printed' ' + test_when_finished "rm -f x.ign .gitignore" && + echo x.ign >.gitignore && + echo "ignore me" >x.ign && + + cat >expect <<-EOF && + ? .gitignore + ? actual + ? expect + EOF + + git status --porcelain=v2 --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'ignored files are printed with --ignored' ' + test_when_finished "rm -f x.ign .gitignore" && + echo x.ign >.gitignore && + echo "ignore me" >x.ign && + + cat >expect <<-EOF && + ? .gitignore + ? actual + ? expect + ! x.ign + EOF + + git status --porcelain=v2 --ignored --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'create and commit permanent ignore file' ' + cat >.gitignore <<-EOF && + actual* + expect* + EOF + + git add .gitignore && + git commit -m ignore_trash && + H1=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $H1 + # branch.head master + EOF + + git status --porcelain=v2 --branch >actual && + test_cmp expect actual +' + +test_expect_success 'verify --intent-to-add output' ' + test_when_finished "git rm -f intent1.add intent2.add" && + touch intent1.add && + echo test >intent2.add && + + git add --intent-to-add intent1.add intent2.add && + + cat >expect <<-EOF && + 1 .A N... 000000 000000 100644 $_z40 $_z40 intent1.add + 1 .A N... 000000 000000 100644 $_z40 $_z40 intent2.add + EOF + + git status --porcelain=v2 >actual && + test_cmp expect actual +' + +test_expect_success 'verify AA (add-add) conflict' ' + test_when_finished "git reset --hard" && + + git branch AA_A master && + git checkout AA_A && + echo "Branch AA_A" >conflict.txt && + OID_AA_A=$(git hash-object -t blob -- conflict.txt) && + git add conflict.txt && + git commit -m "branch aa_a" && + + git branch AA_B master && + git checkout AA_B && + echo "Branch AA_B" >conflict.txt && + OID_AA_B=$(git hash-object -t blob -- conflict.txt) && + git add conflict.txt && + git commit -m "branch aa_b" && + + git branch AA_M AA_B && + git checkout AA_M && + test_must_fail git merge AA_A && + + HM=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $HM + # branch.head AA_M + u AA N... 000000 100644 100644 100644 $_z40 $OID_AA_B $OID_AA_A conflict.txt + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'verify UU (edit-edit) conflict' ' + test_when_finished "git reset --hard" && + + git branch UU_ANC master && + git checkout UU_ANC && + echo "Ancestor" >conflict.txt && + OID_UU_ANC=$(git hash-object -t blob -- conflict.txt) && + git add conflict.txt && + git commit -m "UU_ANC" && + + git branch UU_A UU_ANC && + git checkout UU_A && + echo "Branch UU_A" >conflict.txt && + OID_UU_A=$(git hash-object -t blob -- conflict.txt) && + git add conflict.txt && + git commit -m "branch uu_a" && + + git branch UU_B UU_ANC && + git checkout UU_B && + echo "Branch UU_B" >conflict.txt && + OID_UU_B=$(git hash-object -t blob -- conflict.txt) && + git add conflict.txt && + git commit -m "branch uu_b" && + + git branch UU_M UU_B && + git checkout UU_M && + test_must_fail git merge UU_A && + + HM=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $HM + # branch.head UU_M + u UU N... 100644 100644 100644 100644 $OID_UU_ANC $OID_UU_B $OID_UU_A conflict.txt + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual +' + +test_expect_success 'verify upstream fields in branch header' ' + git checkout master && + test_when_finished "rm -rf sub_repo" && + git clone . sub_repo && + ( + ## Confirm local master tracks remote master. + cd sub_repo && + HUF=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $HUF + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual && + + ## Test ahead/behind. + echo xyz >file_xyz && + git add file_xyz && + git commit -m xyz && + + HUF=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $HUF + # branch.head master + # branch.upstream origin/master + # branch.ab +1 -0 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual && + + ## Repeat the above but without --branch. + cat >expect <<-EOF && + EOF + + git status --porcelain=v2 --untracked-files=all >actual && + test_cmp expect actual && + + ## Test upstream-gone case. Fake this by pointing origin/master at + ## a non-existing commit. + OLD=$(git rev-parse origin/master) && + NEW=$_z40 && + mv .git/packed-refs .git/old-packed-refs && + sed "s/$OLD/$NEW/g" <.git/old-packed-refs >.git/packed-refs && + + HUF=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $HUF + # branch.head master + # branch.upstream origin/master + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'verify --[no-]ahead-behind with V2 format' ' + git checkout master && + test_when_finished "rm -rf sub_repo" && + git clone . sub_repo && + ( + ## Confirm local master tracks remote master. + cd sub_repo && + HUF=$(git rev-parse HEAD) && + + # Confirm --no-ahead-behind reports traditional branch.ab with 0/0 for equal branches. + cat >expect <<-EOF && + # branch.oid $HUF + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + EOF + + git status --no-ahead-behind --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual && + + # Confirm --ahead-behind reports traditional branch.ab with 0/0. + cat >expect <<-EOF && + # branch.oid $HUF + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + EOF + + git status --ahead-behind --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual && + + ## Test non-equal ahead/behind. + echo xyz >file_xyz && + git add file_xyz && + git commit -m xyz && + + HUF=$(git rev-parse HEAD) && + + # Confirm --no-ahead-behind reports branch.ab with ?/? for non-equal branches. + cat >expect <<-EOF && + # branch.oid $HUF + # branch.head master + # branch.upstream origin/master + # branch.ab +? -? + EOF + + git status --no-ahead-behind --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual && + + # Confirm --ahead-behind reports traditional branch.ab with 1/0. + cat >expect <<-EOF && + # branch.oid $HUF + # branch.head master + # branch.upstream origin/master + # branch.ab +1 -0 + EOF + + git status --ahead-behind --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'create and add submodule, submodule appears clean (A. S...)' ' + git checkout master && + git clone . sub_repo && + git clone . super_repo && + ( cd super_repo && + git submodule add ../sub_repo sub1 && + + ## Confirm stage/add of clean submodule. + HMOD=$(git hash-object -t blob -- .gitmodules) && + HSUP=$(git rev-parse HEAD) && + HSUB=$HSUP && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules + 1 A. S... 000000 160000 160000 $_z40 $HSUB sub1 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'untracked changes in added submodule (AM S..U)' ' + ( cd super_repo && + ## create untracked file in the submodule. + ( cd sub1 && + echo "xxxx" >file_in_sub + ) && + + HMOD=$(git hash-object -t blob -- .gitmodules) && + HSUP=$(git rev-parse HEAD) && + HSUB=$HSUP && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules + 1 AM S..U 000000 160000 160000 $_z40 $HSUB sub1 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'staged changes in added submodule (AM S.M.)' ' + ( cd super_repo && + ## stage the changes in the submodule. + ( cd sub1 && + git add file_in_sub + ) && + + HMOD=$(git hash-object -t blob -- .gitmodules) && + HSUP=$(git rev-parse HEAD) && + HSUB=$HSUP && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules + 1 AM S.M. 000000 160000 160000 $_z40 $HSUB sub1 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'staged and unstaged changes in added (AM S.M.)' ' + ( cd super_repo && + ( cd sub1 && + ## make additional unstaged changes (on the same file) in the submodule. + ## This does not cause us to get S.MU (because the submodule does not report + ## a "?" line for the unstaged changes). + echo "more changes" >>file_in_sub + ) && + + HMOD=$(git hash-object -t blob -- .gitmodules) && + HSUP=$(git rev-parse HEAD) && + HSUB=$HSUP && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules + 1 AM S.M. 000000 160000 160000 $_z40 $HSUB sub1 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'staged and untracked changes in added submodule (AM S.MU)' ' + ( cd super_repo && + ( cd sub1 && + ## stage new changes in tracked file. + git add file_in_sub && + ## create new untracked file. + echo "yyyy" >>another_file_in_sub + ) && + + HMOD=$(git hash-object -t blob -- .gitmodules) && + HSUP=$(git rev-parse HEAD) && + HSUB=$HSUP && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules + 1 AM S.MU 000000 160000 160000 $_z40 $HSUB sub1 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'commit within the submodule appears as new commit in super (AM SC..)' ' + ( cd super_repo && + ( cd sub1 && + ## Make a new commit in the submodule. + git add file_in_sub && + rm -f another_file_in_sub && + git commit -m "new commit" + ) && + + HMOD=$(git hash-object -t blob -- .gitmodules) && + HSUP=$(git rev-parse HEAD) && + HSUB=$HSUP && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +0 -0 + 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules + 1 AM SC.. 000000 160000 160000 $_z40 $HSUB sub1 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'stage submodule in super and commit' ' + ( cd super_repo && + ## Stage the new submodule commit in the super. + git add sub1 && + ## Commit the super so that the sub no longer appears as added. + git commit -m "super commit" && + + HSUP=$(git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +1 -0 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_expect_success 'make unstaged changes in existing submodule (.M S.M.)' ' + ( cd super_repo && + ( cd sub1 && + echo "zzzz" >>file_in_sub + ) && + + HSUP=$(git rev-parse HEAD) && + HSUB=$(cd sub1 && git rev-parse HEAD) && + + cat >expect <<-EOF && + # branch.oid $HSUP + # branch.head master + # branch.upstream origin/master + # branch.ab +1 -0 + 1 .M S.M. 160000 160000 160000 $HSUB $HSUB sub1 + EOF + + git status --porcelain=v2 --branch --untracked-files=all >actual && + test_cmp expect actual + ) +' + +test_done diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh index 98bcfe21aa..95653a08ca 100755 --- a/t/t7102-reset.sh +++ b/t/t7102-reset.sh @@ -66,14 +66,14 @@ test_expect_success 'reset --hard message' ' hex=$(git log -1 --format="%h") && git reset --hard > .actual && echo HEAD is now at $hex $(commit_msg) > .expected && - test_cmp .expected .actual + test_i18ncmp .expected .actual ' test_expect_success 'reset --hard message (ISO8859-1 logoutputencoding)' ' hex=$(git log -1 --format="%h") && git -c "i18n.logOutputEncoding=$test_encoding" reset --hard > .actual && echo HEAD is now at $hex $(commit_msg $test_encoding) > .expected && - test_cmp .expected .actual + test_i18ncmp .expected .actual ' >.diff_expect @@ -428,9 +428,9 @@ test_expect_success 'test --mixed <paths>' ' git reset HEAD -- file1 file2 file3 && test_must_fail git diff --quiet && git diff > output && - test_cmp output expect && + test_cmp expect output && git diff --cached > output && - test_cmp output cached_expect + test_cmp cached_expect output ' test_expect_success 'test resetting the index at give paths' ' diff --git a/t/t7103-reset-bare.sh b/t/t7103-reset-bare.sh index 1eef93c2b2..afe36a533c 100755 --- a/t/t7103-reset-bare.sh +++ b/t/t7103-reset-bare.sh @@ -63,7 +63,7 @@ test_expect_success '"mixed" reset is not allowed in bare' ' test_expect_success '"soft" reset is allowed in bare' ' git reset --soft HEAD^ && - test "`git show --pretty=format:%s | head -n 1`" = "one" + test "$(git show --pretty=format:%s | head -n 1)" = "one" ' test_done diff --git a/t/t7112-reset-submodule.sh b/t/t7112-reset-submodule.sh index 2eda6adeb1..a1cb9ff858 100755 --- a/t/t7112-reset-submodule.sh +++ b/t/t7112-reset-submodule.sh @@ -5,6 +5,14 @@ test_description='reset can handle submodules' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-submodule-update.sh +KNOWN_FAILURE_SUBMODULE_RECURSIVE_NESTED=1 +KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1 +KNOWN_FAILURE_SUBMODULE_OVERWRITE_IGNORED_UNTRACKED=1 + +test_submodule_switch_recursing_with_args "reset --keep" + +test_submodule_forced_switch_recursing_with_args "reset --hard" + test_submodule_switch "git reset --keep" test_submodule_switch "git reset --merge" diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 885923610a..76c223c967 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -187,7 +187,7 @@ test_expect_success 'format of merge conflict from checkout -m' ' d >>>>>>> local EOF - test_cmp two expect + test_cmp expect two ' test_expect_success 'checkout --merge --conflict=diff3 <branch>' ' @@ -213,7 +213,7 @@ test_expect_success 'checkout --merge --conflict=diff3 <branch>' ' d >>>>>>> local EOF - test_cmp two expect + test_cmp expect two ' test_expect_success 'switch to another branch while carrying a deletion' ' @@ -257,7 +257,7 @@ test_expect_success 'checkout to detach HEAD' ' git checkout -f renamer && git clean -f && git checkout renamer^ 2>messages && test_i18ngrep "HEAD is now at 7329388" messages && - test_line_count -gt 1 messages && + (test_line_count -gt 1 messages || test -n "$GETTEXT_POISON") && H=$(git rev-parse --verify HEAD) && M=$(git show-ref -s --verify refs/heads/master) && test "z$H" = "z$M" && diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh index 99be5d95d0..7b36954d63 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -432,9 +432,7 @@ test_expect_success 'nested git work tree' ' ( cd foo && git init && - >hello.world - git add . && - git commit -a -m nested + test_commit nested hello.world ) && ( cd bar && @@ -443,9 +441,7 @@ test_expect_success 'nested git work tree' ' ( cd baz/boo && git init && - >deeper.world - git add . && - git commit -a -m deeply.nested + test_commit deeply.nested deeper.world ) && git clean -f -d && test -f foo/.git/index && @@ -455,15 +451,153 @@ test_expect_success 'nested git work tree' ' ! test -d bar ' +test_expect_success 'should clean things that almost look like git but are not' ' + rm -fr almost_git almost_bare_git almost_submodule && + mkdir -p almost_git/.git/objects && + mkdir -p almost_git/.git/refs && + cat >almost_git/.git/HEAD <<-\EOF && + garbage + EOF + cp -r almost_git/.git/ almost_bare_git && + mkdir almost_submodule/ && + cat >almost_submodule/.git <<-\EOF && + garbage + EOF + test_when_finished "rm -rf almost_*" && + git clean -f -d && + test_path_is_missing almost_git && + test_path_is_missing almost_bare_git && + test_path_is_missing almost_submodule +' + +test_expect_success 'should not clean submodules' ' + rm -fr repo to_clean sub1 sub2 && + mkdir repo to_clean && + ( + cd repo && + git init && + test_commit msg hello.world + ) && + git submodule add ./repo/.git sub1 && + git commit -m "sub1" && + git branch before_sub2 && + git submodule add ./repo/.git sub2 && + git commit -m "sub2" && + git checkout before_sub2 && + >to_clean/should_clean.this && + git clean -f -d && + test_path_is_file repo/.git/index && + test_path_is_file repo/hello.world && + test_path_is_file sub1/.git && + test_path_is_file sub1/hello.world && + test_path_is_file sub2/.git && + test_path_is_file sub2/hello.world && + test_path_is_missing to_clean +' + +test_expect_success POSIXPERM,SANITY 'should avoid cleaning possible submodules' ' + rm -fr to_clean possible_sub1 && + mkdir to_clean possible_sub1 && + test_when_finished "rm -rf possible_sub*" && + echo "gitdir: foo" >possible_sub1/.git && + >possible_sub1/hello.world && + chmod 0 possible_sub1/.git && + >to_clean/should_clean.this && + git clean -f -d && + test_path_is_file possible_sub1/.git && + test_path_is_file possible_sub1/hello.world && + test_path_is_missing to_clean +' + +test_expect_success 'nested (empty) git should be kept' ' + rm -fr empty_repo to_clean && + git init empty_repo && + mkdir to_clean && + >to_clean/should_clean.this && + git clean -f -d && + test_path_is_file empty_repo/.git/HEAD && + test_path_is_missing to_clean +' + +test_expect_success 'nested bare repositories should be cleaned' ' + rm -fr bare1 bare2 subdir && + git init --bare bare1 && + git clone --local --bare . bare2 && + mkdir subdir && + cp -r bare2 subdir/bare3 && + git clean -f -d && + test_path_is_missing bare1 && + test_path_is_missing bare2 && + test_path_is_missing subdir +' + +test_expect_failure 'nested (empty) bare repositories should be cleaned even when in .git' ' + rm -fr strange_bare && + mkdir strange_bare && + git init --bare strange_bare/.git && + git clean -f -d && + test_path_is_missing strange_bare +' + +test_expect_failure 'nested (non-empty) bare repositories should be cleaned even when in .git' ' + rm -fr strange_bare && + mkdir strange_bare && + git clone --local --bare . strange_bare/.git && + git clean -f -d && + test_path_is_missing strange_bare +' + +test_expect_success 'giving path in nested git work tree will remove it' ' + rm -fr repo && + mkdir repo && + ( + cd repo && + git init && + mkdir -p bar/baz && + test_commit msg bar/baz/hello.world + ) && + git clean -f -d repo/bar/baz && + test_path_is_file repo/.git/HEAD && + test_path_is_dir repo/bar/ && + test_path_is_missing repo/bar/baz +' + +test_expect_success 'giving path to nested .git will not remove it' ' + rm -fr repo && + mkdir repo untracked && + ( + cd repo && + git init && + test_commit msg hello.world + ) && + git clean -f -d repo/.git && + test_path_is_file repo/.git/HEAD && + test_path_is_dir repo/.git/refs && + test_path_is_dir repo/.git/objects && + test_path_is_dir untracked/ +' + +test_expect_success 'giving path to nested .git/ will remove contents' ' + rm -fr repo untracked && + mkdir repo untracked && + ( + cd repo && + git init && + test_commit msg hello.world + ) && + git clean -f -d repo/.git/ && + test_path_is_dir repo/.git && + test_dir_is_empty repo/.git && + test_path_is_dir untracked/ +' + test_expect_success 'force removal of nested git work tree' ' rm -fr foo bar baz && mkdir -p foo bar baz/boo && ( cd foo && git init && - >hello.world - git add . && - git commit -a -m nested + test_commit nested hello.world ) && ( cd bar && @@ -472,9 +606,7 @@ test_expect_success 'force removal of nested git work tree' ' ( cd baz/boo && git init && - >deeper.world - git add . && - git commit -a -m deeply.nested + test_commit deeply.nested deeper.world ) && git clean -f -f -d && ! test -d foo && @@ -521,4 +653,20 @@ test_expect_success 'git clean -d respects pathspecs (pathspec is prefix of dir) test_path_is_dir foobar ' +test_expect_success 'git clean -d skips untracked dirs containing ignored files' ' + echo /foo/bar >.gitignore && + echo ignoreme >>.gitignore && + rm -rf foo && + mkdir -p foo/a/aa/aaa foo/b/bb/bbb && + touch foo/bar foo/baz foo/a/aa/ignoreme foo/b/ignoreme foo/b/bb/1 foo/b/bb/2 && + git clean -df && + test_path_is_dir foo && + test_path_is_file foo/bar && + test_path_is_missing foo/baz && + test_path_is_file foo/a/aa/ignoreme && + test_path_is_missing foo/a/aa/aaa && + test_path_is_file foo/b/ignoreme && + test_path_is_missing foo/b/bb +' + test_done diff --git a/t/t7301-clean-interactive.sh b/t/t7301-clean-interactive.sh index 3ae394e934..1bf9789c8a 100755 --- a/t/t7301-clean-interactive.sh +++ b/t/t7301-clean-interactive.sh @@ -3,6 +3,7 @@ test_description='git clean -i basic tests' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-terminal.sh test_expect_success 'setup' ' @@ -472,4 +473,14 @@ test_expect_success 'git clean -id with prefix and path (ask)' ' ' +test_expect_success TTY 'git clean -i paints the header in HEADER color' ' + >a.out && + echo q | + test_terminal git clean -i | + test_decode_color | + head -n 1 >header && + # not i18ngrep + grep "^<BOLD>" header +' + test_done diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index 540771ca41..152104412f 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -11,6 +11,10 @@ subcommands of git submodule. . ./test-lib.sh +test_expect_success 'submodule deinit works on empty repository' ' + git submodule deinit --all +' + test_expect_success 'setup - initial commit' ' >t && git add t && @@ -18,14 +22,28 @@ test_expect_success 'setup - initial commit' ' git branch initial ' -test_expect_success 'configuration parsing' ' +test_expect_success 'submodule init aborts on missing .gitmodules file' ' + test_when_finished "git update-index --remove sub" && + git update-index --add --cacheinfo 160000,$(git rev-parse HEAD),sub && + # missing the .gitmodules file here + test_must_fail git submodule init 2>actual && + test_i18ngrep "No url found for submodule path" actual +' + +test_expect_success 'submodule update aborts on missing .gitmodules file' ' + test_when_finished "git update-index --remove sub" && + git update-index --add --cacheinfo 160000,$(git rev-parse HEAD),sub && + # missing the .gitmodules file here + git submodule update sub 2>actual && + test_i18ngrep "Submodule path .sub. not initialized" actual +' + +test_expect_success 'submodule update aborts on missing gitmodules url' ' + test_when_finished "git update-index --remove sub" && + git update-index --add --cacheinfo 160000,$(git rev-parse HEAD),sub && test_when_finished "rm -f .gitmodules" && - cat >.gitmodules <<-\EOF && - [submodule "s"] - path - ignore - EOF - test_must_fail git status + git config -f .gitmodules submodule.s.path sub && + test_must_fail git submodule init ' test_expect_success 'setup - repository in init subdirectory' ' @@ -132,6 +150,20 @@ test_expect_success 'submodule add to .gitignored path with --force' ' ) ' +test_expect_success 'submodule add to reconfigure existing submodule with --force' ' + ( + cd addtest-ignore && + git submodule add --force bogus-url submod && + git submodule add -b initial "$submodurl" submod-branch && + test "bogus-url" = "$(git config -f .gitmodules submodule.submod.url)" && + test "bogus-url" = "$(git config submodule.submod.url)" && + # Restore the url + git submodule add --force "$submodurl" submod + test "$submodurl" = "$(git config -f .gitmodules submodule.submod.url)" && + test "$submodurl" = "$(git config submodule.submod.url)" + ) +' + test_expect_success 'submodule add --branch' ' echo "refs/heads/initial" >expect-head && cat <<-\EOF >expect-heads && @@ -239,6 +271,20 @@ test_expect_success 'submodule add with ./, /.. and // in path' ' test_cmp empty untracked ' +test_expect_success !CYGWIN 'submodule add with \\ in path' ' + test_when_finished "rm -rf parent sub\\with\\backslash" && + + # Initialize a repo with a backslash in its name + git init sub\\with\\backslash && + touch sub\\with\\backslash/empty.file && + git -C sub\\with\\backslash add empty.file && + git -C sub\\with\\backslash commit -m "Added empty.file" && + + # Add that repository as a submodule + git init parent && + git -C parent submodule add ../sub\\with\\backslash +' + test_expect_success 'submodule add in subdirectory' ' echo "refs/heads/master" >expect && >empty && @@ -462,7 +508,7 @@ test_expect_success 'update --init' ' git config --remove-section submodule.example && test_must_fail git config submodule.example.url && - git submodule update init > update.out && + git submodule update init 2> update.out && cat update.out && test_i18ngrep "not initialized" update.out && test_must_fail git rev-parse --resolve-git-dir init/.git && @@ -480,7 +526,7 @@ test_expect_success 'update --init from subdirectory' ' mkdir -p sub && ( cd sub && - git submodule update ../init >update.out && + git submodule update ../init 2>update.out && cat update.out && test_i18ngrep "not initialized" update.out && test_must_fail git rev-parse --resolve-git-dir ../init/.git && @@ -775,6 +821,21 @@ test_expect_success 'moving the superproject does not break submodules' ' ) ' +test_expect_success 'moving the submodule does not break the superproject' ' + ( + cd addtest2 && + git submodule status + ) >actual && + sed -e "s/^ \([^ ]* repo\) .*/-\1/" <actual >expect && + mv addtest2/repo addtest2/repo.bak && + test_when_finished "mv addtest2/repo.bak addtest2/repo" && + ( + cd addtest2 && + git submodule status + ) >actual && + test_cmp expect actual +' + test_expect_success 'submodule add --name allows to replace a submodule with another at the same path' ' ( cd addtest2 && @@ -818,6 +879,47 @@ test_expect_success 'submodule add --name allows to replace a submodule with ano ) ' +test_expect_success 'recursive relative submodules stay relative' ' + test_when_finished "rm -rf super clone2 subsub sub3" && + mkdir subsub && + ( + cd subsub && + git init && + >t && + git add t && + git commit -m "initial commit" + ) && + mkdir sub3 && + ( + cd sub3 && + git init && + >t && + git add t && + git commit -m "initial commit" && + git submodule add ../subsub dirdir/subsub && + git commit -m "add submodule subsub" + ) && + mkdir super && + ( + cd super && + git init && + >t && + git add t && + git commit -m "initial commit" && + git submodule add ../sub3 && + git commit -m "add submodule sub" + ) && + git clone super clone2 && + ( + cd clone2 && + git submodule update --init --recursive && + echo "gitdir: ../.git/modules/sub3" >./sub3/.git_expect && + echo "gitdir: ../../../.git/modules/sub3/modules/dirdir/subsub" >./sub3/dirdir/subsub/.git_expect + ) && + test_cmp clone2/sub3/.git_expect clone2/sub3/.git && + test_cmp clone2/sub3/dirdir/subsub/.git_expect clone2/sub3/dirdir/subsub/.git +' + test_expect_success 'submodule add with an existing name fails unless forced' ' ( cd addtest2 && @@ -849,6 +951,20 @@ test_expect_success 'set up a second submodule' ' git commit -m "submodule example2 added" ' +test_expect_success 'submodule deinit works on repository without submodules' ' + test_when_finished "rm -rf newdirectory" && + mkdir newdirectory && + ( + cd newdirectory && + git init && + >file && + git add file && + git commit -m "repo should not be empty" && + git submodule deinit . && + git submodule deinit --all + ) +' + test_expect_success 'submodule deinit should remove the whole submodule section from .git/config' ' git config submodule.example.foo bar && git config submodule.example2.frotz nitfol && @@ -867,7 +983,7 @@ test_expect_success 'submodule deinit from subdirectory' ' cd sub && git submodule deinit ../init >../output ) && - grep "\\.\\./init" output && + test_i18ngrep "\\.\\./init" output && test -z "$(git config --get-regexp "submodule\.example\.")" && test -n "$(git config --get-regexp "submodule\.example2\.")" && test -f example2/.git && @@ -887,6 +1003,19 @@ test_expect_success 'submodule deinit . deinits all initialized submodules' ' rmdir init example2 ' +test_expect_success 'submodule deinit --all deinits all initialized submodules' ' + git submodule update --init && + git config submodule.example.foo bar && + git config submodule.example2.frotz nitfol && + test_must_fail git submodule deinit && + git submodule deinit --all >actual && + test -z "$(git config --get-regexp "submodule\.example\.")" && + test -z "$(git config --get-regexp "submodule\.example2\.")" && + test_i18ngrep "Cleared directory .init" actual && + test_i18ngrep "Cleared directory .example2" actual && + rmdir init example2 +' + test_expect_success 'submodule deinit deinits a submodule when its work tree is missing or empty' ' git submodule update --init && rm -rf init example2/* example2/.git && @@ -953,6 +1082,10 @@ test_expect_success 'submodule deinit is silent when used on an uninitialized su test_i18ngrep ! "Submodule .example. (.*) unregistered for path .init" actual && test_i18ngrep ! "Submodule .example2. (.*) unregistered for path .example2" actual && test_i18ngrep "Cleared directory .init" actual && + git submodule deinit --all >actual && + test_i18ngrep ! "Submodule .example. (.*) unregistered for path .init" actual && + test_i18ngrep ! "Submodule .example2. (.*) unregistered for path .example2" actual && + test_i18ngrep "Cleared directory .init" actual && rmdir init example2 ' @@ -999,5 +1132,172 @@ test_expect_success 'submodule add clone shallow submodule' ' ) ' +test_expect_success 'submodule helper list is not confused by common prefixes' ' + mkdir -p dir1/b && + ( + cd dir1/b && + git init && + echo hi >testfile2 && + git add . && + git commit -m "test1" + ) && + mkdir -p dir2/b && + ( + cd dir2/b && + git init && + echo hello >testfile1 && + git add . && + git commit -m "test2" + ) && + git submodule add /dir1/b dir1/b && + git submodule add /dir2/b dir2/b && + git commit -m "first submodule commit" && + git submodule--helper list dir1/b |cut -c51- >actual && + echo "dir1/b" >expect && + test_cmp expect actual +' + +test_expect_success 'setup superproject with submodules' ' + git init sub1 && + test_commit -C sub1 test && + test_commit -C sub1 test2 && + git init multisuper && + git -C multisuper submodule add ../sub1 sub0 && + git -C multisuper submodule add ../sub1 sub1 && + git -C multisuper submodule add ../sub1 sub2 && + git -C multisuper submodule add ../sub1 sub3 && + git -C multisuper commit -m "add some submodules" +' + +cat >expect <<-EOF +-sub0 + sub1 (test2) + sub2 (test2) + sub3 (test2) +EOF + +test_expect_success 'submodule update --init with a specification' ' + test_when_finished "rm -rf multisuper_clone" && + pwd=$(pwd) && + git clone file://"$pwd"/multisuper multisuper_clone && + git -C multisuper_clone submodule update --init . ":(exclude)sub0" && + git -C multisuper_clone submodule status |cut -c 1,43- >actual && + test_cmp expect actual +' + +test_expect_success 'submodule update --init with submodule.active set' ' + test_when_finished "rm -rf multisuper_clone" && + pwd=$(pwd) && + git clone file://"$pwd"/multisuper multisuper_clone && + git -C multisuper_clone config submodule.active "." && + git -C multisuper_clone config --add submodule.active ":(exclude)sub0" && + git -C multisuper_clone submodule update --init && + git -C multisuper_clone submodule status |cut -c 1,43- >actual && + test_cmp expect actual +' + +test_expect_success 'submodule update and setting submodule.<name>.active' ' + test_when_finished "rm -rf multisuper_clone" && + pwd=$(pwd) && + git clone file://"$pwd"/multisuper multisuper_clone && + git -C multisuper_clone config --bool submodule.sub0.active "true" && + git -C multisuper_clone config --bool submodule.sub1.active "false" && + git -C multisuper_clone config --bool submodule.sub2.active "true" && + + cat >expect <<-\EOF && + sub0 (test2) + -sub1 + sub2 (test2) + -sub3 + EOF + git -C multisuper_clone submodule update && + git -C multisuper_clone submodule status |cut -c 1,43- >actual && + test_cmp expect actual +' + +test_expect_success 'clone --recurse-submodules with a pathspec works' ' + test_when_finished "rm -rf multisuper_clone" && + cat >expected <<-\EOF && + sub0 (test2) + -sub1 + -sub2 + -sub3 + EOF + + git clone --recurse-submodules="sub0" multisuper multisuper_clone && + git -C multisuper_clone submodule status |cut -c1,43- >actual && + test_cmp expected actual +' + +test_expect_success 'clone with multiple --recurse-submodules options' ' + test_when_finished "rm -rf multisuper_clone" && + cat >expect <<-\EOF && + -sub0 + sub1 (test2) + -sub2 + sub3 (test2) + EOF + + git clone --recurse-submodules="." \ + --recurse-submodules=":(exclude)sub0" \ + --recurse-submodules=":(exclude)sub2" \ + multisuper multisuper_clone && + git -C multisuper_clone submodule status |cut -c1,43- >actual && + test_cmp expect actual +' + +test_expect_success 'clone and subsequent updates correctly auto-initialize submodules' ' + test_when_finished "rm -rf multisuper_clone" && + cat <<-\EOF >expect && + -sub0 + sub1 (test2) + -sub2 + sub3 (test2) + EOF + + cat <<-\EOF >expect2 && + -sub0 + sub1 (test2) + -sub2 + sub3 (test2) + -sub4 + sub5 (test2) + EOF + + git clone --recurse-submodules="." \ + --recurse-submodules=":(exclude)sub0" \ + --recurse-submodules=":(exclude)sub2" \ + --recurse-submodules=":(exclude)sub4" \ + multisuper multisuper_clone && + + git -C multisuper_clone submodule status |cut -c1,43- >actual && + test_cmp expect actual && + + git -C multisuper submodule add ../sub1 sub4 && + git -C multisuper submodule add ../sub1 sub5 && + git -C multisuper commit -m "add more submodules" && + # obtain the new superproject + git -C multisuper_clone pull && + git -C multisuper_clone submodule update --init && + git -C multisuper_clone submodule status |cut -c1,43- >actual && + test_cmp expect2 actual +' + +test_expect_success 'init properly sets the config' ' + test_when_finished "rm -rf multisuper_clone" && + git clone --recurse-submodules="." \ + --recurse-submodules=":(exclude)sub0" \ + multisuper multisuper_clone && + + git -C multisuper_clone submodule init -- sub0 sub1 && + git -C multisuper_clone config --get submodule.sub0.active && + test_must_fail git -C multisuper_clone config --get submodule.sub1.active +' + +test_expect_success 'recursive clone respects -q' ' + test_when_finished "rm -rf multisuper_clone" && + git clone -q --recurse-submodules multisuper multisuper_clone >actual && + test_must_be_empty actual +' test_done diff --git a/t/t7401-submodule-summary.sh b/t/t7401-submodule-summary.sh index 366746f0d4..4e4c455502 100755 --- a/t/t7401-submodule-summary.sh +++ b/t/t7401-submodule-summary.sh @@ -241,9 +241,11 @@ EOF test_cmp expected actual " -test_create_repo sm2 && -head7=$(add_file sm2 foo8 foo9) && -git add sm2 +test_expect_success 'create second submodule' ' + test_create_repo sm2 && + head7=$(add_file sm2 foo8 foo9) && + git add sm2 +' test_expect_success 'multiple submodules' " git submodule summary >actual && diff --git a/t/t7403-submodule-sync.sh b/t/t7403-submodule-sync.sh index 79bc135bf6..0726799e74 100755 --- a/t/t7403-submodule-sync.sh +++ b/t/t7403-submodule-sync.sh @@ -62,13 +62,13 @@ test_expect_success 'change submodule' ' ' reset_submodule_urls () { - local root - root=$(pwd) && ( + root=$(pwd) && cd super-clone/submodule && git config remote.origin.url "$root/submodule" ) && ( + root=$(pwd) && cd super-clone/submodule/sub-submodule && git config remote.origin.url "$root/submodule" ) @@ -157,7 +157,7 @@ test_expect_success '"git submodule sync" should update submodule URLs - subdire cd sub && git submodule sync >../../output ) && - grep "\\.\\./submodule" output && + test_i18ngrep "\\.\\./submodule" output && test -d "$( cd super-clone/submodule && git config remote.origin.url @@ -188,7 +188,7 @@ test_expect_success '"git submodule sync --recursive" should update all submodul cd sub && git submodule sync --recursive >../../output ) && - grep "\\.\\./submodule/sub-submodule" output && + test_i18ngrep "\\.\\./submodule/sub-submodule" output && test -d "$( cd super-clone/submodule && git config remote.origin.url diff --git a/t/t7405-submodule-merge.sh b/t/t7405-submodule-merge.sh index 0d5b42a25b..7bfb2f498d 100755 --- a/t/t7405-submodule-merge.sh +++ b/t/t7405-submodule-merge.sh @@ -119,7 +119,7 @@ test_expect_success 'merge with one side as a fast-forward of the other' ' git ls-tree test-forward sub | cut -f1 | cut -f3 -d" " > actual && (cd sub && git rev-parse sub-d > ../expect) && - test_cmp actual expect) + test_cmp expect actual) ' test_expect_success 'merging should conflict for non fast-forward' ' diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index dda3929d99..6f083c4d68 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -14,8 +14,8 @@ submodule and "git submodule update --rebase/--merge" does not detach the HEAD. compare_head() { - sha_master=`git rev-list --max-count=1 master` - sha_head=`git rev-list --max-count=1 HEAD` + sha_master=$(git rev-list --max-count=1 master) + sha_head=$(git rev-list --max-count=1 HEAD) test "$sha_master" = "$sha_head" } @@ -63,6 +63,10 @@ test_expect_success 'setup a submodule tree' ' git submodule add ../none none && test_tick && git commit -m "none" + ) && + git clone . recursivesuper && + ( cd recursivesuper + git submodule add ../super super ) ' @@ -95,6 +99,64 @@ test_expect_success 'submodule update from subdirectory' ' ) ' +supersha1=$(git -C super rev-parse HEAD) +mergingsha1=$(git -C super/merging rev-parse HEAD) +nonesha1=$(git -C super/none rev-parse HEAD) +rebasingsha1=$(git -C super/rebasing rev-parse HEAD) +submodulesha1=$(git -C super/submodule rev-parse HEAD) +pwd=$(pwd) + +cat <<EOF >expect +Submodule path '../super': checked out '$supersha1' +Submodule path '../super/merging': checked out '$mergingsha1' +Submodule path '../super/none': checked out '$nonesha1' +Submodule path '../super/rebasing': checked out '$rebasingsha1' +Submodule path '../super/submodule': checked out '$submodulesha1' +EOF + +cat <<EOF >expect2 +Submodule 'merging' ($pwd/merging) registered for path '../super/merging' +Submodule 'none' ($pwd/none) registered for path '../super/none' +Submodule 'rebasing' ($pwd/rebasing) registered for path '../super/rebasing' +Submodule 'submodule' ($pwd/submodule) registered for path '../super/submodule' +Cloning into '$pwd/recursivesuper/super/merging'... +done. +Cloning into '$pwd/recursivesuper/super/none'... +done. +Cloning into '$pwd/recursivesuper/super/rebasing'... +done. +Cloning into '$pwd/recursivesuper/super/submodule'... +done. +EOF + +test_expect_success 'submodule update --init --recursive from subdirectory' ' + git -C recursivesuper/super reset --hard HEAD^ && + (cd recursivesuper && + mkdir tmp && + cd tmp && + git submodule update --init --recursive ../super >../../actual 2>../../actual2 + ) && + test_i18ncmp expect actual && + test_i18ncmp expect2 actual2 +' + +cat <<EOF >expect2 +Submodule 'foo/sub' ($pwd/withsubs/../rebasing) registered for path 'sub' +EOF + +test_expect_success 'submodule update --init from and of subdirectory' ' + git init withsubs && + (cd withsubs && + mkdir foo && + git submodule add "$(pwd)/../rebasing" foo/sub && + (cd foo && + git submodule deinit -f sub && + git submodule update --init sub 2>../../actual2 + ) + ) && + test_i18ncmp expect2 actual2 +' + apos="'"; test_expect_success 'submodule update does not fetch already present commits' ' (cd submodule && @@ -164,9 +226,42 @@ test_expect_success 'submodule update --remote should fetch upstream changes' ' ) ' +test_expect_success 'submodule update --remote should fetch upstream changes with .' ' + ( + cd super && + git config -f .gitmodules submodule."submodule".branch "." && + git add .gitmodules && + git commit -m "submodules: update from the respective superproject branch" + ) && + ( + cd submodule && + echo line4a >> file && + git add file && + test_tick && + git commit -m "upstream line4a" && + git checkout -b test-branch && + test_commit on-test-branch + ) && + ( + cd super && + git submodule update --remote --force submodule && + git -C submodule log -1 --oneline >actual + git -C ../submodule log -1 --oneline master >expect + test_cmp expect actual && + git checkout -b test-branch && + git submodule update --remote --force submodule && + git -C submodule log -1 --oneline >actual + git -C ../submodule log -1 --oneline test-branch >expect + test_cmp expect actual && + git checkout master && + git branch -d test-branch && + git reset --hard HEAD^ + ) +' + test_expect_success 'local config should override .gitmodules branch' ' (cd submodule && - git checkout -b test-branch && + git checkout test-branch && echo line5 >> file && git add file && test_tick && @@ -311,16 +406,77 @@ test_expect_success 'submodule update - command in .git/config' ' ) ' +test_expect_success 'submodule update - command in .gitmodules is ignored' ' + test_when_finished "git -C super reset --hard HEAD^" && + git -C super config -f .gitmodules submodule.submodule.update "!false" && + git -C super commit -a -m "add command to .gitmodules file" && + git -C super/submodule reset --hard $submodulesha1^ && + git -C super submodule update submodule +' + +cat << EOF >expect +Execution of 'false $submodulesha1' failed in submodule path 'submodule' +EOF + test_expect_success 'submodule update - command in .git/config catches failure' ' (cd super && git config submodule.submodule.update "!false" ) && (cd super/submodule && - git reset --hard HEAD^ + git reset --hard $submodulesha1^ ) && (cd super && - test_must_fail git submodule update submodule - ) + test_must_fail git submodule update submodule 2>../actual + ) && + test_i18ncmp actual expect +' + +cat << EOF >expect +Execution of 'false $submodulesha1' failed in submodule path '../submodule' +EOF + +test_expect_success 'submodule update - command in .git/config catches failure -- subdirectory' ' + (cd super && + git config submodule.submodule.update "!false" + ) && + (cd super/submodule && + git reset --hard $submodulesha1^ + ) && + (cd super && + mkdir tmp && cd tmp && + test_must_fail git submodule update ../submodule 2>../../actual + ) && + test_i18ncmp actual expect +' + +test_expect_success 'submodule update - command run for initial population of submodule' ' + cat >expect <<-EOF && + Execution of '\''false $submodulesha1'\'' failed in submodule path '\''submodule'\'' + EOF + rm -rf super/submodule && + test_must_fail git -C super submodule update 2>actual && + test_i18ncmp expect actual && + git -C super submodule update --checkout +' + +cat << EOF >expect +Execution of 'false $submodulesha1' failed in submodule path '../super/submodule' +Failed to recurse into submodule path '../super' +EOF + +test_expect_success 'recursive submodule update - command in .git/config catches failure -- subdirectory' ' + (cd recursivesuper && + git submodule update --remote super && + git add super && + git commit -m "update to latest to have more than one commit in submodules" + ) && + git -C recursivesuper/super config submodule.submodule.update "!false" && + git -C recursivesuper/super/submodule reset --hard $submodulesha1^ && + (cd recursivesuper && + mkdir -p tmp && cd tmp && + test_must_fail git submodule update --recursive ../super 2>../../actual + ) && + test_i18ncmp actual expect ' test_expect_success 'submodule init does not copy command into .git/config' ' @@ -355,6 +511,7 @@ test_expect_success 'submodule init picks up merge' ' ' test_expect_success 'submodule update --merge - ignores --merge for new submodules' ' + test_config -C super submodule.submodule.update checkout && (cd super && rm -rf submodule && git submodule update submodule && @@ -367,6 +524,7 @@ test_expect_success 'submodule update --merge - ignores --merge for new submod ' test_expect_success 'submodule update --rebase - ignores --rebase for new submodules' ' + test_config -C super submodule.submodule.update checkout && (cd super && rm -rf submodule && git submodule update submodule && @@ -753,16 +911,35 @@ test_expect_success SYMLINKS 'submodule update can handle symbolic links in pwd' ' test_expect_success 'submodule update clone shallow submodule' ' + test_when_finished "rm -rf super3" && + first=$(git -C cloned submodule status submodule |cut -c2-41) && + second=$(git -C submodule rev-parse HEAD) && + commit_count=$(git -C submodule rev-list --count $first^..$second) && git clone cloned super3 && pwd=$(pwd) && - (cd super3 && - sed -e "s#url = ../#url = file://$pwd/#" <.gitmodules >.gitmodules.tmp && - mv -f .gitmodules.tmp .gitmodules && - git submodule update --init --depth=3 - (cd submodule && - test 1 = $(git log --oneline | wc -l) - ) -) + ( + cd super3 && + sed -e "s#url = ../#url = file://$pwd/#" <.gitmodules >.gitmodules.tmp && + mv -f .gitmodules.tmp .gitmodules && + git submodule update --init --depth=$commit_count && + test 1 = $(git -C submodule log --oneline | wc -l) + ) +' + +test_expect_success 'submodule update clone shallow submodule outside of depth' ' + test_when_finished "rm -rf super3" && + git clone cloned super3 && + pwd=$(pwd) && + ( + cd super3 && + sed -e "s#url = ../#url = file://$pwd/#" <.gitmodules >.gitmodules.tmp && + mv -f .gitmodules.tmp .gitmodules && + test_must_fail git submodule update --init --depth=1 2>actual && + test_i18ngrep "Direct fetching of that commit failed." actual && + git -C ../submodule config uploadpack.allowReachableSHA1InWant true && + git submodule update --init --depth=1 >actual && + test 1 = $(git -C submodule log --oneline | wc -l) + ) ' test_expect_success 'submodule update --recursive drops module name before recursing' ' @@ -774,4 +951,31 @@ test_expect_success 'submodule update --recursive drops module name before recur test_i18ngrep "Submodule path .deeper/submodule/subsubmodule.: checked out" actual ) ' + +test_expect_success 'submodule update can be run in parallel' ' + (cd super2 && + GIT_TRACE=$(pwd)/trace.out git submodule update --jobs 7 && + grep "7 tasks" trace.out && + git config submodule.fetchJobs 8 && + GIT_TRACE=$(pwd)/trace.out git submodule update && + grep "8 tasks" trace.out && + GIT_TRACE=$(pwd)/trace.out git submodule update --jobs 9 && + grep "9 tasks" trace.out + ) +' + +test_expect_success 'git clone passes the parallel jobs config on to submodules' ' + test_when_finished "rm -rf super4" && + GIT_TRACE=$(pwd)/trace.out git clone --recurse-submodules --jobs 7 . super4 && + grep "7 tasks" trace.out && + rm -rf super4 && + git config --global submodule.fetchJobs 8 && + GIT_TRACE=$(pwd)/trace.out git clone --recurse-submodules . super4 && + grep "8 tasks" trace.out && + rm -rf super4 && + GIT_TRACE=$(pwd)/trace.out git clone --recurse-submodules --jobs 9 . super4 && + grep "9 tasks" trace.out && + rm -rf super4 +' + test_done diff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh index 7ca10b8606..6ba5daf42e 100755 --- a/t/t7407-submodule-foreach.sh +++ b/t/t7407-submodule-foreach.sh @@ -178,6 +178,26 @@ test_expect_success 'test messages from "foreach --recursive"' ' ' cat > expect <<EOF +Entering '../nested1' +Entering '../nested1/nested2' +Entering '../nested1/nested2/nested3' +Entering '../nested1/nested2/nested3/submodule' +Entering '../sub1' +Entering '../sub2' +Entering '../sub3' +EOF + +test_expect_success 'test messages from "foreach --recursive" from subdirectory' ' + ( + cd clone2 && + mkdir untracked && + cd untracked && + git submodule foreach --recursive >../../actual + ) && + test_i18ncmp expect actual +' + +cat > expect <<EOF nested1-nested1 nested2-nested2 nested3-nested3 @@ -242,8 +262,12 @@ test_expect_success 'test "status --recursive"' ' test_cmp expect actual ' -sed -e "/nested2 /s/.*/+$nested2sha1 nested1\/nested2 (file2~1)/;/sub[1-3]/d" < expect > expect2 -mv -f expect2 expect +cat > expect <<EOF + $nested1sha1 nested1 (heads/master) ++$nested2sha1 nested1/nested2 (file2~1) + $nested3sha1 nested1/nested2/nested3 (heads/master) + $submodulesha1 nested1/nested2/nested3/submodule (heads/master) +EOF test_expect_success 'ensure "status --cached --recursive" preserves the --cached flag' ' ( @@ -257,6 +281,27 @@ test_expect_success 'ensure "status --cached --recursive" preserves the --cached test_cmp expect actual ' +nested2sha1=$(git -C clone3/nested1/nested2 rev-parse HEAD) + +cat > expect <<EOF + $nested1sha1 ../nested1 (heads/master) ++$nested2sha1 ../nested1/nested2 (file2) + $nested3sha1 ../nested1/nested2/nested3 (heads/master) + $submodulesha1 ../nested1/nested2/nested3/submodule (heads/master) + $sub1sha1 ../sub1 ($sub1sha1_short) + $sub2sha1 ../sub2 ($sub2sha1_short) + $sub3sha1 ../sub3 (heads/master) +EOF + +test_expect_success 'test "status --recursive" from sub directory' ' + ( + cd clone3 && + mkdir tmp && cd tmp && + git submodule status --recursive > ../../actual + ) && + test_cmp expect actual +' + test_expect_success 'use "git clone --recursive" to checkout all submodules' ' git clone --recursive super clone4 && ( diff --git a/t/t7408-submodule-reference.sh b/t/t7408-submodule-reference.sh index b770b2f04d..e159fc5035 100755 --- a/t/t7408-submodule-reference.sh +++ b/t/t7408-submodule-reference.sh @@ -6,76 +6,189 @@ test_description='test clone --reference' . ./test-lib.sh -base_dir=`pwd` - -U=$base_dir/UPLOAD_LOG - -test_expect_success 'preparing first repository' \ -'test_create_repo A && cd A && -echo first > file1 && -git add file1 && -git commit -m A-initial' - -cd "$base_dir" - -test_expect_success 'preparing second repository' \ -'git clone A B && cd B && -echo second > file2 && -git add file2 && -git commit -m B-addition && -git repack -a -d && -git prune' - -cd "$base_dir" - -test_expect_success 'preparing superproject' \ -'test_create_repo super && cd super && -echo file > file && -git add file && -git commit -m B-super-initial' - -cd "$base_dir" - -test_expect_success 'submodule add --reference' \ -'cd super && git submodule add --reference ../B "file://$base_dir/A" sub && -git commit -m B-super-added' - -cd "$base_dir" - -test_expect_success 'after add: existence of info/alternates' \ -'test_line_count = 1 super/.git/modules/sub/objects/info/alternates' - -cd "$base_dir" - -test_expect_success 'that reference gets used with add' \ -'cd super/sub && -echo "0 objects, 0 kilobytes" > expected && -git count-objects > current && -diff expected current' - -cd "$base_dir" - -test_expect_success 'cloning superproject' \ -'git clone super super-clone' - -cd "$base_dir" - -test_expect_success 'update with reference' \ -'cd super-clone && git submodule update --init --reference ../B' - -cd "$base_dir" - -test_expect_success 'after update: existence of info/alternates' \ -'test_line_count = 1 super-clone/.git/modules/sub/objects/info/alternates' - -cd "$base_dir" - -test_expect_success 'that reference gets used with update' \ -'cd super-clone/sub && -echo "0 objects, 0 kilobytes" > expected && -git count-objects > current && -diff expected current' - -cd "$base_dir" +base_dir=$(pwd) + +test_alternate_is_used () { + alternates_file="$1" && + working_dir="$2" && + test_line_count = 1 "$alternates_file" && + echo "0 objects, 0 kilobytes" >expect && + git -C "$working_dir" count-objects >actual && + test_cmp expect actual +} + +test_expect_success 'preparing first repository' ' + test_create_repo A && + ( + cd A && + echo first >file1 && + git add file1 && + git commit -m A-initial + ) +' + +test_expect_success 'preparing second repository' ' + git clone A B && + ( + cd B && + echo second >file2 && + git add file2 && + git commit -m B-addition && + git repack -a -d && + git prune + ) +' + +test_expect_success 'preparing superproject' ' + test_create_repo super && + ( + cd super && + echo file >file && + git add file && + git commit -m B-super-initial + ) +' + +test_expect_success 'submodule add --reference uses alternates' ' + ( + cd super && + git submodule add --reference ../B "file://$base_dir/A" sub && + git commit -m B-super-added && + git repack -ad + ) && + test_alternate_is_used super/.git/modules/sub/objects/info/alternates super/sub +' + +test_expect_success 'that reference gets used with add' ' + ( + cd super/sub && + echo "0 objects, 0 kilobytes" >expected && + git count-objects >current && + diff expected current + ) +' + +# The tests up to this point, and repositories created by them +# (A, B, super and super/sub), are about setting up the stage +# for subsequent tests and meant to be kept throughout the +# remainder of the test. +# Tests from here on, if they create their own test repository, +# are expected to clean after themselves. + +test_expect_success 'updating superproject keeps alternates' ' + test_when_finished "rm -rf super-clone" && + git clone super super-clone && + git -C super-clone submodule update --init --reference ../B && + test_alternate_is_used super-clone/.git/modules/sub/objects/info/alternates super-clone/sub +' + +test_expect_success 'submodules use alternates when cloning a superproject' ' + test_when_finished "rm -rf super-clone" && + git clone --reference super --recursive super super-clone && + ( + cd super-clone && + # test superproject has alternates setup correctly + test_alternate_is_used .git/objects/info/alternates . && + # test submodule has correct setup + test_alternate_is_used .git/modules/sub/objects/info/alternates sub + ) +' + +test_expect_success 'missing submodule alternate fails clone and submodule update' ' + test_when_finished "rm -rf super-clone" && + git clone super super2 && + test_must_fail git clone --recursive --reference super2 super2 super-clone && + ( + cd super-clone && + # test superproject has alternates setup correctly + test_alternate_is_used .git/objects/info/alternates . && + # update of the submodule succeeds + test_must_fail git submodule update --init && + # and we have no alternates: + test_must_fail test_alternate_is_used .git/modules/sub/objects/info/alternates sub && + test_must_fail test_path_is_file sub/file1 + ) +' + +test_expect_success 'ignoring missing submodule alternates passes clone and submodule update' ' + test_when_finished "rm -rf super-clone" && + git clone --reference-if-able super2 --recursive super2 super-clone && + ( + cd super-clone && + # test superproject has alternates setup correctly + test_alternate_is_used .git/objects/info/alternates . && + # update of the submodule succeeds + git submodule update --init && + # and we have no alternates: + test_must_fail test_alternate_is_used .git/modules/sub/objects/info/alternates sub && + test_path_is_file sub/file1 + ) +' + +test_expect_success 'preparing second superproject with a nested submodule plus partial clone' ' + test_create_repo supersuper && + ( + cd supersuper && + echo "I am super super." >file && + git add file && + git commit -m B-super-super-initial + git submodule add "file://$base_dir/super" subwithsub && + git commit -m B-super-super-added && + git submodule update --init --recursive && + git repack -ad + ) && + git clone supersuper supersuper2 && + ( + cd supersuper2 && + git submodule update --init + ) +' + +# At this point there are three root-level positories: A, B, super and super2 + +test_expect_success 'nested submodule alternate in works and is actually used' ' + test_when_finished "rm -rf supersuper-clone" && + git clone --recursive --reference supersuper supersuper supersuper-clone && + ( + cd supersuper-clone && + # test superproject has alternates setup correctly + test_alternate_is_used .git/objects/info/alternates . && + # immediate submodule has alternate: + test_alternate_is_used .git/modules/subwithsub/objects/info/alternates subwithsub && + # nested submodule also has alternate: + test_alternate_is_used .git/modules/subwithsub/modules/sub/objects/info/alternates subwithsub/sub + ) +' + +check_that_two_of_three_alternates_are_used() { + test_alternate_is_used .git/objects/info/alternates . && + # immediate submodule has alternate: + test_alternate_is_used .git/modules/subwithsub/objects/info/alternates subwithsub && + # but nested submodule has no alternate: + test_must_fail test_alternate_is_used .git/modules/subwithsub/modules/sub/objects/info/alternates subwithsub/sub +} + + +test_expect_success 'missing nested submodule alternate fails clone and submodule update' ' + test_when_finished "rm -rf supersuper-clone" && + test_must_fail git clone --recursive --reference supersuper2 supersuper2 supersuper-clone && + ( + cd supersuper-clone && + check_that_two_of_three_alternates_are_used && + # update of the submodule fails + test_must_fail git submodule update --init --recursive + ) +' + +test_expect_success 'missing nested submodule alternate in --reference-if-able mode' ' + test_when_finished "rm -rf supersuper-clone" && + git clone --recursive --reference-if-able supersuper2 supersuper2 supersuper-clone && + ( + cd supersuper-clone && + check_that_two_of_three_alternates_are_used && + # update of the submodule succeeds + git submodule update --init --recursive + ) +' test_done diff --git a/t/t7409-submodule-detached-worktree.sh b/t/t7409-submodule-detached-work-tree.sh index c20717181e..fc018e3638 100755 --- a/t/t7409-submodule-detached-worktree.sh +++ b/t/t7409-submodule-detached-work-tree.sh @@ -6,7 +6,7 @@ test_description='Test submodules on detached working tree This test verifies that "git submodule" initialization, update and addition works -on detahced working trees +on detached working trees ' TEST_NO_CREATE_REPO=1 diff --git a/t/t7410-submodule-checkout-to.sh b/t/t7410-submodule-checkout-to.sh index 8f30aed6cc..1acef32647 100755 --- a/t/t7410-submodule-checkout-to.sh +++ b/t/t7410-submodule-checkout-to.sh @@ -33,7 +33,7 @@ rev1_hash_sub=$(git --git-dir=origin/sub/.git show --pretty=format:%h -q "HEAD~1 test_expect_success 'checkout main' \ 'mkdir default_checkout && (cd clone/main && - git checkout --to "$base_path/default_checkout/main" "$rev1_hash_main")' + git worktree add "$base_path/default_checkout/main" "$rev1_hash_main")' test_expect_failure 'can see submodule diffs just after checkout' \ '(cd default_checkout/main && git diff --submodule master"^!" | grep "file1 updated")' @@ -41,10 +41,20 @@ test_expect_failure 'can see submodule diffs just after checkout' \ test_expect_success 'checkout main and initialize independed clones' \ 'mkdir fully_cloned_submodule && (cd clone/main && - git checkout --to "$base_path/fully_cloned_submodule/main" "$rev1_hash_main") && + git worktree add "$base_path/fully_cloned_submodule/main" "$rev1_hash_main") && (cd fully_cloned_submodule/main && git submodule update)' test_expect_success 'can see submodule diffs after independed cloning' \ '(cd fully_cloned_submodule/main && git diff --submodule master"^!" | grep "file1 updated")' +test_expect_success 'checkout sub manually' \ + 'mkdir linked_submodule && + (cd clone/main && + git worktree add "$base_path/linked_submodule/main" "$rev1_hash_main") && + (cd clone/main/sub && + git worktree add "$base_path/linked_submodule/main/sub" "$rev1_hash_sub")' + +test_expect_success 'can see submodule diffs after manual checkout of linked submodule' \ + '(cd linked_submodule/main && git diff --submodule master"^!" | grep "file1 updated")' + test_done diff --git a/t/t7411-submodule-config.sh b/t/t7411-submodule-config.sh new file mode 100755 index 0000000000..0bde5850ac --- /dev/null +++ b/t/t7411-submodule-config.sh @@ -0,0 +1,142 @@ +#!/bin/sh +# +# Copyright (c) 2014 Heiko Voigt +# + +test_description='Test submodules config cache infrastructure + +This test verifies that parsing .gitmodules configurations directly +from the database and from the worktree works. +' + +TEST_NO_CREATE_REPO=1 +. ./test-lib.sh + +test_expect_success 'submodule config cache setup' ' + mkdir submodule && + (cd submodule && + git init && + echo a >a && + git add . && + git commit -ma + ) && + mkdir super && + (cd super && + git init && + git submodule add ../submodule && + git submodule add ../submodule a && + git commit -m "add as submodule and as a" && + git mv a b && + git commit -m "move a to b" + ) +' + +test_expect_success 'configuration parsing with error' ' + test_when_finished "rm -rf repo" && + test_create_repo repo && + cat >repo/.gitmodules <<-\EOF && + [submodule "s"] + path + ignore + EOF + ( + cd repo && + test_must_fail test-tool submodule-config "" s 2>actual && + test_i18ngrep "bad config" actual + ) +' + +cat >super/expect <<EOF +Submodule name: 'a' for path 'a' +Submodule name: 'a' for path 'b' +Submodule name: 'submodule' for path 'submodule' +Submodule name: 'submodule' for path 'submodule' +EOF + +test_expect_success 'test parsing and lookup of submodule config by path' ' + (cd super && + test-tool submodule-config \ + HEAD^ a \ + HEAD b \ + HEAD^ submodule \ + HEAD submodule \ + >actual && + test_cmp expect actual + ) +' + +test_expect_success 'test parsing and lookup of submodule config by name' ' + (cd super && + test-tool submodule-config --name \ + HEAD^ a \ + HEAD a \ + HEAD^ submodule \ + HEAD submodule \ + >actual && + test_cmp expect actual + ) +' + +cat >super/expect_error <<EOF +Submodule name: 'a' for path 'b' +Submodule name: 'submodule' for path 'submodule' +EOF + +test_expect_success 'error in one submodule config lets continue' ' + (cd super && + cp .gitmodules .gitmodules.bak && + echo " value = \"" >>.gitmodules && + git add .gitmodules && + mv .gitmodules.bak .gitmodules && + git commit -m "add error" && + test-tool submodule-config \ + HEAD b \ + HEAD submodule \ + >actual && + test_cmp expect_error actual + ) +' + +test_expect_success 'error message contains blob reference' ' + (cd super && + sha1=$(git rev-parse HEAD) && + test-tool submodule-config \ + HEAD b \ + HEAD submodule \ + 2>actual_err && + test_i18ngrep "submodule-blob $sha1:.gitmodules" actual_err >/dev/null + ) +' + +test_expect_success 'using different treeishs works' ' + ( + cd super && + git tag new_tag && + tree=$(git rev-parse HEAD^{tree}) && + commit=$(git rev-parse HEAD^{commit}) && + test-tool submodule-config $commit b >expect && + test-tool submodule-config $tree b >actual.1 && + test-tool submodule-config new_tag b >actual.2 && + test_cmp expect actual.1 && + test_cmp expect actual.2 + ) +' + +test_expect_success 'error in history in fetchrecursesubmodule lets continue' ' + (cd super && + git config -f .gitmodules \ + submodule.submodule.fetchrecursesubmodules blabla && + git add .gitmodules && + git config --unset -f .gitmodules \ + submodule.submodule.fetchrecursesubmodules && + git commit -m "add error in fetchrecursesubmodules" && + test-tool submodule-config \ + HEAD b \ + HEAD submodule \ + >actual && + test_cmp expect_error actual && + git reset --hard HEAD^ + ) +' + +test_done diff --git a/t/t7412-submodule-absorbgitdirs.sh b/t/t7412-submodule-absorbgitdirs.sh new file mode 100755 index 0000000000..ce74c12da2 --- /dev/null +++ b/t/t7412-submodule-absorbgitdirs.sh @@ -0,0 +1,128 @@ +#!/bin/sh + +test_description='Test submodule absorbgitdirs + +This test verifies that `git submodue absorbgitdirs` moves a submodules git +directory into the superproject. +' + +. ./test-lib.sh + +test_expect_success 'setup a real submodule' ' + git init sub1 && + test_commit -C sub1 first && + git submodule add ./sub1 && + test_tick && + git commit -m superproject +' + +test_expect_success 'absorb the git dir' ' + >expect.1 && + >expect.2 && + >actual.1 && + >actual.2 && + git status >expect.1 && + git -C sub1 rev-parse HEAD >expect.2 && + git submodule absorbgitdirs && + git fsck && + test -f sub1/.git && + test -d .git/modules/sub1 && + git status >actual.1 && + git -C sub1 rev-parse HEAD >actual.2 && + test_cmp expect.1 actual.1 && + test_cmp expect.2 actual.2 +' + +test_expect_success 'absorbing does not fail for deinitialized submodules' ' + test_when_finished "git submodule update --init" && + git submodule deinit --all && + git submodule absorbgitdirs && + test -d .git/modules/sub1 && + test -d sub1 && + ! test -e sub1/.git +' + +test_expect_success 'setup nested submodule' ' + git init sub1/nested && + test_commit -C sub1/nested first_nested && + git -C sub1 submodule add ./nested && + test_tick && + git -C sub1 commit -m "add nested" && + git add sub1 && + git commit -m "sub1 to include nested submodule" +' + +test_expect_success 'absorb the git dir in a nested submodule' ' + git status >expect.1 && + git -C sub1/nested rev-parse HEAD >expect.2 && + git submodule absorbgitdirs && + test -f sub1/nested/.git && + test -d .git/modules/sub1/modules/nested && + git status >actual.1 && + git -C sub1/nested rev-parse HEAD >actual.2 && + test_cmp expect.1 actual.1 && + test_cmp expect.2 actual.2 +' + +test_expect_success 're-setup nested submodule' ' + # un-absorb the direct submodule, to test if the nested submodule + # is still correct (needs a rewrite of the gitfile only) + rm -rf sub1/.git && + mv .git/modules/sub1 sub1/.git && + GIT_WORK_TREE=. git -C sub1 config --unset core.worktree && + # fixup the nested submodule + echo "gitdir: ../.git/modules/nested" >sub1/nested/.git && + GIT_WORK_TREE=../../../nested git -C sub1/.git/modules/nested config \ + core.worktree "../../../nested" && + # make sure this re-setup is correct + git status --ignore-submodules=none +' + +test_expect_success 'absorb the git dir in a nested submodule' ' + git status >expect.1 && + git -C sub1/nested rev-parse HEAD >expect.2 && + git submodule absorbgitdirs && + test -f sub1/.git && + test -f sub1/nested/.git && + test -d .git/modules/sub1/modules/nested && + git status >actual.1 && + git -C sub1/nested rev-parse HEAD >actual.2 && + test_cmp expect.1 actual.1 && + test_cmp expect.2 actual.2 +' + +test_expect_success 'setup a gitlink with missing .gitmodules entry' ' + git init sub2 && + test_commit -C sub2 first && + git add sub2 && + git commit -m superproject +' + +test_expect_success 'absorbing the git dir fails for incomplete submodules' ' + git status >expect.1 && + git -C sub2 rev-parse HEAD >expect.2 && + test_must_fail git submodule absorbgitdirs && + git -C sub2 fsck && + test -d sub2/.git && + git status >actual && + git -C sub2 rev-parse HEAD >actual.2 && + test_cmp expect.1 actual.1 && + test_cmp expect.2 actual.2 +' + +test_expect_success 'setup a submodule with multiple worktrees' ' + # first create another unembedded git dir in a new submodule + git init sub3 && + test_commit -C sub3 first && + git submodule add ./sub3 && + test_tick && + git commit -m "add another submodule" && + git -C sub3 worktree add ../sub3_second_work_tree +' + +test_expect_success 'absorbing fails for a submodule with multiple worktrees' ' + test_must_fail git submodule absorbgitdirs sub3 2>error && + test_i18ngrep "not supported" error +' + +test_done diff --git a/t/t7413-submodule-is-active.sh b/t/t7413-submodule-is-active.sh new file mode 100755 index 0000000000..c8e7e98331 --- /dev/null +++ b/t/t7413-submodule-is-active.sh @@ -0,0 +1,107 @@ +#!/bin/sh + +test_description='Test submodule--helper is-active + +This test verifies that `git submodue--helper is-active` correctly identifies +submodules which are "active" and interesting to the user. +' + +. ./test-lib.sh + +test_expect_success 'setup' ' + git init sub && + test_commit -C sub initial && + git init super && + test_commit -C super initial && + git -C super submodule add ../sub sub1 && + git -C super submodule add ../sub sub2 && + + # Remove submodule.<name>.active entries in order to test in an + # environment where only URLs are present in the conifg + git -C super config --unset submodule.sub1.active && + git -C super config --unset submodule.sub2.active && + + git -C super commit -a -m "add 2 submodules at sub{1,2}" +' + +test_expect_success 'is-active works with urls' ' + git -C super submodule--helper is-active sub1 && + git -C super submodule--helper is-active sub2 && + + git -C super config --unset submodule.sub1.URL && + test_must_fail git -C super submodule--helper is-active sub1 && + git -C super config submodule.sub1.URL ../sub && + git -C super submodule--helper is-active sub1 +' + +test_expect_success 'is-active works with submodule.<name>.active config' ' + test_when_finished "git -C super config --unset submodule.sub1.active" && + test_when_finished "git -C super config submodule.sub1.URL ../sub" && + + git -C super config --bool submodule.sub1.active "false" && + test_must_fail git -C super submodule--helper is-active sub1 && + + git -C super config --bool submodule.sub1.active "true" && + git -C super config --unset submodule.sub1.URL && + git -C super submodule--helper is-active sub1 +' + +test_expect_success 'is-active works with basic submodule.active config' ' + test_when_finished "git -C super config submodule.sub1.URL ../sub" && + test_when_finished "git -C super config --unset-all submodule.active" && + + git -C super config --add submodule.active "." && + git -C super config --unset submodule.sub1.URL && + + git -C super submodule--helper is-active sub1 && + git -C super submodule--helper is-active sub2 +' + +test_expect_success 'is-active correctly works with paths that are not submodules' ' + test_when_finished "git -C super config --unset-all submodule.active" && + + test_must_fail git -C super submodule--helper is-active not-a-submodule && + + git -C super config --add submodule.active "." && + test_must_fail git -C super submodule--helper is-active not-a-submodule +' + +test_expect_success 'is-active works with exclusions in submodule.active config' ' + test_when_finished "git -C super config --unset-all submodule.active" && + + git -C super config --add submodule.active "." && + git -C super config --add submodule.active ":(exclude)sub1" && + + test_must_fail git -C super submodule--helper is-active sub1 && + git -C super submodule--helper is-active sub2 +' + +test_expect_success 'is-active with submodule.active and submodule.<name>.active' ' + test_when_finished "git -C super config --unset-all submodule.active" && + test_when_finished "git -C super config --unset submodule.sub1.active" && + test_when_finished "git -C super config --unset submodule.sub2.active" && + + git -C super config --add submodule.active "sub1" && + git -C super config --bool submodule.sub1.active "false" && + git -C super config --bool submodule.sub2.active "true" && + + test_must_fail git -C super submodule--helper is-active sub1 && + git -C super submodule--helper is-active sub2 +' + +test_expect_success 'is-active, submodule.active and submodule add' ' + test_when_finished "rm -rf super2" && + git init super2 && + test_commit -C super2 initial && + git -C super2 config --add submodule.active "sub*" && + + # submodule add should only add submodule.<name>.active + # to the config if not matched by the pathspec + git -C super2 submodule add ../sub sub1 && + test_must_fail git -C super2 config --get submodule.sub1.active && + + git -C super2 submodule add ../sub mod && + git -C super2 config --get submodule.mod.active +' + +test_done diff --git a/t/t7414-submodule-mistakes.sh b/t/t7414-submodule-mistakes.sh new file mode 100755 index 0000000000..f2e7df59cf --- /dev/null +++ b/t/t7414-submodule-mistakes.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +test_description='handling of common mistakes people may make with submodules' +. ./test-lib.sh + +test_expect_success 'create embedded repository' ' + git init embed && + test_commit -C embed one +' + +test_expect_success 'git-add on embedded repository warns' ' + test_when_finished "git rm --cached -f embed" && + git add embed 2>stderr && + test_i18ngrep warning stderr +' + +test_expect_success '--no-warn-embedded-repo suppresses warning' ' + test_when_finished "git rm --cached -f embed" && + git add --no-warn-embedded-repo embed 2>stderr && + test_i18ngrep ! warning stderr +' + +test_expect_success 'no warning when updating entry' ' + test_when_finished "git rm --cached -f embed" && + git add embed && + git -C embed commit --allow-empty -m two && + git add embed 2>stderr && + test_i18ngrep ! warning stderr +' + +test_expect_success 'submodule add does not warn' ' + test_when_finished "git rm -rf submodule .gitmodules" && + git submodule add ./embed submodule 2>stderr && + test_i18ngrep ! warning stderr +' + +test_done diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh index 116885a260..170b4810e0 100755 --- a/t/t7500-commit.sh +++ b/t/t7500-commit.sh @@ -130,8 +130,8 @@ EOF test_expect_success 'commit message from template with whitespace issue' ' echo "content galore" >>foo && git add foo && - GIT_EDITOR="$TEST_DIRECTORY"/t7500/add-whitespaced-content git commit \ - --template "$TEMPLATE" && + GIT_EDITOR=\""$TEST_DIRECTORY"\"/t7500/add-whitespaced-content \ + git commit --template "$TEMPLATE" && commit_msg_is "commit message" ' @@ -272,6 +272,14 @@ test_expect_success 'commit --fixup provides correct one-line commit message' ' commit_msg_is "fixup! target message subject line" ' +test_expect_success 'commit --fixup -m"something" -m"extra"' ' + commit_for_rebase_autosquash_setup && + git commit --fixup HEAD~1 -m"something" -m"extra" && + commit_msg_is "fixup! target message subject linesomething + +extra" +' + test_expect_success 'commit --squash works with -F' ' commit_for_rebase_autosquash_setup && echo "log message from file" >msgfile && @@ -325,8 +333,30 @@ test_expect_success 'invalid message options when using --fixup' ' test_must_fail git commit --fixup HEAD~1 --squash HEAD~2 && test_must_fail git commit --fixup HEAD~1 -C HEAD~2 && test_must_fail git commit --fixup HEAD~1 -c HEAD~2 && - test_must_fail git commit --fixup HEAD~1 -m "cmdline message" && test_must_fail git commit --fixup HEAD~1 -F log ' +cat >expected-template <<EOF + +# Please enter the commit message for your changes. Lines starting +# with '#' will be ignored, and an empty message aborts the commit. +# +# Author: A U Thor <author@example.com> +# +# On branch commit-template-check +# Changes to be committed: +# new file: commit-template-check +# +# Untracked files not listed +EOF + +test_expect_success 'new line found before status message in commit template' ' + git checkout -b commit-template-check && + git reset --hard HEAD && + touch commit-template-check && + git add commit-template-check && + GIT_EDITOR="cat >editor-input" git commit --untracked-files=no --allow-empty-message && + test_i18ncmp expected-template editor-input +' + test_done diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index 63e04277f9..9dbbd01fc0 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -18,7 +18,7 @@ test_expect_success 'initial status' ' echo bongo bongo >file && git add file && git status >actual && - test_i18ngrep "Initial commit" actual + test_i18ngrep "No commits yet" actual ' test_expect_success 'fail initial amend' ' @@ -52,6 +52,18 @@ test_expect_success PERL 'can use paths with --interactive' ' git reset --hard HEAD^ ' +test_expect_success 'removed files and relative paths' ' + test_when_finished "rm -rf foo" && + git init foo && + >foo/foo.txt && + git -C foo add foo.txt && + git -C foo commit -m first && + git -C foo rm foo.txt && + + mkdir -p foo/bar && + git -C foo/bar commit -m second ../foo.txt +' + test_expect_success 'using invalid commit with -C' ' test_must_fail git commit --allow-empty -C bogus ' @@ -155,6 +167,15 @@ test_expect_success 'amend --only ignores staged contents' ' git diff --exit-code ' +test_expect_success 'allow-empty --only ignores staged contents' ' + echo changed-again >file && + git add file && + git commit --allow-empty --only -m "empty" && + git cat-file blob HEAD:file >file.actual && + test_cmp file.expect file.actual && + git diff --exit-code +' + test_expect_success 'set up editor' ' cat >editor <<-\EOF && #!/bin/sh @@ -200,6 +221,26 @@ test_expect_success '--amend --edit of empty message' ' test_cmp expect msg ' +test_expect_success '--amend to set message to empty' ' + echo bata >file && + git add file && + git commit -m "unamended" && + git commit --amend --allow-empty-message -m "" && + git diff-tree -s --format=%s HEAD >msg && + echo "" >expect && + test_cmp expect msg +' + +test_expect_success '--amend to set empty message needs --allow-empty-message' ' + echo conga >file && + git add file && + git commit -m "unamended" && + test_must_fail git commit --amend -m "" && + git diff-tree -s --format=%s HEAD >msg && + echo "unamended" >expect && + test_cmp expect msg +' + test_expect_success '-m --edit' ' echo amended >expect && git commit --allow-empty -m buffer && @@ -440,6 +481,42 @@ $alt" && test_cmp expected actual ' +test_expect_success 'signoff respects trailer config' ' + + echo 5 >positive && + git add positive && + git commit -s -m "subject + +non-trailer line +Myfooter: x" && + git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && + ( + echo subject + echo + echo non-trailer line + echo Myfooter: x + echo + echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" + ) >expected && + test_cmp expected actual && + + echo 6 >positive && + git add positive && + git -c "trailer.Myfooter.ifexists=add" commit -s -m "subject + +non-trailer line +Myfooter: x" && + git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && + ( + echo subject + echo + echo non-trailer line + echo Myfooter: x + echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" + ) >expected && + test_cmp expected actual +' + test_expect_success 'multiple -m' ' >negative && @@ -587,4 +664,24 @@ test_expect_success '--only works on to-be-born branch' ' test_cmp expected actual ' +test_expect_success '--dry-run with conflicts fixed from a merge' ' + # setup two branches with conflicting information + # in the same file, resolve the conflict, + # call commit with --dry-run + echo "Initial contents, unimportant" >test-file && + git add test-file && + git commit -m "Initial commit" && + echo "commit-1-state" >test-file && + git commit -m "commit 1" -i test-file && + git tag commit-1 && + git checkout -b branch-2 HEAD^1 && + echo "commit-2-state" >test-file && + git commit -m "commit 2" -i test-file && + ! $(git merge --no-commit commit-1) && + echo "commit-2-state" >test-file && + git add test-file && + git commit --dry-run && + git commit -m "conflicts fixed from merge." +' + test_done diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh index 2e0d557243..d33a3cb331 100755 --- a/t/t7502-commit.sh +++ b/t/t7502-commit.sh @@ -171,9 +171,9 @@ test_expect_success 'verbose' ' test_expect_success 'verbose respects diff config' ' - test_config color.diff always && + test_config diff.noprefix true && git status -v >actual && - grep "\[1mdiff --git" actual + grep "diff --git negative negative" actual ' mesg_with_comment_and_newlines=' @@ -229,14 +229,36 @@ test_expect_success 'cleanup commit messages (scissors option,-F,-e)' ' cat >text <<EOF && # to be kept + + # ------------------------ >8 ------------------------ +# to be kept, too # ------------------------ >8 ------------------------ to be removed +# ------------------------ >8 ------------------------ +to be removed, too +EOF + + cat >expect <<EOF && +# to be kept + + # ------------------------ >8 ------------------------ +# to be kept, too EOF - echo "# to be kept" >expect && git commit --cleanup=scissors -e -F text -a && git cat-file -p HEAD |sed -e "1,/^\$/d">actual && test_cmp expect actual +' + +test_expect_success 'cleanup commit messages (scissors option,-F,-e, scissors on first line)' ' + echo >>negative && + cat >text <<EOF && +# ------------------------ >8 ------------------------ +to be removed +EOF + git commit --cleanup=scissors -e -F text -a --allow-empty-message && + git cat-file -p HEAD |sed -e "1,/^\$/d">actual && + test_must_be_empty actual ' test_expect_success 'cleanup commit messages (strip option,-F)' ' @@ -505,11 +527,6 @@ try_commit_status_combo () { test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' - test_expect_success 'commit' ' - try_commit "" && - test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG - ' - test_expect_success 'commit --status' ' try_commit --status && test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG diff --git a/t/t7504-commit-msg-hook.sh b/t/t7504-commit-msg-hook.sh index 1f53ea8090..302a3a2082 100755 --- a/t/t7504-commit-msg-hook.sh +++ b/t/t7504-commit-msg-hook.sh @@ -101,6 +101,10 @@ cat > "$HOOK" <<EOF exit 1 EOF +commit_msg_is () { + test "$(git log --pretty=format:%s%b -1)" = "$1" +} + test_expect_success 'with failing hook' ' echo "another" >> file && @@ -135,6 +139,32 @@ test_expect_success '--no-verify with failing hook (editor)' ' ' +test_expect_success 'merge fails with failing hook' ' + + test_when_finished "git branch -D newbranch" && + test_when_finished "git checkout -f master" && + git checkout --orphan newbranch && + : >file2 && + git add file2 && + git commit --no-verify file2 -m in-side-branch && + test_must_fail git merge --allow-unrelated-histories master && + commit_msg_is "in-side-branch" # HEAD before merge + +' + +test_expect_success 'merge bypasses failing hook with --no-verify' ' + + test_when_finished "git branch -D newbranch" && + test_when_finished "git checkout -f master" && + git checkout --orphan newbranch && + : >file2 && + git add file2 && + git commit --no-verify file2 -m in-side-branch && + git merge --no-verify --allow-unrelated-histories master && + commit_msg_is "Merge branch '\''master'\'' into newbranch" +' + + chmod -x "$HOOK" test_expect_success POSIXPERM 'with non-executable hook' ' @@ -178,10 +208,6 @@ exit 0 EOF chmod +x "$HOOK" -commit_msg_is () { - test "`git log --pretty=format:%s%b -1`" = "$1" -} - test_expect_success 'hook edits commit message' ' echo "additional" >> file && @@ -217,7 +243,54 @@ test_expect_success "hook doesn't edit commit message (editor)" ' echo "more plus" > FAKE_MSG && GIT_EDITOR="\"\$FAKE_EDITOR\"" git commit --no-verify && commit_msg_is "more plus" +' + +test_expect_success 'hook called in git-merge picks up commit message' ' + test_when_finished "git branch -D newbranch" && + test_when_finished "git checkout -f master" && + git checkout --orphan newbranch && + : >file2 && + git add file2 && + git commit --no-verify file2 -m in-side-branch && + git merge --allow-unrelated-histories master && + commit_msg_is "new message" +' +test_expect_failure 'merge --continue remembers --no-verify' ' + test_when_finished "git branch -D newbranch" && + test_when_finished "git checkout -f master" && + git checkout master && + echo a >file2 && + git add file2 && + git commit --no-verify -m "add file2 to master" && + git checkout -b newbranch master^ && + echo b >file2 && + git add file2 && + git commit --no-verify file2 -m in-side-branch && + git merge --no-verify -m not-rewritten-by-hook master && + # resolve conflict: + echo c >file2 && + git add file2 && + git merge --continue && + commit_msg_is not-rewritten-by-hook ' +# set up fake editor to replace `pick` by `reword` +cat > reword-editor <<'EOF' +#!/bin/sh +mv "$1" "$1".bup && +sed 's/^pick/reword/' <"$1".bup >"$1" +EOF +chmod +x reword-editor +REWORD_EDITOR="$(pwd)/reword-editor" +export REWORD_EDITOR + +test_expect_success 'hook is called for reword during `rebase -i`' ' + + GIT_SEQUENCE_EDITOR="\"$REWORD_EDITOR\"" git rebase -i HEAD^ && + commit_msg_is "new message" + +' + + test_done diff --git a/t/t7505-prepare-commit-msg-hook.sh b/t/t7505-prepare-commit-msg-hook.sh index 03dce09cfe..1f43b3cd4c 100755 --- a/t/t7505-prepare-commit-msg-hook.sh +++ b/t/t7505-prepare-commit-msg-hook.sh @@ -4,6 +4,38 @@ test_description='prepare-commit-msg hook' . ./test-lib.sh +test_expect_success 'set up commits for rebasing' ' + test_commit root && + test_commit a a a && + test_commit b b b && + git checkout -b rebase-me root && + test_commit rebase-a a aa && + test_commit rebase-b b bb && + for i in $(test_seq 1 13) + do + test_commit rebase-$i c $i + done && + git checkout master && + + cat >rebase-todo <<-EOF + pick $(git rev-parse rebase-a) + pick $(git rev-parse rebase-b) + fixup $(git rev-parse rebase-1) + fixup $(git rev-parse rebase-2) + pick $(git rev-parse rebase-3) + fixup $(git rev-parse rebase-4) + squash $(git rev-parse rebase-5) + reword $(git rev-parse rebase-6) + squash $(git rev-parse rebase-7) + fixup $(git rev-parse rebase-8) + fixup $(git rev-parse rebase-9) + edit $(git rev-parse rebase-10) + squash $(git rev-parse rebase-11) + squash $(git rev-parse rebase-12) + edit $(git rev-parse rebase-13) + EOF +' + test_expect_success 'with no hook' ' echo "foo" > file && @@ -31,17 +63,41 @@ mkdir -p "$HOOKDIR" echo "#!$SHELL_PATH" > "$HOOK" cat >> "$HOOK" <<'EOF' -if test "$2" = commit; then - source=$(git rev-parse "$3") +GIT_DIR=$(git rev-parse --git-dir) +if test -d "$GIT_DIR/rebase-merge" +then + rebasing=1 +else + rebasing=0 +fi + +get_last_cmd () { + tail -n1 "$GIT_DIR/rebase-merge/done" | { + read cmd id _ + git log --pretty="[$cmd %s]" -n1 $id + } +} + +if test "$2" = commit +then + if test $rebasing = 1 + then + source="$3" + else + source=$(git rev-parse "$3") + fi else - source=${2-default} + source=${2-default} fi -if test "$GIT_EDITOR" = :; then - sed -e "1s/.*/$source (no editor)/" "$1" > msg.tmp +test "$GIT_EDITOR" = : && source="$source (no editor)" + +if test $rebasing = 1 +then + echo "$source $(get_last_cmd)" >"$1" else - sed -e "1s/.*/$source/" "$1" > msg.tmp + sed -e "1s/.*/$source/" "$1" >msg.tmp + mv msg.tmp "$1" fi -mv msg.tmp "$1" exit 0 EOF chmod +x "$HOOK" @@ -53,7 +109,7 @@ test_expect_success 'with hook (-m)' ' echo "more" >> file && git add file && git commit -m "more" && - test "`git log -1 --pretty=format:%s`" = "message (no editor)" + test "$(git log -1 --pretty=format:%s)" = "message (no editor)" ' @@ -62,7 +118,7 @@ test_expect_success 'with hook (-m editor)' ' echo "more" >> file && git add file && GIT_EDITOR="\"\$FAKE_EDITOR\"" git commit -e -m "more more" && - test "`git log -1 --pretty=format:%s`" = message + test "$(git log -1 --pretty=format:%s)" = message ' @@ -71,7 +127,7 @@ test_expect_success 'with hook (-t)' ' echo "more" >> file && git add file && git commit -t "$(git rev-parse --git-dir)/template" && - test "`git log -1 --pretty=format:%s`" = template + test "$(git log -1 --pretty=format:%s)" = template ' @@ -80,7 +136,7 @@ test_expect_success 'with hook (-F)' ' echo "more" >> file && git add file && (echo more | git commit -F -) && - test "`git log -1 --pretty=format:%s`" = "message (no editor)" + test "$(git log -1 --pretty=format:%s)" = "message (no editor)" ' @@ -89,17 +145,17 @@ test_expect_success 'with hook (-F editor)' ' echo "more" >> file && git add file && (echo more more | GIT_EDITOR="\"\$FAKE_EDITOR\"" git commit -e -F -) && - test "`git log -1 --pretty=format:%s`" = message + test "$(git log -1 --pretty=format:%s)" = message ' test_expect_success 'with hook (-C)' ' - head=`git rev-parse HEAD` && + head=$(git rev-parse HEAD) && echo "more" >> file && git add file && git commit -C $head && - test "`git log -1 --pretty=format:%s`" = "$head (no editor)" + test "$(git log -1 --pretty=format:%s)" = "$head (no editor)" ' @@ -108,27 +164,27 @@ test_expect_success 'with hook (editor)' ' echo "more more" >> file && git add file && GIT_EDITOR="\"\$FAKE_EDITOR\"" git commit && - test "`git log -1 --pretty=format:%s`" = default + test "$(git log -1 --pretty=format:%s)" = default ' test_expect_success 'with hook (--amend)' ' - head=`git rev-parse HEAD` && + head=$(git rev-parse HEAD) && echo "more" >> file && git add file && GIT_EDITOR="\"\$FAKE_EDITOR\"" git commit --amend && - test "`git log -1 --pretty=format:%s`" = "$head" + test "$(git log -1 --pretty=format:%s)" = "$head" ' test_expect_success 'with hook (-c)' ' - head=`git rev-parse HEAD` && + head=$(git rev-parse HEAD) && echo "more" >> file && git add file && GIT_EDITOR="\"\$FAKE_EDITOR\"" git commit -c $head && - test "`git log -1 --pretty=format:%s`" = "$head" + test "$(git log -1 --pretty=format:%s)" = "$head" ' @@ -141,7 +197,7 @@ test_expect_success 'with hook (merge)' ' git commit -m other && git checkout - && git merge --no-ff other && - test "`git log -1 --pretty=format:%s`" = "merge (no editor)" + test "$(git log -1 --pretty=format:%s)" = "merge (no editor)" ' test_expect_success 'with hook and editor (merge)' ' @@ -153,7 +209,64 @@ test_expect_success 'with hook and editor (merge)' ' git commit -m other && git checkout - && env GIT_EDITOR="\"\$FAKE_EDITOR\"" git merge --no-ff -e other && - test "`git log -1 --pretty=format:%s`" = "merge" + test "$(git log -1 --pretty=format:%s)" = "merge" +' + +test_rebase () { + expect=$1 && + mode=$2 && + test_expect_$expect C_LOCALE_OUTPUT "with hook (rebase $mode)" ' + test_when_finished "\ + git rebase --abort + git checkout -f master + git branch -D tmp" && + git checkout -b tmp rebase-me && + GIT_SEQUENCE_EDITOR="cp rebase-todo" && + GIT_EDITOR="\"$FAKE_EDITOR\"" && + ( + export GIT_SEQUENCE_EDITOR GIT_EDITOR && + test_must_fail git rebase $mode b && + echo x >a && + git add a && + test_must_fail git rebase --continue && + echo x >b && + git add b && + git commit && + git rebase --continue && + echo y >a && + git add a && + git commit && + git rebase --continue && + echo y >b && + git add b && + git rebase --continue + ) && + if test $mode = -p # reword amended after pick + then + n=18 + else + n=17 + fi && + git log --pretty=%s -g -n$n HEAD@{1} >actual && + test_cmp "$TEST_DIRECTORY/t7505/expected-rebase$mode" actual + ' +} + +test_rebase success -i +test_rebase success -p + +test_expect_success 'with hook (cherry-pick)' ' + test_when_finished "git checkout -f master" && + git checkout -B other b && + git cherry-pick rebase-1 && + test "$(git log -1 --pretty=format:%s)" = "message (no editor)" +' + +test_expect_success 'with hook and editor (cherry-pick)' ' + test_when_finished "git checkout -f master" && + git checkout -B other b && + git cherry-pick -e rebase-1 && + test "$(git log -1 --pretty=format:%s)" = merge ' cat > "$HOOK" <<'EOF' @@ -164,7 +277,7 @@ EOF test_expect_success 'with failing hook' ' test_when_finished "git checkout -f master" && - head=`git rev-parse HEAD` && + head=$(git rev-parse HEAD) && echo "more" >> file && git add file && test_must_fail env GIT_EDITOR="\"\$FAKE_EDITOR\"" git commit -c $head @@ -174,7 +287,7 @@ test_expect_success 'with failing hook' ' test_expect_success 'with failing hook (--no-verify)' ' test_when_finished "git checkout -f master" && - head=`git rev-parse HEAD` && + head=$(git rev-parse HEAD) && echo "more" >> file && git add file && test_must_fail env GIT_EDITOR="\"\$FAKE_EDITOR\"" git commit --no-verify -c $head @@ -197,4 +310,11 @@ test_expect_success 'with failing hook (merge)' ' ' +test_expect_success C_LOCALE_OUTPUT 'with failing hook (cherry-pick)' ' + test_when_finished "git checkout -f master" && + git checkout -B other b && + test_must_fail git cherry-pick rebase-1 2>actual && + test $(grep -c prepare-commit-msg actual) = 1 +' + test_done diff --git a/t/t7505/expected-rebase-i b/t/t7505/expected-rebase-i new file mode 100644 index 0000000000..c514bdbb94 --- /dev/null +++ b/t/t7505/expected-rebase-i @@ -0,0 +1,17 @@ +message [edit rebase-13] +message (no editor) [edit rebase-13] +message [squash rebase-12] +message (no editor) [squash rebase-11] +default [edit rebase-10] +message (no editor) [edit rebase-10] +message [fixup rebase-9] +message (no editor) [fixup rebase-8] +message (no editor) [squash rebase-7] +message [reword rebase-6] +message [squash rebase-5] +message (no editor) [fixup rebase-4] +message (no editor) [pick rebase-3] +message (no editor) [fixup rebase-2] +message (no editor) [fixup rebase-1] +merge [pick rebase-b] +message [pick rebase-a] diff --git a/t/t7505/expected-rebase-p b/t/t7505/expected-rebase-p new file mode 100644 index 0000000000..93bada596e --- /dev/null +++ b/t/t7505/expected-rebase-p @@ -0,0 +1,18 @@ +message [edit rebase-13] +message (no editor) [edit rebase-13] +message [squash rebase-12] +message (no editor) [squash rebase-11] +default [edit rebase-10] +message (no editor) [edit rebase-10] +message [fixup rebase-9] +message (no editor) [fixup rebase-8] +message (no editor) [squash rebase-7] +HEAD [reword rebase-6] +message (no editor) [reword rebase-6] +message [squash rebase-5] +message (no editor) [fixup rebase-4] +message (no editor) [pick rebase-3] +message (no editor) [fixup rebase-2] +message (no editor) [fixup rebase-1] +merge [pick rebase-b] +message [pick rebase-a] diff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh index d31b34da83..9edf6572ed 100755 --- a/t/t7506-status-submodule.sh +++ b/t/t7506-status-submodule.sh @@ -17,6 +17,12 @@ test_create_repo_with_commit () { ) } +sanitize_output () { + sed -e "s/$_x40/HASH/" -e "s/$_x40/HASH/" output >output2 && + mv output2 output +} + + test_expect_success 'setup' ' test_create_repo_with_commit sub && echo output > .gitignore && @@ -50,6 +56,15 @@ test_expect_success 'status with modified file in submodule (porcelain)' ' EOF ' +test_expect_success 'status with modified file in submodule (short)' ' + (cd sub && git reset --hard) && + echo "changed" >sub/foo && + git status --short >output && + diff output - <<-\EOF + m sub + EOF +' + test_expect_success 'status with added file in submodule' ' (cd sub && git reset --hard && echo >foo && git add foo) && git status >output && @@ -64,6 +79,14 @@ test_expect_success 'status with added file in submodule (porcelain)' ' EOF ' +test_expect_success 'status with added file in submodule (short)' ' + (cd sub && git reset --hard && echo >foo && git add foo) && + git status --short >output && + diff output - <<-\EOF + m sub + EOF +' + test_expect_success 'status with untracked file in submodule' ' (cd sub && git reset --hard) && echo "content" >sub/new-file && @@ -83,6 +106,13 @@ test_expect_success 'status with untracked file in submodule (porcelain)' ' EOF ' +test_expect_success 'status with untracked file in submodule (short)' ' + git status --short >output && + diff output - <<-\EOF + ? sub + EOF +' + test_expect_success 'status with added and untracked file in submodule' ' (cd sub && git reset --hard && echo >foo && git add foo) && echo "content" >sub/new-file && @@ -177,8 +207,24 @@ test_expect_success 'status with added file in modified submodule with .git file test_i18ngrep "modified: sub (new commits, modified content)" output ' +test_expect_success 'status with a lot of untracked files in the submodule' ' + ( + cd sub + i=0 && + while test $i -lt 1024 + do + >some-file-$i + i=$(( $i + 1 )) + done + ) && + git status --porcelain sub 2>err.actual && + test_must_be_empty err.actual && + rm err.actual +' + test_expect_success 'rm submodule contents' ' - rm -rf sub/* sub/.git + rm -rf sub && + mkdir sub ' test_expect_success 'status clean (empty submodule dir)' ' @@ -260,7 +306,7 @@ test_expect_success 'diff with merge conflict in .gitmodules' ' cd super && git diff >../diff_actual 2>&1 ) && - test_cmp diff_actual diff_expect + test_cmp diff_expect diff_actual ' test_expect_success 'diff --submodule with merge conflict in .gitmodules' ' @@ -268,7 +314,94 @@ test_expect_success 'diff --submodule with merge conflict in .gitmodules' ' cd super && git diff --submodule >../diff_submodule_actual 2>&1 ) && - test_cmp diff_submodule_actual diff_submodule_expect + test_cmp diff_submodule_expect diff_submodule_actual +' + +# We'll setup different cases for further testing: +# sub1 will contain a nested submodule, +# sub2 will have an untracked file +# sub3 will have an untracked repository +test_expect_success 'setup superproject with untracked file in nested submodule' ' + ( + cd super && + git clean -dfx && + rm .gitmodules && + git submodule add -f ./sub1 && + git submodule add -f ./sub2 && + git submodule add -f ./sub1 sub3 && + git commit -a -m "messy merge in superproject" && + ( + cd sub1 && + git submodule add ../sub2 && + git commit -a -m "add sub2 to sub1" + ) && + git add sub1 && + git commit -a -m "update sub1 to contain nested sub" + ) && + echo content >super/sub1/sub2/file && + echo content >super/sub2/file && + git -C super/sub3 clone ../../sub2 untracked_repository +' + +test_expect_success 'status with untracked file in nested submodule (porcelain)' ' + git -C super status --porcelain >output && + diff output - <<-\EOF + M sub1 + M sub2 + M sub3 + EOF +' + +test_expect_success 'status with untracked file in nested submodule (porcelain=2)' ' + git -C super status --porcelain=2 >output && + sanitize_output output && + diff output - <<-\EOF + 1 .M S..U 160000 160000 160000 HASH HASH sub1 + 1 .M S..U 160000 160000 160000 HASH HASH sub2 + 1 .M S..U 160000 160000 160000 HASH HASH sub3 + EOF +' + +test_expect_success 'status with untracked file in nested submodule (short)' ' + git -C super status --short >output && + diff output - <<-\EOF + ? sub1 + ? sub2 + ? sub3 + EOF +' + +test_expect_success 'setup superproject with modified file in nested submodule' ' + git -C super/sub1/sub2 add file && + git -C super/sub2 add file +' + +test_expect_success 'status with added file in nested submodule (porcelain)' ' + git -C super status --porcelain >output && + diff output - <<-\EOF + M sub1 + M sub2 + M sub3 + EOF +' + +test_expect_success 'status with added file in nested submodule (porcelain=2)' ' + git -C super status --porcelain=2 >output && + sanitize_output output && + diff output - <<-\EOF + 1 .M S.M. 160000 160000 160000 HASH HASH sub1 + 1 .M S.M. 160000 160000 160000 HASH HASH sub2 + 1 .M S..U 160000 160000 160000 HASH HASH sub3 + EOF +' + +test_expect_success 'status with added file in nested submodule (short)' ' + git -C super status --short >output && + diff output - <<-\EOF + m sub1 + m sub2 + ? sub3 + EOF ' test_done diff --git a/t/t7507-commit-verbose.sh b/t/t7507-commit-verbose.sh index 2ddf28c984..ed2653d46f 100755 --- a/t/t7507-commit-verbose.sh +++ b/t/t7507-commit-verbose.sh @@ -3,11 +3,10 @@ test_description='verbose commit template' . ./test-lib.sh -cat >check-for-diff <<EOF -#!$SHELL_PATH -exec grep '^diff --git' "\$1" +write_script "check-for-diff" <<\EOF && +grep '^diff --git' "$1" >out +exit 0 EOF -chmod +x check-for-diff test_set_editor "$PWD/check-for-diff" cat >message <<'EOF' @@ -23,7 +22,8 @@ test_expect_success 'setup' ' ' test_expect_success 'initial commit shows verbose diff' ' - git commit --amend -v + git commit --amend -v && + test_line_count = 1 out ' test_expect_success 'second commit' ' @@ -39,13 +39,15 @@ check_message() { test_expect_success 'verbose diff is stripped out' ' git commit --amend -v && - check_message message + check_message message && + test_line_count = 1 out ' test_expect_success 'verbose diff is stripped out (mnemonicprefix)' ' git config diff.mnemonicprefix true && git commit --amend -v && - check_message message + check_message message && + test_line_count = 1 out ' cat >diff <<'EOF' @@ -96,4 +98,60 @@ test_expect_success 'verbose diff is stripped out with set core.commentChar' ' test_i18ngrep "Aborting commit due to empty commit message." err ' +test_expect_success 'status does not verbose without --verbose' ' + git status >actual && + ! grep "^diff --git" actual +' + +test_expect_success 'setup -v -v' ' + echo dirty >file +' + +for i in true 1 +do + test_expect_success "commit.verbose=$i and --verbose omitted" " + git -c commit.verbose=$i commit --amend && + test_line_count = 1 out + " +done + +for i in false -2 -1 0 +do + test_expect_success "commit.verbose=$i and --verbose omitted" " + git -c commit.verbose=$i commit --amend && + test_line_count = 0 out + " +done + +for i in 2 3 +do + test_expect_success "commit.verbose=$i and --verbose omitted" " + git -c commit.verbose=$i commit --amend && + test_line_count = 2 out + " +done + +for i in true false -2 -1 0 1 2 3 +do + test_expect_success "commit.verbose=$i and --verbose" " + git -c commit.verbose=$i commit --amend --verbose && + test_line_count = 1 out + " + + test_expect_success "commit.verbose=$i and --no-verbose" " + git -c commit.verbose=$i commit --amend --no-verbose && + test_line_count = 0 out + " + + test_expect_success "commit.verbose=$i and -v -v" " + git -c commit.verbose=$i commit --amend -v -v && + test_line_count = 2 out + " +done + +test_expect_success "status ignores commit.verbose=true" ' + git -c commit.verbose=true status >actual && + ! grep "^diff --git actual" +' + test_done diff --git a/t/t7508-status.sh b/t/t7508-status.sh index c3ed7cb51c..7afadb175a 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -6,6 +6,7 @@ test_description='git status' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-terminal.sh test_expect_success 'status -h in broken repository' ' git config --global advice.statusuoption false && @@ -32,6 +33,17 @@ test_expect_success 'commit -h in broken repository' ' test_i18ngrep "[Uu]sage" broken/usage ' +test_expect_success 'create upstream branch' ' + git checkout -b upstream && + test_commit upstream1 && + test_commit upstream2 && + # leave the first commit on master as root because several + # tests depend on this case; for our upstream we only + # care about commit counts anyway, so a totally divergent + # history is OK + git checkout --orphan master +' + test_expect_success 'setup' ' : >tracked && : >modified && @@ -53,7 +65,9 @@ test_expect_success 'setup' ' echo 1 >dir1/modified && echo 2 >dir2/modified && echo 3 >dir2/added && - git add dir2/added + git add dir2/added && + + git branch --set-upstream-to=upstream ' test_expect_success 'status (1)' ' @@ -75,6 +89,10 @@ EOF test_expect_success 'status --column' ' cat >expect <<\EOF && # On branch master +# Your branch and '\''upstream'\'' have diverged, +# and have 1 and 2 different commits each, respectively. +# (use "git pull" to merge the remote branch into yours) +# # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # @@ -105,6 +123,10 @@ test_expect_success 'status --column status.displayCommentPrefix=false' ' cat >expect <<\EOF # On branch master +# Your branch and 'upstream' have diverged, +# and have 1 and 2 different commits each, respectively. +# (use "git pull" to merge the remote branch into yours) +# # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # @@ -178,6 +200,9 @@ test_expect_success 'commit ignores status.displayCommentPrefix=false in COMMIT_ cat >expect <<\EOF On branch master +Your branch and 'upstream' have diverged, +and have 1 and 2 different commits each, respectively. + Changes to be committed: new file: dir2/added @@ -248,6 +273,10 @@ test_expect_success 'status with gitignore' ' cat >expect <<\EOF && On branch master +Your branch and '\''upstream'\'' have diverged, +and have 1 and 2 different commits each, respectively. + (use "git pull" to merge the remote branch into yours) + Changes to be committed: (use "git reset HEAD <file>..." to unstage) @@ -313,6 +342,10 @@ test_expect_success 'status with gitignore (nothing untracked)' ' cat >expect <<\EOF && On branch master +Your branch and '\''upstream'\'' have diverged, +and have 1 and 2 different commits each, respectively. + (use "git pull" to merge the remote branch into yours) + Changes to be committed: (use "git reset HEAD <file>..." to unstage) @@ -348,7 +381,7 @@ output* EOF cat >expect <<\EOF -## master +## master...upstream [ahead 1, behind 2] M dir1/modified A dir2/added ?? dir1/untracked @@ -360,7 +393,7 @@ EOF test_expect_success 'status -s -b' ' git status -s -b >output && - test_cmp expect output + test_i18ncmp expect output ' @@ -370,7 +403,7 @@ test_expect_success 'status -s -z -b' ' git status -s -z -b >output && nul_to_q <output >output.q && mv output.q output && - test_cmp expect output + test_i18ncmp expect output ' test_expect_success 'setup dir3' ' @@ -382,6 +415,10 @@ test_expect_success 'setup dir3' ' test_expect_success 'status -uno' ' cat >expect <<EOF && On branch master +Your branch and '\''upstream'\'' have diverged, +and have 1 and 2 different commits each, respectively. + (use "git pull" to merge the remote branch into yours) + Changes to be committed: (use "git reset HEAD <file>..." to unstage) @@ -408,6 +445,9 @@ test_expect_success 'status (status.showUntrackedFiles no)' ' test_expect_success 'status -uno (advice.statusHints false)' ' cat >expect <<EOF && On branch master +Your branch and '\''upstream'\'' have diverged, +and have 1 and 2 different commits each, respectively. + Changes to be committed: new file: dir2/added @@ -439,6 +479,10 @@ test_expect_success 'status -s (status.showUntrackedFiles no)' ' test_expect_success 'status -unormal' ' cat >expect <<EOF && On branch master +Your branch and '\''upstream'\'' have diverged, +and have 1 and 2 different commits each, respectively. + (use "git pull" to merge the remote branch into yours) + Changes to be committed: (use "git reset HEAD <file>..." to unstage) @@ -493,6 +537,10 @@ test_expect_success 'status -s (status.showUntrackedFiles normal)' ' test_expect_success 'status -uall' ' cat >expect <<EOF && On branch master +Your branch and '\''upstream'\'' have diverged, +and have 1 and 2 different commits each, respectively. + (use "git pull" to merge the remote branch into yours) + Changes to be committed: (use "git reset HEAD <file>..." to unstage) @@ -552,6 +600,10 @@ test_expect_success 'status -s (status.showUntrackedFiles all)' ' test_expect_success 'status with relative paths' ' cat >expect <<\EOF && On branch master +Your branch and '\''upstream'\'' have diverged, +and have 1 and 2 different commits each, respectively. + (use "git pull" to merge the remote branch into yours) + Changes to be committed: (use "git reset HEAD <file>..." to unstage) @@ -610,13 +662,19 @@ test_expect_success 'status --porcelain ignores relative paths setting' ' test_expect_success 'setup unique colors' ' git config status.color.untracked blue && - git config status.color.branch green + git config status.color.branch green && + git config status.color.localBranch yellow && + git config status.color.remoteBranch cyan ' -test_expect_success 'status with color.ui' ' +test_expect_success TTY 'status with color.ui' ' cat >expect <<\EOF && On branch <GREEN>master<RESET> +Your branch and '\''upstream'\'' have diverged, +and have 1 and 2 different commits each, respectively. + (use "git pull" to merge the remote branch into yours) + Changes to be committed: (use "git reset HEAD <file>..." to unstage) @@ -637,14 +695,14 @@ Untracked files: <BLUE>untracked<RESET> EOF - test_config color.ui always && - git status | test_decode_color >output && + test_config color.ui auto && + test_terminal git status | test_decode_color >output && test_i18ncmp expect output ' -test_expect_success 'status with color.status' ' - test_config color.status always && - git status | test_decode_color >output && +test_expect_success TTY 'status with color.status' ' + test_config color.status auto && + test_terminal git status | test_decode_color >output && test_i18ncmp expect output ' @@ -657,25 +715,25 @@ cat >expect <<\EOF <BLUE>??<RESET> untracked EOF -test_expect_success 'status -s with color.ui' ' +test_expect_success TTY 'status -s with color.ui' ' - git config color.ui always && - git status -s | test_decode_color >output && + git config color.ui auto && + test_terminal git status -s | test_decode_color >output && test_cmp expect output ' -test_expect_success 'status -s with color.status' ' +test_expect_success TTY 'status -s with color.status' ' git config --unset color.ui && - git config color.status always && - git status -s | test_decode_color >output && + git config color.status auto && + test_terminal git status -s | test_decode_color >output && test_cmp expect output ' cat >expect <<\EOF -## <GREEN>master<RESET> +## <YELLOW>master<RESET>...<CYAN>upstream<RESET> [ahead <YELLOW>1<RESET>, behind <CYAN>2<RESET>] <RED>M<RESET> dir1/modified <GREEN>A<RESET> dir2/added <BLUE>??<RESET> dir1/untracked @@ -684,10 +742,10 @@ cat >expect <<\EOF <BLUE>??<RESET> untracked EOF -test_expect_success 'status -s -b with color.status' ' +test_expect_success TTY 'status -s -b with color.status' ' - git status -s -b | test_decode_color >output && - test_cmp expect output + test_terminal git status -s -b | test_decode_color >output && + test_i18ncmp expect output ' @@ -700,20 +758,20 @@ A dir2/added ?? untracked EOF -test_expect_success 'status --porcelain ignores color.ui' ' +test_expect_success TTY 'status --porcelain ignores color.ui' ' git config --unset color.status && - git config color.ui always && - git status --porcelain | test_decode_color >output && + git config color.ui auto && + test_terminal git status --porcelain | test_decode_color >output && test_cmp expect output ' -test_expect_success 'status --porcelain ignores color.status' ' +test_expect_success TTY 'status --porcelain ignores color.status' ' git config --unset color.ui && - git config color.status always && - git status --porcelain | test_decode_color >output && + git config color.status auto && + test_terminal git status --porcelain | test_decode_color >output && test_cmp expect output ' @@ -726,7 +784,7 @@ test_expect_success 'status --porcelain respects -b' ' git status --porcelain -b >output && { - echo "## master" && + echo "## master...upstream [ahead 1, behind 2]" && cat expect } >tmp && mv tmp expect && @@ -739,6 +797,10 @@ test_expect_success 'status --porcelain respects -b' ' test_expect_success 'status without relative paths' ' cat >expect <<\EOF && On branch master +Your branch and '\''upstream'\'' have diverged, +and have 1 and 2 different commits each, respectively. + (use "git pull" to merge the remote branch into yours) + Changes to be committed: (use "git reset HEAD <file>..." to unstage) @@ -785,6 +847,10 @@ test_expect_success 'status -s without relative paths' ' test_expect_success 'dry-run of partial commit excluding new file in index' ' cat >expect <<EOF && On branch master +Your branch and '\''upstream'\'' have diverged, +and have 1 and 2 different commits each, respectively. + (use "git pull" to merge the remote branch into yours) + Changes to be committed: (use "git reset HEAD <file>..." to unstage) @@ -803,7 +869,7 @@ EOF ' cat >expect <<EOF -:100644 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0000000000000000000000000000000000000000 M dir1/modified +:100644 100644 $EMPTY_BLOB 0000000000000000000000000000000000000000 M dir1/modified EOF test_expect_success 'status refreshes the index' ' touch dir2/added && @@ -825,6 +891,10 @@ test_expect_success 'setup status submodule summary' ' test_expect_success 'status submodule summary is disabled by default' ' cat >expect <<EOF && On branch master +Your branch and '\''upstream'\'' have diverged, +and have 1 and 2 different commits each, respectively. + (use "git pull" to merge the remote branch into yours) + Changes to be committed: (use "git reset HEAD <file>..." to unstage) @@ -881,6 +951,10 @@ head=$(cd sm && git rev-parse --short=7 --verify HEAD) test_expect_success 'status submodule summary' ' cat >expect <<EOF && On branch master +Your branch and '\''upstream'\'' have diverged, +and have 1 and 2 different commits each, respectively. + (use "git pull" to merge the remote branch into yours) + Changes to be committed: (use "git reset HEAD <file>..." to unstage) @@ -939,6 +1013,10 @@ test_expect_success 'status -s submodule summary' ' test_expect_success 'status submodule summary (clean submodule): commit' ' cat >expect <<EOF && On branch master +Your branch and '\''upstream'\'' have diverged, +and have 2 and 2 different commits each, respectively. + (use "git pull" to merge the remote branch into yours) + Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) @@ -985,6 +1063,10 @@ test_expect_success 'status -z implies porcelain' ' test_expect_success 'commit --dry-run submodule summary (--amend)' ' cat >expect <<EOF && On branch master +Your branch and '\''upstream'\'' have diverged, +and have 2 and 2 different commits each, respectively. + (use "git pull" to merge the remote branch into yours) + Changes to be committed: (use "git reset HEAD^1 <file>..." to unstage) @@ -1038,6 +1120,10 @@ touch .gitmodules test_expect_success '--ignore-submodules=untracked suppresses submodules with untracked content' ' cat > expect << EOF && On branch master +Your branch and '\''upstream'\'' have diverged, +and have 2 and 2 different commits each, respectively. + (use "git pull" to merge the remote branch into yours) + Changes to be committed: (use "git reset HEAD <file>..." to unstage) @@ -1146,6 +1232,10 @@ test_expect_success '.git/config ignore=dirty suppresses submodules with modifie test_expect_success "--ignore-submodules=untracked doesn't suppress submodules with modified content" ' cat > expect << EOF && On branch master +Your branch and '\''upstream'\'' have diverged, +and have 2 and 2 different commits each, respectively. + (use "git pull" to merge the remote branch into yours) + Changes to be committed: (use "git reset HEAD <file>..." to unstage) @@ -1202,6 +1292,10 @@ head2=$(cd sm && git commit -q -m "2nd commit" foo && git rev-parse --short=7 -- test_expect_success "--ignore-submodules=untracked doesn't suppress submodule summary" ' cat > expect << EOF && On branch master +Your branch and '\''upstream'\'' have diverged, +and have 2 and 2 different commits each, respectively. + (use "git pull" to merge the remote branch into yours) + Changes to be committed: (use "git reset HEAD <file>..." to unstage) @@ -1282,6 +1376,10 @@ test_expect_success ".git/config ignore=dirty doesn't suppress submodule summary cat > expect << EOF ; On branch master +; Your branch and 'upstream' have diverged, +; and have 2 and 2 different commits each, respectively. +; (use "git pull" to merge the remote branch into yours) +; ; Changes to be committed: ; (use "git reset HEAD <file>..." to unstage) ; @@ -1329,6 +1427,10 @@ test_expect_success "status (core.commentchar with two chars with submodule summ test_expect_success "--ignore-submodules=all suppresses submodule summary" ' cat > expect << EOF && On branch master +Your branch and '\''upstream'\'' have diverged, +and have 2 and 2 different commits each, respectively. + (use "git pull" to merge the remote branch into yours) + Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) @@ -1353,6 +1455,10 @@ EOF test_expect_success '.gitmodules ignore=all suppresses unstaged submodule summary' ' cat > expect << EOF && On branch master +Your branch and '\''upstream'\'' have diverged, +and have 2 and 2 different commits each, respectively. + (use "git pull" to merge the remote branch into yours) + Changes to be committed: (use "git reset HEAD <file>..." to unstage) @@ -1377,7 +1483,7 @@ EOF git config --add -f .gitmodules submodule.subname.ignore all && git config --add -f .gitmodules submodule.subname.path sm && git status > output && - test_cmp expect output && + test_i18ncmp expect output && git config -f .gitmodules --remove-section submodule.subname ' @@ -1387,7 +1493,7 @@ test_expect_success '.git/config ignore=all suppresses unstaged submodule summar git config --add submodule.subname.ignore all && git config --add submodule.subname.path sm && git status > output && - test_cmp expect output && + test_i18ncmp expect output && git config --remove-section submodule.subname && git config -f .gitmodules --remove-section submodule.subname ' @@ -1472,6 +1578,10 @@ test_expect_success 'git commit --dry-run will show a staged but ignored submodu git add sm && cat >expect << EOF && On branch master +Your branch and '\''upstream'\'' have diverged, +and have 2 and 2 different commits each, respectively. + (use "git pull" to merge the remote branch into yours) + Changes to be committed: (use "git reset HEAD <file>..." to unstage) @@ -1494,9 +1604,81 @@ EOF test_expect_success 'git commit -m will commit a staged but ignored submodule' ' git commit -uno -m message && git status -s --ignore-submodules=dirty >output && - test_i18ngrep ! "^M. sm" output && + test_i18ngrep ! "^M. sm" output && git config --remove-section submodule.subname && git config -f .gitmodules --remove-section submodule.subname ' +test_expect_success 'show stash info with "--show-stash"' ' + git reset --hard && + git stash clear && + echo 1 >file && + git add file && + git stash && + git status >expected_default && + git status --show-stash >expected_with_stash && + test_i18ngrep "^Your stash currently has 1 entry$" expected_with_stash +' + +test_expect_success 'no stash info with "--show-stash --no-show-stash"' ' + git status --show-stash --no-show-stash >expected_without_stash && + test_cmp expected_default expected_without_stash +' + +test_expect_success '"status.showStash=false" weaker than "--show-stash"' ' + git -c status.showStash=false status --show-stash >actual && + test_cmp expected_with_stash actual +' + +test_expect_success '"status.showStash=true" weaker than "--no-show-stash"' ' + git -c status.showStash=true status --no-show-stash >actual && + test_cmp expected_without_stash actual +' + +test_expect_success 'no additionnal info if no stash entries' ' + git stash clear && + git -c status.showStash=true status >actual && + test_cmp expected_without_stash actual +' + +test_expect_success '"No commits yet" should be noted in status output' ' + git checkout --orphan empty-branch-1 && + git status >output && + test_i18ngrep "No commits yet" output +' + +test_expect_success '"No commits yet" should not be noted in status output' ' + git checkout --orphan empty-branch-2 && + test_commit test-commit-1 && + git status >output && + test_i18ngrep ! "No commits yet" output +' + +test_expect_success '"Initial commit" should be noted in commit template' ' + git checkout --orphan empty-branch-3 && + touch to_be_committed_1 && + git add to_be_committed_1 && + git commit --dry-run >output && + test_i18ngrep "Initial commit" output +' + +test_expect_success '"Initial commit" should not be noted in commit template' ' + git checkout --orphan empty-branch-4 && + test_commit test-commit-2 && + touch to_be_committed_2 && + git add to_be_committed_2 && + git commit --dry-run >output && + test_i18ngrep ! "Initial commit" output +' + +test_expect_success '--no-optional-locks prevents index update' ' + test-tool chmtime =1234567890 .git/index && + git --no-optional-locks status && + test-tool chmtime -v +0 .git/index >out && + grep ^1234567890 out && + git status && + test-tool chmtime -v +0 .git/index >out && + ! grep ^1234567890 out +' + test_done diff --git a/t/t7509-commit.sh b/t/t7509-commit.sh index 9ac794052d..ddef7ea6b0 100755 --- a/t/t7509-commit.sh +++ b/t/t7509-commit.sh @@ -90,22 +90,10 @@ sha1_file() { remove_object() { rm -f $(sha1_file "$*") } -no_reflog() { - cp .git/config .git/config.saved && - echo "[core] logallrefupdates = false" >>.git/config && - test_when_finished "mv -f .git/config.saved .git/config" && - - if test -e .git/logs - then - mv .git/logs . && - test_when_finished "mv logs .git/" - fi -} test_expect_success '--amend option with empty author' ' git cat-file commit Initial >tmp && sed "s/author [^<]* </author </" tmp >empty-author && - no_reflog && sha=$(git hash-object -t commit -w empty-author) && test_when_finished "remove_object $sha" && git checkout $sha && @@ -113,13 +101,12 @@ test_expect_success '--amend option with empty author' ' echo "Empty author test" >>foo && test_tick && test_must_fail git commit -a -m "empty author" --amend 2>err && - grep "empty ident" err + test_i18ngrep "empty ident" err ' test_expect_success '--amend option with missing author' ' git cat-file commit Initial >tmp && sed "s/author [^<]* </author </" tmp >malformed && - no_reflog && sha=$(git hash-object -t commit -w malformed) && test_when_finished "remove_object $sha" && git checkout $sha && @@ -127,7 +114,7 @@ test_expect_success '--amend option with missing author' ' echo "Missing author test" >>foo && test_tick && test_must_fail git commit -a -m "malformed author" --amend 2>err && - grep "empty ident" err + test_i18ngrep "empty ident" err ' test_expect_success '--reset-author makes the commit ours even with --amend option' ' diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh index 13331e533b..762135adea 100755 --- a/t/t7510-signed-commit.sh +++ b/t/t7510-signed-commit.sh @@ -2,6 +2,7 @@ test_description='signed commit tests' . ./test-lib.sh +GNUPGHOME_NOT_USED=$GNUPGHOME . "$TEST_DIRECTORY/lib-gpg.sh" test_expect_success GPG 'create signed commits' ' @@ -45,12 +46,18 @@ test_expect_success GPG 'create signed commits' ' git tag seventh-signed && echo 8 >file && test_tick && git commit -a -m eighth -SB7227189 && - git tag eighth-signed-alt + git tag eighth-signed-alt && + + # commit.gpgsign is still on but this must not be signed + git tag ninth-unsigned $(echo 9 | git commit-tree HEAD^{tree}) && + # explicit -S of course must sign. + git tag tenth-signed $(echo 9 | git commit-tree -S HEAD^{tree}) ' test_expect_success GPG 'verify and show signatures' ' ( - for commit in initial second merge fourth-signed fifth-signed sixth-signed seventh-signed + for commit in initial second merge fourth-signed \ + fifth-signed sixth-signed seventh-signed tenth-signed do git verify-commit $commit && git show --pretty=short --show-signature $commit >actual && @@ -60,7 +67,8 @@ test_expect_success GPG 'verify and show signatures' ' done ) && ( - for commit in merge^2 fourth-unsigned sixth-unsigned seventh-unsigned + for commit in merge^2 fourth-unsigned sixth-unsigned \ + seventh-unsigned ninth-unsigned do test_must_fail git verify-commit $commit && git show --pretty=short --show-signature $commit >actual && @@ -81,6 +89,44 @@ test_expect_success GPG 'verify and show signatures' ' ) ' +test_expect_success GPG 'verify-commit exits success on untrusted signature' ' + git verify-commit eighth-signed-alt 2>actual && + grep "Good signature from" actual && + ! grep "BAD signature from" actual && + grep "not certified" actual +' + +test_expect_success GPG 'verify signatures with --raw' ' + ( + for commit in initial second merge fourth-signed fifth-signed sixth-signed seventh-signed + do + git verify-commit --raw $commit 2>actual && + grep "GOODSIG" actual && + ! grep "BADSIG" actual && + echo $commit OK || exit 1 + done + ) && + ( + for commit in merge^2 fourth-unsigned sixth-unsigned seventh-unsigned + do + test_must_fail git verify-commit --raw $commit 2>actual && + ! grep "GOODSIG" actual && + ! grep "BADSIG" actual && + echo $commit OK || exit 1 + done + ) && + ( + for commit in eighth-signed-alt + do + git verify-commit --raw $commit 2>actual && + grep "GOODSIG" actual && + ! grep "BADSIG" actual && + grep "TRUST_UNDEFINED" actual && + echo $commit OK || exit 1 + done + ) +' + test_expect_success GPG 'show signed commit with signature' ' git show -s initial >commit && git show -s --show-signature initial >show && @@ -145,7 +191,7 @@ test_expect_success GPG 'show bad signature with custom format' ' test_cmp expect actual ' -test_expect_success GPG 'show unknown signature with custom format' ' +test_expect_success GPG 'show untrusted signature with custom format' ' cat >expect <<-\EOF && U 61092E85B7227189 @@ -155,6 +201,16 @@ test_expect_success GPG 'show unknown signature with custom format' ' test_cmp expect actual ' +test_expect_success GPG 'show unknown signature with custom format' ' + cat >expect <<-\EOF && + E + 61092E85B7227189 + + EOF + GNUPGHOME="$GNUPGHOME_NOT_USED" git log -1 --format="%G?%n%GK%n%GS" eighth-signed-alt >actual && + test_cmp expect actual +' + test_expect_success GPG 'show lack of signature with custom format' ' cat >expect <<-\EOF && N @@ -165,4 +221,11 @@ test_expect_success GPG 'show lack of signature with custom format' ' test_cmp expect actual ' +test_expect_success GPG 'log.showsignature behaves like --show-signature' ' + test_config log.showsignature true && + git show initial >actual && + grep "gpg: Signature made" actual && + grep "gpg: Good signature" actual +' + test_done diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh index 68ad2d7454..458608cc1e 100755 --- a/t/t7512-status-help.sh +++ b/t/t7512-status-help.sh @@ -29,6 +29,7 @@ test_expect_success 'status when conflicts unresolved' ' On branch conflicts You have unmerged paths. (fix conflicts and run "git commit") + (use "git merge --abort" to abort the merge) Unmerged paths: (use "git add <file>..." to mark resolution) @@ -134,9 +135,13 @@ test_expect_success 'prepare for rebase_i_conflicts' ' test_expect_success 'status during rebase -i when conflicts unresolved' ' test_when_finished "git rebase --abort" && ONTO=$(git rev-parse --short rebase_i_conflicts) && + LAST_COMMIT=$(git rev-parse --short rebase_i_conflicts_second) && test_must_fail git rebase -i rebase_i_conflicts && cat >expected <<EOF && -rebase in progress; onto $ONTO +interactive rebase in progress; onto $ONTO +Last command done (1 command done): + pick $LAST_COMMIT one_second +No commands remaining. You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''. (fix conflicts and then run "git rebase --continue") (use "git rebase --skip" to skip this patch) @@ -159,10 +164,14 @@ test_expect_success 'status during rebase -i after resolving conflicts' ' git reset --hard rebase_i_conflicts_second && test_when_finished "git rebase --abort" && ONTO=$(git rev-parse --short rebase_i_conflicts) && + LAST_COMMIT=$(git rev-parse --short rebase_i_conflicts_second) && test_must_fail git rebase -i rebase_i_conflicts && git add main.txt && cat >expected <<EOF && -rebase in progress; onto $ONTO +interactive rebase in progress; onto $ONTO +Last command done (1 command done): + pick $LAST_COMMIT one_second +No commands remaining. You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''. (all conflicts fixed: run "git rebase --continue") @@ -183,14 +192,20 @@ test_expect_success 'status when rebasing -i in edit mode' ' git checkout -b rebase_i_edit && test_commit one_rebase_i main.txt one && test_commit two_rebase_i main.txt two && + COMMIT2=$(git rev-parse --short rebase_i_edit) && test_commit three_rebase_i main.txt three && + COMMIT3=$(git rev-parse --short rebase_i_edit) && FAKE_LINES="1 edit 2" && export FAKE_LINES && test_when_finished "git rebase --abort" && ONTO=$(git rev-parse --short HEAD~2) && git rebase -i HEAD~2 && cat >expected <<EOF && -rebase in progress; onto $ONTO +interactive rebase in progress; onto $ONTO +Last commands done (2 commands done): + pick $COMMIT2 two_rebase_i + edit $COMMIT3 three_rebase_i +No commands remaining. You are currently editing a commit while rebasing branch '\''rebase_i_edit'\'' on '\''$ONTO'\''. (use "git commit --amend" to amend the current commit) (use "git rebase --continue" once you are satisfied with your changes) @@ -207,8 +222,11 @@ test_expect_success 'status when splitting a commit' ' git checkout -b split_commit && test_commit one_split main.txt one && test_commit two_split main.txt two && + COMMIT2=$(git rev-parse --short split_commit) && test_commit three_split main.txt three && + COMMIT3=$(git rev-parse --short split_commit) && test_commit four_split main.txt four && + COMMIT4=$(git rev-parse --short split_commit) && FAKE_LINES="1 edit 2 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && @@ -216,7 +234,13 @@ test_expect_success 'status when splitting a commit' ' git rebase -i HEAD~3 && git reset HEAD^ && cat >expected <<EOF && -rebase in progress; onto $ONTO +interactive rebase in progress; onto $ONTO +Last commands done (2 commands done): + pick $COMMIT2 two_split + edit $COMMIT3 three_split +Next command to do (1 remaining command): + pick $COMMIT4 four_split + (use "git rebase --edit-todo" to view and edit) You are currently splitting a commit while rebasing branch '\''split_commit'\'' on '\''$ONTO'\''. (Once your working directory is clean, run "git rebase --continue") @@ -239,7 +263,9 @@ test_expect_success 'status after editing the last commit with --amend during a test_commit one_amend main.txt one && test_commit two_amend main.txt two && test_commit three_amend main.txt three && + COMMIT3=$(git rev-parse --short amend_last) && test_commit four_amend main.txt four && + COMMIT4=$(git rev-parse --short amend_last) && FAKE_LINES="1 2 edit 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && @@ -247,7 +273,12 @@ test_expect_success 'status after editing the last commit with --amend during a git rebase -i HEAD~3 && git commit --amend -m "foo" && cat >expected <<EOF && -rebase in progress; onto $ONTO +interactive rebase in progress; onto $ONTO +Last commands done (3 commands done): + pick $COMMIT3 three_amend + edit $COMMIT4 four_amend + (see more in file .git/rebase-merge/done) +No commands remaining. You are currently editing a commit while rebasing branch '\''amend_last'\'' on '\''$ONTO'\''. (use "git commit --amend" to amend the current commit) (use "git rebase --continue" once you are satisfied with your changes) @@ -273,11 +304,20 @@ test_expect_success 'status: (continue first edit) second edit' ' FAKE_LINES="edit 1 edit 2 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && + COMMIT2=$(git rev-parse --short several_edits^^) && + COMMIT3=$(git rev-parse --short several_edits^) && + COMMIT4=$(git rev-parse --short several_edits) && ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git rebase --continue && cat >expected <<EOF && -rebase in progress; onto $ONTO +interactive rebase in progress; onto $ONTO +Last commands done (2 commands done): + edit $COMMIT2 two_edits + edit $COMMIT3 three_edits +Next command to do (1 remaining command): + pick $COMMIT4 four_edits + (use "git rebase --edit-todo" to view and edit) You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. (use "git commit --amend" to amend the current commit) (use "git rebase --continue" once you are satisfied with your changes) @@ -294,12 +334,21 @@ test_expect_success 'status: (continue first edit) second edit and split' ' FAKE_LINES="edit 1 edit 2 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && + COMMIT2=$(git rev-parse --short several_edits^^) && + COMMIT3=$(git rev-parse --short several_edits^) && + COMMIT4=$(git rev-parse --short several_edits) && ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git rebase --continue && git reset HEAD^ && cat >expected <<EOF && -rebase in progress; onto $ONTO +interactive rebase in progress; onto $ONTO +Last commands done (2 commands done): + edit $COMMIT2 two_edits + edit $COMMIT3 three_edits +Next command to do (1 remaining command): + pick $COMMIT4 four_edits + (use "git rebase --edit-todo" to view and edit) You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. (Once your working directory is clean, run "git rebase --continue") @@ -321,12 +370,21 @@ test_expect_success 'status: (continue first edit) second edit and amend' ' FAKE_LINES="edit 1 edit 2 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && + COMMIT2=$(git rev-parse --short several_edits^^) && + COMMIT3=$(git rev-parse --short several_edits^) && + COMMIT4=$(git rev-parse --short several_edits) && ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git rebase --continue && git commit --amend -m "foo" && cat >expected <<EOF && -rebase in progress; onto $ONTO +interactive rebase in progress; onto $ONTO +Last commands done (2 commands done): + edit $COMMIT2 two_edits + edit $COMMIT3 three_edits +Next command to do (1 remaining command): + pick $COMMIT4 four_edits + (use "git rebase --edit-todo" to view and edit) You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. (use "git commit --amend" to amend the current commit) (use "git rebase --continue" once you are satisfied with your changes) @@ -343,12 +401,21 @@ test_expect_success 'status: (amend first edit) second edit' ' FAKE_LINES="edit 1 edit 2 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && + COMMIT2=$(git rev-parse --short several_edits^^) && + COMMIT3=$(git rev-parse --short several_edits^) && + COMMIT4=$(git rev-parse --short several_edits) && ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git commit --amend -m "a" && git rebase --continue && cat >expected <<EOF && -rebase in progress; onto $ONTO +interactive rebase in progress; onto $ONTO +Last commands done (2 commands done): + edit $COMMIT2 two_edits + edit $COMMIT3 three_edits +Next command to do (1 remaining command): + pick $COMMIT4 four_edits + (use "git rebase --edit-todo" to view and edit) You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. (use "git commit --amend" to amend the current commit) (use "git rebase --continue" once you are satisfied with your changes) @@ -366,12 +433,21 @@ test_expect_success 'status: (amend first edit) second edit and split' ' export FAKE_LINES && test_when_finished "git rebase --abort" && ONTO=$(git rev-parse --short HEAD~3) && + COMMIT2=$(git rev-parse --short several_edits^^) && + COMMIT3=$(git rev-parse --short several_edits^) && + COMMIT4=$(git rev-parse --short several_edits) && git rebase -i HEAD~3 && git commit --amend -m "b" && git rebase --continue && git reset HEAD^ && cat >expected <<EOF && -rebase in progress; onto $ONTO +interactive rebase in progress; onto $ONTO +Last commands done (2 commands done): + edit $COMMIT2 two_edits + edit $COMMIT3 three_edits +Next command to do (1 remaining command): + pick $COMMIT4 four_edits + (use "git rebase --edit-todo" to view and edit) You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. (Once your working directory is clean, run "git rebase --continue") @@ -393,13 +469,22 @@ test_expect_success 'status: (amend first edit) second edit and amend' ' FAKE_LINES="edit 1 edit 2 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && + COMMIT2=$(git rev-parse --short several_edits^^) && + COMMIT3=$(git rev-parse --short several_edits^) && + COMMIT4=$(git rev-parse --short several_edits) && ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git commit --amend -m "c" && git rebase --continue && git commit --amend -m "d" && cat >expected <<EOF && -rebase in progress; onto $ONTO +interactive rebase in progress; onto $ONTO +Last commands done (2 commands done): + edit $COMMIT2 two_edits + edit $COMMIT3 three_edits +Next command to do (1 remaining command): + pick $COMMIT4 four_edits + (use "git rebase --edit-todo" to view and edit) You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. (use "git commit --amend" to amend the current commit) (use "git rebase --continue" once you are satisfied with your changes) @@ -416,6 +501,9 @@ test_expect_success 'status: (split first edit) second edit' ' FAKE_LINES="edit 1 edit 2 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && + COMMIT2=$(git rev-parse --short several_edits^^) && + COMMIT3=$(git rev-parse --short several_edits^) && + COMMIT4=$(git rev-parse --short several_edits) && ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git reset HEAD^ && @@ -423,7 +511,13 @@ test_expect_success 'status: (split first edit) second edit' ' git commit -m "e" && git rebase --continue && cat >expected <<EOF && -rebase in progress; onto $ONTO +interactive rebase in progress; onto $ONTO +Last commands done (2 commands done): + edit $COMMIT2 two_edits + edit $COMMIT3 three_edits +Next command to do (1 remaining command): + pick $COMMIT4 four_edits + (use "git rebase --edit-todo" to view and edit) You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. (use "git commit --amend" to amend the current commit) (use "git rebase --continue" once you are satisfied with your changes) @@ -440,6 +534,9 @@ test_expect_success 'status: (split first edit) second edit and split' ' FAKE_LINES="edit 1 edit 2 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && + COMMIT2=$(git rev-parse --short several_edits^^) && + COMMIT3=$(git rev-parse --short several_edits^) && + COMMIT4=$(git rev-parse --short several_edits) && ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git reset HEAD^ && @@ -448,7 +545,13 @@ test_expect_success 'status: (split first edit) second edit and split' ' git rebase --continue && git reset HEAD^ && cat >expected <<EOF && -rebase in progress; onto $ONTO +interactive rebase in progress; onto $ONTO +Last commands done (2 commands done): + edit $COMMIT2 two_edits + edit $COMMIT3 three_edits +Next command to do (1 remaining command): + pick $COMMIT4 four_edits + (use "git rebase --edit-todo" to view and edit) You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. (Once your working directory is clean, run "git rebase --continue") @@ -470,6 +573,9 @@ test_expect_success 'status: (split first edit) second edit and amend' ' FAKE_LINES="edit 1 edit 2 3" && export FAKE_LINES && test_when_finished "git rebase --abort" && + COMMIT2=$(git rev-parse --short several_edits^^) && + COMMIT3=$(git rev-parse --short several_edits^) && + COMMIT4=$(git rev-parse --short several_edits) && ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git reset HEAD^ && @@ -478,7 +584,13 @@ test_expect_success 'status: (split first edit) second edit and amend' ' git rebase --continue && git commit --amend -m "h" && cat >expected <<EOF && -rebase in progress; onto $ONTO +interactive rebase in progress; onto $ONTO +Last commands done (2 commands done): + edit $COMMIT2 two_edits + edit $COMMIT3 three_edits +Next command to do (1 remaining command): + pick $COMMIT4 four_edits + (use "git rebase --edit-todo" to view and edit) You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. (use "git commit --amend" to amend the current commit) (use "git rebase --continue" once you are satisfied with your changes) @@ -745,4 +857,110 @@ EOF test_i18ncmp expected actual ' +test_expect_success 'prepare for different number of commits rebased' ' + git reset --hard master && + git checkout -b several_commits && + test_commit one_commit main.txt one && + test_commit two_commit main.txt two && + test_commit three_commit main.txt three && + test_commit four_commit main.txt four +' + +test_expect_success 'status: one command done nothing remaining' ' + FAKE_LINES="exec_exit_15" && + export FAKE_LINES && + test_when_finished "git rebase --abort" && + ONTO=$(git rev-parse --short HEAD~3) && + test_must_fail git rebase -i HEAD~3 && + cat >expected <<EOF && +interactive rebase in progress; onto $ONTO +Last command done (1 command done): + exec exit 15 +No commands remaining. +You are currently editing a commit while rebasing branch '\''several_commits'\'' on '\''$ONTO'\''. + (use "git commit --amend" to amend the current commit) + (use "git rebase --continue" once you are satisfied with your changes) + +nothing to commit (use -u to show untracked files) +EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + +test_expect_success 'status: two commands done with some white lines in done file' ' + FAKE_LINES="1 > exec_exit_15 2 3" && + export FAKE_LINES && + test_when_finished "git rebase --abort" && + ONTO=$(git rev-parse --short HEAD~3) && + COMMIT4=$(git rev-parse --short HEAD) && + COMMIT3=$(git rev-parse --short HEAD^) && + COMMIT2=$(git rev-parse --short HEAD^^) && + test_must_fail git rebase -i HEAD~3 && + cat >expected <<EOF && +interactive rebase in progress; onto $ONTO +Last commands done (2 commands done): + pick $COMMIT2 two_commit + exec exit 15 +Next commands to do (2 remaining commands): + pick $COMMIT3 three_commit + pick $COMMIT4 four_commit + (use "git rebase --edit-todo" to view and edit) +You are currently editing a commit while rebasing branch '\''several_commits'\'' on '\''$ONTO'\''. + (use "git commit --amend" to amend the current commit) + (use "git rebase --continue" once you are satisfied with your changes) + +nothing to commit (use -u to show untracked files) +EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + +test_expect_success 'status: two remaining commands with some white lines in todo file' ' + FAKE_LINES="1 2 exec_exit_15 3 > 4" && + export FAKE_LINES && + test_when_finished "git rebase --abort" && + ONTO=$(git rev-parse --short HEAD~4) && + COMMIT4=$(git rev-parse --short HEAD) && + COMMIT3=$(git rev-parse --short HEAD^) && + COMMIT2=$(git rev-parse --short HEAD^^) && + test_must_fail git rebase -i HEAD~4 && + cat >expected <<EOF && +interactive rebase in progress; onto $ONTO +Last commands done (3 commands done): + pick $COMMIT2 two_commit + exec exit 15 + (see more in file .git/rebase-merge/done) +Next commands to do (2 remaining commands): + pick $COMMIT3 three_commit + pick $COMMIT4 four_commit + (use "git rebase --edit-todo" to view and edit) +You are currently editing a commit while rebasing branch '\''several_commits'\'' on '\''$ONTO'\''. + (use "git commit --amend" to amend the current commit) + (use "git rebase --continue" once you are satisfied with your changes) + +nothing to commit (use -u to show untracked files) +EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + +test_expect_success 'status: handle not-yet-started rebase -i gracefully' ' + ONTO=$(git rev-parse --short HEAD^) && + COMMIT=$(git rev-parse --short HEAD) && + EDITOR="git status --untracked-files=no >actual" git rebase -i HEAD^ && + cat >expected <<EOF && +On branch several_commits +No commands done. +Next command to do (1 remaining command): + pick $COMMIT four_commit + (use "git rebase --edit-todo" to view and edit) +You are currently editing a commit while rebasing branch '\''several_commits'\'' on '\''$ONTO'\''. + (use "git commit --amend" to amend the current commit) + (use "git rebase --continue" once you are satisfied with your changes) + +nothing to commit (use -u to show untracked files) +EOF + test_i18ncmp expected actual +' + test_done diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh index bd0ab46750..164719d1c9 100755 --- a/t/t7513-interpret-trailers.sh +++ b/t/t7513-interpret-trailers.sh @@ -93,12 +93,338 @@ test_expect_success 'with config option on the command line' ' Acked-by: Johan Reviewed-by: Peff EOF - echo "Acked-by: Johan" | + { echo; echo "Acked-by: Johan"; } | git -c "trailer.Acked-by.ifexists=addifdifferent" interpret-trailers \ --trailer "Reviewed-by: Peff" --trailer "Acked-by: Johan" >actual && test_cmp expected actual ' +test_expect_success 'with only a title in the message' ' + cat >expected <<-\EOF && + area: change + + Reviewed-by: Peff + Acked-by: Johan + EOF + echo "area: change" | + git interpret-trailers --trailer "Reviewed-by: Peff" \ + --trailer "Acked-by: Johan" >actual && + test_cmp expected actual +' + +test_expect_success 'with multiline title in the message' ' + cat >expected <<-\EOF && + place of + code: change + + Reviewed-by: Peff + Acked-by: Johan + EOF + printf "%s\n" "place of" "code: change" | + git interpret-trailers --trailer "Reviewed-by: Peff" \ + --trailer "Acked-by: Johan" >actual && + test_cmp expected actual +' + +test_expect_success 'with non-trailer lines mixed with Signed-off-by' ' + cat >patch <<-\EOF && + + this is not a trailer + this is not a trailer + Signed-off-by: a <a@example.com> + this is not a trailer + EOF + cat >expected <<-\EOF && + + this is not a trailer + this is not a trailer + Signed-off-by: a <a@example.com> + this is not a trailer + token: value + EOF + git interpret-trailers --trailer "token: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'with non-trailer lines mixed with cherry picked from' ' + cat >patch <<-\EOF && + + this is not a trailer + this is not a trailer + (cherry picked from commit x) + this is not a trailer + EOF + cat >expected <<-\EOF && + + this is not a trailer + this is not a trailer + (cherry picked from commit x) + this is not a trailer + token: value + EOF + git interpret-trailers --trailer "token: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'with non-trailer lines mixed with a configured trailer' ' + cat >patch <<-\EOF && + + this is not a trailer + this is not a trailer + My-trailer: x + this is not a trailer + EOF + cat >expected <<-\EOF && + + this is not a trailer + this is not a trailer + My-trailer: x + this is not a trailer + token: value + EOF + test_config trailer.my.key "My-trailer: " && + git interpret-trailers --trailer "token: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'with non-trailer lines mixed with a non-configured trailer' ' + cat >patch <<-\EOF && + + this is not a trailer + this is not a trailer + I-am-not-configured: x + this is not a trailer + EOF + cat >expected <<-\EOF && + + this is not a trailer + this is not a trailer + I-am-not-configured: x + this is not a trailer + + token: value + EOF + test_config trailer.my.key "My-trailer: " && + git interpret-trailers --trailer "token: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'with all non-configured trailers' ' + cat >patch <<-\EOF && + + I-am-not-configured: x + I-am-also-not-configured: x + EOF + cat >expected <<-\EOF && + + I-am-not-configured: x + I-am-also-not-configured: x + token: value + EOF + test_config trailer.my.key "My-trailer: " && + git interpret-trailers --trailer "token: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'with non-trailer lines only' ' + cat >patch <<-\EOF && + + this is not a trailer + EOF + cat >expected <<-\EOF && + + this is not a trailer + + token: value + EOF + git interpret-trailers --trailer "token: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'line with leading whitespace is not trailer' ' + q_to_tab >patch <<-\EOF && + + Qtoken: value + EOF + q_to_tab >expected <<-\EOF && + + Qtoken: value + + token: value + EOF + git interpret-trailers --trailer "token: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'multiline field treated as one trailer for 25% check' ' + q_to_tab >patch <<-\EOF && + + Signed-off-by: a <a@example.com> + name: value on + Qmultiple lines + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + EOF + q_to_tab >expected <<-\EOF && + + Signed-off-by: a <a@example.com> + name: value on + Qmultiple lines + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + this is not a trailer + name: value + EOF + git interpret-trailers --trailer "name: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'multiline field treated as atomic for placement' ' + q_to_tab >patch <<-\EOF && + + another: trailer + name: value on + Qmultiple lines + another: trailer + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: value on + Qmultiple lines + name: value + another: trailer + EOF + test_config trailer.name.where after && + git interpret-trailers --trailer "name: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'multiline field treated as atomic for replacement' ' + q_to_tab >patch <<-\EOF && + + another: trailer + name: value on + Qmultiple lines + another: trailer + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + another: trailer + name: value + EOF + test_config trailer.name.ifexists replace && + git interpret-trailers --trailer "name: value" patch >actual && + test_cmp expected actual +' + +test_expect_success 'multiline field treated as atomic for difference check' ' + q_to_tab >patch <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + EOF + test_config trailer.name.ifexists addIfDifferent && + + q_to_tab >trailer <<-\EOF && + name: first line + Qsecond line + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + EOF + git interpret-trailers --trailer "$(cat trailer)" patch >actual && + test_cmp expected actual && + + q_to_tab >trailer <<-\EOF && + name: first line + QQQQQsecond line + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + name: first line + QQQQQsecond line + EOF + git interpret-trailers --trailer "$(cat trailer)" patch >actual && + test_cmp expected actual && + + q_to_tab >trailer <<-\EOF && + name: first line *DIFFERENT* + Qsecond line + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + name: first line *DIFFERENT* + Qsecond line + EOF + git interpret-trailers --trailer "$(cat trailer)" patch >actual && + test_cmp expected actual +' + +test_expect_success 'multiline field treated as atomic for neighbor check' ' + q_to_tab >patch <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + EOF + test_config trailer.name.where after && + test_config trailer.name.ifexists addIfDifferentNeighbor && + + q_to_tab >trailer <<-\EOF && + name: first line + Qsecond line + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: first line + Qsecond line + another: trailer + EOF + git interpret-trailers --trailer "$(cat trailer)" patch >actual && + test_cmp expected actual && + + q_to_tab >trailer <<-\EOF && + name: first line + QQQQQsecond line + EOF + q_to_tab >expected <<-\EOF && + + another: trailer + name: first line + Qsecond line + name: first line + QQQQQsecond line + another: trailer + EOF + git interpret-trailers --trailer "$(cat trailer)" patch >actual && + test_cmp expected actual +' + test_expect_success 'with config setup' ' git config trailer.ack.key "Acked-by: " && cat >expected <<-\EOF && @@ -299,6 +625,46 @@ test_expect_success 'with complex patch, args and --trim-empty' ' test_cmp expected actual ' +test_expect_success 'in-place editing with basic patch' ' + cat basic_message >message && + cat basic_patch >>message && + cat basic_message >expected && + echo >>expected && + cat basic_patch >>expected && + git interpret-trailers --in-place message && + test_cmp expected message +' + +test_expect_success 'in-place editing with additional trailer' ' + cat basic_message >message && + cat basic_patch >>message && + cat basic_message >expected && + echo >>expected && + cat >>expected <<-\EOF && + Reviewed-by: Alice + EOF + cat basic_patch >>expected && + git interpret-trailers --trailer "Reviewed-by: Alice" --in-place message && + test_cmp expected message +' + +test_expect_success 'in-place editing on stdin disallowed' ' + test_must_fail git interpret-trailers --trailer "Reviewed-by: Alice" --in-place < basic_message +' + +test_expect_success 'in-place editing on non-existing file' ' + test_must_fail git interpret-trailers --trailer "Reviewed-by: Alice" --in-place nonexisting && + test_path_is_missing nonexisting +' + +test_expect_success POSIXPERM,SANITY "in-place editing doesn't clobber original file on error" ' + cat basic_message >message && + chmod -r message && + test_must_fail git interpret-trailers --trailer "Reviewed-by: Alice" --in-place message && + chmod +r message && + test_cmp message basic_message +' + test_expect_success 'using "where = before"' ' git config trailer.bug.where "before" && cat complex_message_body >expected && @@ -315,6 +681,36 @@ test_expect_success 'using "where = before"' ' test_cmp expected actual ' +test_expect_success 'overriding configuration with "--where after"' ' + git config trailer.ack.where "before" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Fixes: Z + Acked-by= Z + Acked-by= Peff + Reviewed-by: Z + Signed-off-by: Z + EOF + git interpret-trailers --where after --trailer "ack: Peff" \ + complex_message >actual && + test_cmp expected actual +' + +test_expect_success 'using "where = before" with "--no-where"' ' + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Fixes: Z + Acked-by= Peff + Acked-by= Z + Reviewed-by: Z + Signed-off-by: Z + EOF + git interpret-trailers --where after --no-where --trailer "ack: Peff" \ + --trailer "bug: 42" complex_message >actual && + test_cmp expected actual +' + test_expect_success 'using "where = after"' ' git config trailer.ack.where "after" && cat complex_message_body >expected && @@ -581,6 +977,23 @@ test_expect_success 'using "ifExists = add" with "where = after"' ' test_cmp expected actual ' +test_expect_success 'overriding configuration with "--if-exists replace"' ' + git config trailer.fix.key "Fixes: " && + git config trailer.fix.ifExists "add" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Bug #42 + Acked-by= Z + Reviewed-by: + Signed-off-by: Z + Fixes: 22 + EOF + git interpret-trailers --if-exists replace --trailer "review:" \ + --trailer "fix=53" --trailer "fix=22" --trailer "bug: 42" \ + <complex_message >actual && + test_cmp expected actual +' + test_expect_success 'using "ifExists = replace"' ' git config trailer.fix.key "Fixes: " && git config trailer.fix.ifExists "replace" && @@ -660,6 +1073,25 @@ test_expect_success 'the default is "ifMissing = add"' ' test_cmp expected actual ' +test_expect_success 'overriding configuration with "--if-missing doNothing"' ' + git config trailer.ifmissing "add" && + cat complex_message_body >expected && + sed -e "s/ Z\$/ /" >>expected <<-\EOF && + Fixes: Z + Acked-by= Z + Acked-by= Junio + Acked-by= Peff + Reviewed-by: + Signed-off-by: Z + EOF + git interpret-trailers --if-missing doNothing \ + --trailer "review:" --trailer "fix=53" \ + --trailer "cc=Linus" --trailer "ack: Junio" \ + --trailer "fix=22" --trailer "bug: 42" --trailer "ack: Peff" \ + <complex_message >actual && + test_cmp expected actual +' + test_expect_success 'when default "ifMissing" is "doNothing"' ' git config trailer.ifmissing "doNothing" && cat complex_message_body >expected && @@ -892,4 +1324,97 @@ test_expect_success 'with no command and no key' ' test_cmp expected actual ' +test_expect_success 'with cut line' ' + cat >expected <<-\EOF && + my subject + + review: Brian + sign: A U Thor <author@example.com> + # ------------------------ >8 ------------------------ + ignore this + EOF + git interpret-trailers --trailer review:Brian >actual <<-\EOF && + my subject + # ------------------------ >8 ------------------------ + ignore this + EOF + test_cmp expected actual +' + +test_expect_success 'only trailers' ' + git config trailer.sign.command "echo config-value" && + cat >expected <<-\EOF && + existing: existing-value + sign: config-value + added: added-value + EOF + git interpret-trailers \ + --trailer added:added-value \ + --only-trailers >actual <<-\EOF && + my subject + + my body + + existing: existing-value + EOF + test_cmp expected actual +' + +test_expect_success 'only-trailers omits non-trailer in middle of block' ' + git config trailer.sign.command "echo config-value" && + cat >expected <<-\EOF && + Signed-off-by: nobody <nobody@nowhere> + Signed-off-by: somebody <somebody@somewhere> + sign: config-value + EOF + git interpret-trailers --only-trailers >actual <<-\EOF && + subject + + it is important that the trailers below are signed-off-by + so that they meet the "25% trailers Git knows about" heuristic + + Signed-off-by: nobody <nobody@nowhere> + this is not a trailer + Signed-off-by: somebody <somebody@somewhere> + EOF + test_cmp expected actual +' + +test_expect_success 'only input' ' + git config trailer.sign.command "echo config-value" && + cat >expected <<-\EOF && + existing: existing-value + EOF + git interpret-trailers \ + --only-trailers --only-input >actual <<-\EOF && + my subject + + my body + + existing: existing-value + EOF + test_cmp expected actual +' + +test_expect_success 'unfold' ' + cat >expected <<-\EOF && + foo: continued across several lines + EOF + # pass through tr to make leading and trailing whitespace more obvious + tr _ " " <<-\EOF | + my subject + + my body + + foo:_ + __continued + ___across + ____several + _____lines + ___ + EOF + git interpret-trailers --only-trailers --only-input --unfold >actual && + test_cmp expected actual +' + test_done diff --git a/t/t7517-per-repo-email.sh b/t/t7517-per-repo-email.sh new file mode 100755 index 0000000000..2a22fa7588 --- /dev/null +++ b/t/t7517-per-repo-email.sh @@ -0,0 +1,86 @@ +#!/bin/sh +# +# Copyright (c) 2016 Dan Aloni +# Copyright (c) 2016 Jeff King +# + +test_description='per-repo forced setting of email address' + +. ./test-lib.sh + +test_expect_success 'setup a likely user.useConfigOnly use case' ' + # we want to make sure a reflog is written, since that needs + # a non-strict ident. So be sure we have an actual commit. + test_commit foo && + + sane_unset GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL && + sane_unset GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL && + git config user.name "test" && + git config --global user.useConfigOnly true +' + +test_expect_success 'fails committing if clone email is not set' ' + test_must_fail git commit --allow-empty -m msg +' + +test_expect_success 'fails committing if clone email is not set, but EMAIL set' ' + test_must_fail env EMAIL=test@fail.com git commit --allow-empty -m msg +' + +test_expect_success 'succeeds committing if clone email is set' ' + test_config user.email "test@ok.com" && + git commit --allow-empty -m msg +' + +test_expect_success 'succeeds cloning if global email is not set' ' + git clone . clone +' + +test_expect_success 'set up rebase scenarios' ' + # temporarily enable an actual ident for this setup + test_config user.email foo@example.com && + test_commit new && + git branch side-without-commit HEAD^ && + git checkout -b side-with-commit HEAD^ && + test_commit side +' + +test_expect_success 'fast-forward rebase does not care about ident' ' + git checkout -B tmp side-without-commit && + git rebase master +' + +test_expect_success 'non-fast-forward rebase refuses to write commits' ' + test_when_finished "git rebase --abort || true" && + git checkout -B tmp side-with-commit && + test_must_fail git rebase master +' + +test_expect_success 'fast-forward rebase does not care about ident (interactive)' ' + git checkout -B tmp side-without-commit && + git rebase -i master +' + +test_expect_success 'non-fast-forward rebase refuses to write commits (interactive)' ' + test_when_finished "git rebase --abort || true" && + git checkout -B tmp side-with-commit && + test_must_fail git rebase -i master +' + +test_expect_success 'noop interactive rebase does not care about ident' ' + git checkout -B tmp side-with-commit && + git rebase -i HEAD^ +' + +test_expect_success 'fast-forward rebase does not care about ident (preserve)' ' + git checkout -B tmp side-without-commit && + git rebase -p master +' + +test_expect_success 'non-fast-forward rebase refuses to write commits (preserve)' ' + test_when_finished "git rebase --abort || true" && + git checkout -B tmp side-with-commit && + test_must_fail git rebase -p master +' + +test_done diff --git a/t/t7518-ident-corner-cases.sh b/t/t7518-ident-corner-cases.sh new file mode 100755 index 0000000000..b22f631261 --- /dev/null +++ b/t/t7518-ident-corner-cases.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +test_description='corner cases in ident strings' +. ./test-lib.sh + +# confirm that we do not segfault _and_ that we do not say "(null)", as +# glibc systems will quietly handle our NULL pointer +# +# Note also that we can't use "env" here because we need to unset a variable, +# and "-u" is not portable. +test_expect_success 'empty name and missing email' ' + ( + sane_unset GIT_AUTHOR_EMAIL && + GIT_AUTHOR_NAME= && + test_must_fail git commit --allow-empty -m foo 2>err && + test_i18ngrep ! null err + ) +' + +test_expect_success 'commit rejects all-crud name' ' + test_must_fail env GIT_AUTHOR_NAME=" .;<>" \ + git commit --allow-empty -m foo +' + +# We must test the actual error message here, as an unwanted +# auto-detection could fail for other reasons. +test_expect_success 'empty configured name does not auto-detect' ' + ( + sane_unset GIT_AUTHOR_NAME && + test_must_fail \ + git -c user.name= commit --allow-empty -m foo 2>err && + test_i18ngrep "empty ident name" err + ) +' + +test_done diff --git a/t/t7519-status-fsmonitor.sh b/t/t7519-status-fsmonitor.sh new file mode 100755 index 0000000000..756beb0d8e --- /dev/null +++ b/t/t7519-status-fsmonitor.sh @@ -0,0 +1,356 @@ +#!/bin/sh + +test_description='git status with file system watcher' + +. ./test-lib.sh + +# +# To run the entire git test suite using fsmonitor: +# +# copy t/t7519/fsmonitor-all to a location in your path and then set +# GIT_FSMONITOR_TEST=fsmonitor-all and run your tests. +# + +# Note, after "git reset --hard HEAD" no extensions exist other than 'TREE' +# "git update-index --fsmonitor" can be used to get the extension written +# before testing the results. + +clean_repo () { + git reset --hard HEAD && + git clean -fd +} + +dirty_repo () { + : >untracked && + : >dir1/untracked && + : >dir2/untracked && + echo 1 >modified && + echo 2 >dir1/modified && + echo 3 >dir2/modified && + echo 4 >new && + echo 5 >dir1/new && + echo 6 >dir2/new +} + +write_integration_script () { + write_script .git/hooks/fsmonitor-test<<-\EOF + if test "$#" -ne 2 + then + echo "$0: exactly 2 arguments expected" + exit 2 + fi + if test "$1" != 1 + then + echo "Unsupported core.fsmonitor hook version." >&2 + exit 1 + fi + printf "untracked\0" + printf "dir1/untracked\0" + printf "dir2/untracked\0" + printf "modified\0" + printf "dir1/modified\0" + printf "dir2/modified\0" + printf "new\0" + printf "dir1/new\0" + printf "dir2/new\0" + EOF +} + +test_lazy_prereq UNTRACKED_CACHE ' + { git update-index --test-untracked-cache; ret=$?; } && + test $ret -ne 1 +' + +test_expect_success 'setup' ' + mkdir -p .git/hooks && + : >tracked && + : >modified && + mkdir dir1 && + : >dir1/tracked && + : >dir1/modified && + mkdir dir2 && + : >dir2/tracked && + : >dir2/modified && + git -c core.fsmonitor= add . && + git -c core.fsmonitor= commit -m initial && + git config core.fsmonitor .git/hooks/fsmonitor-test && + cat >.gitignore <<-\EOF + .gitignore + expect* + actual* + marker* + EOF +' + +# test that the fsmonitor extension is off by default +test_expect_success 'fsmonitor extension is off by default' ' + test-dump-fsmonitor >actual && + grep "^no fsmonitor" actual +' + +# test that "update-index --fsmonitor" adds the fsmonitor extension +test_expect_success 'update-index --fsmonitor" adds the fsmonitor extension' ' + git update-index --fsmonitor && + test-dump-fsmonitor >actual && + grep "^fsmonitor last update" actual +' + +# test that "update-index --no-fsmonitor" removes the fsmonitor extension +test_expect_success 'update-index --no-fsmonitor" removes the fsmonitor extension' ' + git update-index --no-fsmonitor && + test-dump-fsmonitor >actual && + grep "^no fsmonitor" actual +' + +cat >expect <<EOF && +h dir1/modified +H dir1/tracked +h dir2/modified +H dir2/tracked +h modified +H tracked +EOF + +# test that "update-index --fsmonitor-valid" sets the fsmonitor valid bit +test_expect_success 'update-index --fsmonitor-valid" sets the fsmonitor valid bit' ' + git update-index --fsmonitor && + git update-index --fsmonitor-valid dir1/modified && + git update-index --fsmonitor-valid dir2/modified && + git update-index --fsmonitor-valid modified && + git ls-files -f >actual && + test_cmp expect actual +' + +cat >expect <<EOF && +H dir1/modified +H dir1/tracked +H dir2/modified +H dir2/tracked +H modified +H tracked +EOF + +# test that "update-index --no-fsmonitor-valid" clears the fsmonitor valid bit +test_expect_success 'update-index --no-fsmonitor-valid" clears the fsmonitor valid bit' ' + git update-index --no-fsmonitor-valid dir1/modified && + git update-index --no-fsmonitor-valid dir2/modified && + git update-index --no-fsmonitor-valid modified && + git ls-files -f >actual && + test_cmp expect actual +' + +cat >expect <<EOF && +H dir1/modified +H dir1/tracked +H dir2/modified +H dir2/tracked +H modified +H tracked +EOF + +# test that all files returned by the script get flagged as invalid +test_expect_success 'all files returned by integration script get flagged as invalid' ' + write_integration_script && + dirty_repo && + git update-index --fsmonitor && + git ls-files -f >actual && + test_cmp expect actual +' + +cat >expect <<EOF && +H dir1/modified +h dir1/new +H dir1/tracked +H dir2/modified +h dir2/new +H dir2/tracked +H modified +h new +H tracked +EOF + +# test that newly added files are marked valid +test_expect_success 'newly added files are marked valid' ' + git add new && + git add dir1/new && + git add dir2/new && + git ls-files -f >actual && + test_cmp expect actual +' + +cat >expect <<EOF && +H dir1/modified +h dir1/new +h dir1/tracked +H dir2/modified +h dir2/new +h dir2/tracked +H modified +h new +h tracked +EOF + +# test that all unmodified files get marked valid +test_expect_success 'all unmodified files get marked valid' ' + # modified files result in update-index returning 1 + test_must_fail git update-index --refresh --force-write-index && + git ls-files -f >actual && + test_cmp expect actual +' + +cat >expect <<EOF && +H dir1/modified +h dir1/tracked +h dir2/modified +h dir2/tracked +h modified +h tracked +EOF + +# test that *only* files returned by the integration script get flagged as invalid +test_expect_success '*only* files returned by the integration script get flagged as invalid' ' + write_script .git/hooks/fsmonitor-test<<-\EOF && + printf "dir1/modified\0" + EOF + clean_repo && + git update-index --refresh --force-write-index && + echo 1 >modified && + echo 2 >dir1/modified && + echo 3 >dir2/modified && + test_must_fail git update-index --refresh --force-write-index && + git ls-files -f >actual && + test_cmp expect actual +' + +# Ensure commands that call refresh_index() to move the index back in time +# properly invalidate the fsmonitor cache +test_expect_success 'refresh_index() invalidates fsmonitor cache' ' + write_script .git/hooks/fsmonitor-test<<-\EOF && + EOF + clean_repo && + dirty_repo && + git add . && + git commit -m "to reset" && + git reset HEAD~1 && + git status >actual && + git -c core.fsmonitor= status >expect && + test_i18ncmp expect actual +' + +# test fsmonitor with and without preloadIndex +preload_values="false true" +for preload_val in $preload_values +do + test_expect_success "setup preloadIndex to $preload_val" ' + git config core.preloadIndex $preload_val && + if test $preload_val = true + then + GIT_FORCE_PRELOAD_TEST=$preload_val; export GIT_FORCE_PRELOAD_TEST + else + unset GIT_FORCE_PRELOAD_TEST + fi + ' + + # test fsmonitor with and without the untracked cache (if available) + uc_values="false" + test_have_prereq UNTRACKED_CACHE && uc_values="false true" + for uc_val in $uc_values + do + test_expect_success "setup untracked cache to $uc_val" ' + git config core.untrackedcache $uc_val + ' + + # Status is well tested elsewhere so we'll just ensure that the results are + # the same when using core.fsmonitor. + test_expect_success 'compare status with and without fsmonitor' ' + write_integration_script && + clean_repo && + dirty_repo && + git add new && + git add dir1/new && + git add dir2/new && + git status >actual && + git -c core.fsmonitor= status >expect && + test_i18ncmp expect actual + ' + + # Make sure it's actually skipping the check for modified and untracked + # (if enabled) files unless it is told about them. + test_expect_success "status doesn't detect unreported modifications" ' + write_script .git/hooks/fsmonitor-test<<-\EOF && + :>marker + EOF + clean_repo && + git status && + test_path_is_file marker && + dirty_repo && + rm -f marker && + git status >actual && + test_path_is_file marker && + test_i18ngrep ! "Changes not staged for commit:" actual && + if test $uc_val = true + then + test_i18ngrep ! "Untracked files:" actual + fi && + if test $uc_val = false + then + test_i18ngrep "Untracked files:" actual + fi && + rm -f marker + ' + done +done + +# test that splitting the index dosn't interfere +test_expect_success 'splitting the index results in the same state' ' + write_integration_script && + dirty_repo && + git update-index --fsmonitor && + git ls-files -f >expect && + test-dump-fsmonitor >&2 && echo && + git update-index --fsmonitor --split-index && + test-dump-fsmonitor >&2 && echo && + git ls-files -f >actual && + test_cmp expect actual +' + +test_expect_success UNTRACKED_CACHE 'ignore .git changes when invalidating UNTR' ' + test_create_repo dot-git && + ( + cd dot-git && + mkdir -p .git/hooks && + : >tracked && + : >modified && + mkdir dir1 && + : >dir1/tracked && + : >dir1/modified && + mkdir dir2 && + : >dir2/tracked && + : >dir2/modified && + write_integration_script && + git config core.fsmonitor .git/hooks/fsmonitor-test && + git update-index --untracked-cache && + git update-index --fsmonitor && + GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace-before" \ + git status && + test-dump-untracked-cache >../before + ) && + cat >>dot-git/.git/hooks/fsmonitor-test <<-\EOF && + printf ".git\0" + printf ".git/index\0" + printf "dir1/.git\0" + printf "dir1/.git/index\0" + EOF + ( + cd dot-git && + GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace-after" \ + git status && + test-dump-untracked-cache >../after + ) && + grep "directory invalidation" trace-before >>before && + grep "directory invalidation" trace-after >>after && + # UNTR extension unchanged, dir invalidation count unchanged + test_cmp before after +' + +test_done diff --git a/t/t7519/fsmonitor-all b/t/t7519/fsmonitor-all new file mode 100755 index 0000000000..691bc94dc2 --- /dev/null +++ b/t/t7519/fsmonitor-all @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An test hook script to integrate with git to test fsmonitor. +# +# The hook is passed a version (currently 1) and a time in nanoseconds +# formatted as a string and outputs to stdout all files that have been +# modified since the given time. Paths must be relative to the root of +# the working tree and separated by a single NUL. +# +#echo "$0 $*" >&2 + +if test "$#" -ne 2 +then + echo "$0: exactly 2 arguments expected" >&2 + exit 2 +fi + +if test "$1" != 1 +then + echo "Unsupported core.fsmonitor hook version." >&2 + exit 1 +fi + +echo "/" diff --git a/t/t7519/fsmonitor-none b/t/t7519/fsmonitor-none new file mode 100755 index 0000000000..ed9cf5a6a9 --- /dev/null +++ b/t/t7519/fsmonitor-none @@ -0,0 +1,22 @@ +#!/bin/sh +# +# An test hook script to integrate with git to test fsmonitor. +# +# The hook is passed a version (currently 1) and a time in nanoseconds +# formatted as a string and outputs to stdout all files that have been +# modified since the given time. Paths must be relative to the root of +# the working tree and separated by a single NUL. +# +#echo "$0 $*" >&2 + +if test "$#" -ne 2 +then + echo "$0: exactly 2 arguments expected" >&2 + exit 2 +fi + +if test "$1" != 1 +then + echo "Unsupported core.fsmonitor hook version." >&2 + exit 1 +fi diff --git a/t/t7519/fsmonitor-watchman b/t/t7519/fsmonitor-watchman new file mode 100755 index 0000000000..5514edcf68 --- /dev/null +++ b/t/t7519/fsmonitor-watchman @@ -0,0 +1,133 @@ +#!/usr/bin/perl + +use strict; +use warnings; +use IPC::Open2; + +# An example hook script to integrate Watchman +# (https://facebook.github.io/watchman/) with git to speed up detecting +# new and modified files. +# +# The hook is passed a version (currently 1) and a time in nanoseconds +# formatted as a string and outputs to stdout all files that have been +# modified since the given time. Paths must be relative to the root of +# the working tree and separated by a single NUL. +# +# To enable this hook, rename this file to "query-watchman" and set +# 'git config core.fsmonitor .git/hooks/query-watchman' +# +my ($version, $time) = @ARGV; +#print STDERR "$0 $version $time\n"; + +# Check the hook interface version + +if ($version == 1) { + # convert nanoseconds to seconds + $time = int $time / 1000000000; +} else { + die "Unsupported query-fsmonitor hook version '$version'.\n" . + "Falling back to scanning...\n"; +} + +my $git_work_tree; +if ($^O =~ 'msys' || $^O =~ 'cygwin') { + $git_work_tree = Win32::GetCwd(); + $git_work_tree =~ tr/\\/\//; +} else { + require Cwd; + $git_work_tree = Cwd::cwd(); +} + +my $retry = 1; + +launch_watchman(); + +sub launch_watchman { + + my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j') + or die "open2() failed: $!\n" . + "Falling back to scanning...\n"; + + # In the query expression below we're asking for names of files that + # changed since $time but were not transient (ie created after + # $time but no longer exist). + # + # To accomplish this, we're using the "since" generator to use the + # recency index to select candidate nodes and "fields" to limit the + # output to file names only. Then we're using the "expression" term to + # further constrain the results. + # + # The category of transient files that we want to ignore will have a + # creation clock (cclock) newer than $time_t value and will also not + # currently exist. + + my $query = <<" END"; + ["query", "$git_work_tree", { + "since": $time, + "fields": ["name"], + "expression": ["not", ["allof", ["since", $time, "cclock"], ["not", "exists"]]] + }] + END + + open (my $fh, ">", ".git/watchman-query.json"); + print $fh $query; + close $fh; + + print CHLD_IN $query; + close CHLD_IN; + my $response = do {local $/; <CHLD_OUT>}; + + open ($fh, ">", ".git/watchman-response.json"); + print $fh $response; + close $fh; + + die "Watchman: command returned no output.\n" . + "Falling back to scanning...\n" if $response eq ""; + die "Watchman: command returned invalid output: $response\n" . + "Falling back to scanning...\n" unless $response =~ /^\{/; + + my $json_pkg; + eval { + require JSON::XS; + $json_pkg = "JSON::XS"; + 1; + } or do { + require JSON::PP; + $json_pkg = "JSON::PP"; + }; + + my $o = $json_pkg->new->utf8->decode($response); + + if ($retry > 0 and $o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) { + print STDERR "Adding '$git_work_tree' to watchman's watch list.\n"; + $retry--; + qx/watchman watch "$git_work_tree"/; + die "Failed to make watchman watch '$git_work_tree'.\n" . + "Falling back to scanning...\n" if $? != 0; + + # Watchman will always return all files on the first query so + # return the fast "everything is dirty" flag to git and do the + # Watchman query just to get it over with now so we won't pay + # the cost in git to look up each individual file. + + open ($fh, ">", ".git/watchman-output.out"); + print "/\0"; + close $fh; + + print "/\0"; + eval { launch_watchman() }; + exit 0; + } + + die "Watchman: $o->{error}.\n" . + "Falling back to scanning...\n" if $o->{error}; + + open ($fh, ">", ".git/watchman-output.out"); + binmode $fh, ":utf8"; + print $fh @{$o->{files}}; + close $fh; + + binmode STDOUT, ":utf8"; + local $, = "\0"; + print @{$o->{files}}; +} diff --git a/t/t7520-ignored-hook-warning.sh b/t/t7520-ignored-hook-warning.sh new file mode 100755 index 0000000000..634fb7f23a --- /dev/null +++ b/t/t7520-ignored-hook-warning.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +test_description='ignored hook warning' + +. ./test-lib.sh + +test_expect_success setup ' + hookdir="$(git rev-parse --git-dir)/hooks" && + hook="$hookdir/pre-commit" && + mkdir -p "$hookdir" && + write_script "$hook" <<-\EOF + exit 0 + EOF +' + +test_expect_success 'no warning if hook is not ignored' ' + git commit --allow-empty -m "more" 2>message && + test_i18ngrep ! -e "hook was ignored" message +' + +test_expect_success POSIXPERM 'warning if hook is ignored' ' + chmod -x "$hook" && + git commit --allow-empty -m "even more" 2>message && + test_i18ngrep -e "hook was ignored" message +' + +test_expect_success POSIXPERM 'no warning if advice.ignoredHook set to false' ' + test_config advice.ignoredHook false && + chmod -x "$hook" && + git commit --allow-empty -m "even more" 2>message && + test_i18ngrep ! -e "hook was ignored" message +' + +test_expect_success 'no warning if unset advice.ignoredHook and hook removed' ' + rm -f "$hook" && + test_unconfig advice.ignoredHook && + git commit --allow-empty -m "even more" 2>message && + test_i18ngrep ! -e "hook was ignored" message +' + +test_done diff --git a/t/t7521-ignored-mode.sh b/t/t7521-ignored-mode.sh new file mode 100755 index 0000000000..91790943c3 --- /dev/null +++ b/t/t7521-ignored-mode.sh @@ -0,0 +1,233 @@ +#!/bin/sh + +test_description='git status ignored modes' + +. ./test-lib.sh + +test_expect_success 'setup initial commit and ignore file' ' + cat >.gitignore <<-\EOF && + *.ign + ignored_dir/ + !*.unignore + EOF + git add . && + git commit -m "Initial commit" +' + +test_expect_success 'Verify behavior of status on directories with ignored files' ' + test_when_finished "git clean -fdx" && + cat >expect <<-\EOF && + ? expect + ? output + ! dir/ignored/ignored_1.ign + ! dir/ignored/ignored_2.ign + ! ignored/ignored_1.ign + ! ignored/ignored_2.ign + EOF + + mkdir -p ignored dir/ignored && + touch ignored/ignored_1.ign ignored/ignored_2.ign \ + dir/ignored/ignored_1.ign dir/ignored/ignored_2.ign && + + git status --porcelain=v2 --ignored=matching --untracked-files=all >output && + test_i18ncmp expect output +' + +test_expect_success 'Verify status behavior on directory with tracked & ignored files' ' + test_when_finished "git clean -fdx && git reset HEAD~1 --hard" && + cat >expect <<-\EOF && + ? expect + ? output + ! dir/tracked_ignored/ignored_1.ign + ! dir/tracked_ignored/ignored_2.ign + ! tracked_ignored/ignored_1.ign + ! tracked_ignored/ignored_2.ign + EOF + + mkdir -p tracked_ignored dir/tracked_ignored && + touch tracked_ignored/tracked_1 tracked_ignored/tracked_2 \ + tracked_ignored/ignored_1.ign tracked_ignored/ignored_2.ign \ + dir/tracked_ignored/tracked_1 dir/tracked_ignored/tracked_2 \ + dir/tracked_ignored/ignored_1.ign dir/tracked_ignored/ignored_2.ign && + + git add tracked_ignored/tracked_1 tracked_ignored/tracked_2 \ + dir/tracked_ignored/tracked_1 dir/tracked_ignored/tracked_2 && + git commit -m "commit tracked files" && + + git status --porcelain=v2 --ignored=matching --untracked-files=all >output && + test_i18ncmp expect output +' + +test_expect_success 'Verify status behavior on directory with untracked and ignored files' ' + test_when_finished "git clean -fdx" && + cat >expect <<-\EOF && + ? dir/untracked_ignored/untracked_1 + ? dir/untracked_ignored/untracked_2 + ? expect + ? output + ? untracked_ignored/untracked_1 + ? untracked_ignored/untracked_2 + ! dir/untracked_ignored/ignored_1.ign + ! dir/untracked_ignored/ignored_2.ign + ! untracked_ignored/ignored_1.ign + ! untracked_ignored/ignored_2.ign + EOF + + mkdir -p untracked_ignored dir/untracked_ignored && + touch untracked_ignored/untracked_1 untracked_ignored/untracked_2 \ + untracked_ignored/ignored_1.ign untracked_ignored/ignored_2.ign \ + dir/untracked_ignored/untracked_1 dir/untracked_ignored/untracked_2 \ + dir/untracked_ignored/ignored_1.ign dir/untracked_ignored/ignored_2.ign && + + git status --porcelain=v2 --ignored=matching --untracked-files=all >output && + test_i18ncmp expect output +' + +test_expect_success 'Verify status matching ignored files on ignored directory' ' + test_when_finished "git clean -fdx" && + cat >expect <<-\EOF && + ? expect + ? output + ! ignored_dir/ + EOF + + mkdir ignored_dir && + touch ignored_dir/ignored_1 ignored_dir/ignored_2 \ + ignored_dir/ignored_1.ign ignored_dir/ignored_2.ign && + + git status --porcelain=v2 --ignored=matching --untracked-files=all >output && + test_i18ncmp expect output +' + +test_expect_success 'Verify status behavior on ignored directory containing tracked file' ' + test_when_finished "git clean -fdx && git reset HEAD~1 --hard" && + cat >expect <<-\EOF && + ? expect + ? output + ! ignored_dir/ignored_1 + ! ignored_dir/ignored_1.ign + ! ignored_dir/ignored_2 + ! ignored_dir/ignored_2.ign + EOF + + mkdir ignored_dir && + touch ignored_dir/ignored_1 ignored_dir/ignored_2 \ + ignored_dir/ignored_1.ign ignored_dir/ignored_2.ign \ + ignored_dir/tracked && + git add -f ignored_dir/tracked && + git commit -m "Force add file in ignored directory" && + git status --porcelain=v2 --ignored=matching --untracked-files=all >output && + test_i18ncmp expect output +' + +test_expect_success 'Verify matching ignored files with --untracked-files=normal' ' + test_when_finished "git clean -fdx" && + cat >expect <<-\EOF && + ? expect + ? output + ? untracked_dir/ + ! ignored_dir/ + ! ignored_files/ignored_1.ign + ! ignored_files/ignored_2.ign + EOF + + mkdir ignored_dir ignored_files untracked_dir && + touch ignored_dir/ignored_1 ignored_dir/ignored_2 \ + ignored_files/ignored_1.ign ignored_files/ignored_2.ign \ + untracked_dir/untracked && + git status --porcelain=v2 --ignored=matching --untracked-files=normal >output && + test_i18ncmp expect output +' + +test_expect_success 'Verify matching ignored files with --untracked-files=normal' ' + test_when_finished "git clean -fdx" && + cat >expect <<-\EOF && + ? expect + ? output + ? untracked_dir/ + ! ignored_dir/ + ! ignored_files/ignored_1.ign + ! ignored_files/ignored_2.ign + EOF + + mkdir ignored_dir ignored_files untracked_dir && + touch ignored_dir/ignored_1 ignored_dir/ignored_2 \ + ignored_files/ignored_1.ign ignored_files/ignored_2.ign \ + untracked_dir/untracked && + git status --porcelain=v2 --ignored=matching --untracked-files=normal >output && + test_i18ncmp expect output +' + +test_expect_success 'Verify status behavior on ignored directory containing tracked file' ' + test_when_finished "git clean -fdx && git reset HEAD~1 --hard" && + cat >expect <<-\EOF && + ? expect + ? output + ! ignored_dir/ignored_1 + ! ignored_dir/ignored_1.ign + ! ignored_dir/ignored_2 + ! ignored_dir/ignored_2.ign + EOF + + mkdir ignored_dir && + touch ignored_dir/ignored_1 ignored_dir/ignored_2 \ + ignored_dir/ignored_1.ign ignored_dir/ignored_2.ign \ + ignored_dir/tracked && + git add -f ignored_dir/tracked && + git commit -m "Force add file in ignored directory" && + git status --porcelain=v2 --ignored=matching --untracked-files=normal >output && + test_i18ncmp expect output +' + +test_expect_success 'Verify behavior of status with --ignored=no' ' + test_when_finished "git clean -fdx" && + cat >expect <<-\EOF && + ? expect + ? output + EOF + + mkdir -p ignored dir/ignored && + touch ignored/ignored_1.ign ignored/ignored_2.ign \ + dir/ignored/ignored_1.ign dir/ignored/ignored_2.ign && + + git status --porcelain=v2 --ignored=no --untracked-files=all >output && + test_i18ncmp expect output +' + +test_expect_success 'Verify behavior of status with --ignored=traditional and --untracked-files=all' ' + test_when_finished "git clean -fdx" && + cat >expect <<-\EOF && + ? expect + ? output + ! dir/ignored/ignored_1.ign + ! dir/ignored/ignored_2.ign + ! ignored/ignored_1.ign + ! ignored/ignored_2.ign + EOF + + mkdir -p ignored dir/ignored && + touch ignored/ignored_1.ign ignored/ignored_2.ign \ + dir/ignored/ignored_1.ign dir/ignored/ignored_2.ign && + + git status --porcelain=v2 --ignored=traditional --untracked-files=all >output && + test_i18ncmp expect output +' + +test_expect_success 'Verify behavior of status with --ignored=traditional and --untracked-files=normal' ' + test_when_finished "git clean -fdx" && + cat >expect <<-\EOF && + ? expect + ? output + ! dir/ + ! ignored/ + EOF + + mkdir -p ignored dir/ignored && + touch ignored/ignored_1.ign ignored/ignored_2.ign \ + dir/ignored/ignored_1.ign dir/ignored/ignored_2.ign && + + git status --porcelain=v2 --ignored=traditional --untracked-files=normal >output && + test_i18ncmp expect output +' + +test_done diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 75c50eea15..6736d8d131 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -33,9 +33,11 @@ printf '%s\n' 1 2 3 4 5 6 7 8 9 >file printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >file.1 printf '%s\n' 1 2 3 4 '5 X' 6 7 8 9 >file.5 printf '%s\n' 1 2 3 4 5 6 7 8 '9 X' >file.9 +printf '%s\n' 1 2 3 4 5 6 7 8 '9 Y' >file.9y printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >result.1 printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 9 >result.1-5 printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 '9 X' >result.1-5-9 +printf '%s\n' 1 2 3 4 5 6 7 8 '9 Z' >result.9z >empty create_merge_msgs () { @@ -128,6 +130,12 @@ test_expect_success 'setup' ' git tag c2 && c2=$(git rev-parse HEAD) && git reset --hard "$c0" && + cp file.9y file && + git add file && + test_tick && + git commit -m "commit 7" && + git tag c7 && + git reset --hard "$c0" && cp file.9 file && git add file && test_tick && @@ -146,6 +154,10 @@ test_expect_success 'test option parsing' ' test_must_fail git merge -s foobar c1 && test_must_fail git merge -s=foobar c1 && test_must_fail git merge -m && + test_must_fail git merge --abort foobar && + test_must_fail git merge --abort --quiet && + test_must_fail git merge --continue foobar && + test_must_fail git merge --continue --quiet && test_must_fail git merge ' @@ -218,6 +230,26 @@ test_expect_success 'merge c1 with c2' ' verify_parents $c1 $c2 ' +test_expect_success 'merge --squash c3 with c7' ' + git reset --hard c3 && + test_must_fail git merge --squash c7 && + cat result.9z >file && + git commit --no-edit -a && + + { + cat <<-EOF + Squashed commit of the following: + + $(git show -s c7) + + # Conflicts: + # file + EOF + } >expect && + git cat-file commit HEAD | sed -e '1,/^$/d' >actual && + test_cmp expect actual +' + test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c2 and c3' ' @@ -665,7 +697,43 @@ test_expect_success 'merge --no-ff --edit' ' git cat-file commit HEAD >raw && grep "work done on the side branch" raw && sed "1,/^$/d" >actual raw && - test_cmp actual expected + test_cmp expected actual +' + +test_expect_success 'merge annotated/signed tag w/o tracking' ' + test_when_finished "rm -rf dst; git tag -d anno1" && + git tag -a -m "anno c1" anno1 c1 && + git init dst && + git rev-parse c1 >dst/expect && + ( + # c0 fast-forwards to c1 but because this repository + # is not a "downstream" whose refs/tags follows along + # tag from the "upstream", this pull defaults to --no-ff + cd dst && + git pull .. c0 && + git pull .. anno1 && + git rev-parse HEAD^2 >actual && + test_cmp expect actual + ) +' + +test_expect_success 'merge annotated/signed tag w/ tracking' ' + test_when_finished "rm -rf dst; git tag -d anno1" && + git tag -a -m "anno c1" anno1 c1 && + git init dst && + git rev-parse c1 >dst/expect && + ( + # c0 fast-forwards to c1 and because this repository + # is a "downstream" whose refs/tags follows along + # tag from the "upstream", this pull defaults to --ff + cd dst && + git remote add origin .. && + git pull origin c0 && + git fetch origin && + git merge anno1 && + git rev-parse HEAD >actual && + test_cmp expect actual + ) ' test_expect_success GPG 'merge --ff-only tag' ' @@ -677,7 +745,7 @@ test_expect_success GPG 'merge --ff-only tag' ' git merge --ff-only signed && git rev-parse signed^0 >expect && git rev-parse HEAD >actual && - test_cmp actual expect + test_cmp expect actual ' test_expect_success GPG 'merge --no-edit tag should skip editor' ' @@ -686,10 +754,75 @@ test_expect_success GPG 'merge --no-edit tag should skip editor' ' git tag -f -s -m "A newer commit" signed && git reset --hard c0 && - EDITOR=false git merge --no-edit signed && + EDITOR=false git merge --no-edit --no-ff signed && git rev-parse signed^0 >expect && git rev-parse HEAD^2 >actual && - test_cmp actual expect + test_cmp expect actual +' + +test_expect_success 'set up mod-256 conflict scenario' ' + # 256 near-identical stanzas... + for i in $(test_seq 1 256); do + for j in 1 2 3 4 5; do + echo $i-$j + done + done >file && + git add file && + git commit -m base && + + # one side changes the first line of each to "master" + sed s/-1/-master/ <file >tmp && + mv tmp file && + git commit -am master && + + # and the other to "side"; merging the two will + # yield 256 separate conflicts + git checkout -b side HEAD^ && + sed s/-1/-side/ <file >tmp && + mv tmp file && + git commit -am side +' + +test_expect_success 'merge detects mod-256 conflicts (recursive)' ' + git reset --hard && + test_must_fail git merge -s recursive master +' + +test_expect_success 'merge detects mod-256 conflicts (resolve)' ' + git reset --hard && + test_must_fail git merge -s resolve master +' + +test_expect_success 'merge nothing into void' ' + git init void && + ( + cd void && + git remote add up .. && + git fetch up && + test_must_fail git merge FETCH_HEAD + ) +' + +test_expect_success 'merge can be completed with --continue' ' + git reset --hard c0 && + git merge --no-ff --no-commit c1 && + git merge --continue && + verify_parents $c0 $c1 +' + +write_script .git/FAKE_EDITOR <<EOF +# kill -TERM command added below. +EOF + +test_expect_success EXECKEEPSPID 'killed merge can be completed with --continue' ' + git reset --hard c0 && + ! "$SHELL_PATH" -c '\'' + echo kill -TERM $$ >> .git/FAKE_EDITOR + GIT_EDITOR=.git/FAKE_EDITOR + export GIT_EDITOR + exec git merge --no-ff --edit c1'\'' && + git merge --continue && + verify_parents $c0 $c1 ' test_done diff --git a/t/t7602-merge-octopus-many.sh b/t/t7602-merge-octopus-many.sh index 955f09f8e8..6abe441ae3 100755 --- a/t/t7602-merge-octopus-many.sh +++ b/t/t7602-merge-octopus-many.sh @@ -19,7 +19,7 @@ test_expect_success 'setup' ' git add c$i.c && git commit -m c$i && git tag c$i && - i=`expr $i + 1` || return 1 + i=$(expr $i + 1) || return 1 done ' @@ -30,7 +30,7 @@ test_expect_success 'merge c1 with c2, c3, c4, ... c29' ' while test $i -le 30 do refs="$refs c$i" - i=`expr $i + 1` + i=$(expr $i + 1) done && git merge $refs && test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" && @@ -38,14 +38,14 @@ test_expect_success 'merge c1 with c2, c3, c4, ... c29' ' while test $i -le 30 do test "$(git rev-parse c$i)" = "$(git rev-parse HEAD^$i)" && - i=`expr $i + 1` || return 1 + i=$(expr $i + 1) || return 1 done && git diff --exit-code && i=1 && while test $i -le 30 do test -f c$i.c && - i=`expr $i + 1` || return 1 + i=$(expr $i + 1) || return 1 done ' diff --git a/t/t7605-merge-resolve.sh b/t/t7605-merge-resolve.sh index 0cb9d11f21..5d56c38546 100755 --- a/t/t7605-merge-resolve.sh +++ b/t/t7605-merge-resolve.sh @@ -27,7 +27,7 @@ test_expect_success 'setup' ' git tag c3 ' -test_expect_success 'merge c1 to c2' ' +merge_c1_to_c2_cmds=' git reset --hard c1 && git merge -s resolve c2 && test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" && @@ -41,6 +41,10 @@ test_expect_success 'merge c1 to c2' ' test 3 = $(git ls-files | wc -l) ' +test_expect_success 'merge c1 to c2' "$merge_c1_to_c2_cmds" + +test_expect_success 'merge c1 to c2, again' "$merge_c1_to_c2_cmds" + test_expect_success 'merge c2 to c3 (fails)' ' git reset --hard c2 && test_must_fail git merge -s resolve c3 diff --git a/t/t7607-merge-overwrite.sh b/t/t7607-merge-overwrite.sh index 758a623cdb..9c422bcd7c 100755 --- a/t/t7607-merge-overwrite.sh +++ b/t/t7607-merge-overwrite.sh @@ -97,7 +97,10 @@ test_expect_failure 'will not overwrite unstaged changes in renamed file' ' git mv c1.c other.c && git commit -m rename && cp important other.c && - git merge c1a && + test_must_fail git merge c1a >out && + test_i18ngrep "Refusing to lose dirty file at other.c" out && + test_path_is_file other.c~HEAD && + test $(git hash-object other.c~HEAD) = $(git rev-parse c1a:c1.c) && test_cmp important other.c ' @@ -115,7 +118,7 @@ cat >expect <<\EOF error: The following untracked working tree files would be overwritten by merge: sub sub2 -Please move or remove them before you can merge. +Please move or remove them before you merge. Aborting EOF @@ -125,7 +128,7 @@ test_expect_success 'will not overwrite untracked file in leading path' ' cp important sub && cp important sub2 && test_must_fail git merge sub 2>out && - test_cmp out expect && + test_i18ncmp out expect && test_path_is_missing .git/MERGE_HEAD && test_cmp important sub && test_cmp important sub2 && diff --git a/t/t7609-merge-co-error-msgs.sh b/t/t7609-merge-co-error-msgs.sh index 0e4a682c64..e90413204e 100755 --- a/t/t7609-merge-co-error-msgs.sh +++ b/t/t7609-merge-co-error-msgs.sh @@ -31,20 +31,20 @@ error: The following untracked working tree files would be overwritten by merge: four three two -Please move or remove them before you can merge. +Please move or remove them before you merge. Aborting EOF test_expect_success 'untracked files overwritten by merge (fast and non-fast forward)' ' test_must_fail git merge branch 2>out && - test_cmp out expect && + test_i18ncmp out expect && git commit --allow-empty -m empty && ( GIT_MERGE_VERBOSITY=0 && export GIT_MERGE_VERBOSITY && test_must_fail git merge branch 2>out2 ) && - test_cmp out2 expect && + test_i18ncmp out2 expect && git reset --hard HEAD^ ' @@ -53,10 +53,10 @@ error: Your local changes to the following files would be overwritten by merge: four three two -Please, commit your changes or stash them before you can merge. +Please commit your changes or stash them before you merge. error: The following untracked working tree files would be overwritten by merge: five -Please move or remove them before you can merge. +Please move or remove them before you merge. Aborting EOF @@ -65,14 +65,14 @@ test_expect_success 'untracked files or local changes ovewritten by merge' ' git add three && git add four && test_must_fail git merge branch 2>out && - test_cmp out expect + test_i18ncmp out expect ' cat >expect <<\EOF error: Your local changes to the following files would be overwritten by checkout: rep/one rep/two -Please, commit your changes or stash them before you can switch branches. +Please commit your changes or stash them before you switch branches. Aborting EOF @@ -87,25 +87,25 @@ test_expect_success 'cannot switch branches because of local changes' ' echo uno >rep/one && echo dos >rep/two && test_must_fail git checkout branch 2>out && - test_cmp out expect + test_i18ncmp out expect ' cat >expect <<\EOF error: Your local changes to the following files would be overwritten by checkout: rep/one rep/two -Please, commit your changes or stash them before you can switch branches. +Please commit your changes or stash them before you switch branches. Aborting EOF test_expect_success 'not uptodate file porcelain checkout error' ' git add rep/one rep/two && test_must_fail git checkout branch 2>out && - test_cmp out expect + test_i18ncmp out expect ' cat >expect <<\EOF -error: Updating the following directories would lose untracked files in it: +error: Updating the following directories would lose untracked files in them: rep rep2 @@ -132,7 +132,7 @@ test_expect_success 'not_uptodate_dir porcelain checkout error' ' >rep/untracked-file && >rep2/untracked-file && test_must_fail git checkout branch 2>out && - test_cmp out ../expect + test_i18ncmp out ../expect ' test_done diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index 7eeb207b32..1a430b9c40 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -55,6 +55,22 @@ test_expect_success 'setup' ' git rm file12 && git commit -m "branch1 changes" && + git checkout -b delete-base branch1 && + mkdir -p a/a && + (echo one; echo two; echo 3; echo 4) >a/a/file.txt && + git add a/a/file.txt && + git commit -m"base file" && + git checkout -b move-to-b delete-base && + mkdir -p b/b && + git mv a/a/file.txt b/b/file.txt && + (echo one; echo two; echo 4) >b/b/file.txt && + git commit -a -m"move to b" && + git checkout -b move-to-c delete-base && + mkdir -p c/c && + git mv a/a/file.txt c/c/file.txt && + (echo one; echo two; echo 3) >c/c/file.txt && + git commit -a -m"move to c" && + git checkout -b stash1 master && echo stash1 change file11 >file11 && git add file11 && @@ -86,6 +102,23 @@ test_expect_success 'setup' ' git rm file11 && git commit -m "master updates" && + git clean -fdx && + git checkout -b order-file-start master && + echo start >a && + echo start >b && + git add a b && + git commit -m start && + git checkout -b order-file-side1 order-file-start && + echo side1 >a && + echo side1 >b && + git add a b && + git commit -m side1 && + git checkout -b order-file-side2 order-file-start && + echo side2 >a && + echo side2 >b && + git add a b && + git commit -m side2 && + git config merge.tool mytool && git config mergetool.mytool.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" && git config mergetool.mytool.trustExitCode true && @@ -94,7 +127,8 @@ test_expect_success 'setup' ' ' test_expect_success 'custom mergetool' ' - git checkout -b test1 branch1 && + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && git submodule update -N && test_must_fail git merge master >/dev/null 2>&1 && ( yes "" | git mergetool both >/dev/null 2>&1 ) && @@ -112,8 +146,13 @@ test_expect_success 'custom mergetool' ' ' test_expect_success 'mergetool crlf' ' + test_when_finished "git reset --hard" && + # This test_config line must go after the above reset line so that + # core.autocrlf is unconfigured before reset runs. (The + # test_config command uses test_when_finished internally and + # test_when_finished is LIFO.) test_config core.autocrlf true && - git checkout -b test2 branch1 && + git checkout -b test$test_count branch1 && test_must_fail git merge master >/dev/null 2>&1 && ( yes "" | git mergetool file1 >/dev/null 2>&1 ) && ( yes "" | git mergetool file2 >/dev/null 2>&1 ) && @@ -128,13 +167,12 @@ test_expect_success 'mergetool crlf' ' test "$(printf x | cat subdir/file3 -)" = "$(printf "master new sub\r\nx")" && git submodule update -N && test "$(cat submod/bar)" = "master submodule" && - git commit -m "branch1 resolved with mergetool - autocrlf" && - test_config core.autocrlf false && - git reset --hard + git commit -m "branch1 resolved with mergetool - autocrlf" ' test_expect_success 'mergetool in subdir' ' - git checkout -b test3 branch1 && + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && git submodule update -N && ( cd subdir && @@ -145,8 +183,13 @@ test_expect_success 'mergetool in subdir' ' ' test_expect_success 'mergetool on file in parent dir' ' + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && + git submodule update -N && ( cd subdir && + test_must_fail git merge master >/dev/null 2>&1 && + ( yes "" | git mergetool file3 >/dev/null 2>&1 ) && ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) && ( yes "" | git mergetool ../file2 ../spaced\ name >/dev/null 2>&1 ) && ( yes "" | git mergetool ../both >/dev/null 2>&1 ) && @@ -161,7 +204,8 @@ test_expect_success 'mergetool on file in parent dir' ' ' test_expect_success 'mergetool skips autoresolved' ' - git checkout -b test4 branch1 && + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && git submodule update -N && test_must_fail git merge master && test -n "$(git ls-files -u)" && @@ -169,14 +213,34 @@ test_expect_success 'mergetool skips autoresolved' ' ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) && ( yes "l" | git mergetool submod >/dev/null 2>&1 ) && output="$(git mergetool --no-prompt)" && - test "$output" = "No files need merging" && - git reset --hard + test "$output" = "No files need merging" +' + +test_expect_success 'mergetool merges all from subdir (rerere disabled)' ' + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && + test_config rerere.enabled false && + ( + cd subdir && + test_must_fail git merge master && + ( yes "r" | git mergetool ../submod ) && + ( yes "d" "d" | git mergetool --no-prompt ) && + test "$(cat ../file1)" = "master updated" && + test "$(cat ../file2)" = "master new" && + test "$(cat file3)" = "master new sub" && + ( cd .. && git submodule update -N ) && + test "$(cat ../submod/bar)" = "master submodule" && + git commit -m "branch2 resolved by mergetool from subdir" + ) ' -test_expect_success 'mergetool merges all from subdir' ' +test_expect_success 'mergetool merges all from subdir (rerere enabled)' ' + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && + test_config rerere.enabled true && + rm -rf .git/rr-cache && ( cd subdir && - test_config rerere.enabled false && test_must_fail git merge master && ( yes "r" | git mergetool ../submod ) && ( yes "d" "d" | git mergetool --no-prompt ) && @@ -190,20 +254,21 @@ test_expect_success 'mergetool merges all from subdir' ' ' test_expect_success 'mergetool skips resolved paths when rerere is active' ' + test_when_finished "git reset --hard" && test_config rerere.enabled true && rm -rf .git/rr-cache && - git checkout -b test5 branch1 && + git checkout -b test$test_count branch1 && git submodule update -N && test_must_fail git merge master >/dev/null 2>&1 && ( yes "l" | git mergetool --no-prompt submod >/dev/null 2>&1 ) && ( yes "d" "d" | git mergetool --no-prompt >/dev/null 2>&1 ) && git submodule update -N && output="$(yes "n" | git mergetool --no-prompt)" && - test "$output" = "No files need merging" && - git reset --hard + test "$output" = "No files need merging" ' test_expect_success 'conflicted stash sets up rerere' ' + test_when_finished "git reset --hard" && test_config rerere.enabled true && git checkout stash1 && echo "Conflicting stash content" >file11 && @@ -231,25 +296,78 @@ test_expect_success 'conflicted stash sets up rerere' ' ' test_expect_success 'mergetool takes partial path' ' - git reset --hard && + test_when_finished "git reset --hard" && test_config rerere.enabled false && - git checkout -b test12 branch1 && + git checkout -b test$test_count branch1 && git submodule update -N && test_must_fail git merge master && ( yes "" | git mergetool subdir ) && - test "$(cat subdir/file3)" = "master new sub" && - git reset --hard + test "$(cat subdir/file3)" = "master new sub" +' + +test_expect_success 'mergetool delete/delete conflict' ' + test_when_finished "git reset --hard" && + git checkout -b test$test_count move-to-c && + test_must_fail git merge move-to-b && + echo d | git mergetool a/a/file.txt && + ! test -f a/a/file.txt && + git reset --hard && + test_must_fail git merge move-to-b && + echo m | git mergetool a/a/file.txt && + test -f b/b/file.txt && + git reset --hard && + test_must_fail git merge move-to-b && + ! echo a | git mergetool a/a/file.txt && + ! test -f a/a/file.txt +' + +test_expect_success 'mergetool produces no errors when keepBackup is used' ' + test_when_finished "git reset --hard" && + git checkout -b test$test_count move-to-c && + test_config mergetool.keepBackup true && + test_must_fail git merge move-to-b && + : >expect && + echo d | git mergetool a/a/file.txt 2>actual && + test_cmp expect actual && + ! test -d a +' + +test_expect_success 'mergetool honors tempfile config for deleted files' ' + test_when_finished "git reset --hard" && + git checkout -b test$test_count move-to-c && + test_config mergetool.keepTemporaries false && + test_must_fail git merge move-to-b && + echo d | git mergetool a/a/file.txt && + ! test -d a +' + +test_expect_success 'mergetool keeps tempfiles when aborting delete/delete' ' + test_when_finished "git reset --hard" && + test_when_finished "git clean -fdx" && + git checkout -b test$test_count move-to-c && + test_config mergetool.keepTemporaries true && + test_must_fail git merge move-to-b && + ! (echo a; echo n) | git mergetool a/a/file.txt && + test -d a/a && + cat >expect <<-\EOF && + file_BASE_.txt + file_LOCAL_.txt + file_REMOTE_.txt + EOF + ls -1 a/a | sed -e "s/[0-9]*//g" >actual && + test_cmp expect actual ' test_expect_success 'deleted vs modified submodule' ' - git checkout -b test6 branch1 && + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && git submodule update -N && mv submod submod-movedaside && git rm --cached submod && git commit -m "Submodule deleted from branch" && - git checkout -b test6.a test6 && + git checkout -b test$test_count.a test$test_count && test_must_fail git merge master && test -n "$(git ls-files -u)" && ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && @@ -265,7 +383,7 @@ test_expect_success 'deleted vs modified submodule' ' git commit -m "Merge resolved by keeping module" && mv submod submod-movedaside && - git checkout -b test6.b test6 && + git checkout -b test$test_count.b test$test_count && git submodule update -N && test_must_fail git merge master && test -n "$(git ls-files -u)" && @@ -279,9 +397,9 @@ test_expect_success 'deleted vs modified submodule' ' git commit -m "Merge resolved by deleting module" && mv submod-movedaside submod && - git checkout -b test6.c master && + git checkout -b test$test_count.c master && git submodule update -N && - test_must_fail git merge test6 && + test_must_fail git merge test$test_count && test -n "$(git ls-files -u)" && ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && ( yes "" | git mergetool both >/dev/null 2>&1 ) && @@ -295,9 +413,9 @@ test_expect_success 'deleted vs modified submodule' ' git commit -m "Merge resolved by deleting module" && mv submod.orig submod && - git checkout -b test6.d master && + git checkout -b test$test_count.d master && git submodule update -N && - test_must_fail git merge test6 && + test_must_fail git merge test$test_count && test -n "$(git ls-files -u)" && ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && ( yes "" | git mergetool both >/dev/null 2>&1 ) && @@ -308,19 +426,19 @@ test_expect_success 'deleted vs modified submodule' ' test "$(cat submod/bar)" = "master submodule" && output="$(git mergetool --no-prompt)" && test "$output" = "No files need merging" && - git commit -m "Merge resolved by keeping module" && - git reset --hard HEAD + git commit -m "Merge resolved by keeping module" ' test_expect_success 'file vs modified submodule' ' - git checkout -b test7 branch1 && + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && git submodule update -N && mv submod submod-movedaside && git rm --cached submod && echo not a submodule >submod && git add submod && git commit -m "Submodule path becomes file" && - git checkout -b test7.a branch1 && + git checkout -b test$test_count.a branch1 && test_must_fail git merge master && test -n "$(git ls-files -u)" && ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && @@ -336,7 +454,7 @@ test_expect_success 'file vs modified submodule' ' git commit -m "Merge resolved by keeping module" && mv submod submod-movedaside && - git checkout -b test7.b test7 && + git checkout -b test$test_count.b test$test_count && test_must_fail git merge master && test -n "$(git ls-files -u)" && ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && @@ -349,11 +467,11 @@ test_expect_success 'file vs modified submodule' ' test "$output" = "No files need merging" && git commit -m "Merge resolved by keeping file" && - git checkout -b test7.c master && + git checkout -b test$test_count.c master && rmdir submod && mv submod-movedaside submod && test ! -e submod.orig && git submodule update -N && - test_must_fail git merge test7 && + test_must_fail git merge test$test_count && test -n "$(git ls-files -u)" && ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && ( yes "" | git mergetool both >/dev/null 2>&1 ) && @@ -366,10 +484,10 @@ test_expect_success 'file vs modified submodule' ' test "$output" = "No files need merging" && git commit -m "Merge resolved by keeping file" && - git checkout -b test7.d master && + git checkout -b test$test_count.d master && rmdir submod && mv submod.orig submod && git submodule update -N && - test_must_fail git merge test7 && + test_must_fail git merge test$test_count && test -n "$(git ls-files -u)" && ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) && ( yes "" | git mergetool both>/dev/null 2>&1 ) && @@ -384,7 +502,8 @@ test_expect_success 'file vs modified submodule' ' ' test_expect_success 'submodule in subdirectory' ' - git checkout -b test10 branch1 && + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && git submodule update -N && ( cd subdir && @@ -396,56 +515,57 @@ test_expect_success 'submodule in subdirectory' ' git commit -m "add initial versions" ) ) && + test_when_finished "rm -rf subdir/subdir_module" && git submodule add git://example.com/subsubmodule subdir/subdir_module && git add subdir/subdir_module && git commit -m "add submodule in subdirectory" && - git checkout -b test10.a test10 && + git checkout -b test$test_count.a test$test_count && git submodule update -N && ( cd subdir/subdir_module && git checkout -b super10.a && - echo test10.a >file15 && + echo test$test_count.a >file15 && git add file15 && git commit -m "on branch 10.a" ) && git add subdir/subdir_module && - git commit -m "change submodule in subdirectory on test10.a" && + git commit -m "change submodule in subdirectory on test$test_count.a" && - git checkout -b test10.b test10 && + git checkout -b test$test_count.b test$test_count && git submodule update -N && ( cd subdir/subdir_module && git checkout -b super10.b && - echo test10.b >file15 && + echo test$test_count.b >file15 && git add file15 && git commit -m "on branch 10.b" ) && git add subdir/subdir_module && - git commit -m "change submodule in subdirectory on test10.b" && + git commit -m "change submodule in subdirectory on test$test_count.b" && - test_must_fail git merge test10.a >/dev/null 2>&1 && + test_must_fail git merge test$test_count.a >/dev/null 2>&1 && ( cd subdir && ( yes "l" | git mergetool subdir_module ) ) && - test "$(cat subdir/subdir_module/file15)" = "test10.b" && + test "$(cat subdir/subdir_module/file15)" = "test$test_count.b" && git submodule update -N && - test "$(cat subdir/subdir_module/file15)" = "test10.b" && + test "$(cat subdir/subdir_module/file15)" = "test$test_count.b" && git reset --hard && git submodule update -N && - test_must_fail git merge test10.a >/dev/null 2>&1 && + test_must_fail git merge test$test_count.a >/dev/null 2>&1 && ( yes "r" | git mergetool subdir/subdir_module ) && - test "$(cat subdir/subdir_module/file15)" = "test10.b" && + test "$(cat subdir/subdir_module/file15)" = "test$test_count.b" && git submodule update -N && - test "$(cat subdir/subdir_module/file15)" = "test10.a" && - git commit -m "branch1 resolved with mergetool" && - rm -rf subdir/subdir_module + test "$(cat subdir/subdir_module/file15)" = "test$test_count.a" && + git commit -m "branch1 resolved with mergetool" ' test_expect_success 'directory vs modified submodule' ' - git checkout -b test11 branch1 && + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && mv submod submod-movedaside && git rm --cached submod && mkdir submod && @@ -459,7 +579,7 @@ test_expect_success 'directory vs modified submodule' ' test "$(cat submod/file16)" = "not a submodule" && rm -rf submod.orig && - git reset --hard >/dev/null 2>&1 && + git reset --hard && test_must_fail git merge master && test -n "$(git ls-files -u)" && test ! -e submod.orig && @@ -471,70 +591,131 @@ test_expect_success 'directory vs modified submodule' ' ( cd submod && git clean -f && git reset --hard ) && git submodule update -N && test "$(cat submod/bar)" = "master submodule" && - git reset --hard >/dev/null 2>&1 && rm -rf submod-movedaside && + git reset --hard && + rm -rf submod-movedaside && - git checkout -b test11.c master && + git checkout -b test$test_count.c master && git submodule update -N && - test_must_fail git merge test11 && + test_must_fail git merge test$test_count && test -n "$(git ls-files -u)" && ( yes "l" | git mergetool submod ) && git submodule update -N && test "$(cat submod/bar)" = "master submodule" && - git reset --hard >/dev/null 2>&1 && + git reset --hard && git submodule update -N && - test_must_fail git merge test11 && + test_must_fail git merge test$test_count && test -n "$(git ls-files -u)" && test ! -e submod.orig && ( yes "r" | git mergetool submod ) && test "$(cat submod/file16)" = "not a submodule" && - git reset --hard master >/dev/null 2>&1 && + git reset --hard master && ( cd submod && git clean -f && git reset --hard ) && git submodule update -N ' test_expect_success 'file with no base' ' - git checkout -b test13 branch1 && + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && test_must_fail git merge master && git mergetool --no-prompt --tool mybase -- both && >expected && - test_cmp both expected && - git reset --hard master >/dev/null 2>&1 + test_cmp expected both ' test_expect_success 'custom commands override built-ins' ' - git checkout -b test14 branch1 && + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && test_config mergetool.defaults.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" && test_config mergetool.defaults.trustExitCode true && test_must_fail git merge master && git mergetool --no-prompt --tool defaults -- both && echo master both added >expected && - test_cmp both expected && - git reset --hard master >/dev/null 2>&1 + test_cmp expected both ' test_expect_success 'filenames seen by tools start with ./' ' - git checkout -b test15 branch1 && + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && test_config mergetool.writeToTemp false && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && test_config mergetool.myecho.trustExitCode true && test_must_fail git merge master && git mergetool --no-prompt --tool myecho -- both >actual && - grep ^\./both_LOCAL_ actual >/dev/null && - git reset --hard master >/dev/null 2>&1 + grep ^\./both_LOCAL_ actual >/dev/null +' + +test_lazy_prereq MKTEMP ' + tempdir=$(mktemp -d -t foo.XXXXXX) && + test -d "$tempdir" && + rmdir "$tempdir" ' -test_expect_success 'temporary filenames are used with mergetool.writeToTemp' ' - git checkout -b test16 branch1 && +test_expect_success MKTEMP 'temporary filenames are used with mergetool.writeToTemp' ' + test_when_finished "git reset --hard" && + git checkout -b test$test_count branch1 && test_config mergetool.writeToTemp true && test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && test_config mergetool.myecho.trustExitCode true && test_must_fail git merge master && git mergetool --no-prompt --tool myecho -- both >actual && - test_must_fail grep ^\./both_LOCAL_ actual >/dev/null && - grep /both_LOCAL_ actual >/dev/null && - git reset --hard master >/dev/null 2>&1 + ! grep ^\./both_LOCAL_ actual >/dev/null && + grep /both_LOCAL_ actual >/dev/null +' + +test_expect_success 'diff.orderFile configuration is honored' ' + test_when_finished "git reset --hard" && + git checkout -b test$test_count order-file-side2 && + test_config diff.orderFile order-file && + test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && + test_config mergetool.myecho.trustExitCode true && + echo b >order-file && + echo a >>order-file && + test_must_fail git merge order-file-side1 && + cat >expect <<-\EOF && + Merging: + b + a + EOF + + # make sure "order-file" that is ambiguous between + # rev and path is understood correctly. + git branch order-file HEAD && + + git mergetool --no-prompt --tool myecho >output && + git grep --no-index -h -A2 Merging: output >actual && + test_cmp expect actual +' +test_expect_success 'mergetool -Oorder-file is honored' ' + test_when_finished "git reset --hard" && + git checkout -b test$test_count order-file-side2 && + test_config diff.orderFile order-file && + test_config mergetool.myecho.cmd "echo \"\$LOCAL\"" && + test_config mergetool.myecho.trustExitCode true && + echo b >order-file && + echo a >>order-file && + test_must_fail git merge order-file-side1 && + cat >expect <<-\EOF && + Merging: + a + b + EOF + git mergetool -O/dev/null --no-prompt --tool myecho >output && + git grep --no-index -h -A2 Merging: output >actual && + test_cmp expect actual && + git reset --hard && + + git config --unset diff.orderFile && + test_must_fail git merge order-file-side1 && + cat >expect <<-\EOF && + Merging: + b + a + EOF + git mergetool -Oorder-file --no-prompt --tool myecho >output && + git grep --no-index -h -A2 Merging: output >actual && + test_cmp expect actual ' test_done diff --git a/t/t7612-merge-verify-signatures.sh b/t/t7612-merge-verify-signatures.sh index 8ae69a61c3..e797c74112 100755 --- a/t/t7612-merge-verify-signatures.sh +++ b/t/t7612-merge-verify-signatures.sh @@ -35,27 +35,72 @@ test_expect_success GPG 'create signed commits' ' ' test_expect_success GPG 'merge unsigned commit with verification' ' + test_when_finished "git reset --hard && git checkout initial" && test_must_fail git merge --ff-only --verify-signatures side-unsigned 2>mergeerror && test_i18ngrep "does not have a GPG signature" mergeerror ' +test_expect_success GPG 'merge unsigned commit with merge.verifySignatures=true' ' + test_when_finished "git reset --hard && git checkout initial" && + test_config merge.verifySignatures true && + test_must_fail git merge --ff-only side-unsigned 2>mergeerror && + test_i18ngrep "does not have a GPG signature" mergeerror +' + test_expect_success GPG 'merge commit with bad signature with verification' ' + test_when_finished "git reset --hard && git checkout initial" && test_must_fail git merge --ff-only --verify-signatures $(cat forged.commit) 2>mergeerror && test_i18ngrep "has a bad GPG signature" mergeerror ' +test_expect_success GPG 'merge commit with bad signature with merge.verifySignatures=true' ' + test_when_finished "git reset --hard && git checkout initial" && + test_config merge.verifySignatures true && + test_must_fail git merge --ff-only $(cat forged.commit) 2>mergeerror && + test_i18ngrep "has a bad GPG signature" mergeerror +' + test_expect_success GPG 'merge commit with untrusted signature with verification' ' + test_when_finished "git reset --hard && git checkout initial" && test_must_fail git merge --ff-only --verify-signatures side-untrusted 2>mergeerror && test_i18ngrep "has an untrusted GPG signature" mergeerror ' +test_expect_success GPG 'merge commit with untrusted signature with merge.verifySignatures=true' ' + test_when_finished "git reset --hard && git checkout initial" && + test_config merge.verifySignatures true && + test_must_fail git merge --ff-only side-untrusted 2>mergeerror && + test_i18ngrep "has an untrusted GPG signature" mergeerror +' + test_expect_success GPG 'merge signed commit with verification' ' + test_when_finished "git reset --hard && git checkout initial" && git merge --verbose --ff-only --verify-signatures side-signed >mergeoutput && test_i18ngrep "has a good GPG signature" mergeoutput ' +test_expect_success GPG 'merge signed commit with merge.verifySignatures=true' ' + test_when_finished "git reset --hard && git checkout initial" && + test_config merge.verifySignatures true && + git merge --verbose --ff-only side-signed >mergeoutput && + test_i18ngrep "has a good GPG signature" mergeoutput +' + test_expect_success GPG 'merge commit with bad signature without verification' ' + test_when_finished "git reset --hard && git checkout initial" && + git merge $(cat forged.commit) +' + +test_expect_success GPG 'merge commit with bad signature with merge.verifySignatures=false' ' + test_when_finished "git reset --hard && git checkout initial" && + test_config merge.verifySignatures false && git merge $(cat forged.commit) ' +test_expect_success GPG 'merge commit with bad signature with merge.verifySignatures=true and --no-verify-signatures' ' + test_when_finished "git reset --hard && git checkout initial" && + test_config merge.verifySignatures true && + git merge --no-verify-signatures $(cat forged.commit) +' + test_done diff --git a/t/t7614-merge-signoff.sh b/t/t7614-merge-signoff.sh new file mode 100755 index 0000000000..c1b8446f49 --- /dev/null +++ b/t/t7614-merge-signoff.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +test_description='git merge --signoff + +This test runs git merge --signoff and makes sure that it works. +' + +. ./test-lib.sh + +# Setup test files +test_setup() { + # Expected commit message after merge --signoff + cat >expected-signed <<EOF && +Merge branch 'master' into other-branch + +Signed-off-by: $(git var GIT_COMMITTER_IDENT | sed -e "s/>.*/>/") +EOF + + # Expected commit message after merge without --signoff (or with --no-signoff) + cat >expected-unsigned <<EOF && +Merge branch 'master' into other-branch +EOF + + # Initial commit and feature branch to merge master into it. + git commit --allow-empty -m "Initial empty commit" && + git checkout -b other-branch && + test_commit other-branch file1 1 +} + +# Setup repository, files & feature branch +# This step must be run if You want to test 2,3 or 4 +# Order of 2,3,4 is not important, but 1 must be run before +# For example `-r 1,4` or `-r 1,4,2 -v` etc +# But not `-r 2` or `-r 4,3,2,1` +test_expect_success 'setup' ' + test_setup +' + +# Test with --signoff flag +test_expect_success 'git merge --signoff adds a sign-off line' ' + git checkout master && + test_commit master-branch-2 file2 2 && + git checkout other-branch && + git merge master --signoff --no-edit && + git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && + test_cmp expected-signed actual +' + +# Test without --signoff flag +test_expect_success 'git merge does not add a sign-off line' ' + git checkout master && + test_commit master-branch-3 file3 3 && + git checkout other-branch && + git merge master --no-edit && + git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && + test_cmp expected-unsigned actual +' + +# Test for --no-signoff flag +test_expect_success 'git merge --no-signoff flag cancels --signoff flag' ' + git checkout master && + test_commit master-branch-4 file4 4 && + git checkout other-branch && + git merge master --no-edit --signoff --no-signoff && + git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && + test_cmp expected-unsigned actual +' + +test_done diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh index 021c5479bd..6061a04147 100755 --- a/t/t7700-repack.sh +++ b/t/t7700-repack.sh @@ -69,7 +69,7 @@ test_expect_success 'writing bitmaps via config can duplicate .keep objects' ' test_expect_success 'loose objects in alternate ODB are not repacked' ' mkdir alt_objects && - echo `pwd`/alt_objects > .git/objects/info/alternates && + echo $(pwd)/alt_objects > .git/objects/info/alternates && echo content3 > file3 && objsha1=$(GIT_OBJECT_DIRECTORY=alt_objects git hash-object -w file3) && git add file3 && @@ -168,7 +168,7 @@ test_expect_success 'packed unreachable obs in alternate ODB are not loosened' ' ' test_expect_success 'local packed unreachable obs that exist in alternate ODB are not loosened' ' - echo `pwd`/alt_objects > .git/objects/info/alternates && + echo $(pwd)/alt_objects > .git/objects/info/alternates && echo "$csha1" | git pack-objects --non-empty --all --reflog pack && rm -f .git/objects/pack/* && mv pack-* .git/objects/pack/ && diff --git a/t/t7701-repack-unpack-unreachable.sh b/t/t7701-repack-unpack-unreachable.sh index b66e383866..8a586ab021 100755 --- a/t/t7701-repack-unpack-unreachable.sh +++ b/t/t7701-repack-unpack-unreachable.sh @@ -90,7 +90,7 @@ test_expect_success 'unpacked objects receive timestamp of pack file' ' tmppack=".git/objects/pack/tmp_pack" && ln "$packfile" "$tmppack" && git repack -A -l -d && - test-chmtime -v +0 "$tmppack" "$fsha1path" "$csha1path" "$tsha1path" \ + test-tool chmtime -v +0 "$tmppack" "$fsha1path" "$csha1path" "$tsha1path" \ > mtimes && compare_mtimes < mtimes ' @@ -103,7 +103,7 @@ test_expect_success 'do not bother loosening old objects' ' git prune-packed && git cat-file -p $obj1 && git cat-file -p $obj2 && - test-chmtime =-86400 .git/objects/pack/pack-$pack2.pack && + test-tool chmtime =-86400 .git/objects/pack/pack-$pack2.pack && git repack -A -d --unpack-unreachable=1.hour.ago && git cat-file -p $obj1 && test_must_fail git cat-file -p $obj2 @@ -117,9 +117,37 @@ test_expect_success 'keep packed objects found only in index' ' git reset HEAD^ && git reflog expire --expire=now --all && git add file && - test-chmtime =-86400 .git/objects/pack/* && + test-tool chmtime =-86400 .git/objects/pack/* && git gc --prune=1.hour.ago && git cat-file blob :file ' +test_expect_success 'repack -k keeps unreachable packed objects' ' + # create packed-but-unreachable object + sha1=$(echo unreachable-packed | git hash-object -w --stdin) && + pack=$(echo $sha1 | git pack-objects .git/objects/pack/pack) && + git prune-packed && + + # -k should keep it + git repack -adk && + git cat-file -p $sha1 && + + # and double check that without -k it would have been removed + git repack -ad && + test_must_fail git cat-file -p $sha1 +' + +test_expect_success 'repack -k packs unreachable loose objects' ' + # create loose unreachable object + sha1=$(echo would-be-deleted-loose | git hash-object -w --stdin) && + objpath=.git/objects/$(echo $sha1 | sed "s,..,&/,") && + test_path_is_file $objpath && + + # and confirm that the loose object goes away, but we can + # still access it (ergo, it is packed) + git repack -adk && + test_path_is_missing $objpath && + git cat-file -p $sha1 +' + test_done diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index ea35a0241c..668bbee73c 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -20,11 +20,23 @@ difftool_test_setup () prompt_given () { prompt="$1" - test "$prompt" = "Launch 'test-tool' [Y/n]: branch" + test "$prompt" = "Launch 'test-tool' [Y/n]? branch" } +test_expect_success 'basic usage requires no repo' ' + test_expect_code 129 git difftool -h >output && + test_i18ngrep ^usage: output && + # create a ceiling directory to prevent Git from finding a repo + mkdir -p not/repo && + test_when_finished rm -r not && + test_expect_code 129 \ + env GIT_CEILING_DIRECTORIES="$(pwd)/not" \ + git -C not/repo difftool -h >output && + test_i18ngrep ^usage: output +' + # Create a file on master and change it on branch -test_expect_success PERL 'setup' ' +test_expect_success 'setup' ' echo master >file && git add file && git commit -m "added file" && @@ -36,7 +48,7 @@ test_expect_success PERL 'setup' ' ' # Configure a custom difftool.<tool>.cmd and use it -test_expect_success PERL 'custom commands' ' +test_expect_success 'custom commands' ' difftool_test_setup && test_config difftool.test-tool.cmd "cat \"\$REMOTE\"" && echo master >expect && @@ -49,21 +61,21 @@ test_expect_success PERL 'custom commands' ' test_cmp expect actual ' -test_expect_success PERL 'custom tool commands override built-ins' ' +test_expect_success 'custom tool commands override built-ins' ' test_config difftool.vimdiff.cmd "cat \"\$REMOTE\"" && echo master >expect && git difftool --tool vimdiff --no-prompt branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool ignores bad --tool values' ' +test_expect_success 'difftool ignores bad --tool values' ' : >expect && test_must_fail \ git difftool --no-prompt --tool=bad-tool branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool forwards arguments to diff' ' +test_expect_success 'difftool forwards arguments to diff' ' difftool_test_setup && >for-diff && git add for-diff && @@ -76,40 +88,40 @@ test_expect_success PERL 'difftool forwards arguments to diff' ' rm for-diff ' -test_expect_success PERL 'difftool ignores exit code' ' +test_expect_success 'difftool ignores exit code' ' test_config difftool.error.cmd false && git difftool -y -t error branch ' -test_expect_success PERL 'difftool forwards exit code with --trust-exit-code' ' +test_expect_success 'difftool forwards exit code with --trust-exit-code' ' test_config difftool.error.cmd false && test_must_fail git difftool -y --trust-exit-code -t error branch ' -test_expect_success PERL 'difftool forwards exit code with --trust-exit-code for built-ins' ' +test_expect_success 'difftool forwards exit code with --trust-exit-code for built-ins' ' test_config difftool.vimdiff.path false && test_must_fail git difftool -y --trust-exit-code -t vimdiff branch ' -test_expect_success PERL 'difftool honors difftool.trustExitCode = true' ' +test_expect_success 'difftool honors difftool.trustExitCode = true' ' test_config difftool.error.cmd false && test_config difftool.trustExitCode true && test_must_fail git difftool -y -t error branch ' -test_expect_success PERL 'difftool honors difftool.trustExitCode = false' ' +test_expect_success 'difftool honors difftool.trustExitCode = false' ' test_config difftool.error.cmd false && test_config difftool.trustExitCode false && git difftool -y -t error branch ' -test_expect_success PERL 'difftool ignores exit code with --no-trust-exit-code' ' +test_expect_success 'difftool ignores exit code with --no-trust-exit-code' ' test_config difftool.error.cmd false && test_config difftool.trustExitCode true && git difftool -y --no-trust-exit-code -t error branch ' -test_expect_success PERL 'difftool stops on error with --trust-exit-code' ' +test_expect_success 'difftool stops on error with --trust-exit-code' ' test_when_finished "rm -f for-diff .git/fail-right-file" && test_when_finished "git reset -- for-diff" && write_script .git/fail-right-file <<-\EOF && @@ -124,7 +136,13 @@ test_expect_success PERL 'difftool stops on error with --trust-exit-code' ' test_cmp expect actual ' -test_expect_success PERL 'difftool honors --gui' ' +test_expect_success 'difftool honors exit status if command not found' ' + test_config difftool.nonexistent.cmd i-dont-exist && + test_config difftool.trustExitCode false && + test_must_fail git difftool -y -t nonexistent branch +' + +test_expect_success 'difftool honors --gui' ' difftool_test_setup && test_config merge.tool bogus-tool && test_config diff.tool bogus-tool && @@ -135,7 +153,7 @@ test_expect_success PERL 'difftool honors --gui' ' test_cmp expect actual ' -test_expect_success PERL 'difftool --gui last setting wins' ' +test_expect_success 'difftool --gui last setting wins' ' difftool_test_setup && : >expect && git difftool --no-prompt --gui --no-gui >actual && @@ -149,7 +167,7 @@ test_expect_success PERL 'difftool --gui last setting wins' ' test_cmp expect actual ' -test_expect_success PERL 'difftool --gui works without configured diff.guitool' ' +test_expect_success 'difftool --gui works without configured diff.guitool' ' difftool_test_setup && echo branch >expect && git difftool --no-prompt --gui branch >actual && @@ -157,7 +175,7 @@ test_expect_success PERL 'difftool --gui works without configured diff.guitool' ' # Specify the diff tool using $GIT_DIFF_TOOL -test_expect_success PERL 'GIT_DIFF_TOOL variable' ' +test_expect_success 'GIT_DIFF_TOOL variable' ' difftool_test_setup && git config --unset diff.tool && echo branch >expect && @@ -167,7 +185,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL variable' ' # Test the $GIT_*_TOOL variables and ensure # that $GIT_DIFF_TOOL always wins unless --tool is specified -test_expect_success PERL 'GIT_DIFF_TOOL overrides' ' +test_expect_success 'GIT_DIFF_TOOL overrides' ' difftool_test_setup && test_config diff.tool bogus-tool && test_config merge.tool bogus-tool && @@ -185,7 +203,7 @@ test_expect_success PERL 'GIT_DIFF_TOOL overrides' ' # Test that we don't have to pass --no-prompt to difftool # when $GIT_DIFFTOOL_NO_PROMPT is true -test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' ' +test_expect_success 'GIT_DIFFTOOL_NO_PROMPT variable' ' difftool_test_setup && echo branch >expect && GIT_DIFFTOOL_NO_PROMPT=true git difftool branch >actual && @@ -194,7 +212,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' ' # git-difftool supports the difftool.prompt variable. # Test that GIT_DIFFTOOL_PROMPT can override difftool.prompt = false -test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' ' +test_expect_success 'GIT_DIFFTOOL_PROMPT variable' ' difftool_test_setup && test_config difftool.prompt false && echo >input && @@ -204,7 +222,7 @@ test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' ' ' # Test that we don't have to pass --no-prompt when difftool.prompt is false -test_expect_success PERL 'difftool.prompt config variable is false' ' +test_expect_success 'difftool.prompt config variable is false' ' difftool_test_setup && test_config difftool.prompt false && echo branch >expect && @@ -213,7 +231,7 @@ test_expect_success PERL 'difftool.prompt config variable is false' ' ' # Test that we don't have to pass --no-prompt when mergetool.prompt is false -test_expect_success PERL 'difftool merge.prompt = false' ' +test_expect_success 'difftool merge.prompt = false' ' difftool_test_setup && test_might_fail git config --unset difftool.prompt && test_config mergetool.prompt false && @@ -223,7 +241,7 @@ test_expect_success PERL 'difftool merge.prompt = false' ' ' # Test that the -y flag can override difftool.prompt = true -test_expect_success PERL 'difftool.prompt can overridden with -y' ' +test_expect_success 'difftool.prompt can overridden with -y' ' difftool_test_setup && test_config difftool.prompt true && echo branch >expect && @@ -232,7 +250,7 @@ test_expect_success PERL 'difftool.prompt can overridden with -y' ' ' # Test that the --prompt flag can override difftool.prompt = false -test_expect_success PERL 'difftool.prompt can overridden with --prompt' ' +test_expect_success 'difftool.prompt can overridden with --prompt' ' difftool_test_setup && test_config difftool.prompt false && echo >input && @@ -242,7 +260,7 @@ test_expect_success PERL 'difftool.prompt can overridden with --prompt' ' ' # Test that the last flag passed on the command-line wins -test_expect_success PERL 'difftool last flag wins' ' +test_expect_success 'difftool last flag wins' ' difftool_test_setup && echo branch >expect && git difftool --prompt --no-prompt branch >actual && @@ -255,7 +273,7 @@ test_expect_success PERL 'difftool last flag wins' ' # git-difftool falls back to git-mergetool config variables # so test that behavior here -test_expect_success PERL 'difftool + mergetool config variables' ' +test_expect_success 'difftool + mergetool config variables' ' test_config merge.tool test-tool && test_config mergetool.test-tool.cmd "cat \$LOCAL" && echo branch >expect && @@ -269,49 +287,49 @@ test_expect_success PERL 'difftool + mergetool config variables' ' test_cmp expect actual ' -test_expect_success PERL 'difftool.<tool>.path' ' +test_expect_success 'difftool.<tool>.path' ' test_config difftool.tkdiff.path echo && git difftool --tool=tkdiff --no-prompt branch >output && - lines=$(grep file output | wc -l) && - test "$lines" -eq 1 + grep file output >grep-output && + test_line_count = 1 grep-output ' -test_expect_success PERL 'difftool --extcmd=cat' ' +test_expect_success 'difftool --extcmd=cat' ' echo branch >expect && echo master >>expect && git difftool --no-prompt --extcmd=cat branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd cat' ' +test_expect_success 'difftool --extcmd cat' ' echo branch >expect && echo master >>expect && git difftool --no-prompt --extcmd=cat branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool -x cat' ' +test_expect_success 'difftool -x cat' ' echo branch >expect && echo master >>expect && git difftool --no-prompt -x cat branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd echo arg1' ' +test_expect_success 'difftool --extcmd echo arg1' ' echo file >expect && git difftool --no-prompt \ --extcmd sh\ -c\ \"echo\ \$1\" branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd cat arg1' ' +test_expect_success 'difftool --extcmd cat arg1' ' echo master >expect && git difftool --no-prompt \ --extcmd sh\ -c\ \"cat\ \$1\" branch >actual && test_cmp expect actual ' -test_expect_success PERL 'difftool --extcmd cat arg2' ' +test_expect_success 'difftool --extcmd cat arg2' ' echo branch >expect && git difftool --no-prompt \ --extcmd sh\ -c\ \"cat\ \$2\" branch >actual && @@ -319,7 +337,7 @@ test_expect_success PERL 'difftool --extcmd cat arg2' ' ' # Create a second file on master and a different version on branch -test_expect_success PERL 'setup with 2 files different' ' +test_expect_success 'setup with 2 files different' ' echo m2 >file2 && git add file2 && git commit -m "added file2" && @@ -331,7 +349,7 @@ test_expect_success PERL 'setup with 2 files different' ' git checkout master ' -test_expect_success PERL 'say no to the first file' ' +test_expect_success 'say no to the first file' ' (echo n && echo) >input && git difftool -x cat branch <input >output && grep m2 output && @@ -340,7 +358,7 @@ test_expect_success PERL 'say no to the first file' ' ! grep branch output ' -test_expect_success PERL 'say no to the second file' ' +test_expect_success 'say no to the second file' ' (echo && echo n) >input && git difftool -x cat branch <input >output && grep master output && @@ -349,7 +367,7 @@ test_expect_success PERL 'say no to the second file' ' ! grep br2 output ' -test_expect_success PERL 'ending prompt input with EOF' ' +test_expect_success 'ending prompt input with EOF' ' git difftool -x cat branch </dev/null >output && ! grep master output && ! grep branch output && @@ -357,29 +375,49 @@ test_expect_success PERL 'ending prompt input with EOF' ' ! grep br2 output ' -test_expect_success PERL 'difftool --tool-help' ' +test_expect_success 'difftool --tool-help' ' git difftool --tool-help >output && grep tool output ' -test_expect_success PERL 'setup change in subdirectory' ' +test_expect_success 'setup change in subdirectory' ' git checkout master && mkdir sub && echo master >sub/sub && git add sub/sub && git commit -m "added sub/sub" && + git tag v1 && echo test >>file && echo test >>sub/sub && git add file sub/sub && git commit -m "modified both" ' +test_expect_success 'difftool -d with growing paths' ' + a=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa && + git init growing && + ( + cd growing && + echo "test -f \"\$2/b\"" | write_script .git/test-for-b.sh && + one=$(printf 1 | git hash-object -w --stdin) && + two=$(printf 2 | git hash-object -w --stdin) && + git update-index --add \ + --cacheinfo 100644,$one,$a --cacheinfo 100644,$two,b && + tree1=$(git write-tree) && + git update-index --add \ + --cacheinfo 100644,$two,$a --cacheinfo 100644,$one,b && + tree2=$(git write-tree) && + git checkout -- $a && + git difftool -d --extcmd .git/test-for-b.sh $tree1 $tree2 + ) +' + run_dir_diff_test () { - test_expect_success PERL "$1 --no-symlinks" " + test_expect_success "$1 --no-symlinks" " symlinks=--no-symlinks && $2 " - test_expect_success PERL,SYMLINKS "$1 --symlinks" " + test_expect_success SYMLINKS "$1 --symlinks" " symlinks=--symlinks && $2 " @@ -403,12 +441,70 @@ run_dir_diff_test 'difftool --dir-diff ignores --prompt' ' grep file output ' -run_dir_diff_test 'difftool --dir-diff from subdirectory' ' +run_dir_diff_test 'difftool --dir-diff branch from subdirectory' ' ( cd sub && git difftool --dir-diff $symlinks --extcmd ls branch >output && + # "sub" must only exist in "right" + # "file" and "file2" must be listed in both "left" and "right" + grep sub output >sub-output && + test_line_count = 1 sub-output && + grep file"$" output >file-output && + test_line_count = 2 file-output && + grep file2 output >file2-output && + test_line_count = 2 file2-output + ) +' + +run_dir_diff_test 'difftool --dir-diff v1 from subdirectory' ' + ( + cd sub && + git difftool --dir-diff $symlinks --extcmd ls v1 >output && + # "sub" and "file" exist in both v1 and HEAD. + # "file2" is unchanged. + grep sub output >sub-output && + test_line_count = 2 sub-output && + grep file output >file-output && + test_line_count = 2 file-output && + ! grep file2 output + ) +' + +run_dir_diff_test 'difftool --dir-diff branch from subdirectory w/ pathspec' ' + ( + cd sub && + git difftool --dir-diff $symlinks --extcmd ls branch -- .>output && + # "sub" only exists in "right" + # "file" and "file2" must not be listed + grep sub output >sub-output && + test_line_count = 1 sub-output && + ! grep file output + ) +' + +run_dir_diff_test 'difftool --dir-diff v1 from subdirectory w/ pathspec' ' + ( + cd sub && + git difftool --dir-diff $symlinks --extcmd ls v1 -- .>output && + # "sub" exists in v1 and HEAD + # "file" is filtered out by the pathspec + grep sub output >sub-output && + test_line_count = 2 sub-output && + ! grep file output + ) +' + +run_dir_diff_test 'difftool --dir-diff from subdirectory with GIT_DIR set' ' + ( + GIT_DIR=$(pwd)/.git && + export GIT_DIR && + GIT_WORK_TREE=$(pwd) && + export GIT_WORK_TREE && + cd sub && + git difftool --dir-diff $symlinks --extcmd ls \ + branch -- sub >output && grep sub output && - grep file output + ! grep file output ) ' @@ -419,22 +515,45 @@ run_dir_diff_test 'difftool --dir-diff when worktree file is missing' ' grep file2 output ' +run_dir_diff_test 'difftool --dir-diff with unmerged files' ' + test_when_finished git reset --hard && + test_config difftool.echo.cmd "echo ok" && + git checkout -B conflict-a && + git checkout -B conflict-b && + git checkout conflict-a && + echo a >>file && + git add file && + git commit -m conflict-a && + git checkout conflict-b && + echo b >>file && + git add file && + git commit -m conflict-b && + git checkout master && + git merge conflict-a && + test_must_fail git merge conflict-b && + cat >expect <<-EOF && + ok + EOF + git difftool --dir-diff $symlinks -t echo >actual && + test_cmp expect actual +' + write_script .git/CHECK_SYMLINKS <<\EOF for f in file file2 sub/sub do echo "$f" - readlink "$2/$f" + ls -ld "$2/$f" | sed -e 's/.* -> //' done >actual EOF -test_expect_success PERL,SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' ' +test_expect_success SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' ' cat >expect <<-EOF && file - $(pwd)/file + $PWD/file file2 - $(pwd)/file2 + $PWD/file2 sub/sub - $(pwd)/sub/sub + $PWD/sub/sub EOF git difftool --dir-diff --symlink \ --extcmd "./.git/CHECK_SYMLINKS" branch HEAD && @@ -448,14 +567,14 @@ EOF run_dir_diff_test 'difftool --dir-diff syncs worktree with unstaged change' ' test_when_finished git reset --hard && echo "orig content" >file && - git difftool -d $symlinks --extcmd "$(pwd)/modify-right-file" branch && + git difftool -d $symlinks --extcmd "$PWD/modify-right-file" branch && echo "new content" >expect && test_cmp expect file ' run_dir_diff_test 'difftool --dir-diff syncs worktree without unstaged change' ' test_when_finished git reset --hard && - git difftool -d $symlinks --extcmd "$(pwd)/modify-right-file" branch && + git difftool -d $symlinks --extcmd "$PWD/modify-right-file" branch && echo "new content" >expect && test_cmp expect file ' @@ -464,9 +583,9 @@ write_script modify-file <<\EOF echo "new content" >file EOF -test_expect_success PERL 'difftool --no-symlinks does not overwrite working tree file ' ' +test_expect_success 'difftool --no-symlinks does not overwrite working tree file ' ' echo "orig content" >file && - git difftool --dir-diff --no-symlinks --extcmd "$(pwd)/modify-file" branch && + git difftool --dir-diff --no-symlinks --extcmd "$PWD/modify-file" branch && echo "new content" >expect && test_cmp expect file ' @@ -477,12 +596,12 @@ echo "tmp content" >"$2/file" && echo "$2" >tmpdir EOF -test_expect_success PERL 'difftool --no-symlinks detects conflict ' ' +test_expect_success 'difftool --no-symlinks detects conflict ' ' ( TMPDIR=$TRASH_DIRECTORY && export TMPDIR && echo "orig content" >file && - test_must_fail git difftool --dir-diff --no-symlinks --extcmd "$(pwd)/modify-both-files" branch && + test_must_fail git difftool --dir-diff --no-symlinks --extcmd "$PWD/modify-both-files" branch && echo "wt content" >expect && test_cmp expect file && echo "tmp content" >expect && @@ -490,18 +609,100 @@ test_expect_success PERL 'difftool --no-symlinks detects conflict ' ' ) ' -test_expect_success PERL 'difftool properly honors gitlink and core.worktree' ' +test_expect_success 'difftool properly honors gitlink and core.worktree' ' + test_when_finished rm -rf submod/ule && git submodule add ./. submod/ule && + test_config -C submod/ule diff.tool checktrees && + test_config -C submod/ule difftool.checktrees.cmd '\'' + test -d "$LOCAL" && test -d "$REMOTE" && echo good + '\'' && ( cd submod/ule && - test_config diff.tool checktrees && - test_config difftool.checktrees.cmd '\'' - test -d "$LOCAL" && test -d "$REMOTE" && echo good - '\'' && + echo good >expect && + git difftool --tool=checktrees --dir-diff HEAD~ >actual && + test_cmp expect actual && + rm -f expect actual + ) +' + +test_expect_success SYMLINKS 'difftool --dir-diff symlinked directories' ' + test_when_finished git reset --hard && + git init dirlinks && + ( + cd dirlinks && + git config diff.tool checktrees && + git config difftool.checktrees.cmd "echo good" && + mkdir foo && + : >foo/bar && + git add foo/bar && + test_commit symlink-one && + ln -s foo link && + git add link && + test_commit symlink-two && echo good >expect && git difftool --tool=checktrees --dir-diff HEAD~ >actual && test_cmp expect actual ) ' +test_expect_success SYMLINKS 'difftool --dir-diff handles modified symlinks' ' + test_when_finished git reset --hard && + touch b && + ln -s b c && + git add b c && + test_tick && + git commit -m initial && + touch d && + rm c && + ln -s d c && + cat >expect <<-EOF && + b + c + + c + EOF + git difftool --symlinks --dir-diff --extcmd ls >output && + grep -v ^/ output >actual && + test_cmp expect actual && + + git difftool --no-symlinks --dir-diff --extcmd ls >output && + grep -v ^/ output >actual && + test_cmp expect actual && + + # The left side contains symlink "c" that points to "b" + test_config difftool.cat.cmd "cat \$LOCAL/c" && + printf "%s\n" b >expect && + + git difftool --symlinks --dir-diff --tool cat >actual && + test_cmp expect actual && + + git difftool --symlinks --no-symlinks --dir-diff --tool cat >actual && + test_cmp expect actual && + + # The right side contains symlink "c" that points to "d" + test_config difftool.cat.cmd "cat \$REMOTE/c" && + printf "%s\n" d >expect && + + git difftool --symlinks --dir-diff --tool cat >actual && + test_cmp expect actual && + + git difftool --no-symlinks --dir-diff --tool cat >actual && + test_cmp expect actual && + + # Deleted symlinks + rm -f c && + cat >expect <<-EOF && + b + c + + EOF + git difftool --symlinks --dir-diff --extcmd ls >output && + grep -v ^/ output >actual && + test_cmp expect actual && + + git difftool --no-symlinks --dir-diff --extcmd ls >output && + grep -v ^/ output >actual && + test_cmp expect actual +' + test_done diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh index 028ffe4a05..1797f632a3 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -9,7 +9,9 @@ test_description='git grep various. . ./test-lib.sh cat >hello.c <<EOF +#include <assert.h> #include <stdio.h> + int main(int argc, const char **argv) { printf("Hello world.\n"); @@ -37,6 +39,10 @@ test_expect_success setup ' echo "a+bc" echo "abc" } >ab && + { + echo d && + echo 0 + } >d0 && echo vvv >v && echo ww w >w && echo x x xx x >x && @@ -54,6 +60,18 @@ test_expect_success setup ' echo " line with leading space3" echo "line without leading space2" } >space && + cat >hello.ps1 <<-\EOF && + # No-op. + function dummy() {} + + # Say hello. + function hello() { + echo "Hello world." + } # hello + + # Still a no-op. + function dummy() {} + EOF git add . && test_tick && git commit -m initial @@ -175,7 +193,7 @@ do test_expect_success "grep -c $L (no /dev/null)" ' ! git grep -c test $H | grep /dev/null - ' + ' test_expect_success "grep --max-depth -1 $L" ' { @@ -269,12 +287,16 @@ do test_cmp expected actual ' - test_expect_success LIBPCRE "grep $L with grep.patterntype=perl" ' + test_expect_success PCRE "grep $L with grep.patterntype=perl" ' echo "${HC}ab:a+b*c" >expected && git -c grep.patterntype=perl grep "a\x{2b}b\x{2a}c" $H ab >actual && test_cmp expected actual ' + test_expect_success !PCRE "grep $L with grep.patterntype=perl errors without PCRE" ' + test_must_fail git -c grep.patterntype=perl grep "foo.*bar" + ' + test_expect_success "grep $L with grep.patternType=default and grep.extendedRegexp=true" ' echo "${HC}ab:abc" >expected && git \ @@ -353,7 +375,7 @@ test_expect_success 'grep -l -C' ' cat >expected <<EOF file:5 EOF -test_expect_success 'grep -l -C' ' +test_expect_success 'grep -c -C' ' git grep -c -C1 foo >actual && test_cmp expected actual ' @@ -364,6 +386,11 @@ test_expect_success 'grep -L -C' ' test_cmp expected actual ' +test_expect_success 'grep --files-without-match --quiet' ' + git grep --files-without-match --quiet nonexistent_string >actual && + test_cmp /dev/null actual +' + cat >expected <<EOF file:foo mmap bar_mmap EOF @@ -579,7 +606,7 @@ test_expect_success 'log grep (9)' ' ' test_expect_success 'log grep (9)' ' - git log -g --grep-reflog="commit: third" --author="non-existant" --pretty=tformat:%s >actual && + git log -g --grep-reflog="commit: third" --author="non-existent" --pretty=tformat:%s >actual && : >expect && test_cmp expect actual ' @@ -715,6 +742,7 @@ test_expect_success 'grep -p' ' cat >expected <<EOF hello.c-#include <stdio.h> +hello.c- hello.c=int main(int argc, const char **argv) hello.c-{ hello.c- printf("Hello world.\n"); @@ -741,17 +769,70 @@ test_expect_success 'grep -W' ' ' cat >expected <<EOF -hello.c= printf("Hello world.\n"); -hello.c: return 0; -hello.c- /* char ?? */ +hello.c-#include <assert.h> +hello.c:#include <stdio.h> EOF +test_expect_success 'grep -W shows no trailing empty lines' ' + git grep -W stdio >actual && + test_cmp expected actual +' + test_expect_success 'grep -W with userdiff' ' test_when_finished "rm -f .gitattributes" && - git config diff.custom.xfuncname "(printf.*|})$" && - echo "hello.c diff=custom" >.gitattributes && - git grep -W return >actual && - test_cmp expected actual + git config diff.custom.xfuncname "^function .*$" && + echo "hello.ps1 diff=custom" >.gitattributes && + git grep -W echo >function-context-userdiff-actual +' + +test_expect_success ' includes preceding comment' ' + grep "# Say hello" function-context-userdiff-actual +' + +test_expect_success ' includes function line' ' + grep "=function hello" function-context-userdiff-actual +' + +test_expect_success ' includes matching line' ' + grep ": echo" function-context-userdiff-actual +' + +test_expect_success ' includes last line of the function' ' + grep "} # hello" function-context-userdiff-actual +' + +for threads in $(test_seq 0 10) +do + test_expect_success "grep --threads=$threads & -c grep.threads=$threads" " + git grep --threads=$threads . >actual.$threads && + if test $threads -ge 1 + then + test_cmp actual.\$(($threads - 1)) actual.$threads + fi && + git -c grep.threads=$threads grep . >actual.$threads && + if test $threads -ge 1 + then + test_cmp actual.\$(($threads - 1)) actual.$threads + fi + " +done + +test_expect_success !PTHREADS,C_LOCALE_OUTPUT 'grep --threads=N or pack.threads=N warns when no pthreads' ' + git grep --threads=2 Hello hello_world 2>err && + grep ^warning: err >warnings && + test_line_count = 1 warnings && + grep -F "no threads support, ignoring --threads" err && + git -c grep.threads=2 grep Hello hello_world 2>err && + grep ^warning: err >warnings && + test_line_count = 1 warnings && + grep -F "no threads support, ignoring grep.threads" err && + git -c grep.threads=2 grep --threads=4 Hello hello_world 2>err && + grep ^warning: err >warnings && + test_line_count = 2 warnings && + grep -F "no threads support, ignoring --threads" err && + grep -F "no threads support, ignoring grep.threads" err && + git -c grep.threads=0 grep --threads=0 Hello hello_world 2>err && + test_line_count = 0 err ' test_expect_success 'grep from a subdirectory to search wider area (1)' ' @@ -791,12 +872,12 @@ test_expect_success 'outside of git repository' ' } >non/expect.full && echo file2:world >non/expect.sub && ( - GIT_CEILING_DIRECTORIES="$(pwd)/non/git" && + GIT_CEILING_DIRECTORIES="$(pwd)/non" && export GIT_CEILING_DIRECTORIES && cd non/git && test_must_fail git grep o && git grep --no-index o >../actual.full && - test_cmp ../expect.full ../actual.full + test_cmp ../expect.full ../actual.full && cd sub && test_must_fail git grep o && git grep --no-index o >../../actual.sub && @@ -805,7 +886,7 @@ test_expect_success 'outside of git repository' ' echo ".*o*" >non/git/.gitignore && ( - GIT_CEILING_DIRECTORIES="$(pwd)/non/git" && + GIT_CEILING_DIRECTORIES="$(pwd)/non" && export GIT_CEILING_DIRECTORIES && cd non/git && test_must_fail git grep o && @@ -813,7 +894,7 @@ test_expect_success 'outside of git repository' ' test_cmp ../expect.full ../actual.full && { - echo ".gitignore:.*o*" + echo ".gitignore:.*o*" && cat ../expect.full } >../expect.with.ignored && git grep --no-index --no-exclude o >../actual.full && @@ -821,6 +902,47 @@ test_expect_success 'outside of git repository' ' ) ' +test_expect_success 'outside of git repository with fallbackToNoIndex' ' + rm -fr non && + mkdir -p non/git/sub && + echo hello >non/git/file1 && + echo world >non/git/sub/file2 && + cat <<-\EOF >non/expect.full && + file1:hello + sub/file2:world + EOF + echo file2:world >non/expect.sub && + ( + GIT_CEILING_DIRECTORIES="$(pwd)/non" && + export GIT_CEILING_DIRECTORIES && + cd non/git && + test_must_fail git -c grep.fallbackToNoIndex=false grep o && + git -c grep.fallbackToNoIndex=true grep o >../actual.full && + test_cmp ../expect.full ../actual.full && + cd sub && + test_must_fail git -c grep.fallbackToNoIndex=false grep o && + git -c grep.fallbackToNoIndex=true grep o >../../actual.sub && + test_cmp ../../expect.sub ../../actual.sub + ) && + + echo ".*o*" >non/git/.gitignore && + ( + GIT_CEILING_DIRECTORIES="$(pwd)/non" && + export GIT_CEILING_DIRECTORIES && + cd non/git && + test_must_fail git -c grep.fallbackToNoIndex=false grep o && + git -c grep.fallbackToNoIndex=true grep --exclude-standard o >../actual.full && + test_cmp ../expect.full ../actual.full && + + { + echo ".gitignore:.*o*" && + cat ../expect.full + } >../expect.with.ignored && + git -c grep.fallbackToNoIndex grep --no-exclude o >../actual.full && + test_cmp ../expect.with.ignored ../actual.full + ) +' + test_expect_success 'inside git repository but with --no-index' ' rm -fr is && mkdir -p is/git/sub && @@ -864,6 +986,33 @@ test_expect_success 'inside git repository but with --no-index' ' ) ' +test_expect_success 'grep --no-index descends into repos, but not .git' ' + rm -fr non && + mkdir -p non/git && + ( + GIT_CEILING_DIRECTORIES="$(pwd)/non" && + export GIT_CEILING_DIRECTORIES && + cd non/git && + + echo magic >file && + git init repo && + ( + cd repo && + echo magic >file && + git add file && + git commit -m foo && + echo magic >.git/file + ) && + + cat >expect <<-\EOF && + file + repo/file + EOF + git grep -l --no-index magic >actual && + test_cmp expect actual + ) +' + test_expect_success 'setup double-dash tests' ' cat >double-dash <<EOF && -- @@ -897,21 +1046,101 @@ test_expect_success 'grep -e -- -- path' ' test_cmp expected actual ' +test_expect_success 'dashdash disambiguates rev as rev' ' + test_when_finished "rm -f master" && + echo content >master && + echo master:hello.c >expect && + git grep -l o master -- hello.c >actual && + test_cmp expect actual +' + +test_expect_success 'dashdash disambiguates pathspec as pathspec' ' + test_when_finished "git rm -f master" && + echo content >master && + git add master && + echo master:content >expect && + git grep o -- master >actual && + test_cmp expect actual +' + +test_expect_success 'report bogus arg without dashdash' ' + test_must_fail git grep o does-not-exist +' + +test_expect_success 'report bogus rev with dashdash' ' + test_must_fail git grep o hello.c -- +' + +test_expect_success 'allow non-existent path with dashdash' ' + # We need a real match so grep exits with success. + tree=$(git ls-tree HEAD | + sed s/hello.c/not-in-working-tree/ | + git mktree) && + git grep o "$tree" -- not-in-working-tree +' + +test_expect_success 'grep --no-index pattern -- path' ' + rm -fr non && + mkdir -p non/git && + ( + GIT_CEILING_DIRECTORIES="$(pwd)/non" && + export GIT_CEILING_DIRECTORIES && + cd non/git && + echo hello >hello && + echo goodbye >goodbye && + echo hello:hello >expect && + git grep --no-index o -- hello >actual && + test_cmp expect actual + ) +' + +test_expect_success 'grep --no-index complains of revs' ' + test_must_fail git grep --no-index o master -- 2>err && + test_i18ngrep "cannot be used with revs" err +' + +test_expect_success 'grep --no-index prefers paths to revs' ' + test_when_finished "rm -f master" && + echo content >master && + echo master:content >expect && + git grep --no-index o master >actual && + test_cmp expect actual +' + +test_expect_success 'grep --no-index does not "diagnose" revs' ' + test_must_fail git grep --no-index o :1:hello.c 2>err && + test_i18ngrep ! -i "did you mean" err +' + cat >expected <<EOF hello.c:int main(int argc, const char **argv) hello.c: printf("Hello world.\n"); EOF -test_expect_success LIBPCRE 'grep --perl-regexp pattern' ' +test_expect_success PCRE 'grep --perl-regexp pattern' ' git grep --perl-regexp "\p{Ps}.*?\p{Pe}" hello.c >actual && test_cmp expected actual ' -test_expect_success LIBPCRE 'grep -P pattern' ' +test_expect_success !PCRE 'grep --perl-regexp pattern errors without PCRE' ' + test_must_fail git grep --perl-regexp "foo.*bar" +' + +test_expect_success PCRE 'grep -P pattern' ' git grep -P "\p{Ps}.*?\p{Pe}" hello.c >actual && test_cmp expected actual ' +test_expect_success LIBPCRE2 "grep -P with (*NO_JIT) doesn't error out" ' + git grep -P "(*NO_JIT)\p{Ps}.*?\p{Pe}" hello.c >actual && + test_cmp expected actual + +' + +test_expect_success !PCRE 'grep -P pattern errors without PCRE' ' + test_must_fail git grep -P "foo.*bar" +' + test_expect_success 'grep pattern with grep.extendedRegexp=true' ' >empty && test_must_fail git -c grep.extendedregexp=true \ @@ -919,13 +1148,13 @@ test_expect_success 'grep pattern with grep.extendedRegexp=true' ' test_cmp empty actual ' -test_expect_success LIBPCRE 'grep -P pattern with grep.extendedRegexp=true' ' +test_expect_success PCRE 'grep -P pattern with grep.extendedRegexp=true' ' git -c grep.extendedregexp=true \ grep -P "\p{Ps}.*?\p{Pe}" hello.c >actual && test_cmp expected actual ' -test_expect_success LIBPCRE 'grep -P -v pattern' ' +test_expect_success PCRE 'grep -P -v pattern' ' { echo "ab:a+b*c" echo "ab:a+bc" @@ -934,7 +1163,7 @@ test_expect_success LIBPCRE 'grep -P -v pattern' ' test_cmp expected actual ' -test_expect_success LIBPCRE 'grep -P -i pattern' ' +test_expect_success PCRE 'grep -P -i pattern' ' cat >expected <<-EOF && hello.c: printf("Hello world.\n"); EOF @@ -942,7 +1171,7 @@ test_expect_success LIBPCRE 'grep -P -i pattern' ' test_cmp expected actual ' -test_expect_success LIBPCRE 'grep -P -w pattern' ' +test_expect_success PCRE 'grep -P -w pattern' ' { echo "hello_world:Hello world" echo "hello_world:HeLLo world" @@ -951,6 +1180,13 @@ test_expect_success LIBPCRE 'grep -P -w pattern' ' test_cmp expected actual ' +test_expect_success PCRE 'grep -P backreferences work (the PCRE NO_AUTO_CAPTURE flag is not set)' ' + git grep -P -h "(?P<one>.)(?P=one)" hello_world >actual && + test_cmp hello_world actual && + git grep -P -h "(.)\1" hello_world >actual && + test_cmp hello_world actual +' + test_expect_success 'grep -G invalidpattern properly dies ' ' test_must_fail git grep -G "a[" ' @@ -967,11 +1203,11 @@ test_expect_success 'grep invalidpattern properly dies with grep.patternType=ext test_must_fail git -c grep.patterntype=extended grep "a[" ' -test_expect_success LIBPCRE 'grep -P invalidpattern properly dies ' ' +test_expect_success PCRE 'grep -P invalidpattern properly dies ' ' test_must_fail git grep -P "a[" ' -test_expect_success LIBPCRE 'grep invalidpattern properly dies with grep.patternType=perl' ' +test_expect_success PCRE 'grep invalidpattern properly dies with grep.patternType=perl' ' test_must_fail git -c grep.patterntype=perl grep "a[" ' @@ -1024,40 +1260,40 @@ test_expect_success 'grep pattern with grep.patternType=fixed, =basic, =extended ' test_expect_success 'grep -G -F -P -E pattern' ' - >empty && - test_must_fail git grep -G -F -P -E "a\x{2b}b\x{2a}c" ab >actual && - test_cmp empty actual + echo "d0:d" >expected && + git grep -G -F -P -E "[\d]" d0 >actual && + test_cmp expected actual ' test_expect_success 'grep pattern with grep.patternType=fixed, =basic, =perl, =extended' ' - >empty && - test_must_fail git \ + echo "d0:d" >expected && + git \ -c grep.patterntype=fixed \ -c grep.patterntype=basic \ -c grep.patterntype=perl \ -c grep.patterntype=extended \ - grep "a\x{2b}b\x{2a}c" ab >actual && - test_cmp empty actual + grep "[\d]" d0 >actual && + test_cmp expected actual ' -test_expect_success LIBPCRE 'grep -G -F -E -P pattern' ' - echo "ab:a+b*c" >expected && - git grep -G -F -E -P "a\x{2b}b\x{2a}c" ab >actual && +test_expect_success PCRE 'grep -G -F -E -P pattern' ' + echo "d0:0" >expected && + git grep -G -F -E -P "[\d]" d0 >actual && test_cmp expected actual ' -test_expect_success LIBPCRE 'grep pattern with grep.patternType=fixed, =basic, =extended, =perl' ' - echo "ab:a+b*c" >expected && +test_expect_success PCRE 'grep pattern with grep.patternType=fixed, =basic, =extended, =perl' ' + echo "d0:0" >expected && git \ -c grep.patterntype=fixed \ -c grep.patterntype=basic \ -c grep.patterntype=extended \ -c grep.patterntype=perl \ - grep "a\x{2b}b\x{2a}c" ab >actual && + grep "[\d]" d0 >actual && test_cmp expected actual ' -test_expect_success LIBPCRE 'grep -P pattern with grep.patternType=fixed' ' +test_expect_success PCRE 'grep -P pattern with grep.patternType=fixed' ' echo "ab:a+b*c" >expected && git \ -c grep.patterntype=fixed \ @@ -1164,8 +1400,8 @@ test_expect_success 'grep --heading' ' cat >expected <<EOF <BOLD;GREEN>hello.c<RESET> -2:int main(int argc, const <BLACK;BYELLOW>char<RESET> **argv) -6: /* <BLACK;BYELLOW>char<RESET> ?? */ +4:int main(int argc, const <BLACK;BYELLOW>char<RESET> **argv) +8: /* <BLACK;BYELLOW>char<RESET> ?? */ <BOLD;GREEN>hello_world<RESET> 3:Hel<BLACK;BYELLOW>lo_w<RESET>orld @@ -1192,12 +1428,12 @@ space: line with leading space2 space: line with leading space3 EOF -test_expect_success LIBPCRE 'grep -E "^ "' ' +test_expect_success PCRE 'grep -E "^ "' ' git grep -E "^ " space >actual && test_cmp expected actual ' -test_expect_success LIBPCRE 'grep -P "^ "' ' +test_expect_success PCRE 'grep -P "^ "' ' git grep -P "^ " space >actual && test_cmp expected actual ' @@ -1272,7 +1508,7 @@ test_expect_success 'grep --color -e A --and --not -e B with context' ' ' cat >expected <<EOF -hello.c-#include <stdio.h> +hello.c- hello.c=int main(int argc, const char **argv) hello.c-{ hello.c: pr<RED>int<RESET>f("<RED>Hello<RESET> world.\n"); @@ -1296,4 +1532,62 @@ test_expect_success 'grep --color -e A --and -e B -p with context' ' test_cmp expected actual ' +test_expect_success 'grep can find things only in the work tree' ' + : >work-tree-only && + git add work-tree-only && + test_when_finished "git rm -f work-tree-only" && + echo "find in work tree" >work-tree-only && + git grep --quiet "find in work tree" && + test_must_fail git grep --quiet --cached "find in work tree" && + test_must_fail git grep --quiet "find in work tree" HEAD +' + +test_expect_success 'grep can find things only in the work tree (i-t-a)' ' + echo "intend to add this" >intend-to-add && + git add -N intend-to-add && + test_when_finished "git rm -f intend-to-add" && + git grep --quiet "intend to add this" && + test_must_fail git grep --quiet --cached "intend to add this" && + test_must_fail git grep --quiet "intend to add this" HEAD +' + +test_expect_success 'grep does not search work tree with assume unchanged' ' + echo "intend to add this" >intend-to-add && + git add -N intend-to-add && + git update-index --assume-unchanged intend-to-add && + test_when_finished "git rm -f intend-to-add" && + test_must_fail git grep --quiet "intend to add this" && + test_must_fail git grep --quiet --cached "intend to add this" && + test_must_fail git grep --quiet "intend to add this" HEAD +' + +test_expect_success 'grep can find things only in the index' ' + echo "only in the index" >cache-this && + git add cache-this && + rm cache-this && + test_when_finished "git rm --cached cache-this" && + test_must_fail git grep --quiet "only in the index" && + git grep --quiet --cached "only in the index" && + test_must_fail git grep --quiet "only in the index" HEAD +' + +test_expect_success 'grep does not report i-t-a with -L --cached' ' + echo "intend to add this" >intend-to-add && + git add -N intend-to-add && + test_when_finished "git rm -f intend-to-add" && + git ls-files | grep -v "^intend-to-add\$" >expected && + git grep -L --cached "nonexistent_string" >actual && + test_cmp expected actual +' + +test_expect_success 'grep does not report i-t-a and assume unchanged with -L' ' + echo "intend to add this" >intend-to-add-assume-unchanged && + git add -N intend-to-add-assume-unchanged && + test_when_finished "git rm -f intend-to-add-assume-unchanged" && + git update-index --assume-unchanged intend-to-add-assume-unchanged && + git ls-files | grep -v "^intend-to-add-assume-unchanged\$" >expected && + git grep -L "nonexistent_string" >actual && + test_cmp expected actual +' + test_done diff --git a/t/t7812-grep-icase-non-ascii.sh b/t/t7812-grep-icase-non-ascii.sh new file mode 100755 index 0000000000..0c685d3598 --- /dev/null +++ b/t/t7812-grep-icase-non-ascii.sh @@ -0,0 +1,56 @@ +#!/bin/sh + +test_description='grep icase on non-English locales' + +. ./lib-gettext.sh + +test_expect_success GETTEXT_LOCALE 'setup' ' + test_write_lines "TILRAUN: Halló Heimur!" >file && + git add file && + LC_ALL="$is_IS_locale" && + export LC_ALL +' + +test_have_prereq GETTEXT_LOCALE && +test-tool regex "HALLÓ" "Halló" ICASE && +test_set_prereq REGEX_LOCALE + +test_expect_success REGEX_LOCALE 'grep literal string, no -F' ' + git grep -i "TILRAUN: Halló Heimur!" && + git grep -i "TILRAUN: HALLÓ HEIMUR!" +' + +test_expect_success GETTEXT_LOCALE,PCRE 'grep pcre utf-8 icase' ' + git grep --perl-regexp "TILRAUN: H.lló Heimur!" && + git grep --perl-regexp -i "TILRAUN: H.lló Heimur!" && + git grep --perl-regexp -i "TILRAUN: H.LLÓ HEIMUR!" +' + +test_expect_success GETTEXT_LOCALE,PCRE 'grep pcre utf-8 string with "+"' ' + test_write_lines "TILRAUN: Hallóó Heimur!" >file2 && + git add file2 && + git grep -l --perl-regexp "TILRAUN: H.lló+ Heimur!" >actual && + echo file >expected && + echo file2 >>expected && + test_cmp expected actual +' + +test_expect_success REGEX_LOCALE 'grep literal string, with -F' ' + git grep -i -F "TILRAUN: Halló Heimur!" && + git grep -i -F "TILRAUN: HALLÓ HEIMUR!" +' + +test_expect_success REGEX_LOCALE 'grep string with regex, with -F' ' + test_write_lines "TILRAUN: Halló Heimur [abc]!" >file3 && + git add file3 && + git grep -i -F "TILRAUN: Halló Heimur [abc]!" file3 +' + +test_expect_success REGEX_LOCALE 'pickaxe -i on non-ascii' ' + git commit -m first && + git log --format=%f -i -S"TILRAUN: HALLÓ HEIMUR!" >actual && + echo first >expected && + test_cmp expected actual +' + +test_done diff --git a/t/t7813-grep-icase-iso.sh b/t/t7813-grep-icase-iso.sh new file mode 100755 index 0000000000..701e08a8e5 --- /dev/null +++ b/t/t7813-grep-icase-iso.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +test_description='grep icase on non-English locales' + +. ./lib-gettext.sh + +test_expect_success GETTEXT_ISO_LOCALE 'setup' ' + printf "TILRAUN: Halló Heimur!" >file && + git add file && + LC_ALL="$is_IS_iso_locale" && + export LC_ALL +' + +test_expect_success GETTEXT_ISO_LOCALE,PCRE 'grep pcre string' ' + git grep --perl-regexp -i "TILRAUN: H.lló Heimur!" && + git grep --perl-regexp -i "TILRAUN: H.LLÓ HEIMUR!" +' + +test_done diff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh new file mode 100755 index 0000000000..7184113b9b --- /dev/null +++ b/t/t7814-grep-recurse-submodules.sh @@ -0,0 +1,383 @@ +#!/bin/sh + +test_description='Test grep recurse-submodules feature + +This test verifies the recurse-submodules feature correctly greps across +submodules. +' + +. ./test-lib.sh + +test_expect_success 'setup directory structure and submodule' ' + echo "(1|2)d(3|4)" >a && + mkdir b && + echo "(3|4)" >b/b && + git add a b && + git commit -m "add a and b" && + git init submodule && + echo "(1|2)d(3|4)" >submodule/a && + git -C submodule add a && + git -C submodule commit -m "add a" && + git submodule add ./submodule && + git commit -m "added submodule" +' + +test_expect_success 'grep correctly finds patterns in a submodule' ' + cat >expect <<-\EOF && + a:(1|2)d(3|4) + b/b:(3|4) + submodule/a:(1|2)d(3|4) + EOF + + git grep -e "(3|4)" --recurse-submodules >actual && + test_cmp expect actual +' + +test_expect_success 'grep finds patterns in a submodule via config' ' + test_config submodule.recurse true && + # expect from previous test + git grep -e "(3|4)" >actual && + test_cmp expect actual +' + +test_expect_success 'grep --no-recurse-submodules overrides config' ' + test_config submodule.recurse true && + cat >expect <<-\EOF && + a:(1|2)d(3|4) + b/b:(3|4) + EOF + + git grep -e "(3|4)" --no-recurse-submodules >actual && + test_cmp expect actual +' + +test_expect_success 'grep and basic pathspecs' ' + cat >expect <<-\EOF && + submodule/a:(1|2)d(3|4) + EOF + + git grep -e. --recurse-submodules -- submodule >actual && + test_cmp expect actual +' + +test_expect_success 'grep and nested submodules' ' + git init submodule/sub && + echo "(1|2)d(3|4)" >submodule/sub/a && + git -C submodule/sub add a && + git -C submodule/sub commit -m "add a" && + git -C submodule submodule add ./sub && + git -C submodule add sub && + git -C submodule commit -m "added sub" && + git add submodule && + git commit -m "updated submodule" && + + cat >expect <<-\EOF && + a:(1|2)d(3|4) + b/b:(3|4) + submodule/a:(1|2)d(3|4) + submodule/sub/a:(1|2)d(3|4) + EOF + + git grep -e "(3|4)" --recurse-submodules >actual && + test_cmp expect actual +' + +test_expect_success 'grep and multiple patterns' ' + cat >expect <<-\EOF && + a:(1|2)d(3|4) + submodule/a:(1|2)d(3|4) + submodule/sub/a:(1|2)d(3|4) + EOF + + git grep -e "(3|4)" --and -e "(1|2)" --recurse-submodules >actual && + test_cmp expect actual +' + +test_expect_success 'grep and multiple patterns' ' + cat >expect <<-\EOF && + b/b:(3|4) + EOF + + git grep -e "(3|4)" --and --not -e "(1|2)" --recurse-submodules >actual && + test_cmp expect actual +' + +test_expect_success 'basic grep tree' ' + cat >expect <<-\EOF && + HEAD:a:(1|2)d(3|4) + HEAD:b/b:(3|4) + HEAD:submodule/a:(1|2)d(3|4) + HEAD:submodule/sub/a:(1|2)d(3|4) + EOF + + git grep -e "(3|4)" --recurse-submodules HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'grep tree HEAD^' ' + cat >expect <<-\EOF && + HEAD^:a:(1|2)d(3|4) + HEAD^:b/b:(3|4) + HEAD^:submodule/a:(1|2)d(3|4) + EOF + + git grep -e "(3|4)" --recurse-submodules HEAD^ >actual && + test_cmp expect actual +' + +test_expect_success 'grep tree HEAD^^' ' + cat >expect <<-\EOF && + HEAD^^:a:(1|2)d(3|4) + HEAD^^:b/b:(3|4) + EOF + + git grep -e "(3|4)" --recurse-submodules HEAD^^ >actual && + test_cmp expect actual +' + +test_expect_success 'grep tree and pathspecs' ' + cat >expect <<-\EOF && + HEAD:submodule/a:(1|2)d(3|4) + HEAD:submodule/sub/a:(1|2)d(3|4) + EOF + + git grep -e "(3|4)" --recurse-submodules HEAD -- submodule >actual && + test_cmp expect actual +' + +test_expect_success 'grep tree and pathspecs' ' + cat >expect <<-\EOF && + HEAD:submodule/a:(1|2)d(3|4) + HEAD:submodule/sub/a:(1|2)d(3|4) + EOF + + git grep -e "(3|4)" --recurse-submodules HEAD -- "submodule*a" >actual && + test_cmp expect actual +' + +test_expect_success 'grep tree and more pathspecs' ' + cat >expect <<-\EOF && + HEAD:submodule/a:(1|2)d(3|4) + EOF + + git grep -e "(3|4)" --recurse-submodules HEAD -- "submodul?/a" >actual && + test_cmp expect actual +' + +test_expect_success 'grep tree and more pathspecs' ' + cat >expect <<-\EOF && + HEAD:submodule/sub/a:(1|2)d(3|4) + EOF + + git grep -e "(3|4)" --recurse-submodules HEAD -- "submodul*/sub/a" >actual && + test_cmp expect actual +' + +test_expect_success !MINGW 'grep recurse submodule colon in name' ' + git init parent && + test_when_finished "rm -rf parent" && + echo "(1|2)d(3|4)" >"parent/fi:le" && + git -C parent add "fi:le" && + git -C parent commit -m "add fi:le" && + + git init "su:b" && + test_when_finished "rm -rf su:b" && + echo "(1|2)d(3|4)" >"su:b/fi:le" && + git -C "su:b" add "fi:le" && + git -C "su:b" commit -m "add fi:le" && + + git -C parent submodule add "../su:b" "su:b" && + git -C parent commit -m "add submodule" && + + cat >expect <<-\EOF && + fi:le:(1|2)d(3|4) + su:b/fi:le:(1|2)d(3|4) + EOF + git -C parent grep -e "(1|2)d(3|4)" --recurse-submodules >actual && + test_cmp expect actual && + + cat >expect <<-\EOF && + HEAD:fi:le:(1|2)d(3|4) + HEAD:su:b/fi:le:(1|2)d(3|4) + EOF + git -C parent grep -e "(1|2)d(3|4)" --recurse-submodules HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'grep history with moved submoules' ' + git init parent && + test_when_finished "rm -rf parent" && + echo "(1|2)d(3|4)" >parent/file && + git -C parent add file && + git -C parent commit -m "add file" && + + git init sub && + test_when_finished "rm -rf sub" && + echo "(1|2)d(3|4)" >sub/file && + git -C sub add file && + git -C sub commit -m "add file" && + + git -C parent submodule add ../sub dir/sub && + git -C parent commit -m "add submodule" && + + cat >expect <<-\EOF && + dir/sub/file:(1|2)d(3|4) + file:(1|2)d(3|4) + EOF + git -C parent grep -e "(1|2)d(3|4)" --recurse-submodules >actual && + test_cmp expect actual && + + git -C parent mv dir/sub sub-moved && + git -C parent commit -m "moved submodule" && + + cat >expect <<-\EOF && + file:(1|2)d(3|4) + sub-moved/file:(1|2)d(3|4) + EOF + git -C parent grep -e "(1|2)d(3|4)" --recurse-submodules >actual && + test_cmp expect actual && + + cat >expect <<-\EOF && + HEAD^:dir/sub/file:(1|2)d(3|4) + HEAD^:file:(1|2)d(3|4) + EOF + git -C parent grep -e "(1|2)d(3|4)" --recurse-submodules HEAD^ >actual && + test_cmp expect actual +' + +test_expect_success 'grep using relative path' ' + test_when_finished "rm -rf parent sub" && + git init sub && + echo "(1|2)d(3|4)" >sub/file && + git -C sub add file && + git -C sub commit -m "add file" && + + git init parent && + echo "(1|2)d(3|4)" >parent/file && + git -C parent add file && + mkdir parent/src && + echo "(1|2)d(3|4)" >parent/src/file2 && + git -C parent add src/file2 && + git -C parent submodule add ../sub && + git -C parent commit -m "add files and submodule" && + + # From top works + cat >expect <<-\EOF && + file:(1|2)d(3|4) + src/file2:(1|2)d(3|4) + sub/file:(1|2)d(3|4) + EOF + git -C parent grep --recurse-submodules -e "(1|2)d(3|4)" >actual && + test_cmp expect actual && + + # Relative path to top + cat >expect <<-\EOF && + ../file:(1|2)d(3|4) + file2:(1|2)d(3|4) + ../sub/file:(1|2)d(3|4) + EOF + git -C parent/src grep --recurse-submodules -e "(1|2)d(3|4)" -- .. >actual && + test_cmp expect actual && + + # Relative path to submodule + cat >expect <<-\EOF && + ../sub/file:(1|2)d(3|4) + EOF + git -C parent/src grep --recurse-submodules -e "(1|2)d(3|4)" -- ../sub >actual && + test_cmp expect actual +' + +test_expect_success 'grep from a subdir' ' + test_when_finished "rm -rf parent sub" && + git init sub && + echo "(1|2)d(3|4)" >sub/file && + git -C sub add file && + git -C sub commit -m "add file" && + + git init parent && + mkdir parent/src && + echo "(1|2)d(3|4)" >parent/src/file && + git -C parent add src/file && + git -C parent submodule add ../sub src/sub && + git -C parent submodule add ../sub sub && + git -C parent commit -m "add files and submodules" && + + # Verify grep from root works + cat >expect <<-\EOF && + src/file:(1|2)d(3|4) + src/sub/file:(1|2)d(3|4) + sub/file:(1|2)d(3|4) + EOF + git -C parent grep --recurse-submodules -e "(1|2)d(3|4)" >actual && + test_cmp expect actual && + + # Verify grep from a subdir works + cat >expect <<-\EOF && + file:(1|2)d(3|4) + sub/file:(1|2)d(3|4) + EOF + git -C parent/src grep --recurse-submodules -e "(1|2)d(3|4)" >actual && + test_cmp expect actual +' + +test_incompatible_with_recurse_submodules () +{ + test_expect_success "--recurse-submodules and $1 are incompatible" " + test_must_fail git grep -e. --recurse-submodules $1 2>actual && + test_i18ngrep 'not supported with --recurse-submodules' actual + " +} + +test_incompatible_with_recurse_submodules --untracked +test_incompatible_with_recurse_submodules --no-index + +test_expect_success 'grep --recurse-submodules should pass the pattern type along' ' + # Fixed + test_must_fail git grep -F --recurse-submodules -e "(.|.)[\d]" && + test_must_fail git -c grep.patternType=fixed grep --recurse-submodules -e "(.|.)[\d]" && + + # Basic + git grep -G --recurse-submodules -e "(.|.)[\d]" >actual && + cat >expect <<-\EOF && + a:(1|2)d(3|4) + submodule/a:(1|2)d(3|4) + submodule/sub/a:(1|2)d(3|4) + EOF + test_cmp expect actual && + git -c grep.patternType=basic grep --recurse-submodules -e "(.|.)[\d]" >actual && + test_cmp expect actual && + + # Extended + git grep -E --recurse-submodules -e "(.|.)[\d]" >actual && + cat >expect <<-\EOF && + .gitmodules:[submodule "submodule"] + .gitmodules: path = submodule + .gitmodules: url = ./submodule + a:(1|2)d(3|4) + submodule/.gitmodules:[submodule "sub"] + submodule/a:(1|2)d(3|4) + submodule/sub/a:(1|2)d(3|4) + EOF + test_cmp expect actual && + git -c grep.patternType=extended grep --recurse-submodules -e "(.|.)[\d]" >actual && + test_cmp expect actual && + git -c grep.extendedRegexp=true grep --recurse-submodules -e "(.|.)[\d]" >actual && + test_cmp expect actual && + + # Perl + if test_have_prereq PCRE + then + git grep -P --recurse-submodules -e "(.|.)[\d]" >actual && + cat >expect <<-\EOF && + a:(1|2)d(3|4) + b/b:(3|4) + submodule/a:(1|2)d(3|4) + submodule/sub/a:(1|2)d(3|4) + EOF + test_cmp expect actual && + git -c grep.patternType=perl grep --recurse-submodules -e "(.|.)[\d]" >actual && + test_cmp expect actual + fi +' + +test_done diff --git a/t/t8002-blame.sh b/t/t8002-blame.sh index 5cdf3f178e..380e1c1054 100755 --- a/t/t8002-blame.sh +++ b/t/t8002-blame.sh @@ -6,6 +6,11 @@ test_description='git blame' PROG='git blame -c' . "$TEST_DIRECTORY"/annotate-tests.sh +test_expect_success 'blame untracked file in empty repo' ' + >untracked && + test_must_fail git blame untracked +' + PROG='git blame -c -e' test_expect_success 'blame --show-email' ' check_count \ @@ -19,4 +24,98 @@ test_expect_success 'blame --show-email' ' "<E at test dot git>" 1 ' +test_expect_success 'setup showEmail tests' ' + echo "bin: test number 1" >one && + git add one && + GIT_AUTHOR_NAME=name1 \ + GIT_AUTHOR_EMAIL=email1@test.git \ + git commit -m First --date="2010-01-01 01:00:00" && + cat >expected_n <<-\EOF && + (name1 2010-01-01 01:00:00 +0000 1) bin: test number 1 + EOF + cat >expected_e <<-\EOF + (<email1@test.git> 2010-01-01 01:00:00 +0000 1) bin: test number 1 + EOF +' + +find_blame () { + sed -e 's/^[^(]*//' +} + +test_expect_success 'blame with no options and no config' ' + git blame one >blame && + find_blame <blame >result && + test_cmp expected_n result +' + +test_expect_success 'blame with showemail options' ' + git blame --show-email one >blame1 && + find_blame <blame1 >result && + test_cmp expected_e result && + git blame -e one >blame2 && + find_blame <blame2 >result && + test_cmp expected_e result && + git blame --no-show-email one >blame3 && + find_blame <blame3 >result && + test_cmp expected_n result +' + +test_expect_success 'blame with showEmail config false' ' + git config blame.showEmail false && + git blame one >blame1 && + find_blame <blame1 >result && + test_cmp expected_n result && + git blame --show-email one >blame2 && + find_blame <blame2 >result && + test_cmp expected_e result && + git blame -e one >blame3 && + find_blame <blame3 >result && + test_cmp expected_e result && + git blame --no-show-email one >blame4 && + find_blame <blame4 >result && + test_cmp expected_n result +' + +test_expect_success 'blame with showEmail config true' ' + git config blame.showEmail true && + git blame one >blame1 && + find_blame <blame1 >result && + test_cmp expected_e result && + git blame --no-show-email one >blame2 && + find_blame <blame2 >result && + test_cmp expected_n result +' + +test_expect_success 'set up abbrev tests' ' + test_commit abbrev && + sha1=$(git rev-parse --verify HEAD) && + check_abbrev () { + expect=$1; shift + echo $sha1 | cut -c 1-$expect >expect && + git blame "$@" abbrev.t >actual && + perl -lne "/[0-9a-f]+/ and print \$&" <actual >actual.sha && + test_cmp expect actual.sha + } +' + +test_expect_success 'blame --abbrev=<n> works' ' + # non-boundary commits get +1 for alignment + check_abbrev 31 --abbrev=30 HEAD && + check_abbrev 30 --abbrev=30 ^HEAD +' + +test_expect_success 'blame -l aligns regular and boundary commits' ' + check_abbrev 40 -l HEAD && + check_abbrev 39 -l ^HEAD +' + +test_expect_success 'blame --abbrev=40 behaves like -l' ' + check_abbrev 40 --abbrev=40 HEAD && + check_abbrev 39 --abbrev=40 ^HEAD +' + +test_expect_success '--no-abbrev works like --abbrev=40' ' + check_abbrev 40 --no-abbrev +' + test_done diff --git a/t/t8003-blame-corner-cases.sh b/t/t8003-blame-corner-cases.sh index 16f1442c1e..661f9d430d 100755 --- a/t/t8003-blame-corner-cases.sh +++ b/t/t8003-blame-corner-cases.sh @@ -41,12 +41,12 @@ test_expect_success setup ' test_tick && GIT_AUTHOR_NAME=Fourth git commit -m Fourth && - { - echo ABC - echo DEF - echo XXXX - echo GHIJK - } >cow && + cat >cow <<-\EOF && + ABC + DEF + XXXX + GHIJK + EOF git add cow && test_tick && GIT_AUTHOR_NAME=Fifth git commit -m Fifth @@ -115,11 +115,11 @@ test_expect_success 'append with -C -C -C' ' test_expect_success 'blame wholesale copy' ' git blame -f -C -C1 HEAD^ -- cow | sed -e "$pick_fc" >current && - { - echo mouse-Initial - echo mouse-Second - echo mouse-Third - } >expected && + cat >expected <<-\EOF && + mouse-Initial + mouse-Second + mouse-Third + EOF test_cmp expected current ' @@ -127,16 +127,61 @@ test_expect_success 'blame wholesale copy' ' test_expect_success 'blame wholesale copy and more' ' git blame -f -C -C1 HEAD -- cow | sed -e "$pick_fc" >current && - { - echo mouse-Initial - echo mouse-Second - echo cow-Fifth - echo mouse-Third - } >expected && + cat >expected <<-\EOF && + mouse-Initial + mouse-Second + cow-Fifth + mouse-Third + EOF test_cmp expected current ' +test_expect_success 'blame wholesale copy and more in the index' ' + + cat >horse <<-\EOF && + ABC + DEF + XXXX + YYYY + GHIJK + EOF + git add horse && + test_when_finished "git rm -f horse" && + git blame -f -C -C1 -- horse | sed -e "$pick_fc" >current && + cat >expected <<-\EOF && + mouse-Initial + mouse-Second + cow-Fifth + horse-Not + mouse-Third + EOF + test_cmp expected current + +' + +test_expect_success 'blame during cherry-pick with file rename conflict' ' + + test_when_finished "git reset --hard && git checkout master" && + git checkout HEAD~3 && + echo MOUSE >> mouse && + git mv mouse rodent && + git add rodent && + GIT_AUTHOR_NAME=Rodent git commit -m "rodent" && + git checkout --detach master && + (git cherry-pick HEAD@{1} || test $? -eq 1) && + git show HEAD@{1}:rodent > rodent && + git add rodent && + git blame -f -C -C1 rodent | sed -e "$pick_fc" >current && + cat current && + cat >expected <<-\EOF && + mouse-Initial + mouse-Second + rodent-Not + EOF + test_cmp expected current +' + test_expect_success 'blame path that used to be a directory' ' mkdir path && echo A A A A A >path/file && @@ -153,7 +198,7 @@ test_expect_success 'blame path that used to be a directory' ' ' test_expect_success 'blame to a commit with no author name' ' - TREE=`git rev-parse HEAD:` && + TREE=$(git rev-parse HEAD:) && cat >badcommit <<EOF && tree $TREE author <noname> 1234567890 +0000 @@ -161,18 +206,18 @@ committer David Reiss <dreiss@facebook.com> 1234567890 +0000 some message EOF - COMMIT=`git hash-object -t commit -w badcommit` && + COMMIT=$(git hash-object -t commit -w badcommit) && git --no-pager blame $COMMIT -- uno >/dev/null ' test_expect_success 'blame -L with invalid start' ' test_must_fail git blame -L5 tres 2>errors && - grep "has only 2 lines" errors + test_i18ngrep "has only 2 lines" errors ' test_expect_success 'blame -L with invalid end' ' test_must_fail git blame -L1,5 tres 2>errors && - grep "has only 2 lines" errors + test_i18ngrep "has only 2 lines" errors ' test_expect_success 'blame parses <end> part of -L' ' @@ -212,4 +257,18 @@ test_expect_success 'blame file with CRLF attributes text' ' grep "A U Thor" actual ' +test_expect_success 'blame file with CRLF core.autocrlf=true' ' + git config core.autocrlf false && + printf "testcase\r\n" >crlfinrepo && + >.gitattributes && + git add crlfinrepo && + git commit -m "add crlfinrepo" && + git config core.autocrlf true && + mv crlfinrepo tmp && + git checkout crlfinrepo && + rm tmp && + git blame crlfinrepo >actual && + grep "A U Thor" actual +' + test_done diff --git a/t/t8005-blame-i18n.sh b/t/t8005-blame-i18n.sh index 847d098c09..75da219ed1 100755 --- a/t/t8005-blame-i18n.sh +++ b/t/t8005-blame-i18n.sh @@ -33,11 +33,15 @@ author $SJIS_NAME summary $SJIS_MSG EOF +filter_author_summary () { + sed -n -e '/^author /p' -e '/^summary /p' "$@" +} + test_expect_success !MINGW \ 'blame respects i18n.commitencoding' ' - git blame --incremental file | \ - egrep "^(author|summary) " > actual && - test_cmp actual expected + git blame --incremental file >output && + filter_author_summary output >actual && + test_cmp expected actual ' cat >expected <<EOF @@ -52,9 +56,9 @@ EOF test_expect_success !MINGW \ 'blame respects i18n.logoutputencoding' ' git config i18n.logoutputencoding eucJP && - git blame --incremental file | \ - egrep "^(author|summary) " > actual && - test_cmp actual expected + git blame --incremental file >output && + filter_author_summary output >actual && + test_cmp expected actual ' cat >expected <<EOF @@ -68,9 +72,9 @@ EOF test_expect_success !MINGW \ 'blame respects --encoding=UTF-8' ' - git blame --incremental --encoding=UTF-8 file | \ - egrep "^(author|summary) " > actual && - test_cmp actual expected + git blame --incremental --encoding=UTF-8 file >output && + filter_author_summary output >actual && + test_cmp expected actual ' cat >expected <<EOF @@ -84,9 +88,9 @@ EOF test_expect_success !MINGW \ 'blame respects --encoding=none' ' - git blame --incremental --encoding=none file | \ - egrep "^(author|summary) " > actual && - test_cmp actual expected + git blame --incremental --encoding=none file >output && + filter_author_summary output >actual && + test_cmp expected actual ' test_done diff --git a/t/t8008-blame-formats.sh b/t/t8008-blame-formats.sh index 29f84a6dd1..ae4b579d24 100755 --- a/t/t8008-blame-formats.sh +++ b/t/t8008-blame-formats.sh @@ -12,22 +12,25 @@ test_expect_success 'setup' ' echo c >>file && echo d >>file && test_tick && - git commit -a -m two + git commit -a -m two && + ID1=$(git rev-parse HEAD^) && + shortID1="^$(git rev-parse HEAD^ |cut -c 1-17)" && + ID2=$(git rev-parse HEAD) && + shortID2="$(git rev-parse HEAD |cut -c 1-18)" ' -cat >expect <<'EOF' -^baf5e0b (A U Thor 2005-04-07 15:13:13 -0700 1) a -8825379d (A U Thor 2005-04-07 15:14:13 -0700 2) b -8825379d (A U Thor 2005-04-07 15:14:13 -0700 3) c -8825379d (A U Thor 2005-04-07 15:14:13 -0700 4) d +cat >expect <<EOF +$shortID1 (A U Thor 2005-04-07 15:13:13 -0700 1) a +$shortID2 (A U Thor 2005-04-07 15:14:13 -0700 2) b +$shortID2 (A U Thor 2005-04-07 15:14:13 -0700 3) c +$shortID2 (A U Thor 2005-04-07 15:14:13 -0700 4) d EOF test_expect_success 'normal blame output' ' - git blame file >actual && + git blame --abbrev=17 file >actual && test_cmp expect actual ' -ID1=baf5e0b3869e0b2b2beb395a3720c7b51eac94fc -COMMIT1='author A U Thor +COMMIT1="author A U Thor author-mail <author@example.com> author-time 1112911993 author-tz -0700 @@ -37,9 +40,8 @@ committer-time 1112911993 committer-tz -0700 summary one boundary -filename file' -ID2=8825379dfb8a1267b58e8e5bcf69eec838f685ec -COMMIT2='author A U Thor +filename file" +COMMIT2="author A U Thor author-mail <author@example.com> author-time 1112912053 author-tz -0700 @@ -48,8 +50,8 @@ committer-mail <committer@example.com> committer-time 1112912053 committer-tz -0700 summary two -previous baf5e0b3869e0b2b2beb395a3720c7b51eac94fc file -filename file' +previous $ID1 file +filename file" cat >expect <<EOF $ID1 1 1 1 @@ -87,4 +89,21 @@ test_expect_success 'blame --line-porcelain output' ' test_cmp expect actual ' +test_expect_success '--porcelain detects first non-blank line as subject' ' + ( + GIT_INDEX_FILE=.git/tmp-index && + export GIT_INDEX_FILE && + echo "This is it" >single-file && + git add single-file && + tree=$(git write-tree) && + commit=$(printf "%s\n%s\n%s\n\n\n \noneline\n\nbody\n" \ + "tree $tree" \ + "author A <a@b.c> 123456789 +0000" \ + "committer C <c@d.e> 123456789 +0000" | + git hash-object -w -t commit --stdin) && + git blame --porcelain $commit -- single-file >output && + grep "^summary oneline$" output + ) +' + test_done diff --git a/t/t8009-blame-vs-topicbranches.sh b/t/t8009-blame-vs-topicbranches.sh new file mode 100755 index 0000000000..72596e38b2 --- /dev/null +++ b/t/t8009-blame-vs-topicbranches.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +test_description='blaming trough history with topic branches' +. ./test-lib.sh + +# Creates the history shown below. '*'s mark the first parent in the merges. +# The only line of file.t is changed in commit B2 +# +# +---C1 +# / \ +# A0--A1--*A2--*A3 +# \ / +# B1-B2 +# +test_expect_success setup ' + test_commit A0 file.t line0 && + test_commit A1 && + git reset --hard A0 && + test_commit B1 && + test_commit B2 file.t line0changed && + git reset --hard A1 && + test_merge A2 B2 && + git reset --hard A1 && + test_commit C1 && + git reset --hard A2 && + test_merge A3 C1 + ' + +test_expect_success 'blame --reverse --first-parent finds A1' ' + git blame --porcelain --reverse --first-parent A0..A3 -- file.t >actual_full && + head -n 1 <actual_full | sed -e "s/ .*//" >actual && + git rev-parse A1 >expect && + test_cmp expect actual + ' + +test_done diff --git a/t/t8010-cat-file-filters.sh b/t/t8010-cat-file-filters.sh new file mode 100755 index 0000000000..0f86c19174 --- /dev/null +++ b/t/t8010-cat-file-filters.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +test_description='git cat-file filters support' +. ./test-lib.sh + +test_expect_success 'setup ' ' + echo "*.txt eol=crlf diff=txt" >.gitattributes && + echo "hello" | append_cr >world.txt && + git add .gitattributes world.txt && + test_tick && + git commit -m "Initial commit" +' + +has_cr () { + tr '\015' Q <"$1" | grep Q >/dev/null +} + +test_expect_success 'no filters with `git show`' ' + git show HEAD:world.txt >actual && + ! has_cr actual + +' + +test_expect_success 'no filters with cat-file' ' + git cat-file blob HEAD:world.txt >actual && + ! has_cr actual +' + +test_expect_success 'cat-file --filters converts to worktree version' ' + git cat-file --filters HEAD:world.txt >actual && + has_cr actual +' + +test_expect_success 'cat-file --filters --path=<path> works' ' + sha1=$(git rev-parse -q --verify HEAD:world.txt) && + git cat-file --filters --path=world.txt $sha1 >actual && + has_cr actual +' + +test_expect_success 'cat-file --textconv --path=<path> works' ' + sha1=$(git rev-parse -q --verify HEAD:world.txt) && + test_config diff.txt.textconv "tr A-Za-z N-ZA-Mn-za-m <" && + git cat-file --textconv --path=hello.txt $sha1 >rot13 && + test uryyb = "$(cat rot13 | remove_cr)" +' + +test_expect_success '--path=<path> complains without --textconv/--filters' ' + sha1=$(git rev-parse -q --verify HEAD:world.txt) && + test_must_fail git cat-file --path=hello.txt blob $sha1 >actual 2>err && + test ! -s actual && + grep "path.*needs.*filters" err +' + +test_expect_success '--textconv/--filters complain without path' ' + test_must_fail git cat-file --textconv HEAD && + test_must_fail git cat-file --filters HEAD +' + +test_expect_success 'cat-file --textconv --batch works' ' + sha1=$(git rev-parse -q --verify HEAD:world.txt) && + test_config diff.txt.textconv "tr A-Za-z N-ZA-Mn-za-m <" && + printf "%s hello.txt\n%s hello\n" $sha1 $sha1 | + git cat-file --textconv --batch >actual && + printf "%s blob 6\nuryyb\r\n\n%s blob 6\nhello\n\n" \ + $sha1 $sha1 >expect && + test_cmp expect actual +' + +test_done diff --git a/t/t8011-blame-split-file.sh b/t/t8011-blame-split-file.sh new file mode 100755 index 0000000000..831125047b --- /dev/null +++ b/t/t8011-blame-split-file.sh @@ -0,0 +1,117 @@ +#!/bin/sh + +test_description=' +The general idea is that we have a single file whose lines come from +multiple other files, and those individual files were modified in the same +commits. That means that we will see the same commit in multiple contexts, +and each one should be attributed to the correct file. + +Note that we need to use "blame -C" to find the commit for all lines. We will +not bother testing that the non-C case fails to find it. That is how blame +behaves now, but it is not a property we want to make sure is retained. +' +. ./test-lib.sh + +# help avoid typing and reading long strings of similar lines +# in the tests below +generate_expect () { + while read nr data + do + i=0 + while test $i -lt $nr + do + echo $data + i=$((i + 1)) + done + done +} + +test_expect_success 'setup split file case' ' + # use lines long enough to trigger content detection + test_seq 1000 1010 >one && + test_seq 2000 2010 >two && + git add one two && + test_commit base && + + sed "6s/^/modified /" <one >one.tmp && + mv one.tmp one && + sed "6s/^/modified /" <two >two.tmp && + mv two.tmp two && + git add -u && + test_commit modified && + + cat one two >combined && + git add combined && + git rm one two && + test_commit combined +' + +test_expect_success 'setup simulated porcelain' ' + # This just reads porcelain-ish output and tries + # to output the value of a given field for each line (either by + # reading the field that accompanies this line, or referencing + # the information found last time the commit was mentioned). + cat >read-porcelain.pl <<-\EOF + my $field = shift; + while (<>) { + if (/^[0-9a-f]{40} /) { + flush(); + $hash = $&; + } elsif (/^$field (.*)/) { + $cache{$hash} = $1; + } + } + flush(); + + sub flush { + return unless defined $hash; + if (defined $cache{$hash}) { + print "$cache{$hash}\n"; + } else { + print "NONE\n"; + } + } + EOF +' + +for output in porcelain line-porcelain +do + test_expect_success "generate --$output output" ' + git blame --root -C --$output combined >output + ' + + test_expect_success "$output output finds correct commits" ' + generate_expect >expect <<-\EOF && + 5 base + 1 modified + 10 base + 1 modified + 5 base + EOF + perl read-porcelain.pl summary <output >actual && + test_cmp expect actual + ' + + test_expect_success "$output output shows correct filenames" ' + generate_expect >expect <<-\EOF && + 11 one + 11 two + EOF + perl read-porcelain.pl filename <output >actual && + test_cmp expect actual + ' + + test_expect_success "$output output shows correct previous pointer" ' + generate_expect >expect <<-EOF && + 5 NONE + 1 $(git rev-parse modified^) one + 10 NONE + 1 $(git rev-parse modified^) two + 5 NONE + EOF + perl read-porcelain.pl previous <output >actual && + test_cmp expect actual + ' +done + +test_done diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index a3663da49b..e80eacbb1b 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -6,6 +6,12 @@ test_description='git send-email' # May be altered later in the test PREREQ="PERL" +replace_variable_fields () { + sed -e "s/^\(Date:\).*/\1 DATE-STRING/" \ + -e "s/^\(Message-Id:\).*/\1 MESSAGE-ID-STRING/" \ + -e "s/^\(X-Mailer:\).*/\1 X-MAILER-STRING/" +} + test_expect_success $PREREQ 'prepare reference tree' ' echo "1A quick brown fox jumps over the" >file && echo "lazy dog" >>file && @@ -36,7 +42,7 @@ clean_fake_sendmail () { } test_expect_success $PREREQ 'Extract patches' ' - patches=`git format-patch -s --cc="One <one@example.com>" --cc=two@example.com -n HEAD^1` + patches=$(git format-patch -s --cc="One <one@example.com>" --cc=two@example.com -n HEAD^1) ' # Test no confirm early to ensure remaining tests will not hang @@ -50,7 +56,7 @@ test_no_confirm () { --smtp-server="$(pwd)/fake.sendmail" \ $@ \ $patches >stdout && - test_must_fail grep "Send this email" stdout && + ! grep "Send this email" stdout && >no_confirm_okay } @@ -140,6 +146,57 @@ test_expect_success $PREREQ 'Verify commandline' ' test_cmp expected commandline1 ' +test_expect_success $PREREQ 'setup expect for cc trailer' " +cat >expected-cc <<\EOF +!recipient@example.com! +!author@example.com! +!one@example.com! +!two@example.com! +!three@example.com! +!four@example.com! +!five@example.com! +!six@example.com! +EOF +" + +test_expect_success $PREREQ 'cc trailer with various syntax' ' + test_commit cc-trailer && + test_when_finished "git reset --hard HEAD^" && + git commit --amend -F - <<-EOF && + Test Cc: trailers. + + Cc: one@example.com + Cc: <two@example.com> # trailing comments are ignored + Cc: <three@example.com>, <not.four@example.com> one address per line + Cc: "Some # Body" <four@example.com> [ <also.a.comment> ] + Cc: five@example.com # not.six@example.com + Cc: six@example.com, not.seven@example.com + EOF + clean_fake_sendmail && + git send-email -1 --to=recipient@example.com \ + --smtp-server="$(pwd)/fake.sendmail" && + test_cmp expected-cc commandline1 +' + +test_expect_success $PREREQ 'setup fake get_maintainer.pl script for cc trailer' " + write_script expected-cc-script.sh <<-EOF + echo 'One Person <one@example.com> (supporter:THIS (FOO/bar))' + echo 'Two Person <two@example.com> (maintainer:THIS THING)' + echo 'Third List <three@example.com> (moderated list:THIS THING (FOO/bar))' + echo '<four@example.com> (moderated list:FOR THING)' + echo 'five@example.com (open list:FOR THING (FOO/bar))' + echo 'six@example.com (open list)' + EOF +" + +test_expect_success $PREREQ 'cc trailer with get_maintainer.pl output' ' + clean_fake_sendmail && + git send-email -1 --to=recipient@example.com \ + --cc-cmd=./expected-cc-script.sh \ + --smtp-server="$(pwd)/fake.sendmail" && + test_cmp expected-cc commandline1 +' + test_expect_success $PREREQ 'setup expect' " cat >expected-show-all-headers <<\EOF 0001-Second.patch @@ -167,6 +224,7 @@ Message-Id: MESSAGE-ID-STRING X-Mailer: X-MAILER-STRING In-Reply-To: <unique-message-id@example.com> References: <unique-message-id@example.com> +Reply-To: Reply <reply@example.com> Result: OK EOF @@ -259,15 +317,13 @@ test_expect_success $PREREQ 'Show all headers' ' --dry-run \ --suppress-cc=sob \ --from="Example <from@example.com>" \ + --reply-to="Reply <reply@example.com>" \ --to=to@example.com \ --cc=cc@example.com \ --bcc=bcc@example.com \ --in-reply-to="<unique-message-id@example.com>" \ --smtp-server relay.example.com \ - $patches | - sed -e "s/^\(Date:\).*/\1 DATE-STRING/" \ - -e "s/^\(Message-Id:\).*/\1 MESSAGE-ID-STRING/" \ - -e "s/^\(X-Mailer:\).*/\1 X-MAILER-STRING/" \ + $patches | replace_variable_fields \ >actual-show-all-headers && test_cmp expected-show-all-headers actual-show-all-headers ' @@ -312,13 +368,19 @@ test_expect_success $PREREQ,!AUTOIDENT 'broken implicit ident aborts send-email' ) ' +test_expect_success $PREREQ 'setup tocmd and cccmd scripts' ' + write_script tocmd-sed <<-\EOF && + sed -n -e "s/^tocmd--//p" "$1" + EOF + write_script cccmd-sed <<-\EOF + sed -n -e "s/^cccmd--//p" "$1" + EOF +' + test_expect_success $PREREQ 'tocmd works' ' clean_fake_sendmail && cp $patches tocmd.patch && echo tocmd--tocmd@example.com >>tocmd.patch && - write_script tocmd-sed <<-\EOF && - sed -n -e "s/^tocmd--//p" "$1" - EOF git send-email \ --from="Example <nobody@example.com>" \ --to-cmd=./tocmd-sed \ @@ -332,9 +394,6 @@ test_expect_success $PREREQ 'cccmd works' ' clean_fake_sendmail && cp $patches cccmd.patch && echo "cccmd-- cccmd@example.com" >>cccmd.patch && - write_script cccmd-sed <<-\EOF && - sed -n -e "s/^cccmd--//p" "$1" - EOF git send-email \ --from="Example <nobody@example.com>" \ --to=nobody@example.com \ @@ -526,10 +585,7 @@ test_suppression () { --from="Example <from@example.com>" \ --to=to@example.com \ --smtp-server relay.example.com \ - $patches | - sed -e "s/^\(Date:\).*/\1 DATE-STRING/" \ - -e "s/^\(Message-Id:\).*/\1 MESSAGE-ID-STRING/" \ - -e "s/^\(X-Mailer:\).*/\1 X-MAILER-STRING/" \ + $patches | replace_variable_fields \ >actual-suppress-$1${2+"-$2"} && test_cmp expected-suppress-$1${2+"-$2"} actual-suppress-$1${2+"-$2"} } @@ -1145,7 +1201,7 @@ test_expect_success $PREREQ '--no-bcc overrides sendemail.bcc' ' ' test_expect_success $PREREQ 'patches To headers are used by default' ' - patch=`git format-patch -1 --to="bodies@example.com"` && + patch=$(git format-patch -1 --to="bodies@example.com") && test_when_finished "rm $patch" && git send-email \ --dry-run \ @@ -1156,7 +1212,7 @@ test_expect_success $PREREQ 'patches To headers are used by default' ' ' test_expect_success $PREREQ 'patches To headers are appended to' ' - patch=`git format-patch -1 --to="bodies@example.com"` && + patch=$(git format-patch -1 --to="bodies@example.com") && test_when_finished "rm $patch" && git send-email \ --dry-run \ @@ -1169,8 +1225,8 @@ test_expect_success $PREREQ 'patches To headers are appended to' ' ' test_expect_success $PREREQ 'To headers from files reset each patch' ' - patch1=`git format-patch -1 --to="bodies@example.com"` && - patch2=`git format-patch -1 --to="other@example.com" HEAD~` && + patch1=$(git format-patch -1 --to="bodies@example.com") && + patch2=$(git format-patch -1 --to="other@example.com" HEAD~) && test_when_finished "rm $patch1 && rm $patch2" && git send-email \ --dry-run \ @@ -1228,7 +1284,7 @@ test_expect_success $PREREQ 'asks about and fixes 8bit encodings' ' grep email-using-8bit stdout && grep "Which 8bit encoding" stdout && egrep "Content|MIME" msgtxt1 >actual && - test_cmp actual content-type-decl + test_cmp content-type-decl actual ' test_expect_success $PREREQ 'sendemail.8bitEncoding works' ' @@ -1239,7 +1295,7 @@ test_expect_success $PREREQ 'sendemail.8bitEncoding works' ' --smtp-server="$(pwd)/fake.sendmail" \ email-using-8bit >stdout && egrep "Content|MIME" msgtxt1 >actual && - test_cmp actual content-type-decl + test_cmp content-type-decl actual ' test_expect_success $PREREQ '--8bit-encoding overrides sendemail.8bitEncoding' ' @@ -1251,7 +1307,7 @@ test_expect_success $PREREQ '--8bit-encoding overrides sendemail.8bitEncoding' ' --8bit-encoding=UTF-8 \ email-using-8bit >stdout && egrep "Content|MIME" msgtxt1 >actual && - test_cmp actual content-type-decl + test_cmp content-type-decl actual ' test_expect_success $PREREQ 'setup expect' ' @@ -1482,7 +1538,7 @@ test_cover_addresses () { clean_fake_sendmail && rm -fr outdir && git format-patch --cover-letter -2 -o outdir && - cover=`echo outdir/0000-*.patch` && + cover=$(echo outdir/0000-*.patch) && mv $cover cover-to-edit.patch && perl -pe "s/^From:/$header: extra\@address.com\nFrom:/" cover-to-edit.patch >"$cover" && git send-email \ @@ -1521,6 +1577,21 @@ test_expect_success $PREREQ 'cccover adds Cc to all mail' ' test_cover_addresses "Cc" ' +test_expect_success $PREREQ 'escaped quotes in sendemail.aliasfiletype=mutt' ' + clean_fake_sendmail && + echo "alias sbd \\\"Dot U. Sir\\\" <somebody@example.org>" >.mutt && + git config --replace-all sendemail.aliasesfile "$(pwd)/.mutt" && + git config sendemail.aliasfiletype mutt && + git send-email \ + --from="Example <nobody@example.com>" \ + --to=sbd \ + --smtp-server="$(pwd)/fake.sendmail" \ + outdir/0001-*.patch \ + 2>errors >out && + grep "^!somebody@example\.org!$" commandline1 && + grep -F "To: \"Dot U. Sir\" <somebody@example.org>" out +' + test_expect_success $PREREQ 'sendemail.aliasfiletype=mailrc' ' clean_fake_sendmail && echo "alias sbd somebody@example.org" >.mailrc && @@ -1549,10 +1620,117 @@ test_expect_success $PREREQ 'sendemail.aliasfile=~/.mailrc' ' grep "^!someone@example\.org!$" commandline1 ' -test_expect_success $PREREQ 'sendemail.aliasfiletype=sendmail' ' - clean_fake_sendmail && rm -fr outdir && - git format-patch -1 -o outdir && - cat >>.tmp-email-aliases <<-\EOF && +test_dump_aliases () { + msg="$1" && shift && + filetype="$1" && shift && + printf '%s\n' "$@" >expect && + cat >.tmp-email-aliases && + + test_expect_success $PREREQ "$msg" ' + clean_fake_sendmail && rm -fr outdir && + git config --replace-all sendemail.aliasesfile \ + "$(pwd)/.tmp-email-aliases" && + git config sendemail.aliasfiletype "$filetype" && + git send-email --dump-aliases 2>errors >actual && + test_cmp expect actual + ' +} + +test_dump_aliases '--dump-aliases sendmail format' \ + 'sendmail' \ + 'abgroup' \ + 'alice' \ + 'bcgrp' \ + 'bob' \ + 'chloe' <<-\EOF + alice: Alice W Land <awol@example.com> + bob: Robert Bobbyton <bob@example.com> + chloe: chloe@example.com + abgroup: alice, bob + bcgrp: bob, chloe, Other <o@example.com> + EOF + +test_dump_aliases '--dump-aliases mutt format' \ + 'mutt' \ + 'alice' \ + 'bob' \ + 'chloe' \ + 'donald' <<-\EOF + alias alice Alice W Land <awol@example.com> + alias donald Donald C Carlton <donc@example.com> + alias bob Robert Bobbyton <bob@example.com> + alias chloe chloe@example.com + EOF + +test_dump_aliases '--dump-aliases mailrc format' \ + 'mailrc' \ + 'alice' \ + 'bob' \ + 'chloe' \ + 'eve' <<-\EOF + alias alice Alice W Land <awol@example.com> + alias eve Eve <eve@example.com> + alias bob Robert Bobbyton <bob@example.com> + alias chloe chloe@example.com + EOF + +test_dump_aliases '--dump-aliases pine format' \ + 'pine' \ + 'alice' \ + 'bob' \ + 'chloe' \ + 'eve' <<-\EOF + alice Alice W Land <awol@example.com> + eve Eve <eve@example.com> + bob Robert Bobbyton <bob@example.com> + chloe chloe@example.com + EOF + +test_dump_aliases '--dump-aliases gnus format' \ + 'gnus' \ + 'alice' \ + 'bob' \ + 'chloe' \ + 'eve' <<-\EOF + (define-mail-alias "alice" "awol@example.com") + (define-mail-alias "eve" "eve@example.com") + (define-mail-alias "bob" "bob@example.com") + (define-mail-alias "chloe" "chloe@example.com") + EOF + +test_expect_success '--dump-aliases must be used alone' ' + test_must_fail git send-email --dump-aliases --to=janice@example.com -1 refs/heads/accounting +' + +test_sendmail_aliases () { + msg="$1" && shift && + expect="$@" && + cat >.tmp-email-aliases && + + test_expect_success $PREREQ "$msg" ' + clean_fake_sendmail && rm -fr outdir && + git format-patch -1 -o outdir && + git config --replace-all sendemail.aliasesfile \ + "$(pwd)/.tmp-email-aliases" && + git config sendemail.aliasfiletype sendmail && + git send-email \ + --from="Example <nobody@example.com>" \ + --to=alice --to=bcgrp \ + --smtp-server="$(pwd)/fake.sendmail" \ + outdir/0001-*.patch \ + 2>errors >out && + for i in $expect + do + grep "^!$i!$" commandline1 || return 1 + done + ' +} + +test_sendmail_aliases 'sendemail.aliasfiletype=sendmail' \ + 'awol@example\.com' \ + 'bob@example\.com' \ + 'chloe@example\.com' \ + 'o@example\.com' <<-\EOF alice: Alice W Land <awol@example.com> bob: Robert Bobbyton <bob@example.com> # this is a comment @@ -1561,19 +1739,99 @@ test_expect_success $PREREQ 'sendemail.aliasfiletype=sendmail' ' abgroup: alice, bob bcgrp: bob, chloe, Other <o@example.com> EOF - git config --replace-all sendemail.aliasesfile \ - "$(pwd)/.tmp-email-aliases" && - git config sendemail.aliasfiletype sendmail && + +test_sendmail_aliases 'sendmail aliases line folding' \ + alice1 \ + bob1 bob2 \ + chuck1 chuck2 \ + darla1 darla2 darla3 \ + elton1 elton2 elton3 \ + fred1 fred2 \ + greg1 <<-\EOF + alice: alice1 + bob: bob1,\ + bob2 + chuck: chuck1, + chuck2 + darla: darla1,\ + darla2, + darla3 + elton: elton1, + elton2,\ + elton3 + fred: fred1,\ + fred2 + greg: greg1 + bcgrp: bob, chuck, darla, elton, fred, greg + EOF + +test_sendmail_aliases 'sendmail aliases tolerate bogus line folding' \ + alice1 bob1 <<-\EOF + alice: alice1 + bcgrp: bob1\ + EOF + +test_sendmail_aliases 'sendmail aliases empty' alice bcgrp <<-\EOF + EOF + +test_expect_success $PREREQ 'alias support in To header' ' + clean_fake_sendmail && + echo "alias sbd someone@example.org" >.mailrc && + test_config sendemail.aliasesfile ".mailrc" && + test_config sendemail.aliasfiletype mailrc && + git format-patch --stdout -1 --to=sbd >aliased.patch && git send-email \ --from="Example <nobody@example.com>" \ - --to=alice --to=bcgrp \ --smtp-server="$(pwd)/fake.sendmail" \ - outdir/0001-*.patch \ + aliased.patch \ + 2>errors >out && + grep "^!someone@example\.org!$" commandline1 +' + +test_expect_success $PREREQ 'alias support in Cc header' ' + clean_fake_sendmail && + echo "alias sbd someone@example.org" >.mailrc && + test_config sendemail.aliasesfile ".mailrc" && + test_config sendemail.aliasfiletype mailrc && + git format-patch --stdout -1 --cc=sbd >aliased.patch && + git send-email \ + --from="Example <nobody@example.com>" \ + --smtp-server="$(pwd)/fake.sendmail" \ + aliased.patch \ + 2>errors >out && + grep "^!someone@example\.org!$" commandline1 +' + +test_expect_success $PREREQ 'tocmd works with aliases' ' + clean_fake_sendmail && + echo "alias sbd someone@example.org" >.mailrc && + test_config sendemail.aliasesfile ".mailrc" && + test_config sendemail.aliasfiletype mailrc && + git format-patch --stdout -1 >tocmd.patch && + echo tocmd--sbd >>tocmd.patch && + git send-email \ + --from="Example <nobody@example.com>" \ + --to-cmd=./tocmd-sed \ + --smtp-server="$(pwd)/fake.sendmail" \ + tocmd.patch \ 2>errors >out && - grep "^!awol@example\.com!$" commandline1 && - grep "^!bob@example\.com!$" commandline1 && - grep "^!chloe@example\.com!$" commandline1 && - grep "^!o@example\.com!$" commandline1 + grep "^!someone@example\.org!$" commandline1 +' + +test_expect_success $PREREQ 'cccmd works with aliases' ' + clean_fake_sendmail && + echo "alias sbd someone@example.org" >.mailrc && + test_config sendemail.aliasesfile ".mailrc" && + test_config sendemail.aliasfiletype mailrc && + git format-patch --stdout -1 >cccmd.patch && + echo cccmd--sbd >>cccmd.patch && + git send-email \ + --from="Example <nobody@example.com>" \ + --cc-cmd=./cccmd-sed \ + --smtp-server="$(pwd)/fake.sendmail" \ + cccmd.patch \ + 2>errors >out && + grep "^!someone@example\.org!$" commandline1 ' do_xmailer_test () { @@ -1609,4 +1867,120 @@ test_expect_success $PREREQ '--[no-]xmailer with sendemail.xmailer=false' ' do_xmailer_test 1 "--xmailer" ' +test_expect_success $PREREQ 'setup expected-list' ' + git send-email \ + --dry-run \ + --from="Example <from@example.com>" \ + --to="To 1 <to1@example.com>" \ + --to="to2@example.com" \ + --to="to3@example.com" \ + --cc="Cc 1 <cc1@example.com>" \ + --cc="Cc2 <cc2@example.com>" \ + --bcc="bcc1@example.com" \ + --bcc="bcc2@example.com" \ + 0001-add-master.patch | replace_variable_fields \ + >expected-list +' + +test_expect_success $PREREQ 'use email list in --cc --to and --bcc' ' + git send-email \ + --dry-run \ + --from="Example <from@example.com>" \ + --to="To 1 <to1@example.com>, to2@example.com" \ + --to="to3@example.com" \ + --cc="Cc 1 <cc1@example.com>, Cc2 <cc2@example.com>" \ + --bcc="bcc1@example.com, bcc2@example.com" \ + 0001-add-master.patch | replace_variable_fields \ + >actual-list && + test_cmp expected-list actual-list +' + +test_expect_success $PREREQ 'aliases work with email list' ' + echo "alias to2 to2@example.com" >.mutt && + echo "alias cc1 Cc 1 <cc1@example.com>" >>.mutt && + test_config sendemail.aliasesfile ".mutt" && + test_config sendemail.aliasfiletype mutt && + git send-email \ + --dry-run \ + --from="Example <from@example.com>" \ + --to="To 1 <to1@example.com>, to2, to3@example.com" \ + --cc="cc1, Cc2 <cc2@example.com>" \ + --bcc="bcc1@example.com, bcc2@example.com" \ + 0001-add-master.patch | replace_variable_fields \ + >actual-list && + test_cmp expected-list actual-list +' + +test_expect_success $PREREQ 'leading and trailing whitespaces are removed' ' + echo "alias to2 to2@example.com" >.mutt && + echo "alias cc1 Cc 1 <cc1@example.com>" >>.mutt && + test_config sendemail.aliasesfile ".mutt" && + test_config sendemail.aliasfiletype mutt && + TO1=$(echo "QTo 1 <to1@example.com>" | q_to_tab) && + TO2=$(echo "QZto2" | qz_to_tab_space) && + CC1=$(echo "cc1" | append_cr) && + BCC1=$(echo "Q bcc1@example.com Q" | q_to_nul) && + git send-email \ + --dry-run \ + --from=" Example <from@example.com>" \ + --to="$TO1" \ + --to="$TO2" \ + --to=" to3@example.com " \ + --cc="$CC1" \ + --cc="Cc2 <cc2@example.com>" \ + --bcc="$BCC1" \ + --bcc="bcc2@example.com" \ + 0001-add-master.patch | replace_variable_fields \ + >actual-list && + test_cmp expected-list actual-list +' + +test_expect_success $PREREQ 'invoke hook' ' + mkdir -p .git/hooks && + + write_script .git/hooks/sendemail-validate <<-\EOF && + # test that we have the correct environment variable, pwd, and + # argument + case "$GIT_DIR" in + *.git) + true + ;; + *) + false + ;; + esac && + test -f 0001-add-master.patch && + grep "add master" "$1" + EOF + + mkdir subdir && + ( + # Test that it works even if we are not at the root of the + # working tree + cd subdir && + git send-email \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/../fake.sendmail" \ + ../0001-add-master.patch && + + # Verify error message when a patch is rejected by the hook + sed -e "s/add master/x/" ../0001-add-master.patch >../another.patch && + git send-email \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/../fake.sendmail" \ + ../another.patch 2>err + test_i18ngrep "rejected by sendemail-validate hook" err + ) +' + +test_expect_success $PREREQ 'test that send-email works outside a repo' ' + nongit git send-email \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + "$(pwd)/0001-add-master.patch" +' + test_done diff --git a/t/t9003-help-autocorrect.sh b/t/t9003-help-autocorrect.sh new file mode 100755 index 0000000000..b1c7919c4a --- /dev/null +++ b/t/t9003-help-autocorrect.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +test_description='help.autocorrect finding a match' +. ./test-lib.sh + +test_expect_success 'setup' ' + # An alias + git config alias.lgf "log --format=%s --first-parent" && + + # A random user-defined command + write_script git-distimdistim <<-EOF && + echo distimdistim was called + EOF + + PATH="$PATH:." && + export PATH && + + git commit --allow-empty -m "a single log entry" && + + # Sanity check + git lgf >actual && + echo "a single log entry" >expect && + test_cmp expect actual && + + git distimdistim >actual && + echo "distimdistim was called" >expect && + test_cmp expect actual +' + +test_expect_success 'autocorrect showing candidates' ' + git config help.autocorrect 0 && + + test_must_fail git lfg 2>actual && + grep "^ lgf" actual && + + test_must_fail git distimdist 2>actual && + grep "^ distimdistim" actual +' + +test_expect_success 'autocorrect running commands' ' + git config help.autocorrect -1 && + + git lfg >actual && + echo "a single log entry" >expect && + test_cmp expect actual && + + git distimdist >actual && + echo "distimdistim was called" >expect && + test_cmp expect actual +' + +test_done diff --git a/t/t9004-example.sh b/t/t9004-example.sh new file mode 100755 index 0000000000..7e8894a4a7 --- /dev/null +++ b/t/t9004-example.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +test_description='check that example code compiles and runs' +. ./test-lib.sh + +test_expect_success 'decorate' ' + test-tool example-decorate +' + +test_done diff --git a/t/t9010-svn-fe.sh b/t/t9010-svn-fe.sh index 6dafe7e99a..8eaaca6f99 100755 --- a/t/t9010-svn-fe.sh +++ b/t/t9010-svn-fe.sh @@ -4,12 +4,13 @@ test_description='check svn dumpfile importer' . ./test-lib.sh +if test_have_prereq !PIPE +then + skip_all="svn dumpfile importer testing requires the PIPE prerequisite" + test_done +fi + reinit_git () { - if ! test_declared_prereq PIPE - then - echo >&4 "reinit_git: need to declare PIPE prerequisite" - return 127 - fi rm -fr .git && rm -f stream backflow && git init && @@ -54,19 +55,19 @@ text_no_props () { >empty -test_expect_success PIPE 'empty dump' ' +test_expect_success 'empty dump' ' reinit_git && echo "SVN-fs-dump-format-version: 2" >input && try_dump input ' -test_expect_success PIPE 'v4 dumps not supported' ' +test_expect_success 'v4 dumps not supported' ' reinit_git && echo "SVN-fs-dump-format-version: 4" >v4.dump && try_dump v4.dump must_fail ' -test_expect_failure PIPE 'empty revision' ' +test_expect_failure 'empty revision' ' reinit_git && printf "rev <nobody, nobody@local>: %s\n" "" "" >expect && cat >emptyrev.dump <<-\EOF && @@ -86,7 +87,7 @@ test_expect_failure PIPE 'empty revision' ' test_cmp expect actual ' -test_expect_success PIPE 'empty properties' ' +test_expect_success 'empty properties' ' reinit_git && printf "rev <nobody, nobody@local>: %s\n" "" "" >expect && cat >emptyprop.dump <<-\EOF && @@ -109,7 +110,7 @@ test_expect_success PIPE 'empty properties' ' test_cmp expect actual ' -test_expect_success PIPE 'author name and commit message' ' +test_expect_success 'author name and commit message' ' reinit_git && echo "<author@example.com, author@example.com@local>" >expect.author && cat >message <<-\EOF && @@ -143,7 +144,7 @@ test_expect_success PIPE 'author name and commit message' ' test_cmp expect.author actual.author ' -test_expect_success PIPE 'unsupported properties are ignored' ' +test_expect_success 'unsupported properties are ignored' ' reinit_git && echo author >expect && cat >extraprop.dump <<-\EOF && @@ -168,7 +169,7 @@ test_expect_success PIPE 'unsupported properties are ignored' ' test_cmp expect actual ' -test_expect_failure PIPE 'timestamp and empty file' ' +test_expect_failure 'timestamp and empty file' ' echo author@example.com >expect.author && echo 1999-01-01 >expect.date && echo file >expect.files && @@ -210,7 +211,7 @@ test_expect_failure PIPE 'timestamp and empty file' ' test_cmp empty file ' -test_expect_success PIPE 'directory with files' ' +test_expect_success 'directory with files' ' reinit_git && printf "%s\n" directory/file1 directory/file2 >expect.files && echo hi >hi && @@ -263,7 +264,7 @@ test_expect_success PIPE 'directory with files' ' test_cmp hi directory/file2 ' -test_expect_success PIPE 'branch name with backslash' ' +test_expect_success 'branch name with backslash' ' reinit_git && sort <<-\EOF >expect.branch-files && trunk/file1 @@ -362,7 +363,7 @@ test_expect_success PIPE 'branch name with backslash' ' test_cmp expect.branch-files actual.branch-files ' -test_expect_success PIPE 'node without action' ' +test_expect_success 'node without action' ' reinit_git && cat >inaction.dump <<-\EOF && SVN-fs-dump-format-version: 3 @@ -383,7 +384,7 @@ test_expect_success PIPE 'node without action' ' try_dump inaction.dump must_fail ' -test_expect_success PIPE 'action: add node without text' ' +test_expect_success 'action: add node without text' ' reinit_git && cat >textless.dump <<-\EOF && SVN-fs-dump-format-version: 3 @@ -405,7 +406,7 @@ test_expect_success PIPE 'action: add node without text' ' try_dump textless.dump must_fail ' -test_expect_failure PIPE 'change file mode but keep old content' ' +test_expect_failure 'change file mode but keep old content' ' reinit_git && cat >expect <<-\EOF && OBJID @@ -481,7 +482,7 @@ test_expect_failure PIPE 'change file mode but keep old content' ' test_cmp hello actual.target ' -test_expect_success PIPE 'NUL in property value' ' +test_expect_success 'NUL in property value' ' reinit_git && echo "commit message" >expect.message && { @@ -507,7 +508,7 @@ test_expect_success PIPE 'NUL in property value' ' test_cmp expect.message actual.message ' -test_expect_success PIPE 'NUL in log message, file content, and property name' ' +test_expect_success 'NUL in log message, file content, and property name' ' # Caveat: svnadmin 1.6.16 (r1073529) truncates at \0 in the # svn:specialQnotreally example. reinit_git && @@ -587,7 +588,7 @@ test_expect_success PIPE 'NUL in log message, file content, and property name' ' test_cmp expect.hello2 actual.hello2 ' -test_expect_success PIPE 'change file mode and reiterate content' ' +test_expect_success 'change file mode and reiterate content' ' reinit_git && cat >expect <<-\EOF && OBJID @@ -667,7 +668,7 @@ test_expect_success PIPE 'change file mode and reiterate content' ' test_cmp hello actual.target ' -test_expect_success PIPE 'deltas supported' ' +test_expect_success 'deltas supported' ' reinit_git && { # (old) h + (inline) ello + (old) \n @@ -731,7 +732,7 @@ test_expect_success PIPE 'deltas supported' ' try_dump delta.dump ' -test_expect_success PIPE 'property deltas supported' ' +test_expect_success 'property deltas supported' ' reinit_git && cat >expect <<-\EOF && OBJID @@ -796,7 +797,7 @@ test_expect_success PIPE 'property deltas supported' ' test_cmp expect actual ' -test_expect_success PIPE 'properties on /' ' +test_expect_success 'properties on /' ' reinit_git && cat <<-\EOF >expect && OBJID @@ -850,7 +851,7 @@ test_expect_success PIPE 'properties on /' ' test_cmp expect actual ' -test_expect_success PIPE 'deltas for typechange' ' +test_expect_success 'deltas for typechange' ' reinit_git && cat >expect <<-\EOF && OBJID @@ -935,7 +936,7 @@ test_expect_success PIPE 'deltas for typechange' ' test_cmp expect actual ' -test_expect_success PIPE 'deltas need not consume the whole preimage' ' +test_expect_success 'deltas need not consume the whole preimage' ' reinit_git && cat >expect <<-\EOF && OBJID @@ -1040,7 +1041,7 @@ test_expect_success PIPE 'deltas need not consume the whole preimage' ' test_cmp expect.3 actual.3 ' -test_expect_success PIPE 'no hang for delta trying to read past end of preimage' ' +test_expect_success 'no hang for delta trying to read past end of preimage' ' reinit_git && { # COPY 1 @@ -1087,7 +1088,7 @@ test_expect_success 'set up svn repo' ' fi ' -test_expect_success SVNREPO,PIPE 't9135/svn.dump' ' +test_expect_success SVNREPO 't9135/svn.dump' ' mkdir -p simple-git && ( cd simple-git && diff --git a/t/t9020-remote-svn.sh b/t/t9020-remote-svn.sh index 4d81ba1c2c..6fca08e5e3 100755 --- a/t/t9020-remote-svn.sh +++ b/t/t9020-remote-svn.sh @@ -25,8 +25,8 @@ init_git () { git init && #git remote add svnsim testsvn::sim:///$TEST_DIRECTORY/t9020/example.svnrdump # let's reuse an existing dump file!? - git remote add svnsim testsvn::sim://$TEST_DIRECTORY/t9154/svn.dump - git remote add svnfile testsvn::file://$TEST_DIRECTORY/t9154/svn.dump + git remote add svnsim "testsvn::sim://$TEST_DIRECTORY/t9154/svn.dump" + git remote add svnfile "testsvn::file://$TEST_DIRECTORY/t9154/svn.dump" } if test -e "$GIT_BUILD_DIR/git-remote-testsvn" diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 4fea8d901b..c937330a5f 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -8,8 +8,6 @@ GIT_SVN_LC_ALL=${LC_ALL:-$LANG} . ./lib-git-svn.sh -say 'define NO_SVN_TESTS to skip git svn tests' - case "$GIT_SVN_LC_ALL" in *.UTF-8) test_set_prereq UTF8 @@ -19,6 +17,14 @@ case "$GIT_SVN_LC_ALL" in ;; esac +test_expect_success 'git svn --version works anywhere' ' + nongit git svn --version +' + +test_expect_success 'git svn help works anywhere' ' + nongit git svn help +' + test_expect_success \ 'initialize git svn' ' mkdir import && @@ -45,13 +51,13 @@ test_expect_success "checkout from svn" 'svn co "$svnrepo" "$SVN_TREE"' name='try a deep --rmdir with a commit' test_expect_success "$name" ' - git checkout -f -b mybranch ${remotes_git_svn} && + git checkout -f -b mybranch remotes/git-svn && mv dir/a/b/c/d/e/file dir/file && cp dir/file file && git update-index --add --remove dir/a/b/c/d/e/file dir/file file && git commit -m "$name" && git svn set-tree --find-copies-harder --rmdir \ - ${remotes_git_svn}..mybranch && + remotes/git-svn..mybranch && svn_cmd up "$SVN_TREE" && test -d "$SVN_TREE"/dir && test ! -d "$SVN_TREE"/dir/a' @@ -65,14 +71,14 @@ test_expect_success "$name" " git update-index --add dir/file/file && git commit -m '$name' && test_must_fail git svn set-tree --find-copies-harder --rmdir \ - ${remotes_git_svn}..mybranch + remotes/git-svn..mybranch " name='detect node change from directory to file #1' test_expect_success "$name" ' rm -rf dir "$GIT_DIR"/index && - git checkout -f -b mybranch2 ${remotes_git_svn} && + git checkout -f -b mybranch2 remotes/git-svn && mv bar/zzz zzz && rm -rf bar && mv zzz bar && @@ -80,14 +86,14 @@ test_expect_success "$name" ' git update-index --add -- bar && git commit -m "$name" && test_must_fail git svn set-tree --find-copies-harder --rmdir \ - ${remotes_git_svn}..mybranch2 + remotes/git-svn..mybranch2 ' name='detect node change from file to directory #2' test_expect_success "$name" ' rm -f "$GIT_DIR"/index && - git checkout -f -b mybranch3 ${remotes_git_svn} && + git checkout -f -b mybranch3 remotes/git-svn && rm bar/zzz && git update-index --remove bar/zzz && mkdir bar/zzz && @@ -95,7 +101,7 @@ test_expect_success "$name" ' git update-index --add bar/zzz/yyy && git commit -m "$name" && git svn set-tree --find-copies-harder --rmdir \ - ${remotes_git_svn}..mybranch3 && + remotes/git-svn..mybranch3 && svn_cmd up "$SVN_TREE" && test -d "$SVN_TREE"/bar/zzz && test -e "$SVN_TREE"/bar/zzz/yyy @@ -104,7 +110,7 @@ test_expect_success "$name" ' name='detect node change from directory to file #2' test_expect_success "$name" ' rm -f "$GIT_DIR"/index && - git checkout -f -b mybranch4 ${remotes_git_svn} && + git checkout -f -b mybranch4 remotes/git-svn && rm -rf dir && git update-index --remove -- dir/file && touch dir && @@ -112,67 +118,67 @@ test_expect_success "$name" ' git update-index --add -- dir && git commit -m "$name" && test_must_fail git svn set-tree --find-copies-harder --rmdir \ - ${remotes_git_svn}..mybranch4 + remotes/git-svn..mybranch4 ' name='remove executable bit from a file' -test_expect_success "$name" ' +test_expect_success POSIXPERM "$name" ' rm -f "$GIT_DIR"/index && - git checkout -f -b mybranch5 ${remotes_git_svn} && + git checkout -f -b mybranch5 remotes/git-svn && chmod -x exec.sh && git update-index exec.sh && git commit -m "$name" && git svn set-tree --find-copies-harder --rmdir \ - ${remotes_git_svn}..mybranch5 && + remotes/git-svn..mybranch5 && svn_cmd up "$SVN_TREE" && test ! -x "$SVN_TREE"/exec.sh' name='add executable bit back file' -test_expect_success "$name" ' +test_expect_success POSIXPERM "$name" ' chmod +x exec.sh && git update-index exec.sh && git commit -m "$name" && git svn set-tree --find-copies-harder --rmdir \ - ${remotes_git_svn}..mybranch5 && + remotes/git-svn..mybranch5 && svn_cmd up "$SVN_TREE" && test -x "$SVN_TREE"/exec.sh' name='executable file becomes a symlink to file' -test_expect_success "$name" ' +test_expect_success SYMLINKS "$name" ' rm exec.sh && ln -s file exec.sh && git update-index exec.sh && git commit -m "$name" && git svn set-tree --find-copies-harder --rmdir \ - ${remotes_git_svn}..mybranch5 && + remotes/git-svn..mybranch5 && svn_cmd up "$SVN_TREE" && test -h "$SVN_TREE"/exec.sh' name='new symlink is added to a file that was also just made executable' -test_expect_success "$name" ' +test_expect_success POSIXPERM,SYMLINKS "$name" ' chmod +x file && ln -s file exec-2.sh && git update-index --add file exec-2.sh && git commit -m "$name" && git svn set-tree --find-copies-harder --rmdir \ - ${remotes_git_svn}..mybranch5 && + remotes/git-svn..mybranch5 && svn_cmd up "$SVN_TREE" && test -x "$SVN_TREE"/file && test -h "$SVN_TREE"/exec-2.sh' name='modify a symlink to become a file' -test_expect_success "$name" ' +test_expect_success POSIXPERM,SYMLINKS "$name" ' echo git help >help && rm exec-2.sh && cp help exec-2.sh && git update-index exec-2.sh && git commit -m "$name" && git svn set-tree --find-copies-harder --rmdir \ - ${remotes_git_svn}..mybranch5 && + remotes/git-svn..mybranch5 && svn_cmd up "$SVN_TREE" && test -f "$SVN_TREE"/exec-2.sh && test ! -h "$SVN_TREE"/exec-2.sh && @@ -181,7 +187,8 @@ test_expect_success "$name" ' name="commit with UTF-8 message: locale: $GIT_SVN_LC_ALL" LC_ALL="$GIT_SVN_LC_ALL" export LC_ALL -test_expect_success UTF8 "$name" " +# This test relies on the previous test, hence requires POSIXPERM,SYMLINKS +test_expect_success UTF8,POSIXPERM,SYMLINKS "$name" " echo '# hello' >> exec-2.sh && git update-index exec-2.sh && git commit -m 'éïâˆ' && @@ -193,7 +200,7 @@ GIT_SVN_ID=alt export GIT_SVN_ID test_expect_success "$name" \ 'git svn init "$svnrepo" && git svn fetch && - git rev-list --pretty=raw ${remotes_git_svn} | grep ^tree | uniq > a && + git rev-list --pretty=raw remotes/git-svn | grep ^tree | uniq > a && git rev-list --pretty=raw remotes/alt | grep ^tree | uniq > b && test_cmp a b' @@ -214,19 +221,19 @@ tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e tree 8f51f74cf0163afc9ad68a4b1537288c4558b5a4 EOF -test_expect_success "$name" "test_cmp a expected" +test_expect_success POSIXPERM,SYMLINKS "$name" "test_cmp a expected" -test_expect_success 'exit if remote refs are ambigious' " +test_expect_success 'exit if remote refs are ambigious' ' git config --add svn-remote.svn.fetch \ - bar:refs/${remotes_git_svn} && + bar:refs/remotes/git-svn && test_must_fail git svn migrate -" +' test_expect_success 'exit if init-ing a would clobber a URL' ' svnadmin create "${PWD}/svnrepo2" && svn mkdir -m "mkdir bar" "${svnrepo}2/bar" && git config --unset svn-remote.svn.fetch \ - "^bar:refs/${remotes_git_svn}$" && + "^bar:refs/remotes/git-svn$" && test_must_fail git svn init "${svnrepo}2/bar" ' @@ -236,7 +243,7 @@ test_expect_success \ git config --get svn-remote.svn.fetch \ "^bar:refs/remotes/bar$" && git config --get svn-remote.svn.fetch \ - "^:refs/${remotes_git_svn}$" + "^:refs/remotes/git-svn$" ' test_expect_success 'dcommit $rev does not clobber current branch' ' @@ -258,45 +265,45 @@ test_expect_success 'dcommit $rev does not clobber current branch' ' git branch -D my-bar ' -test_expect_success 'able to dcommit to a subdirectory' " +test_expect_success 'able to dcommit to a subdirectory' ' git svn fetch -i bar && git checkout -b my-bar refs/remotes/bar && echo abc > d && git update-index --add d && - git commit -m '/bar/d should be in the log' && + git commit -m "/bar/d should be in the log" && git svn dcommit -i bar && - test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" && + test -z "$(git diff refs/heads/my-bar refs/remotes/bar)" && mkdir newdir && echo new > newdir/dir && git update-index --add newdir/dir && - git commit -m 'add a new directory' && + git commit -m "add a new directory" && git svn dcommit -i bar && - test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" && + test -z "$(git diff refs/heads/my-bar refs/remotes/bar)" && echo foo >> newdir/dir && git update-index newdir/dir && - git commit -m 'modify a file in new directory' && + git commit -m "modify a file in new directory" && git svn dcommit -i bar && - test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" - " + test -z "$(git diff refs/heads/my-bar refs/remotes/bar)" +' test_expect_success 'dcommit should not fail with a touched file' ' test_commit "commit-new-file-foo2" foo2 && - test-chmtime =-60 foo && + test-tool chmtime =-60 foo && git svn dcommit ' test_expect_success 'rebase should not fail with a touched file' ' - test-chmtime =-60 foo && + test-tool chmtime =-60 foo && git svn rebase ' -test_expect_success 'able to set-tree to a subdirectory' " +test_expect_success 'able to set-tree to a subdirectory' ' echo cba > d && git update-index d && - git commit -m 'update /bar/d' && + git commit -m "update /bar/d" && git svn set-tree -i bar HEAD && - test -z \"\`git diff refs/heads/my-bar refs/remotes/bar\`\" - " + test -z "$(git diff refs/heads/my-bar refs/remotes/bar)" +' test_expect_success 'git-svn works in a bare repository' ' mkdir bare-repo && diff --git a/t/t9101-git-svn-props.sh b/t/t9101-git-svn-props.sh index 8869f5018e..07bfb63777 100755 --- a/t/t9101-git-svn-props.sh +++ b/t/t9101-git-svn-props.sh @@ -26,27 +26,27 @@ cd import EOF printf "Hello\r\nWorld\r\n" > crlf - a_crlf=`git hash-object -w crlf` + a_crlf=$(git hash-object -w crlf) printf "Hello\rWorld\r" > cr - a_cr=`git hash-object -w cr` + a_cr=$(git hash-object -w cr) printf "Hello\nWorld\n" > lf - a_lf=`git hash-object -w lf` + a_lf=$(git hash-object -w lf) printf "Hello\r\nWorld" > ne_crlf - a_ne_crlf=`git hash-object -w ne_crlf` + a_ne_crlf=$(git hash-object -w ne_crlf) printf "Hello\nWorld" > ne_lf - a_ne_lf=`git hash-object -w ne_lf` + a_ne_lf=$(git hash-object -w ne_lf) printf "Hello\rWorld" > ne_cr - a_ne_cr=`git hash-object -w ne_cr` + a_ne_cr=$(git hash-object -w ne_cr) touch empty - a_empty=`git hash-object -w empty` + a_empty=$(git hash-object -w empty) printf "\n" > empty_lf - a_empty_lf=`git hash-object -w empty_lf` + a_empty_lf=$(git hash-object -w empty_lf) printf "\r" > empty_cr - a_empty_cr=`git hash-object -w empty_cr` + a_empty_cr=$(git hash-object -w empty_cr) printf "\r\n" > empty_crlf - a_empty_crlf=`git hash-object -w empty_crlf` + a_empty_crlf=$(git hash-object -w empty_crlf) svn_cmd import --no-auto-props -m 'import for git svn' . "$svnrepo" >/dev/null cd .. @@ -73,14 +73,14 @@ test_expect_success 'fetch revisions from svn' 'git svn fetch' name='test svn:keywords ignoring' test_expect_success "$name" \ - 'git checkout -b mybranch ${remotes_git_svn} && + 'git checkout -b mybranch remotes/git-svn && echo Hi again >> kw.c && git commit -a -m "test keywords ignoring" && - git svn set-tree ${remotes_git_svn}..mybranch && - git pull . ${remotes_git_svn}' + git svn set-tree remotes/git-svn..mybranch && + git pull . remotes/git-svn' expect='/* $Id$ */' -got="`sed -ne 2p kw.c`" +got="$(sed -ne 2p kw.c)" test_expect_success 'raw $Id$ found in kw.c' "test '$expect' = '$got'" test_expect_success "propset CR on crlf files" ' @@ -95,7 +95,7 @@ test_expect_success "propset CR on crlf files" ' test_expect_success 'fetch and pull latest from svn and checkout a new wc' \ 'git svn fetch && - git pull . ${remotes_git_svn} && + git pull . remotes/git-svn && svn_cmd co "$svnrepo" new_wc' for i in crlf ne_crlf lf ne_lf cr ne_cr empty_cr empty_lf empty empty_crlf @@ -107,8 +107,8 @@ done cd test_wc printf '$Id$\rHello\rWorld\r' > cr printf '$Id$\rHello\rWorld' > ne_cr - a_cr=`printf '$Id$\r\nHello\r\nWorld\r\n' | git hash-object --stdin` - a_ne_cr=`printf '$Id$\r\nHello\r\nWorld' | git hash-object --stdin` + a_cr=$(printf '$Id$\r\nHello\r\nWorld\r\n' | git hash-object --stdin) + a_ne_cr=$(printf '$Id$\r\nHello\r\nWorld' | git hash-object --stdin) test_expect_success 'Set CRLF on cr files' \ 'svn_cmd propset svn:eol-style CRLF cr && svn_cmd propset svn:eol-style CRLF ne_cr && @@ -117,10 +117,10 @@ cd test_wc svn_cmd commit -m "propset CRLF on cr files"' cd .. test_expect_success 'fetch and pull latest from svn' \ - 'git svn fetch && git pull . ${remotes_git_svn}' + 'git svn fetch && git pull . remotes/git-svn' -b_cr="`git hash-object cr`" -b_ne_cr="`git hash-object ne_cr`" +b_cr="$(git hash-object cr)" +b_ne_cr="$(git hash-object ne_cr)" test_expect_success 'CRLF + $Id$' "test '$a_cr' = '$b_cr'" test_expect_success 'CRLF + $Id$ (no newline)' "test '$a_ne_cr' = '$b_ne_cr'" @@ -168,7 +168,7 @@ cat >create-ignore-index.expect <<\EOF EOF test_expect_success 'test create-ignore' " - git svn fetch && git pull . ${remotes_git_svn} && + git svn fetch && git pull . remotes/git-svn && git svn create-ignore && cmp ./.gitignore create-ignore.expect && cmp ./deeply/.gitignore create-ignore.expect && diff --git a/t/t9102-git-svn-deep-rmdir.sh b/t/t9102-git-svn-deep-rmdir.sh index eb70f4839c..66cd51102c 100755 --- a/t/t9102-git-svn-deep-rmdir.sh +++ b/t/t9102-git-svn-deep-rmdir.sh @@ -17,7 +17,7 @@ test_expect_success 'initialize repo' ' test_expect_success 'mirror via git svn' ' git svn init "$svnrepo" && git svn fetch && - git checkout -f -b test-rmdir ${remotes_git_svn} + git checkout -f -b test-rmdir remotes/git-svn ' test_expect_success 'Try a commit on rmdir' ' diff --git a/t/t9103-git-svn-tracked-directory-removed.sh b/t/t9103-git-svn-tracked-directory-removed.sh index 3413164cb1..b28271345c 100755 --- a/t/t9103-git-svn-tracked-directory-removed.sh +++ b/t/t9103-git-svn-tracked-directory-removed.sh @@ -23,17 +23,19 @@ test_expect_success 'make history for tracking' ' test_expect_success 'clone repo with git' ' git svn clone -s "$svnrepo" x && - test -f x/FOLLOWME && - test ! -f x/README + test_path_is_file x/FOLLOWME && + test_path_is_missing x/README ' -test_expect_success 'make sure r2 still has old file' " - cd x && - test -n \"\$(git svn find-rev r1)\" && - git reset --hard \$(git svn find-rev r1) && - test -f README && - test ! -f FOLLOWME && - test x\$(git svn find-rev r2) = x -" +test_expect_success 'make sure r2 still has old file' ' + ( + cd x && + test -n "$(git svn find-rev r1)" && + git reset --hard "$(git svn find-rev r1)" && + test_path_is_file README && + test_path_is_missing FOLLOWME && + test -z "$(git svn find-rev r2)" + ) +' test_done diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 83f17e13e8..a735fa3717 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -31,12 +31,12 @@ test_expect_success 'initialize repo' ' test_expect_success 'init and fetch a moved directory' ' git svn init --minimize-url -i thunk "$svnrepo"/thunk && git svn fetch -i thunk && - test "`git rev-parse --verify refs/remotes/thunk@2`" \ - = "`git rev-parse --verify refs/remotes/thunk~1`" && - test "`git cat-file blob refs/remotes/thunk:readme |\ - sed -n -e "3p"`" = goodbye && - test -z "`git config --get svn-remote.svn.fetch \ - "^trunk:refs/remotes/thunk@2$"`" + test "$(git rev-parse --verify refs/remotes/thunk@2)" \ + = "$(git rev-parse --verify refs/remotes/thunk~1)" && + git cat-file blob refs/remotes/thunk:readme >actual && + test "$(sed -n -e "3p" actual)" = goodbye && + test -z "$(git config --get svn-remote.svn.fetch \ + "^trunk:refs/remotes/thunk@2$")" ' test_expect_success 'init and fetch from one svn-remote' ' @@ -46,10 +46,10 @@ test_expect_success 'init and fetch from one svn-remote' ' git config --add svn-remote.svn.fetch \ thunk:refs/remotes/svn/thunk && git svn fetch -i svn/thunk && - test "`git rev-parse --verify refs/remotes/svn/trunk`" \ - = "`git rev-parse --verify refs/remotes/svn/thunk~1`" && - test "`git cat-file blob refs/remotes/svn/thunk:readme |\ - sed -n -e "3p"`" = goodbye + test "$(git rev-parse --verify refs/remotes/svn/trunk)" \ + = "$(git rev-parse --verify refs/remotes/svn/thunk~1)" && + git cat-file blob refs/remotes/svn/thunk:readme >actual && + test "$(sed -n -e "3p" actual)" = goodbye ' test_expect_success 'follow deleted parent' ' @@ -61,9 +61,9 @@ test_expect_success 'follow deleted parent' ' junk:refs/remotes/svn/junk && git svn fetch -i svn/thunk && git svn fetch -i svn/junk && - test -z "`git diff svn/junk svn/trunk`" && - test "`git merge-base svn/junk svn/trunk`" \ - = "`git rev-parse svn/trunk`" + test -z "$(git diff svn/junk svn/trunk)" && + test "$(git merge-base svn/junk svn/trunk)" \ + = "$(git rev-parse svn/trunk)" ' test_expect_success 'follow larger parent' ' @@ -80,10 +80,10 @@ test_expect_success 'follow larger parent' ' git rev-parse --verify refs/remotes/larger && git rev-parse --verify \ refs/remotes/larger-parent && - test "`git merge-base \ + test "$(git merge-base \ refs/remotes/larger-parent \ - refs/remotes/larger`" = \ - "`git rev-parse refs/remotes/larger`" + refs/remotes/larger)" = \ + "$(git rev-parse refs/remotes/larger)" ' test_expect_success 'follow higher-level parent' ' @@ -106,8 +106,9 @@ test_expect_success 'follow deleted directory' ' svn_cmd rm -m "remove glob" "$svnrepo"/glob && git svn init --minimize-url -i glob "$svnrepo"/glob && git svn fetch -i glob && - test "`git cat-file blob refs/remotes/glob:blob/bye`" = hi && - test "`git ls-tree refs/remotes/glob | wc -l `" -eq 1 + test "$(git cat-file blob refs/remotes/glob:blob/bye)" = hi && + git ls-tree refs/remotes/glob >actual && + test_line_count = 1 actual ' # ref: r9270 of the Subversion repository: (http://svn.collab.net/repos/svn) @@ -142,9 +143,9 @@ test_expect_success 'follow-parent avoids deleting relevant info' ' git svn init --minimize-url -i r9270-t \ "$svnrepo"/r9270/trunk/subversion/bindings/swig/perl/native/t && git svn fetch -i r9270-t && - test `git rev-list r9270-t | wc -l` -eq 2 && - test "`git ls-tree --name-only r9270-t~1`" = \ - "`git ls-tree --name-only r9270-t`" + test $(git rev-list r9270-t | wc -l) -eq 2 && + test "$(git ls-tree --name-only r9270-t~1)" = \ + "$(git ls-tree --name-only r9270-t)" ' test_expect_success "track initial change if it was only made to parent" ' @@ -152,11 +153,11 @@ test_expect_success "track initial change if it was only made to parent" ' git svn init --minimize-url -i r9270-d \ "$svnrepo"/r9270/drunk/subversion/bindings/swig/perl/native/t && git svn fetch -i r9270-d && - test `git rev-list r9270-d | wc -l` -eq 3 && - test "`git ls-tree --name-only r9270-t`" = \ - "`git ls-tree --name-only r9270-d`" && - test "`git rev-parse r9270-t`" = \ - "`git rev-parse r9270-d~1`" + test $(git rev-list r9270-d | wc -l) -eq 3 && + test "$(git ls-tree --name-only r9270-t)" = \ + "$(git ls-tree --name-only r9270-d)" && + test "$(git rev-parse r9270-t)" = \ + "$(git rev-parse r9270-d~1)" ' test_expect_success "follow-parent is atomic" ' @@ -193,19 +194,20 @@ test_expect_success "follow-parent is atomic" ' git svn fetch -i stunk && git svn init --minimize-url -i flunked "$svnrepo"/flunked && git svn fetch -i flunked && - test "`git rev-parse --verify refs/remotes/flunk@18`" \ - = "`git rev-parse --verify refs/remotes/stunk`" && - test "`git rev-parse --verify refs/remotes/flunk~1`" \ - = "`git rev-parse --verify refs/remotes/stunk`" && - test "`git rev-parse --verify refs/remotes/flunked~1`" \ - = "`git rev-parse --verify refs/remotes/stunk~1`" + test "$(git rev-parse --verify refs/remotes/flunk@18)" \ + = "$(git rev-parse --verify refs/remotes/stunk)" && + test "$(git rev-parse --verify refs/remotes/flunk~1)" \ + = "$(git rev-parse --verify refs/remotes/stunk)" && + test "$(git rev-parse --verify refs/remotes/flunked~1)" \ + = "$(git rev-parse --verify refs/remotes/stunk~1)" ' test_expect_success "track multi-parent paths" ' svn_cmd cp -m "resurrect /glob" "$svnrepo"/r9270 "$svnrepo"/glob && git svn multi-fetch && - test `git cat-file commit refs/remotes/glob | \ - grep "^parent " | wc -l` -eq 2 + git cat-file commit refs/remotes/glob >actual && + grep "^parent " actual >actual2 && + test_line_count = 2 actual2 ' test_expect_success "multi-fetch continues to work" " diff --git a/t/t9105-git-svn-commit-diff.sh b/t/t9105-git-svn-commit-diff.sh index 5d0afeae6c..6ed5f74e25 100755 --- a/t/t9105-git-svn-commit-diff.sh +++ b/t/t9105-git-svn-commit-diff.sh @@ -18,8 +18,8 @@ test_expect_success 'initialize repo' ' git commit -a -m "another" ' -head=`git rev-parse --verify HEAD^0` -prev=`git rev-parse --verify HEAD^1` +head=$(git rev-parse --verify HEAD^0) +prev=$(git rev-parse --verify HEAD^1) # the internals of the commit-diff command are the same as the regular # commit, so only a basic test of functionality is needed since we've diff --git a/t/t9106-git-svn-commit-diff-clobber.sh b/t/t9106-git-svn-commit-diff-clobber.sh index f6d7ac7c5f..dbe8deac0d 100755 --- a/t/t9106-git-svn-commit-diff-clobber.sh +++ b/t/t9106-git-svn-commit-diff-clobber.sh @@ -44,7 +44,7 @@ test_expect_success 'commit complementing change from git' ' test_expect_success 'dcommit fails to commit because of conflict' ' git svn init "$svnrepo" && git svn fetch && - git reset --hard refs/${remotes_git_svn} && + git reset --hard refs/remotes/git-svn && svn_cmd co "$svnrepo" t.svn && ( cd t.svn && @@ -59,7 +59,7 @@ test_expect_success 'dcommit fails to commit because of conflict' ' ' test_expect_success 'dcommit does the svn equivalent of an index merge' " - git reset --hard refs/${remotes_git_svn} && + git reset --hard refs/remotes/git-svn && echo 'index merge' > file2 && git update-index --add file2 && git commit -a -m 'index merge' && @@ -81,7 +81,7 @@ test_expect_success 'commit another change from svn side' ' ' test_expect_success 'multiple dcommit from git svn will not clobber svn' " - git reset --hard refs/${remotes_git_svn} && + git reset --hard refs/remotes/git-svn && echo new file >> new-file && git update-index --add new-file && git commit -a -m 'new file' && diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index 6e69fc4c65..ceaa5bad10 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -19,15 +19,16 @@ test_expect_success 'setup old-looking metadata' ' git svn init "$svnrepo" && git svn fetch && rm -rf "$GIT_DIR"/svn && - git update-ref refs/heads/git-svn-HEAD refs/${remotes_git_svn} && - git update-ref refs/heads/svn-HEAD refs/${remotes_git_svn} && - git update-ref -d refs/${remotes_git_svn} refs/${remotes_git_svn} + git update-ref refs/heads/git-svn-HEAD refs/remotes/git-svn && + git update-ref refs/heads/svn-HEAD refs/remotes/git-svn && + git update-ref -d refs/remotes/git-svn refs/remotes/git-svn ' -head=`git rev-parse --verify refs/heads/git-svn-HEAD^0` -test_expect_success 'git-svn-HEAD is a real HEAD' "test -n '$head'" +test_expect_success 'git-svn-HEAD is a real HEAD' ' + git rev-parse --verify refs/heads/git-svn-HEAD^0 +' -svnrepo_escaped=`echo $svnrepo | sed 's/ /%20/'` +svnrepo_escaped=$(echo $svnrepo | sed 's/ /%20/g') test_expect_success 'initialize old-style (v0) git svn layout' ' mkdir -p "$GIT_DIR"/git-svn/info "$GIT_DIR"/svn/info && @@ -35,30 +36,32 @@ test_expect_success 'initialize old-style (v0) git svn layout' ' echo "$svnrepo" > "$GIT_DIR"/svn/info/url && git svn migrate && ! test -d "$GIT_DIR"/git-svn && - git rev-parse --verify refs/${remotes_git_svn}^0 && + git rev-parse --verify refs/remotes/git-svn^0 && git rev-parse --verify refs/remotes/svn^0 && test "$(git config --get svn-remote.svn.url)" = "$svnrepo_escaped" && - test `git config --get svn-remote.svn.fetch` = \ - ":refs/${remotes_git_svn}" + test $(git config --get svn-remote.svn.fetch) = \ + ":refs/remotes/git-svn" ' test_expect_success 'initialize a multi-repository repo' ' git svn init "$svnrepo" -T trunk -t tags -b branches && git config --get-all svn-remote.svn.fetch > fetch.out && grep "^trunk:refs/remotes/origin/trunk$" fetch.out && - test -n "`git config --get svn-remote.svn.branches \ - "^branches/\*:refs/remotes/origin/\*$"`" && - test -n "`git config --get svn-remote.svn.tags \ - "^tags/\*:refs/remotes/origin/tags/\*$"`" && + test -n "$(git config --get svn-remote.svn.branches \ + "^branches/\*:refs/remotes/origin/\*$")" && + test -n "$(git config --get svn-remote.svn.tags \ + "^tags/\*:refs/remotes/origin/tags/\*$")" && git config --unset svn-remote.svn.branches \ "^branches/\*:refs/remotes/origin/\*$" && git config --unset svn-remote.svn.tags \ "^tags/\*:refs/remotes/origin/tags/\*$" && git config --add svn-remote.svn.fetch "branches/a:refs/remotes/origin/a" && git config --add svn-remote.svn.fetch "branches/b:refs/remotes/origin/b" && - for i in tags/0.1 tags/0.2 tags/0.3; do + for i in tags/0.1 tags/0.2 tags/0.3 + do git config --add svn-remote.svn.fetch \ - $i:refs/remotes/origin/$i || exit 1; done && + $i:refs/remotes/origin/$i || return 1 + done && git config --get-all svn-remote.svn.fetch > fetch.out && grep "^trunk:refs/remotes/origin/trunk$" fetch.out && grep "^branches/a:refs/remotes/origin/a$" fetch.out && @@ -66,37 +69,45 @@ test_expect_success 'initialize a multi-repository repo' ' grep "^tags/0\.1:refs/remotes/origin/tags/0\.1$" fetch.out && grep "^tags/0\.2:refs/remotes/origin/tags/0\.2$" fetch.out && grep "^tags/0\.3:refs/remotes/origin/tags/0\.3$" fetch.out && - grep "^:refs/${remotes_git_svn}" fetch.out + grep "^:refs/remotes/git-svn" fetch.out ' # refs should all be different, but the trees should all be the same: -test_expect_success 'multi-fetch works on partial urls + paths' " +test_expect_success 'multi-fetch works on partial urls + paths' ' + refs="trunk a b tags/0.1 tags/0.2 tags/0.3" && git svn multi-fetch && - for i in trunk a b tags/0.1 tags/0.2 tags/0.3; do - git rev-parse --verify refs/remotes/origin/\$i^0 >> refs.out || exit 1; - done && - test -z \"\`sort < refs.out | uniq -d\`\" && - for i in trunk a b tags/0.1 tags/0.2 tags/0.3; do - for j in trunk a b tags/0.1 tags/0.2 tags/0.3; do - if test \$j != \$i; then continue; fi - test -z \"\`git diff refs/remotes/origin/\$i \ - refs/remotes/origin/\$j\`\" ||exit 1; done; done - " + for i in $refs + do + git rev-parse --verify refs/remotes/origin/$i^0 || return 1; + done >refs.out && + test -z "$(sort <refs.out | uniq -d)" && + for i in $refs + do + for j in $refs + do + git diff --exit-code refs/remotes/origin/$i \ + refs/remotes/origin/$j || + return 1 + done + done +' test_expect_success 'migrate --minimize on old inited layout' ' git config --unset-all svn-remote.svn.fetch && git config --unset-all svn-remote.svn.url && rm -rf "$GIT_DIR"/svn && - for i in `cat fetch.out`; do - path=`expr $i : "\([^:]*\):.*$"` - ref=`expr $i : "[^:]*:\(refs/remotes/.*\)$"` + for i in $(cat fetch.out) + do + path=$(expr $i : "\([^:]*\):.*$") + ref=$(expr $i : "[^:]*:\(refs/remotes/.*\)$") if test -z "$ref"; then continue; fi if test -n "$path"; then path="/$path"; fi - ( mkdir -p "$GIT_DIR"/svn/$ref/info/ && - echo "$svnrepo"$path > "$GIT_DIR"/svn/$ref/info/url ) || exit 1; + mkdir -p "$GIT_DIR"/svn/$ref/info/ && + echo "$svnrepo"$path >"$GIT_DIR"/svn/$ref/info/url || + return 1 done && git svn migrate --minimize && - test -z "`git config -l | grep "^svn-remote\.git-svn\."`" && + test -z "$(git config -l | grep "^svn-remote\.git-svn\.")" && git config --get-all svn-remote.svn.fetch > fetch.out && grep "^trunk:refs/remotes/origin/trunk$" fetch.out && grep "^branches/a:refs/remotes/origin/a$" fetch.out && @@ -104,7 +115,7 @@ test_expect_success 'migrate --minimize on old inited layout' ' grep "^tags/0\.1:refs/remotes/origin/tags/0\.1$" fetch.out && grep "^tags/0\.2:refs/remotes/origin/tags/0\.2$" fetch.out && grep "^tags/0\.3:refs/remotes/origin/tags/0\.3$" fetch.out && - grep "^:refs/${remotes_git_svn}" fetch.out + grep "^:refs/remotes/git-svn" fetch.out ' test_expect_success ".rev_db auto-converted to .rev_map.UUID" ' diff --git a/t/t9108-git-svn-glob.sh b/t/t9108-git-svn-glob.sh index d732d31302..6990f64364 100755 --- a/t/t9108-git-svn-glob.sh +++ b/t/t9108-git-svn-glob.sh @@ -47,13 +47,13 @@ test_expect_success 'test refspec globbing' ' git config --add svn-remote.svn.tags\ "tags/*/src/a:refs/remotes/tags/*" && git svn multi-fetch && - git log --pretty=oneline refs/remotes/tags/end | \ - sed -e "s/^.\{41\}//" > output.end && + git log --pretty=oneline refs/remotes/tags/end >actual && + sed -e "s/^.\{41\}//" actual >output.end && test_cmp expect.end output.end && - test "`git rev-parse refs/remotes/tags/end~1`" = \ - "`git rev-parse refs/remotes/branches/start`" && - test "`git rev-parse refs/remotes/branches/start~2`" = \ - "`git rev-parse refs/remotes/trunk`" && + test "$(git rev-parse refs/remotes/tags/end~1)" = \ + "$(git rev-parse refs/remotes/branches/start)" && + test "$(git rev-parse refs/remotes/branches/start~2)" = \ + "$(git rev-parse refs/remotes/trunk)" && test_must_fail git rev-parse refs/remotes/tags/end@3 ' @@ -75,20 +75,25 @@ test_expect_success 'test left-hand-side only globbing' ' svn_cmd commit -m "try to try" ) && git svn fetch two && - test `git rev-list refs/remotes/two/tags/end | wc -l` -eq 6 && - test `git rev-list refs/remotes/two/branches/start | wc -l` -eq 3 && - test `git rev-parse refs/remotes/two/branches/start~2` = \ - `git rev-parse refs/remotes/two/trunk` && - test `git rev-parse refs/remotes/two/tags/end~3` = \ - `git rev-parse refs/remotes/two/branches/start` && - git log --pretty=oneline refs/remotes/two/tags/end | \ - sed -e "s/^.\{41\}//" > output.two && + git rev-list refs/remotes/two/tags/end >actual && + test_line_count = 6 actual && + git rev-list refs/remotes/two/branches/start >actual && + test_line_count = 3 actual && + test $(git rev-parse refs/remotes/two/branches/start~2) = \ + $(git rev-parse refs/remotes/two/trunk) && + test $(git rev-parse refs/remotes/two/tags/end~3) = \ + $(git rev-parse refs/remotes/two/branches/start) && + git log --pretty=oneline refs/remotes/two/tags/end >actual && + sed -e "s/^.\{41\}//" actual >output.two && test_cmp expect.two output.two ' -echo "Only one set of wildcard directories" \ - "(e.g. '*' or '*/*/*') is supported: 'branches/*/t/*'" > expect.three -echo "" >> expect.three +test_expect_success 'prepare test disallow multi-globs' " +cat >expect.three <<EOF +Only one set of wildcards (e.g. '*' or '*/*/*') is supported: branches/*/t/* + +EOF + " test_expect_success 'test disallow multi-globs' ' git config --add svn-remote.three.url "$svnrepo" && diff --git a/t/t9109-git-svn-multi-glob.sh b/t/t9109-git-svn-multi-glob.sh index c318f9f946..c1e7542a37 100755 --- a/t/t9109-git-svn-multi-glob.sh +++ b/t/t9109-git-svn-multi-glob.sh @@ -47,13 +47,13 @@ test_expect_success 'test refspec globbing' ' git config --add svn-remote.svn.tags\ "tags/*/src/a:refs/remotes/tags/*" && git svn multi-fetch && - git log --pretty=oneline refs/remotes/tags/end | \ - sed -e "s/^.\{41\}//" > output.end && + git log --pretty=oneline refs/remotes/tags/end >actual && + sed -e "s/^.\{41\}//" actual >output.end && test_cmp expect.end output.end && - test "`git rev-parse refs/remotes/tags/end~1`" = \ - "`git rev-parse refs/remotes/branches/v1/start`" && - test "`git rev-parse refs/remotes/branches/v1/start~2`" = \ - "`git rev-parse refs/remotes/trunk`" && + test "$(git rev-parse refs/remotes/tags/end~1)" = \ + "$(git rev-parse refs/remotes/branches/v1/start)" && + test "$(git rev-parse refs/remotes/branches/v1/start~2)" = \ + "$(git rev-parse refs/remotes/trunk)" && test_must_fail git rev-parse refs/remotes/tags/end@3 ' @@ -75,14 +75,16 @@ test_expect_success 'test left-hand-side only globbing' ' svn_cmd commit -m "try to try" ) && git svn fetch two && - test `git rev-list refs/remotes/two/tags/end | wc -l` -eq 6 && - test `git rev-list refs/remotes/two/branches/v1/start | wc -l` -eq 3 && - test `git rev-parse refs/remotes/two/branches/v1/start~2` = \ - `git rev-parse refs/remotes/two/trunk` && - test `git rev-parse refs/remotes/two/tags/end~3` = \ - `git rev-parse refs/remotes/two/branches/v1/start` && - git log --pretty=oneline refs/remotes/two/tags/end | \ - sed -e "s/^.\{41\}//" > output.two && + git rev-list refs/remotes/two/tags/end >actual && + test_line_count = 6 actual && + git rev-list refs/remotes/two/branches/v1/start >actual && + test_line_count = 3 actual && + test $(git rev-parse refs/remotes/two/branches/v1/start~2) = \ + $(git rev-parse refs/remotes/two/trunk) && + test $(git rev-parse refs/remotes/two/tags/end~3) = \ + $(git rev-parse refs/remotes/two/branches/v1/start) && + git log --pretty=oneline refs/remotes/two/tags/end >actual && + sed -e "s/^.\{41\}//" actual >output.two && test_cmp expect.two output.two ' cat > expect.four <<EOF @@ -124,20 +126,25 @@ test_expect_success 'test another branch' ' git config --add svn-remote.four.tags \ "tags/*:refs/remotes/four/tags/*" && git svn fetch four && - test `git rev-list refs/remotes/four/tags/next | wc -l` -eq 5 && - test `git rev-list refs/remotes/four/branches/v2/start | wc -l` -eq 3 && - test `git rev-parse refs/remotes/four/branches/v2/start~2` = \ - `git rev-parse refs/remotes/four/trunk` && - test `git rev-parse refs/remotes/four/tags/next~2` = \ - `git rev-parse refs/remotes/four/branches/v2/start` && - git log --pretty=oneline refs/remotes/four/tags/next | \ - sed -e "s/^.\{41\}//" > output.four && + git rev-list refs/remotes/four/tags/next >actual && + test_line_count = 5 actual && + git rev-list refs/remotes/four/branches/v2/start >actual && + test_line_count = 3 actual && + test $(git rev-parse refs/remotes/four/branches/v2/start~2) = \ + $(git rev-parse refs/remotes/four/trunk) && + test $(git rev-parse refs/remotes/four/tags/next~2) = \ + $(git rev-parse refs/remotes/four/branches/v2/start) && + git log --pretty=oneline refs/remotes/four/tags/next >actual && + sed -e "s/^.\{41\}//" actual >output.four && test_cmp expect.four output.four ' -echo "Only one set of wildcard directories" \ - "(e.g. '*' or '*/*/*') is supported: 'branches/*/t/*'" > expect.three -echo "" >> expect.three +test_expect_success 'prepare test disallow multiple globs' " +cat >expect.three <<EOF +Only one set of wildcards (e.g. '*' or '*/*/*') is supported: branches/*/t/* + +EOF + " test_expect_success 'test disallow multiple globs' ' git config --add svn-remote.three.url "$svnrepo" && diff --git a/t/t9110-git-svn-use-svm-props.sh b/t/t9110-git-svn-use-svm-props.sh index a06e4c5b8e..ad37d980c9 100755 --- a/t/t9110-git-svn-use-svm-props.sh +++ b/t/t9110-git-svn-use-svm-props.sh @@ -21,37 +21,37 @@ uuid=161ce429-a9dd-4828-af4a-52023f968c89 bar_url=http://mayonaise/svnrepo/bar test_expect_success 'verify metadata for /bar' " - git cat-file commit refs/remotes/bar | \ - grep '^${git_svn_id}: $bar_url@12 $uuid$' && - git cat-file commit refs/remotes/bar~1 | \ - grep '^${git_svn_id}: $bar_url@11 $uuid$' && - git cat-file commit refs/remotes/bar~2 | \ - grep '^${git_svn_id}: $bar_url@10 $uuid$' && - git cat-file commit refs/remotes/bar~3 | \ - grep '^${git_svn_id}: $bar_url@9 $uuid$' && - git cat-file commit refs/remotes/bar~4 | \ - grep '^${git_svn_id}: $bar_url@6 $uuid$' && - git cat-file commit refs/remotes/bar~5 | \ - grep '^${git_svn_id}: $bar_url@1 $uuid$' + git cat-file commit refs/remotes/bar >actual && + grep '^git-svn-id: $bar_url@12 $uuid$' actual && + git cat-file commit refs/remotes/bar~1 >actual && + grep '^git-svn-id: $bar_url@11 $uuid$' actual && + git cat-file commit refs/remotes/bar~2 >actual && + grep '^git-svn-id: $bar_url@10 $uuid$' actual && + git cat-file commit refs/remotes/bar~3 >actual && + grep '^git-svn-id: $bar_url@9 $uuid$' actual && + git cat-file commit refs/remotes/bar~4 >actual && + grep '^git-svn-id: $bar_url@6 $uuid$' actual && + git cat-file commit refs/remotes/bar~5 >actual && + grep '^git-svn-id: $bar_url@1 $uuid$' actual " e_url=http://mayonaise/svnrepo/dir/a/b/c/d/e test_expect_success 'verify metadata for /dir/a/b/c/d/e' " - git cat-file commit refs/remotes/e | \ - grep '^${git_svn_id}: $e_url@1 $uuid$' + git cat-file commit refs/remotes/e >actual && + grep '^git-svn-id: $e_url@1 $uuid$' actual " dir_url=http://mayonaise/svnrepo/dir test_expect_success 'verify metadata for /dir' " - git cat-file commit refs/remotes/dir | \ - grep '^${git_svn_id}: $dir_url@2 $uuid$' && - git cat-file commit refs/remotes/dir~1 | \ - grep '^${git_svn_id}: $dir_url@1 $uuid$' + git cat-file commit refs/remotes/dir >actual && + grep '^git-svn-id: $dir_url@2 $uuid$' actual && + git cat-file commit refs/remotes/dir~1 >actual && + grep '^git-svn-id: $dir_url@1 $uuid$' actual " test_expect_success 'find commit based on SVN revision number' " - git svn find-rev r12 | - grep `git rev-parse HEAD` + git svn find-rev r12 >actual && + grep $(git rev-parse HEAD) actual " test_expect_success 'empty rebase' " diff --git a/t/t9111-git-svn-use-svnsync-props.sh b/t/t9111-git-svn-use-svnsync-props.sh index bd081c2ec3..6c93073551 100755 --- a/t/t9111-git-svn-use-svnsync-props.sh +++ b/t/t9111-git-svn-use-svnsync-props.sh @@ -20,32 +20,32 @@ uuid=161ce429-a9dd-4828-af4a-52023f968c89 bar_url=http://mayonaise/svnrepo/bar test_expect_success 'verify metadata for /bar' " - git cat-file commit refs/remotes/bar | \ - grep '^${git_svn_id}: $bar_url@12 $uuid$' && - git cat-file commit refs/remotes/bar~1 | \ - grep '^${git_svn_id}: $bar_url@11 $uuid$' && - git cat-file commit refs/remotes/bar~2 | \ - grep '^${git_svn_id}: $bar_url@10 $uuid$' && - git cat-file commit refs/remotes/bar~3 | \ - grep '^${git_svn_id}: $bar_url@9 $uuid$' && - git cat-file commit refs/remotes/bar~4 | \ - grep '^${git_svn_id}: $bar_url@6 $uuid$' && - git cat-file commit refs/remotes/bar~5 | \ - grep '^${git_svn_id}: $bar_url@1 $uuid$' + git cat-file commit refs/remotes/bar >actual && + grep '^git-svn-id: $bar_url@12 $uuid$' actual && + git cat-file commit refs/remotes/bar~1 >actual && + grep '^git-svn-id: $bar_url@11 $uuid$' actual && + git cat-file commit refs/remotes/bar~2 >actual && + grep '^git-svn-id: $bar_url@10 $uuid$' actual && + git cat-file commit refs/remotes/bar~3 >actual && + grep '^git-svn-id: $bar_url@9 $uuid$' actual && + git cat-file commit refs/remotes/bar~4 >actual && + grep '^git-svn-id: $bar_url@6 $uuid$' actual && + git cat-file commit refs/remotes/bar~5 >actual && + grep '^git-svn-id: $bar_url@1 $uuid$' actual " e_url=http://mayonaise/svnrepo/dir/a/b/c/d/e test_expect_success 'verify metadata for /dir/a/b/c/d/e' " - git cat-file commit refs/remotes/e | \ - grep '^${git_svn_id}: $e_url@1 $uuid$' + git cat-file commit refs/remotes/e >actual && + grep '^git-svn-id: $e_url@1 $uuid$' actual " dir_url=http://mayonaise/svnrepo/dir test_expect_success 'verify metadata for /dir' " - git cat-file commit refs/remotes/dir | \ - grep '^${git_svn_id}: $dir_url@2 $uuid$' && - git cat-file commit refs/remotes/dir~1 | \ - grep '^${git_svn_id}: $dir_url@1 $uuid$' + git cat-file commit refs/remotes/dir >actual && + grep '^git-svn-id: $dir_url@2 $uuid$' actual && + git cat-file commit refs/remotes/dir~1 >actual && + grep '^git-svn-id: $dir_url@1 $uuid$' actual " test_done diff --git a/t/t9114-git-svn-dcommit-merge.sh b/t/t9114-git-svn-dcommit-merge.sh index fb41876677..32317d6bca 100755 --- a/t/t9114-git-svn-dcommit-merge.sh +++ b/t/t9114-git-svn-dcommit-merge.sh @@ -27,9 +27,7 @@ cat << EOF # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, -# MA 02111-1307 USA +# along with this program; if not, see <http://www.gnu.org/licenses/>. # EOF } @@ -68,9 +66,10 @@ test_expect_success 'setup git mirror and merge' ' test_debug 'gitk --all & sleep 1' test_expect_success 'verify pre-merge ancestry' " - test x\`git rev-parse --verify refs/heads/svn^2\` = \ - x\`git rev-parse --verify refs/heads/merge\` && - git cat-file commit refs/heads/svn^ | grep '^friend$' + test x\$(git rev-parse --verify refs/heads/svn^2) = \ + x\$(git rev-parse --verify refs/heads/merge) && + git cat-file commit refs/heads/svn^ >actual && + grep '^friend$' actual " test_expect_success 'git svn dcommit merges' " @@ -80,16 +79,17 @@ test_expect_success 'git svn dcommit merges' " test_debug 'gitk --all & sleep 1' test_expect_success 'verify post-merge ancestry' " - test x\`git rev-parse --verify refs/heads/svn\` = \ - x\`git rev-parse --verify refs/remotes/origin/trunk \` && - test x\`git rev-parse --verify refs/heads/svn^2\` = \ - x\`git rev-parse --verify refs/heads/merge\` && - git cat-file commit refs/heads/svn^ | grep '^friend$' + test x\$(git rev-parse --verify refs/heads/svn) = \ + x\$(git rev-parse --verify refs/remotes/origin/trunk) && + test x\$(git rev-parse --verify refs/heads/svn^2) = \ + x\$(git rev-parse --verify refs/heads/merge) && + git cat-file commit refs/heads/svn^ >actual && + grep '^friend$' actual " test_expect_success 'verify merge commit message' " - git rev-list --pretty=raw -1 refs/heads/svn | \ - grep \" Merge branch 'merge' into svn\" + git rev-list --pretty=raw -1 refs/heads/svn >actual && + grep \" Merge branch 'merge' into svn\" actual " test_done diff --git a/t/t9115-git-svn-dcommit-funky-renames.sh b/t/t9115-git-svn-dcommit-funky-renames.sh index 6a48e40429..64bb495834 100755 --- a/t/t9115-git-svn-dcommit-funky-renames.sh +++ b/t/t9115-git-svn-dcommit-funky-renames.sh @@ -8,9 +8,10 @@ test_description='git svn dcommit can commit renames of files with ugly names' . ./lib-git-svn.sh test_expect_success 'load repository with strange names' ' - svnadmin load -q "$rawsvnrepo" < "$TEST_DIRECTORY"/t9115/funky-names.dump && - start_httpd gtk+ - ' + svnadmin load -q "$rawsvnrepo" <"$TEST_DIRECTORY"/t9115/funky-names.dump +' + +maybe_start_httpd gtk+ test_expect_success 'init and fetch repository' ' git svn init "$svnrepo" && @@ -77,11 +78,47 @@ test_expect_success 'make a commit to test rebase' ' ' test_expect_success 'git svn rebase works inside a fresh-cloned repository' ' - cd test-rebase && + ( + cd test-rebase && git svn rebase && test -e test-rebase-main && test -e test-rebase - ' + )' + +# Without this, LC_ALL=C as set in test-lib.sh, and Cygwin converts +# non-ASCII characters in filenames unexpectedly, and causes errors. +# https://cygwin.com/cygwin-ug-net/using-specialnames.html#pathnames-specialchars +# > Some characters are disallowed in filenames on Windows filesystems. ... +# ... +# > ... All of the above characters, except for the backslash, are converted +# > to special UNICODE characters in the range 0xf000 to 0xf0ff (the +# > "Private use area") when creating or accessing files. +prepare_a_utf8_locale +test_expect_success UTF8,!MINGW,!UTF8_NFD_TO_NFC 'svn.pathnameencoding=cp932 new file on dcommit' ' + LC_ALL=$a_utf8_locale && + export LC_ALL && + neq=$(printf "\201\202") && + git config svn.pathnameencoding cp932 && + echo neq >"$neq" && + git add "$neq" && + git commit -m "neq" && + git svn dcommit +' + +# See the comment on the above test for setting of LC_ALL. +test_expect_success !MINGW,!UTF8_NFD_TO_NFC 'svn.pathnameencoding=cp932 rename on dcommit' ' + LC_ALL=$a_utf8_locale && + export LC_ALL && + inf=$(printf "\201\207") && + git config svn.pathnameencoding cp932 && + echo inf >"$inf" && + git add "$inf" && + git commit -m "inf" && + git svn dcommit && + git mv "$inf" inf && + git commit -m "inf rename" && + git svn dcommit +' stop_httpd diff --git a/t/t9117-git-svn-init-clone.sh b/t/t9117-git-svn-init-clone.sh index a66f43c6b1..044f65e916 100755 --- a/t/t9117-git-svn-init-clone.sh +++ b/t/t9117-git-svn-init-clone.sh @@ -55,7 +55,7 @@ test_expect_success 'clone to target directory with --stdlayout' ' test_expect_success 'init without -s/-T/-b/-t does not warn' ' test ! -d trunk && git svn init "$svnrepo"/project/trunk trunk 2>warning && - test_must_fail grep -q prefix warning && + ! grep -q prefix warning && rm -rf trunk && rm -f warning ' @@ -63,7 +63,7 @@ test_expect_success 'init without -s/-T/-b/-t does not warn' ' test_expect_success 'clone without -s/-T/-b/-t does not warn' ' test ! -d trunk && git svn clone "$svnrepo"/project/trunk 2>warning && - test_must_fail grep -q prefix warning && + ! grep -q prefix warning && rm -rf trunk && rm -f warning ' @@ -86,7 +86,7 @@ EOF test_expect_success 'init with -s/-T/-b/-t assumes --prefix=origin/' ' test ! -d project && git svn init -s "$svnrepo"/project project 2>warning && - test_must_fail grep -q prefix warning && + ! grep -q prefix warning && test_svn_configured_prefix "origin/" && rm -rf project && rm -f warning @@ -95,7 +95,7 @@ test_expect_success 'init with -s/-T/-b/-t assumes --prefix=origin/' ' test_expect_success 'clone with -s/-T/-b/-t assumes --prefix=origin/' ' test ! -d project && git svn clone -s "$svnrepo"/project 2>warning && - test_must_fail grep -q prefix warning && + ! grep -q prefix warning && test_svn_configured_prefix "origin/" && rm -rf project && rm -f warning @@ -104,7 +104,7 @@ test_expect_success 'clone with -s/-T/-b/-t assumes --prefix=origin/' ' test_expect_success 'init with -s/-T/-b/-t and --prefix "" still works' ' test ! -d project && git svn init -s "$svnrepo"/project project --prefix "" 2>warning && - test_must_fail grep -q prefix warning && + ! grep -q prefix warning && test_svn_configured_prefix "" && rm -rf project && rm -f warning @@ -113,10 +113,16 @@ test_expect_success 'init with -s/-T/-b/-t and --prefix "" still works' ' test_expect_success 'clone with -s/-T/-b/-t and --prefix "" still works' ' test ! -d project && git svn clone -s "$svnrepo"/project --prefix "" 2>warning && - test_must_fail grep -q prefix warning && + ! grep -q prefix warning && test_svn_configured_prefix "" && rm -rf project && rm -f warning ' +test_expect_success 'init with -T as a full url works' ' + test ! -d project && + git svn init -T "$svnrepo"/project/trunk project && + rm -rf project + ' + test_done diff --git a/t/t9118-git-svn-funky-branch-names.sh b/t/t9118-git-svn-funky-branch-names.sh index ed4d1369cc..41a026637f 100755 --- a/t/t9118-git-svn-funky-branch-names.sh +++ b/t/t9118-git-svn-funky-branch-names.sh @@ -23,19 +23,22 @@ test_expect_success 'setup svnrepo' ' "$svnrepo/pr ject/branches/$scary_uri" && svn_cmd cp -m "leading dot" "$svnrepo/pr ject/trunk" \ "$svnrepo/pr ject/branches/.leading_dot" && - svn_cmd cp -m "trailing dot" "$svnrepo/pr ject/trunk" \ - "$svnrepo/pr ject/branches/trailing_dot." && + if test_have_prereq !MINGW + then + svn_cmd cp -m "trailing dot" "$svnrepo/pr ject/trunk" \ + "$svnrepo/pr ject/branches/trailing_dot." + fi && svn_cmd cp -m "trailing .lock" "$svnrepo/pr ject/trunk" \ "$svnrepo/pr ject/branches/trailing_dotlock.lock" && svn_cmd cp -m "reflog" "$svnrepo/pr ject/trunk" \ "$svnrepo/pr ject/branches/not-a@{0}reflog@" && - start_httpd + maybe_start_httpd ' # SVN 1.7 will truncate "not-a%40{0]" to just "not-a". # Look at what SVN wound up naming the branch and use that. # Be sure to escape the @ if it shows up. -non_reflog=`svn_cmd ls "$svnrepo/pr ject/branches" | grep not-a | sed 's/\///' | sed 's/@/%40/'` +non_reflog=$(svn_cmd ls "$svnrepo/pr ject/branches" | grep not-a | sed 's/\///' | sed 's/@/%40/') test_expect_success 'test clone with funky branch names' ' git svn clone -s "$svnrepo/pr ject" project && @@ -45,7 +48,10 @@ test_expect_success 'test clone with funky branch names' ' git rev-parse "refs/remotes/origin/more%20fun%20plugin!" && git rev-parse "refs/remotes/origin/$scary_ref" && git rev-parse "refs/remotes/origin/%2Eleading_dot" && - git rev-parse "refs/remotes/origin/trailing_dot%2E" && + if test_have_prereq !MINGW + then + git rev-parse "refs/remotes/origin/trailing_dot%2E" + fi && git rev-parse "refs/remotes/origin/trailing_dotlock%2Elock" && git rev-parse "refs/remotes/origin/$non_reflog" ) diff --git a/t/t9119-git-svn-info.sh b/t/t9119-git-svn-info.sh index f16f3234a1..88241baee3 100755 --- a/t/t9119-git-svn-info.sh +++ b/t/t9119-git-svn-info.sh @@ -8,7 +8,7 @@ test_description='git svn info' # Tested with: svn, version 1.4.4 (r25188) # Tested with: svn, version 1.6.[12345689] -v=`svn_cmd --version | sed -n -e 's/^svn, version \(1\.[0-9]*\.[0-9]*\).*$/\1/p'` +v=$(svn_cmd --version | sed -n -e 's/^svn, version \(1\.[0-9]*\.[0-9]*\).*$/\1/p') case $v in 1.[456].*) ;; diff --git a/t/t9120-git-svn-clone-with-percent-escapes.sh b/t/t9120-git-svn-clone-with-percent-escapes.sh index 1c84ce1023..b28a1741e3 100755 --- a/t/t9120-git-svn-clone-with-percent-escapes.sh +++ b/t/t9120-git-svn-clone-with-percent-escapes.sh @@ -15,14 +15,14 @@ test_expect_success 'setup svnrepo' ' svn_cmd cp -m "tag" "$svnrepo/pr ject/trunk" \ "$svnrepo/pr ject/tags/v1" && rm -rf project && - start_httpd + maybe_start_httpd ' test_expect_success 'test clone with percent escapes' ' git svn clone "$svnrepo/pr%20ject" clone && ( cd clone && - git rev-parse refs/${remotes_git_svn} + git rev-parse refs/remotes/git-svn ) ' @@ -42,7 +42,7 @@ test_expect_success 'test clone trunk with percent escapes and minimize-url' ' git svn clone --minimize-url "$svnrepo/pr%20ject/trunk" minimize && ( cd minimize && - git rev-parse refs/${remotes_git_svn} + git rev-parse refs/remotes/git-svn ) ' @@ -50,7 +50,7 @@ test_expect_success 'test clone trunk with percent escapes' ' git svn clone "$svnrepo/pr%20ject/trunk" trunk && ( cd trunk && - git rev-parse refs/${remotes_git_svn} + git rev-parse refs/remotes/git-svn ) ' diff --git a/t/t9123-git-svn-rebuild-with-rewriteroot.sh b/t/t9123-git-svn-rebuild-with-rewriteroot.sh index fd8184787f..ead404589e 100755 --- a/t/t9123-git-svn-rebuild-with-rewriteroot.sh +++ b/t/t9123-git-svn-rebuild-with-rewriteroot.sh @@ -17,7 +17,7 @@ rm -rf import test_expect_success 'init, fetch and checkout repository' ' git svn init --rewrite-root=http://invalid.invalid/ "$svnrepo" && git svn fetch && - git checkout -b mybranch ${remotes_git_svn} + git checkout -b mybranch remotes/git-svn ' test_expect_success 'remove rev_map' ' diff --git a/t/t9124-git-svn-dcommit-auto-props.sh b/t/t9124-git-svn-dcommit-auto-props.sh index aa841e1299..9f7231d5b7 100755 --- a/t/t9124-git-svn-dcommit-auto-props.sh +++ b/t/t9124-git-svn-dcommit-auto-props.sh @@ -34,8 +34,7 @@ test_expect_success 'enable auto-props config' ' ' test_expect_success 'add files matching auto-props' ' - echo "#!$SHELL_PATH" >exec1.sh && - chmod +x exec1.sh && + write_script exec1.sh </dev/null && echo "hello" >hello.txt && echo bar >bar && git add exec1.sh hello.txt bar && @@ -48,8 +47,7 @@ test_expect_success 'disable auto-props config' ' ' test_expect_success 'add files matching disabled auto-props' ' - echo "#$SHELL_PATH" >exec2.sh && - chmod +x exec2.sh && + write_script exec2.sh </dev/null && echo "world" >world.txt && echo zot >zot && git add exec2.sh world.txt zot && @@ -65,7 +63,10 @@ test_expect_success 'check resulting svn repository' ' cd svnrepo && # Check properties from first commit. - test "x$(svn_cmd propget svn:executable exec1.sh)" = "x*" && + if test_have_prereq POSIXPERM + then + test "x$(svn_cmd propget svn:executable exec1.sh)" = "x*" + fi && test "x$(svn_cmd propget svn:mime-type exec1.sh)" = \ "xapplication/x-shellscript" && test "x$(svn_cmd propget svn:mime-type hello.txt)" = "xtext/plain" && @@ -73,7 +74,10 @@ test_expect_success 'check resulting svn repository' ' test "x$(svn_cmd propget svn:mime-type bar)" = "x" && # Check properties from second commit. - test "x$(svn_cmd propget svn:executable exec2.sh)" = "x*" && + if test_have_prereq POSIXPERM + then + test "x$(svn_cmd propget svn:executable exec2.sh)" = "x*" + fi && test "x$(svn_cmd propget svn:mime-type exec2.sh)" = "x" && test "x$(svn_cmd propget svn:mime-type world.txt)" = "x" && test "x$(svn_cmd propget svn:eol-style world.txt)" = "x" && diff --git a/t/t9129-git-svn-i18n-commitencoding.sh b/t/t9129-git-svn-i18n-commitencoding.sh index 8cfdfe790f..8dbd6476fa 100755 --- a/t/t9129-git-svn-i18n-commitencoding.sh +++ b/t/t9129-git-svn-i18n-commitencoding.sh @@ -7,29 +7,19 @@ test_description='git svn honors i18n.commitEncoding in config' . ./lib-git-svn.sh compare_git_head_with () { - nr=`wc -l < "$1"` + nr=$(wc -l < "$1") a=7 b=$(($a + $nr - 1)) git cat-file commit HEAD | sed -ne "$a,${b}p" >current && test_cmp current "$1" } -a_utf8_locale=$(locale -a | sed -n '/\.[uU][tT][fF]-*8$/{ - p - q -}') - -if test -n "$a_utf8_locale" -then - test_set_prereq UTF8 -else - say "# UTF-8 locale not available, some tests are skipped" -fi +prepare_a_utf8_locale compare_svn_head_with () { # extract just the log message and strip out committer info. # don't use --limit here since svn 1.1.x doesn't have it, - LC_ALL="$a_utf8_locale" svn log `git svn info --url` | perl -w -e ' + LC_ALL="$a_utf8_locale" svn log $(git svn info --url) | perl -w -e ' use bytes; $/ = ("-"x72) . "\n"; my @x = <STDIN>; diff --git a/t/t9130-git-svn-authors-file.sh b/t/t9130-git-svn-authors-file.sh index c44de267a1..d8262854bb 100755 --- a/t/t9130-git-svn-authors-file.sh +++ b/t/t9130-git-svn-authors-file.sh @@ -26,11 +26,12 @@ test_expect_success 'start import with incomplete authors file' ' test_expect_success 'imported 2 revisions successfully' ' ( cd x - test "`git rev-list refs/remotes/git-svn | wc -l`" -eq 2 && - git rev-list -1 --pretty=raw refs/remotes/git-svn | \ - grep "^author BBBBBBB BBBBBBB <bb@example\.com> " && - git rev-list -1 --pretty=raw refs/remotes/git-svn~1 | \ - grep "^author AAAAAAA AAAAAAA <aa@example\.com> " + git rev-list refs/remotes/git-svn >actual && + test_line_count = 2 actual && + git rev-list -1 --pretty=raw refs/remotes/git-svn >actual && + grep "^author BBBBBBB BBBBBBB <bb@example\.com> " actual && + git rev-list -1 --pretty=raw refs/remotes/git-svn~1 >actual && + grep "^author AAAAAAA AAAAAAA <aa@example\.com> " actual ) ' @@ -43,11 +44,12 @@ test_expect_success 'continues to import once authors have been added' ' ( cd x git svn fetch --authors-file=../svn-authors && - test "`git rev-list refs/remotes/git-svn | wc -l`" -eq 4 && - git rev-list -1 --pretty=raw refs/remotes/git-svn | \ - grep "^author DDDDDDD DDDDDDD <dd@example\.com> " && - git rev-list -1 --pretty=raw refs/remotes/git-svn~1 | \ - grep "^author CCCCCCC CCCCCCC <cc@example\.com> " + git rev-list refs/remotes/git-svn >actual && + test_line_count = 4 actual && + git rev-list -1 --pretty=raw refs/remotes/git-svn >actual && + grep "^author DDDDDDD DDDDDDD <dd@example\.com> " actual && + git rev-list -1 --pretty=raw refs/remotes/git-svn~1 >actual && + grep "^author CCCCCCC CCCCCCC <cc@example\.com> " actual ) ' @@ -73,8 +75,8 @@ tmp_config_get () { test_expect_success 'failure happened without negative side effects' ' ( cd aa-work && - test 6 -eq "`tmp_config_get svn-remote.svn.branches-maxRev`" && - test 6 -eq "`tmp_config_get svn-remote.svn.tags-maxRev`" + test 6 -eq "$(tmp_config_get svn-remote.svn.branches-maxRev)" && + test 6 -eq "$(tmp_config_get svn-remote.svn.tags-maxRev)" ) ' @@ -86,12 +88,12 @@ test_expect_success 'fetch continues after authors-file is fixed' ' ( cd aa-work && git svn fetch --authors-file=../svn-authors && - test 8 -eq "`tmp_config_get svn-remote.svn.branches-maxRev`" && - test 8 -eq "`tmp_config_get svn-remote.svn.tags-maxRev`" + test 8 -eq "$(tmp_config_get svn-remote.svn.branches-maxRev)" && + test 8 -eq "$(tmp_config_get svn-remote.svn.tags-maxRev)" ) ' -test_expect_success 'fresh clone with svn.authors-file in config' ' +test_expect_success !MINGW 'fresh clone with svn.authors-file in config' ' ( rm -r "$GIT_DIR" && test x = x"$(git config svn.authorsfile)" && @@ -102,12 +104,28 @@ test_expect_success 'fresh clone with svn.authors-file in config' ' test x"$HOME"/svn-authors = x"$(git config svn.authorsfile)" && git svn clone "$svnrepo" gitconfig.clone && cd gitconfig.clone && - nr_ex=$(git log | grep "^Author:.*example.com" | wc -l) && - nr_rev=$(git rev-list HEAD | wc -l) && + git log >actual && + nr_ex=$(grep "^Author:.*example.com" actual | wc -l) && + git rev-list HEAD >actual && + nr_rev=$(wc -l <actual) && test $nr_rev -eq $nr_ex ) ' +cat >> svn-authors <<EOF +ff = FFFFFFF FFFFFFF <> +EOF + +test_expect_success 'authors-file imported user without email' ' + svn_cmd mkdir -m aa/branches/ff --username ff "$svnrepo/aa/branches/ff" && + ( + cd aa-work && + git svn fetch --authors-file=../svn-authors && + git rev-list -1 --pretty=raw refs/remotes/origin/ff | \ + grep "^author FFFFFFF FFFFFFF <> " + ) + ' + test_debug 'GIT_DIR=gitconfig.clone/.git git log' test_done diff --git a/t/t9132-git-svn-broken-symlink.sh b/t/t9132-git-svn-broken-symlink.sh index 6c4c90b036..aeceffaf7b 100755 --- a/t/t9132-git-svn-broken-symlink.sh +++ b/t/t9132-git-svn-broken-symlink.sh @@ -87,7 +87,7 @@ test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" x' test_expect_success SYMLINKS '"bar" is a symlink that points to "asdf"' ' test -L x/bar && - (cd x && test xasdf = x"`git cat-file blob HEAD:bar`") + (cd x && test xasdf = x"$(git cat-file blob HEAD:bar)") ' test_expect_success 'get "bar" => symlink fix from svn' ' @@ -96,7 +96,7 @@ test_expect_success 'get "bar" => symlink fix from svn' ' test_expect_success SYMLINKS '"bar" remains a proper symlink' ' test -L x/bar && - (cd x && test xdoink = x"`git cat-file blob HEAD:bar`") + (cd x && test xdoink = x"$(git cat-file blob HEAD:bar)") ' test_done diff --git a/t/t9137-git-svn-dcommit-clobber-series.sh b/t/t9137-git-svn-dcommit-clobber-series.sh index d60da63f7a..5fa07a369f 100755 --- a/t/t9137-git-svn-dcommit-clobber-series.sh +++ b/t/t9137-git-svn-dcommit-clobber-series.sh @@ -16,15 +16,15 @@ test_expect_success 'initialize repo' ' ' test_expect_success '(supposedly) non-conflicting change from SVN' ' - test x"`sed -n -e 58p < file`" = x58 && - test x"`sed -n -e 61p < file`" = x61 && + test x"$(sed -n -e 58p < file)" = x58 && + test x"$(sed -n -e 61p < file)" = x61 && svn_cmd co "$svnrepo" tmp && (cd tmp && perl -i.bak -p -e "s/^58$/5588/" file && perl -i.bak -p -e "s/^61$/6611/" file && poke file && - test x"`sed -n -e 58p < file`" = x5588 && - test x"`sed -n -e 61p < file`" = x6611 && + test x"$(sed -n -e 58p < file)" = x5588 && + test x"$(sed -n -e 61p < file)" = x6611 && svn_cmd commit -m "58 => 5588, 61 => 6611" ) ' @@ -38,20 +38,20 @@ test_expect_success 'some unrelated changes to git' " " test_expect_success 'change file but in unrelated area' " - test x\"\`sed -n -e 4p < file\`\" = x4 && - test x\"\`sed -n -e 7p < file\`\" = x7 && + test x\"\$(sed -n -e 4p < file)\" = x4 && + test x\"\$(sed -n -e 7p < file)\" = x7 && perl -i.bak -p -e 's/^4\$/4444/' file && perl -i.bak -p -e 's/^7\$/7777/' file && - test x\"\`sed -n -e 4p < file\`\" = x4444 && - test x\"\`sed -n -e 7p < file\`\" = x7777 && + test x\"\$(sed -n -e 4p < file)\" = x4444 && + test x\"\$(sed -n -e 7p < file)\" = x7777 && git commit -m '4 => 4444, 7 => 7777' file && git svn dcommit && svn_cmd up tmp && cd tmp && - test x\"\`sed -n -e 4p < file\`\" = x4444 && - test x\"\`sed -n -e 7p < file\`\" = x7777 && - test x\"\`sed -n -e 58p < file\`\" = x5588 && - test x\"\`sed -n -e 61p < file\`\" = x6611 + test x\"\$(sed -n -e 4p < file)\" = x4444 && + test x\"\$(sed -n -e 7p < file)\" = x7777 && + test x\"\$(sed -n -e 58p < file)\" = x5588 && + test x\"\$(sed -n -e 61p < file)\" = x6611 " test_expect_success 'attempt to dcommit with a dirty index' ' diff --git a/t/t9138-git-svn-authors-prog.sh b/t/t9138-git-svn-authors-prog.sh index 2937f4c265..93ef44fae8 100755 --- a/t/t9138-git-svn-authors-prog.sh +++ b/t/t9138-git-svn-authors-prog.sh @@ -9,7 +9,9 @@ test_description='git svn authors prog tests' write_script svn-authors-prog "$PERL_PATH" <<-\EOF $_ = shift; - if (s/-sub$//) { + if (s/-hermit//) { + print "$_ <>\n"; + } elsif (s/-sub$//) { print "$_ <$_\@sub.example.com>\n"; } else { print "$_ <$_\@example.com>\n"; @@ -37,44 +39,67 @@ test_expect_success 'import authors with prog and file' ' test_expect_success 'imported 6 revisions successfully' ' ( cd x - test "`git rev-list refs/remotes/git-svn | wc -l`" -eq 6 + git rev-list refs/remotes/git-svn >actual && + test_line_count = 6 actual ) ' test_expect_success 'authors-prog ran correctly' ' ( cd x - git rev-list -1 --pretty=raw refs/remotes/git-svn~1 | \ - grep "^author ee-foo <ee-foo@example\.com> " && - git rev-list -1 --pretty=raw refs/remotes/git-svn~2 | \ - grep "^author dd <dd@sub\.example\.com> " && - git rev-list -1 --pretty=raw refs/remotes/git-svn~3 | \ - grep "^author cc <cc@sub\.example\.com> " && - git rev-list -1 --pretty=raw refs/remotes/git-svn~4 | \ - grep "^author bb <bb@example\.com> " && - git rev-list -1 --pretty=raw refs/remotes/git-svn~5 | \ - grep "^author aa <aa@example\.com> " + git rev-list -1 --pretty=raw refs/remotes/git-svn~1 >actual && + grep "^author ee-foo <ee-foo@example\.com> " actual && + git rev-list -1 --pretty=raw refs/remotes/git-svn~2 >actual && + grep "^author dd <dd@sub\.example\.com> " actual && + git rev-list -1 --pretty=raw refs/remotes/git-svn~3 >actual && + grep "^author cc <cc@sub\.example\.com> " actual && + git rev-list -1 --pretty=raw refs/remotes/git-svn~4 >actual && + grep "^author bb <bb@example\.com> " actual && + git rev-list -1 --pretty=raw refs/remotes/git-svn~5 >actual && + grep "^author aa <aa@example\.com> " actual ) ' test_expect_success 'authors-file overrode authors-prog' ' ( cd x - git rev-list -1 --pretty=raw refs/remotes/git-svn | \ - grep "^author FFFFFFF FFFFFFF <fFf@other\.example\.com> " + git rev-list -1 --pretty=raw refs/remotes/git-svn >actual && + grep "^author FFFFFFF FFFFFFF <fFf@other\.example\.com> " actual ) ' git --git-dir=x/.git config --unset svn.authorsfile git --git-dir=x/.git config --unset svn.authorsprog +test_expect_success 'authors-prog imported user without email' ' + svn mkdir -m gg --username gg-hermit "$svnrepo"/gg && + ( + cd x && + git svn fetch --authors-prog=../svn-authors-prog && + git rev-list -1 --pretty=raw refs/remotes/git-svn | \ + grep "^author gg <> " + ) +' + +test_expect_success 'imported without authors-prog and authors-file' ' + svn mkdir -m hh --username hh "$svnrepo"/hh && + ( + uuid=$(svn info "$svnrepo" | + sed -n "s/^Repository UUID: //p") && + cd x && + git svn fetch && + git rev-list -1 --pretty=raw refs/remotes/git-svn | \ + grep "^author hh <hh@$uuid> " + ) +' + test_expect_success 'authors-prog handled special characters in username' ' svn mkdir -m bad --username "xyz; touch evil" "$svnrepo"/bad && ( cd x && git svn --authors-prog=../svn-authors-prog fetch && - git rev-list -1 --pretty=raw refs/remotes/git-svn | - grep "^author xyz; touch evil <xyz; touch evil@example\.com> " && + git rev-list -1 --pretty=raw refs/remotes/git-svn >actual && + grep "^author xyz; touch evil <xyz; touch evil@example\.com> " actual && ! test -f evil ) ' diff --git a/t/t9142-git-svn-shallow-clone.sh b/t/t9142-git-svn-shallow-clone.sh index e21ee5f663..9ee23be640 100755 --- a/t/t9142-git-svn-shallow-clone.sh +++ b/t/t9142-git-svn-shallow-clone.sh @@ -18,7 +18,7 @@ test_expect_success 'setup test repository' ' svn_cmd add foo && svn_cmd commit -m "add foo" ) && - start_httpd + maybe_start_httpd ' test_expect_success 'clone trunk with "-r HEAD"' ' diff --git a/t/t9145-git-svn-master-branch.sh b/t/t9145-git-svn-master-branch.sh index 6559137493..3bbf341f6a 100755 --- a/t/t9145-git-svn-master-branch.sh +++ b/t/t9145-git-svn-master-branch.sh @@ -17,8 +17,8 @@ test_expect_success 'git svn clone --stdlayout sets up trunk as master' ' git svn clone -s "$svnrepo" g && ( cd g && - test x`git rev-parse --verify refs/remotes/origin/trunk^0` = \ - x`git rev-parse --verify refs/heads/master^0` + test x$(git rev-parse --verify refs/remotes/origin/trunk^0) = \ + x$(git rev-parse --verify refs/heads/master^0) ) ' diff --git a/t/t9150-svk-mergetickets.sh b/t/t9150-svk-mergetickets.sh index 24c2421bfc..1bb676bede 100755 --- a/t/t9150-svk-mergetickets.sh +++ b/t/t9150-svk-mergetickets.sh @@ -19,7 +19,7 @@ test_expect_success 'load svk depot' " uuid=b48289b2-9c08-4d72-af37-0358a40b9c15 test_expect_success 'svk merges were represented coming in' " - [ `git cat-file commit HEAD | grep parent | wc -l` -eq 2 ] + [ $(git cat-file commit HEAD | grep parent | wc -l) -eq 2 ] " test_done diff --git a/t/t9153-git-svn-rewrite-uuid.sh b/t/t9153-git-svn-rewrite-uuid.sh index 88a2cfa233..8cb2b5c69c 100755 --- a/t/t9153-git-svn-rewrite-uuid.sh +++ b/t/t9153-git-svn-rewrite-uuid.sh @@ -16,10 +16,10 @@ test_expect_success 'load svn repo' " " test_expect_success 'verify uuid' " - git cat-file commit refs/remotes/git-svn~0 | \ - grep '^${git_svn_id}: .*@2 $uuid$' && - git cat-file commit refs/remotes/git-svn~1 | \ - grep '^${git_svn_id}: .*@1 $uuid$' + git cat-file commit refs/remotes/git-svn~0 >actual && + grep '^git-svn-id: .*@2 $uuid$' actual && + git cat-file commit refs/remotes/git-svn~1 >actual && + grep '^git-svn-id: .*@1 $uuid$' actual " test_done diff --git a/t/t9158-git-svn-mergeinfo.sh b/t/t9158-git-svn-mergeinfo.sh index 13f78f2682..a875b45102 100755 --- a/t/t9158-git-svn-mergeinfo.sh +++ b/t/t9158-git-svn-mergeinfo.sh @@ -7,8 +7,6 @@ test_description='git svn mergeinfo propagation' . ./lib-git-svn.sh -say 'define NO_SVN_TESTS to skip git svn tests' - test_expect_success 'initialize source svn repo' ' svn_cmd mkdir -m x "$svnrepo"/trunk && svn_cmd co "$svnrepo"/trunk "$SVN_TREE" && diff --git a/t/t9160-git-svn-preserve-empty-dirs.sh b/t/t9160-git-svn-preserve-empty-dirs.sh index b4a4434604..0ede3cfedb 100755 --- a/t/t9160-git-svn-preserve-empty-dirs.sh +++ b/t/t9160-git-svn-preserve-empty-dirs.sh @@ -11,7 +11,6 @@ local Git repository with placeholder files.' . ./lib-git-svn.sh -say 'define NO_SVN_TESTS to skip git svn tests' GIT_REPO=git-svn-repo test_expect_success 'initialize source svn repo containing empty dirs' ' diff --git a/t/t9168-git-svn-partially-globbed-names.sh b/t/t9168-git-svn-partially-globbed-names.sh new file mode 100755 index 0000000000..bdf6e84999 --- /dev/null +++ b/t/t9168-git-svn-partially-globbed-names.sh @@ -0,0 +1,229 @@ +#!/bin/sh +test_description='git svn globbing refspecs with prefixed globs' +. ./lib-git-svn.sh + +test_expect_success 'prepare test refspec prefixed globbing' ' + cat >expect.end <<EOF +the end +hi +start a new branch +initial +EOF + ' + +test_expect_success 'test refspec prefixed globbing' ' + mkdir -p trunk/src/a trunk/src/b trunk/doc && + echo "hello world" >trunk/src/a/readme && + echo "goodbye world" >trunk/src/b/readme && + svn_cmd import -m "initial" trunk "$svnrepo"/trunk && + svn_cmd co "$svnrepo" tmp && + ( + cd tmp && + mkdir branches tags && + svn_cmd add branches tags && + svn_cmd cp trunk branches/b_start && + svn_cmd commit -m "start a new branch" && + svn_cmd up && + echo "hi" >>branches/b_start/src/b/readme && + poke branches/b_start/src/b/readme && + echo "hey" >>branches/b_start/src/a/readme && + poke branches/b_start/src/a/readme && + svn_cmd commit -m "hi" && + svn_cmd up && + svn_cmd cp branches/b_start tags/t_end && + echo "bye" >>tags/t_end/src/b/readme && + poke tags/t_end/src/b/readme && + echo "aye" >>tags/t_end/src/a/readme && + poke tags/t_end/src/a/readme && + svn_cmd commit -m "the end" && + echo "byebye" >>tags/t_end/src/b/readme && + poke tags/t_end/src/b/readme && + svn_cmd commit -m "nothing to see here" + ) && + git config --add svn-remote.svn.url "$svnrepo" && + git config --add svn-remote.svn.fetch \ + "trunk/src/a:refs/remotes/trunk" && + git config --add svn-remote.svn.branches \ + "branches/b_*/src/a:refs/remotes/branches/b_*" && + git config --add svn-remote.svn.tags\ + "tags/t_*/src/a:refs/remotes/tags/t_*" && + git svn multi-fetch && + git log --pretty=oneline refs/remotes/tags/t_end >actual && + sed -e "s/^.\{41\}//" actual >output.end && + test_cmp expect.end output.end && + test "$(git rev-parse refs/remotes/tags/t_end~1)" = \ + "$(git rev-parse refs/remotes/branches/b_start)" && + test "$(git rev-parse refs/remotes/branches/b_start~2)" = \ + "$(git rev-parse refs/remotes/trunk)" && + test_must_fail git rev-parse refs/remotes/tags/t_end@3 + ' + +test_expect_success 'prepare test left-hand-side only prefixed globbing' ' + echo try to try >expect.two && + echo nothing to see here >>expect.two && + cat expect.end >>expect.two + ' + +test_expect_success 'test left-hand-side only prefixed globbing' ' + git config --add svn-remote.two.url "$svnrepo" && + git config --add svn-remote.two.fetch trunk:refs/remotes/two/trunk && + git config --add svn-remote.two.branches \ + "branches/b_*:refs/remotes/two/branches/*" && + git config --add svn-remote.two.tags \ + "tags/t_*:refs/remotes/two/tags/*" && + ( + cd tmp && + echo "try try" >>tags/t_end/src/b/readme && + poke tags/t_end/src/b/readme && + svn_cmd commit -m "try to try" + ) && + git svn fetch two && + git rev-list refs/remotes/two/tags/t_end >actual && + test_line_count = 6 actual && + git rev-list refs/remotes/two/branches/b_start >actual && + test_line_count = 3 actual && + test $(git rev-parse refs/remotes/two/branches/b_start~2) = \ + $(git rev-parse refs/remotes/two/trunk) && + test $(git rev-parse refs/remotes/two/tags/t_end~3) = \ + $(git rev-parse refs/remotes/two/branches/b_start) && + git log --pretty=oneline refs/remotes/two/tags/t_end >actual && + sed -e "s/^.\{41\}//" actual >output.two && + test_cmp expect.two output.two + ' + +test_expect_success 'prepare test prefixed globs match just prefix' ' + cat >expect.three <<EOF +Tag commit to t_ +Branch commit to b_ +initial +EOF + ' + +test_expect_success 'test prefixed globs match just prefix' ' + git config --add svn-remote.three.url "$svnrepo" && + git config --add svn-remote.three.fetch \ + trunk:refs/remotes/three/trunk && + git config --add svn-remote.three.branches \ + "branches/b_*:refs/remotes/three/branches/*" && + git config --add svn-remote.three.tags \ + "tags/t_*:refs/remotes/three/tags/*" && + ( + cd tmp && + svn_cmd cp trunk branches/b_ && + echo "Branch commit to b_" >>branches/b_/src/a/readme && + poke branches/b_/src/a/readme && + svn_cmd commit -m "Branch commit to b_" && + svn_cmd up && svn_cmd cp branches/b_ tags/t_ && + echo "Tag commit to t_" >>tags/t_/src/a/readme && + poke tags/t_/src/a/readme && + svn_cmd commit -m "Tag commit to t_" && + svn_cmd up + ) && + git svn fetch three && + git rev-list refs/remotes/three/branches/b_ >actual && + test_line_count = 2 actual && + git rev-list refs/remotes/three/tags/t_ >actual && + test_line_count = 3 actual && + test $(git rev-parse refs/remotes/three/branches/b_~1) = \ + $(git rev-parse refs/remotes/three/trunk) && + test $(git rev-parse refs/remotes/three/tags/t_~1) = \ + $(git rev-parse refs/remotes/three/branches/b_) && + git log --pretty=oneline refs/remotes/three/tags/t_ >actual && + sed -e "s/^.\{41\}//" actual >output.three && + test_cmp expect.three output.three + ' + +test_expect_success 'prepare test disallow prefixed multi-globs' " +cat >expect.four <<EOF +Only one set of wildcards (e.g. '*' or '*/*/*') is supported: branches/b_*/t/* + +EOF + " + +test_expect_success 'test disallow prefixed multi-globs' ' + git config --add svn-remote.four.url "$svnrepo" && + git config --add svn-remote.four.fetch \ + trunk:refs/remotes/four/trunk && + git config --add svn-remote.four.branches \ + "branches/b_*/t/*:refs/remotes/four/branches/*" && + git config --add svn-remote.four.tags \ + "tags/t_*/*:refs/remotes/four/tags/*" && + ( + cd tmp && + echo "try try" >>tags/t_end/src/b/readme && + poke tags/t_end/src/b/readme && + svn_cmd commit -m "try to try" + ) && + test_must_fail git svn fetch four 2>stderr.four && + test_cmp expect.four stderr.four && + git config --unset svn-remote.four.branches && + git config --unset svn-remote.four.tags + ' + +test_expect_success 'prepare test globbing in the middle of the word' ' + cat >expect.five <<EOF +Tag commit to fghij +Branch commit to abcde +initial +EOF + ' + +test_expect_success 'test globbing in the middle of the word' ' + git config --add svn-remote.five.url "$svnrepo" && + git config --add svn-remote.five.fetch \ + trunk:refs/remotes/five/trunk && + git config --add svn-remote.five.branches \ + "branches/a*e:refs/remotes/five/branches/*" && + git config --add svn-remote.five.tags \ + "tags/f*j:refs/remotes/five/tags/*" && + ( + cd tmp && + svn_cmd cp trunk branches/abcde && + echo "Branch commit to abcde" >>branches/abcde/src/a/readme && + poke branches/b_/src/a/readme && + svn_cmd commit -m "Branch commit to abcde" && + svn_cmd up && + svn_cmd cp branches/abcde tags/fghij && + echo "Tag commit to fghij" >>tags/fghij/src/a/readme && + poke tags/fghij/src/a/readme && + svn_cmd commit -m "Tag commit to fghij" && + svn_cmd up + ) && + git svn fetch five && + git rev-list refs/remotes/five/branches/abcde >actual && + test_line_count = 2 actual && + git rev-list refs/remotes/five/tags/fghij >actual && + test_line_count = 3 actual && + test $(git rev-parse refs/remotes/five/branches/abcde~1) = \ + $(git rev-parse refs/remotes/five/trunk) && + test $(git rev-parse refs/remotes/five/tags/fghij~1) = \ + $(git rev-parse refs/remotes/five/branches/abcde) && + git log --pretty=oneline refs/remotes/five/tags/fghij >actual && + sed -e "s/^.\{41\}//" actual >output.five && + test_cmp expect.five output.five + ' + +test_expect_success 'prepare test disallow multiple asterisks in one word' " + echo \"Only one '*' is allowed in a pattern: 'a*c*e'\" >expect.six && + echo \"\" >>expect.six + " + +test_expect_success 'test disallow multiple asterisks in one word' ' + git config --add svn-remote.six.url "$svnrepo" && + git config --add svn-remote.six.fetch \ + trunk:refs/remotes/six/trunk && + git config --add svn-remote.six.branches \ + "branches/a*c*e:refs/remotes/six/branches/*" && + git config --add svn-remote.six.tags \ + "tags/f*h*j:refs/remotes/six/tags/*" && + ( + cd tmp && + echo "try try" >>tags/fghij/src/b/readme && + poke tags/fghij/src/b/readme && + svn_cmd commit -m "try to try" + ) && + test_must_fail git svn fetch six 2>stderr.six && + test_cmp expect.six stderr.six + ' + +test_done diff --git a/t/t9169-git-svn-dcommit-crlf.sh b/t/t9169-git-svn-dcommit-crlf.sh new file mode 100755 index 0000000000..54b1f61a2a --- /dev/null +++ b/t/t9169-git-svn-dcommit-crlf.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +test_description='git svn dcommit CRLF' +. ./lib-git-svn.sh + +test_expect_success 'setup commit repository' ' + svn_cmd mkdir -m "$test_description" "$svnrepo/dir" && + git svn clone "$svnrepo" work && + ( + cd work && + echo foo >>foo && + git update-index --add foo && + printf "a\\r\\n\\r\\nb\\r\\nc\\r\\n" >cmt && + p=$(git rev-parse HEAD) && + t=$(git write-tree) && + cmt=$(git commit-tree -p $p $t <cmt) && + git update-ref refs/heads/master $cmt && + git cat-file commit HEAD | tail -n4 >out && + test_cmp cmt out && + git svn dcommit && + printf "a\\n\\nb\\nc\\n" >exp && + git cat-file commit HEAD | sed -ne 6,9p >out && + test_cmp exp out + ) +' + +test_done diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index 812c9cd462..1319415ba8 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh @@ -18,6 +18,11 @@ then test_done fi +if ! test_have_prereq NOT_ROOT; then + skip_all='When cvs is compiled with CVS_BADROOT commits as root fail' + test_done +fi + CVSROOT=$PWD/tmpcvsroot CVSWORK=$PWD/cvswork GIT_DIR=$PWD/.git @@ -35,7 +40,7 @@ exit 1 check_entries () { # $1 == directory, $2 == expected - grep '^/' "$1/CVS/Entries" | sort | cut -d/ -f2,3,5 >actual + sed -ne '/^\//p' "$1/CVS/Entries" | sort | cut -d/ -f2,3,5 >actual if test -z "$2" then >expected @@ -197,7 +202,7 @@ if p="Ã…/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/Ã¥/ä/ö" && then # This test contains UTF-8 characters -test_expect_success \ +test_expect_success !MINGW \ 'File with non-ascii file name' \ 'mkdir -p Ã…/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/Ã¥/ä/ö && echo Foo >Ã…/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/Ã¥/ä/ö/gÃ¥rdetsÃ¥gÃ¥rdet.txt && diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index aac126fd57..dc79df7b04 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -7,23 +7,6 @@ test_description='test git fast-import utility' . ./test-lib.sh . "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash -# Print $1 bytes from stdin to stdout. -# -# This could be written as "head -c $1", but IRIX "head" does not -# support the -c option. -head_c () { - perl -e ' - my $len = $ARGV[1]; - while ($len > 0) { - my $s; - my $nread = sysread(STDIN, $s, $len); - die "cannot read: $!" unless defined($nread); - print $s; - $len -= $nread; - } - ' - "$1" -} - verify_packs () { for p in .git/objects/pack/*.pack do @@ -47,1077 +30,1080 @@ file5_data='an inline file. file6_data='#!/bin/sh echo "$@"' ->empty - ### ### series A ### -test_tick - test_expect_success 'empty stream succeeds' ' + git config fastimport.unpackLimit 0 && git fast-import </dev/null ' -cat >input <<INPUT_END -blob -mark :2 -data <<EOF -$file2_data -EOF - -blob -mark :3 -data <<END -$file3_data -END - -blob -mark :4 -data $file4_len -$file4_data -commit refs/heads/master -mark :5 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -initial -COMMIT - -M 644 :2 file2 -M 644 :3 file3 -M 755 :4 file4 - -tag series-A -from :5 -data <<EOF -An annotated tag without a tagger -EOF - -tag series-A-blob -from :3 -data <<EOF -An annotated tag that annotates a blob. -EOF - -INPUT_END -test_expect_success \ - 'A: create pack from stdin' \ - 'git fast-import --export-marks=marks.out <input && - git whatchanged master' +test_expect_success 'truncated stream complains' ' + echo "tag foo" | test_must_fail git fast-import +' + +test_expect_success 'A: create pack from stdin' ' + test_tick && + cat >input <<-INPUT_END && + blob + mark :2 + data <<EOF + $file2_data + EOF + + blob + mark :3 + data <<END + $file3_data + END + + blob + mark :4 + data $file4_len + $file4_data + commit refs/heads/master + mark :5 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + initial + COMMIT + + M 644 :2 file2 + M 644 :3 file3 + M 755 :4 file4 + + tag series-A + from :5 + data <<EOF + An annotated tag without a tagger + EOF + + tag series-A-blob + from :3 + data <<EOF + An annotated tag that annotates a blob. + EOF + + INPUT_END + git fast-import --export-marks=marks.out <input && + git whatchanged master +' test_expect_success 'A: verify pack' ' verify_packs ' -cat >expect <<EOF -author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - -initial -EOF -test_expect_success \ - 'A: verify commit' \ - 'git cat-file commit master | sed 1d >actual && - test_cmp expect actual' - -cat >expect <<EOF -100644 blob file2 -100644 blob file3 -100755 blob file4 -EOF -test_expect_success \ - 'A: verify tree' \ - 'git cat-file -p master^{tree} | sed "s/ [0-9a-f]* / /" >actual && - test_cmp expect actual' - -echo "$file2_data" >expect -test_expect_success \ - 'A: verify file2' \ - 'git cat-file blob master:file2 >actual && test_cmp expect actual' - -echo "$file3_data" >expect -test_expect_success \ - 'A: verify file3' \ - 'git cat-file blob master:file3 >actual && test_cmp expect actual' - -printf "$file4_data" >expect -test_expect_success \ - 'A: verify file4' \ - 'git cat-file blob master:file4 >actual && test_cmp expect actual' - -cat >expect <<EOF -object $(git rev-parse refs/heads/master) -type commit -tag series-A - -An annotated tag without a tagger -EOF +test_expect_success 'A: verify commit' ' + cat >expect <<-EOF && + author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + + initial + EOF + git cat-file commit master | sed 1d >actual && + test_cmp expect actual +' + +test_expect_success 'A: verify tree' ' + cat >expect <<-EOF && + 100644 blob file2 + 100644 blob file3 + 100755 blob file4 + EOF + git cat-file -p master^{tree} | sed "s/ [0-9a-f]* / /" >actual && + test_cmp expect actual +' + +test_expect_success 'A: verify file2' ' + echo "$file2_data" >expect && + git cat-file blob master:file2 >actual && + test_cmp expect actual +' + +test_expect_success 'A: verify file3' ' + echo "$file3_data" >expect && + git cat-file blob master:file3 >actual && + test_cmp expect actual +' + +test_expect_success 'A: verify file4' ' + printf "$file4_data" >expect && + git cat-file blob master:file4 >actual && + test_cmp expect actual +' + test_expect_success 'A: verify tag/series-A' ' + cat >expect <<-EOF && + object $(git rev-parse refs/heads/master) + type commit + tag series-A + + An annotated tag without a tagger + EOF git cat-file tag tags/series-A >actual && test_cmp expect actual ' -cat >expect <<EOF -object $(git rev-parse refs/heads/master:file3) -type blob -tag series-A-blob - -An annotated tag that annotates a blob. -EOF test_expect_success 'A: verify tag/series-A-blob' ' + cat >expect <<-EOF && + object $(git rev-parse refs/heads/master:file3) + type blob + tag series-A-blob + + An annotated tag that annotates a blob. + EOF git cat-file tag tags/series-A-blob >actual && test_cmp expect actual ' -cat >expect <<EOF -:2 `git rev-parse --verify master:file2` -:3 `git rev-parse --verify master:file3` -:4 `git rev-parse --verify master:file4` -:5 `git rev-parse --verify master^0` -EOF -test_expect_success \ - 'A: verify marks output' \ - 'test_cmp expect marks.out' +test_expect_success 'A: verify marks output' ' + cat >expect <<-EOF && + :2 $(git rev-parse --verify master:file2) + :3 $(git rev-parse --verify master:file3) + :4 $(git rev-parse --verify master:file4) + :5 $(git rev-parse --verify master^0) + EOF + test_cmp expect marks.out +' -test_expect_success \ - 'A: verify marks import' \ - 'git fast-import \ +test_expect_success 'A: verify marks import' ' + git fast-import \ --import-marks=marks.out \ --export-marks=marks.new \ </dev/null && - test_cmp expect marks.new' - -test_tick -new_blob=$(echo testing | git hash-object --stdin) -cat >input <<INPUT_END -tag series-A-blob-2 -from $(git rev-parse refs/heads/master:file3) -data <<EOF -Tag blob by sha1. -EOF - -blob -mark :6 -data <<EOF -testing -EOF - -commit refs/heads/new_blob -committer <> 0 +0000 -data 0 -M 644 :6 new_blob -#pretend we got sha1 from fast-import -ls "new_blob" - -tag series-A-blob-3 -from $new_blob -data <<EOF -Tag new_blob. -EOF -INPUT_END - -cat >expect <<EOF -object $(git rev-parse refs/heads/master:file3) -type blob -tag series-A-blob-2 - -Tag blob by sha1. -object $new_blob -type blob -tag series-A-blob-3 - -Tag new_blob. -EOF - -test_expect_success \ - 'A: tag blob by sha1' \ - 'git fast-import <input && + test_cmp expect marks.new +' + +test_expect_success 'A: tag blob by sha1' ' + test_tick && + new_blob=$(echo testing | git hash-object --stdin) && + cat >input <<-INPUT_END && + tag series-A-blob-2 + from $(git rev-parse refs/heads/master:file3) + data <<EOF + Tag blob by sha1. + EOF + + blob + mark :6 + data <<EOF + testing + EOF + + commit refs/heads/new_blob + committer <> 0 +0000 + data 0 + M 644 :6 new_blob + #pretend we got sha1 from fast-import + ls "new_blob" + + tag series-A-blob-3 + from $new_blob + data <<EOF + Tag new_blob. + EOF + INPUT_END + + cat >expect <<-EOF && + object $(git rev-parse refs/heads/master:file3) + type blob + tag series-A-blob-2 + + Tag blob by sha1. + object $new_blob + type blob + tag series-A-blob-3 + + Tag new_blob. + EOF + + git fast-import <input && git cat-file tag tags/series-A-blob-2 >actual && git cat-file tag tags/series-A-blob-3 >>actual && - test_cmp expect actual' + test_cmp expect actual +' -test_tick -cat >input <<INPUT_END -commit refs/heads/verify--import-marks -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -recreate from :5 -COMMIT +test_expect_success 'A: verify marks import does not crash' ' + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/verify--import-marks + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + recreate from :5 + COMMIT -from :5 -M 755 :2 copy-of-file2 + from :5 + M 755 :2 copy-of-file2 -INPUT_END -test_expect_success \ - 'A: verify marks import does not crash' \ - 'git fast-import --import-marks=marks.out <input && - git whatchanged verify--import-marks' + INPUT_END + + git fast-import --import-marks=marks.out <input && + git whatchanged verify--import-marks +' test_expect_success 'A: verify pack' ' verify_packs ' -cat >expect <<EOF -:000000 100755 0000000000000000000000000000000000000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 A copy-of-file2 -EOF -git diff-tree -M -r master verify--import-marks >actual -test_expect_success \ - 'A: verify diff' \ - 'compare_diff_raw expect actual && - test `git rev-parse --verify master:file2` \ - = `git rev-parse --verify verify--import-marks:copy-of-file2`' - -test_tick -mt=$(git hash-object --stdin < /dev/null) -: >input.blob -: >marks.exp -: >tree.exp - -cat >input.commit <<EOF -commit refs/heads/verify--dump-marks -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -test the sparse array dumping routines with exponentially growing marks -COMMIT -EOF - -i=0 -l=4 -m=6 -n=7 -while test "$i" -lt 27; do - cat >>input.blob <<EOF -blob -mark :$l -data 0 -blob -mark :$m -data 0 -blob -mark :$n -data 0 -EOF - echo "M 100644 :$l l$i" >>input.commit - echo "M 100644 :$m m$i" >>input.commit - echo "M 100644 :$n n$i" >>input.commit - - echo ":$l $mt" >>marks.exp - echo ":$m $mt" >>marks.exp - echo ":$n $mt" >>marks.exp - - printf "100644 blob $mt\tl$i\n" >>tree.exp - printf "100644 blob $mt\tm$i\n" >>tree.exp - printf "100644 blob $mt\tn$i\n" >>tree.exp - - l=$(($l + $l)) - m=$(($m + $m)) - n=$(($l + $n)) - - i=$((1 + $i)) -done - -sort tree.exp > tree.exp_s +test_expect_success 'A: verify diff' ' + cat >expect <<-EOF && + :000000 100755 0000000000000000000000000000000000000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 A copy-of-file2 + EOF + git diff-tree -M -r master verify--import-marks >actual && + compare_diff_raw expect actual && + test $(git rev-parse --verify master:file2) \ + = $(git rev-parse --verify verify--import-marks:copy-of-file2) +' test_expect_success 'A: export marks with large values' ' + test_tick && + mt=$(git hash-object --stdin < /dev/null) && + >input.blob && + >marks.exp && + >tree.exp && + + cat >input.commit <<-EOF && + commit refs/heads/verify--dump-marks + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + test the sparse array dumping routines with exponentially growing marks + COMMIT + EOF + + i=0 l=4 m=6 n=7 && + while test "$i" -lt 27 + do + cat >>input.blob <<-EOF && + blob + mark :$l + data 0 + blob + mark :$m + data 0 + blob + mark :$n + data 0 + EOF + echo "M 100644 :$l l$i" >>input.commit && + echo "M 100644 :$m m$i" >>input.commit && + echo "M 100644 :$n n$i" >>input.commit && + + echo ":$l $mt" >>marks.exp && + echo ":$m $mt" >>marks.exp && + echo ":$n $mt" >>marks.exp && + + printf "100644 blob $mt\tl$i\n" >>tree.exp && + printf "100644 blob $mt\tm$i\n" >>tree.exp && + printf "100644 blob $mt\tn$i\n" >>tree.exp && + + l=$(($l + $l)) && + m=$(($m + $m)) && + n=$(($l + $n)) && + + i=$((1 + $i)) || return 1 + done && + + sort tree.exp > tree.exp_s && + cat input.blob input.commit | git fast-import --export-marks=marks.large && git ls-tree refs/heads/verify--dump-marks >tree.out && test_cmp tree.exp_s tree.out && - test_cmp marks.exp marks.large' + test_cmp marks.exp marks.large +' ### ### series B ### -test_tick -cat >input <<INPUT_END -commit refs/heads/branch -mark :1 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -corrupt -COMMIT +test_expect_success 'B: fail on invalid blob sha1' ' + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/branch + mark :1 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + corrupt + COMMIT -from refs/heads/master -M 755 0000000000000000000000000000000000000001 zero1 + from refs/heads/master + M 755 0000000000000000000000000000000000000001 zero1 + + INPUT_END + + test_when_finished "rm -f .git/objects/pack_* .git/objects/index_*" && + test_must_fail git fast-import <input +' + +test_expect_success 'B: accept branch name "TEMP_TAG"' ' + cat >input <<-INPUT_END && + commit TEMP_TAG + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + tag base + COMMIT + + from refs/heads/master + + INPUT_END + + test_when_finished "rm -f .git/TEMP_TAG + git gc + git prune" && + git fast-import <input && + test -f .git/TEMP_TAG && + test $(git rev-parse master) = $(git rev-parse TEMP_TAG^) +' -INPUT_END -test_expect_success 'B: fail on invalid blob sha1' ' - test_must_fail git fast-import <input -' -rm -f .git/objects/pack_* .git/objects/index_* - -cat >input <<INPUT_END -commit TEMP_TAG -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -tag base -COMMIT - -from refs/heads/master - -INPUT_END -test_expect_success \ - 'B: accept branch name "TEMP_TAG"' \ - 'git fast-import <input && - test -f .git/TEMP_TAG && - test `git rev-parse master` = `git rev-parse TEMP_TAG^`' -rm -f .git/TEMP_TAG - -git gc 2>/dev/null >/dev/null -git prune 2>/dev/null >/dev/null - -cat >input <<INPUT_END -commit refs/heads/empty-committer-1 -committer <> $GIT_COMMITTER_DATE -data <<COMMIT -empty commit -COMMIT -INPUT_END test_expect_success 'B: accept empty committer' ' + cat >input <<-INPUT_END && + commit refs/heads/empty-committer-1 + committer <> $GIT_COMMITTER_DATE + data <<COMMIT + empty commit + COMMIT + INPUT_END + + test_when_finished "git update-ref -d refs/heads/empty-committer-1 + git gc + git prune" && git fast-import <input && out=$(git fsck) && echo "$out" && test -z "$out" ' -git update-ref -d refs/heads/empty-committer-1 || true -git gc 2>/dev/null >/dev/null -git prune 2>/dev/null >/dev/null - -cat >input <<INPUT_END -commit refs/heads/empty-committer-2 -committer <a@b.com> $GIT_COMMITTER_DATE -data <<COMMIT -empty commit -COMMIT -INPUT_END test_expect_success 'B: accept and fixup committer with no name' ' + cat >input <<-INPUT_END && + commit refs/heads/empty-committer-2 + committer <a@b.com> $GIT_COMMITTER_DATE + data <<COMMIT + empty commit + COMMIT + INPUT_END + + test_when_finished "git update-ref -d refs/heads/empty-committer-2 + git gc + git prune" && git fast-import <input && out=$(git fsck) && echo "$out" && test -z "$out" ' -git update-ref -d refs/heads/empty-committer-2 || true - -git gc 2>/dev/null >/dev/null -git prune 2>/dev/null >/dev/null -cat >input <<INPUT_END -commit refs/heads/invalid-committer -committer Name email> $GIT_COMMITTER_DATE -data <<COMMIT -empty commit -COMMIT -INPUT_END test_expect_success 'B: fail on invalid committer (1)' ' + cat >input <<-INPUT_END && + commit refs/heads/invalid-committer + committer Name email> $GIT_COMMITTER_DATE + data <<COMMIT + empty commit + COMMIT + INPUT_END + + test_when_finished "git update-ref -d refs/heads/invalid-committer" && test_must_fail git fast-import <input ' -git update-ref -d refs/heads/invalid-committer || true -cat >input <<INPUT_END -commit refs/heads/invalid-committer -committer Name <e<mail> $GIT_COMMITTER_DATE -data <<COMMIT -empty commit -COMMIT -INPUT_END test_expect_success 'B: fail on invalid committer (2)' ' + cat >input <<-INPUT_END && + commit refs/heads/invalid-committer + committer Name <e<mail> $GIT_COMMITTER_DATE + data <<COMMIT + empty commit + COMMIT + INPUT_END + + test_when_finished "git update-ref -d refs/heads/invalid-committer" && test_must_fail git fast-import <input ' -git update-ref -d refs/heads/invalid-committer || true -cat >input <<INPUT_END -commit refs/heads/invalid-committer -committer Name <email>> $GIT_COMMITTER_DATE -data <<COMMIT -empty commit -COMMIT -INPUT_END test_expect_success 'B: fail on invalid committer (3)' ' + cat >input <<-INPUT_END && + commit refs/heads/invalid-committer + committer Name <email>> $GIT_COMMITTER_DATE + data <<COMMIT + empty commit + COMMIT + INPUT_END + + test_when_finished "git update-ref -d refs/heads/invalid-committer" && test_must_fail git fast-import <input ' -git update-ref -d refs/heads/invalid-committer || true -cat >input <<INPUT_END -commit refs/heads/invalid-committer -committer Name <email $GIT_COMMITTER_DATE -data <<COMMIT -empty commit -COMMIT -INPUT_END test_expect_success 'B: fail on invalid committer (4)' ' + cat >input <<-INPUT_END && + commit refs/heads/invalid-committer + committer Name <email $GIT_COMMITTER_DATE + data <<COMMIT + empty commit + COMMIT + INPUT_END + + test_when_finished "git update-ref -d refs/heads/invalid-committer" && test_must_fail git fast-import <input ' -git update-ref -d refs/heads/invalid-committer || true -cat >input <<INPUT_END -commit refs/heads/invalid-committer -committer Name<email> $GIT_COMMITTER_DATE -data <<COMMIT -empty commit -COMMIT -INPUT_END test_expect_success 'B: fail on invalid committer (5)' ' + cat >input <<-INPUT_END && + commit refs/heads/invalid-committer + committer Name<email> $GIT_COMMITTER_DATE + data <<COMMIT + empty commit + COMMIT + INPUT_END + + test_when_finished "git update-ref -d refs/heads/invalid-committer" && test_must_fail git fast-import <input ' -git update-ref -d refs/heads/invalid-committer || true ### ### series C ### -newf=`echo hi newf | git hash-object -w --stdin` -oldf=`git rev-parse --verify master:file2` -test_tick -cat >input <<INPUT_END -commit refs/heads/branch -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -second -COMMIT - -from refs/heads/master -M 644 $oldf file2/oldf -M 755 $newf file2/newf -D file3 - -INPUT_END -test_expect_success \ - 'C: incremental import create pack from stdin' \ - 'git fast-import <input && - git whatchanged branch' +test_expect_success 'C: incremental import create pack from stdin' ' + newf=$(echo hi newf | git hash-object -w --stdin) && + oldf=$(git rev-parse --verify master:file2) && + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/branch + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + second + COMMIT + + from refs/heads/master + M 644 $oldf file2/oldf + M 755 $newf file2/newf + D file3 + + INPUT_END + + git fast-import <input && + git whatchanged branch +' test_expect_success 'C: verify pack' ' verify_packs ' -test_expect_success \ - 'C: validate reuse existing blob' \ - 'test $newf = `git rev-parse --verify branch:file2/newf` && - test $oldf = `git rev-parse --verify branch:file2/oldf`' - -cat >expect <<EOF -parent `git rev-parse --verify master^0` -author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - -second -EOF -test_expect_success \ - 'C: verify commit' \ - 'git cat-file commit branch | sed 1d >actual && - test_cmp expect actual' - -cat >expect <<EOF -:000000 100755 0000000000000000000000000000000000000000 f1fb5da718392694d0076d677d6d0e364c79b0bc A file2/newf -:100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 R100 file2 file2/oldf -:100644 000000 0d92e9f3374ae2947c23aa477cbc68ce598135f1 0000000000000000000000000000000000000000 D file3 -EOF -git diff-tree -M -r master branch >actual -test_expect_success \ - 'C: validate rename result' \ - 'compare_diff_raw expect actual' +test_expect_success 'C: validate reuse existing blob' ' + test $newf = $(git rev-parse --verify branch:file2/newf) && + test $oldf = $(git rev-parse --verify branch:file2/oldf) +' + +test_expect_success 'C: verify commit' ' + cat >expect <<-EOF && + parent $(git rev-parse --verify master^0) + author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + + second + EOF + + git cat-file commit branch | sed 1d >actual && + test_cmp expect actual +' + +test_expect_success 'C: validate rename result' ' + cat >expect <<-EOF && + :000000 100755 0000000000000000000000000000000000000000 f1fb5da718392694d0076d677d6d0e364c79b0bc A file2/newf + :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 R100 file2 file2/oldf + :100644 000000 0d92e9f3374ae2947c23aa477cbc68ce598135f1 0000000000000000000000000000000000000000 D file3 + EOF + git diff-tree -M -r master branch >actual && + compare_diff_raw expect actual +' ### ### series D ### -test_tick -cat >input <<INPUT_END -commit refs/heads/branch -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -third -COMMIT - -from refs/heads/branch^0 -M 644 inline newdir/interesting -data <<EOF -$file5_data -EOF - -M 755 inline newdir/exec.sh -data <<EOF -$file6_data -EOF - -INPUT_END -test_expect_success \ - 'D: inline data in commit' \ - 'git fast-import <input && - git whatchanged branch' +test_expect_success 'D: inline data in commit' ' + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/branch + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + third + COMMIT + + from refs/heads/branch^0 + M 644 inline newdir/interesting + data <<EOF + $file5_data + EOF + + M 755 inline newdir/exec.sh + data <<EOF + $file6_data + EOF + + INPUT_END + + git fast-import <input && + git whatchanged branch +' test_expect_success 'D: verify pack' ' verify_packs ' -cat >expect <<EOF -:000000 100755 0000000000000000000000000000000000000000 e74b7d465e52746be2b4bae983670711e6e66657 A newdir/exec.sh -:000000 100644 0000000000000000000000000000000000000000 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 A newdir/interesting -EOF -git diff-tree -M -r branch^ branch >actual -test_expect_success \ - 'D: validate new files added' \ - 'compare_diff_raw expect actual' +test_expect_success 'D: validate new files added' ' + cat >expect <<-EOF && + :000000 100755 0000000000000000000000000000000000000000 e74b7d465e52746be2b4bae983670711e6e66657 A newdir/exec.sh + :000000 100644 0000000000000000000000000000000000000000 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 A newdir/interesting + EOF + git diff-tree -M -r branch^ branch >actual && + compare_diff_raw expect actual +' -echo "$file5_data" >expect -test_expect_success \ - 'D: verify file5' \ - 'git cat-file blob branch:newdir/interesting >actual && - test_cmp expect actual' +test_expect_success 'D: verify file5' ' + echo "$file5_data" >expect && + git cat-file blob branch:newdir/interesting >actual && + test_cmp expect actual +' -echo "$file6_data" >expect -test_expect_success \ - 'D: verify file6' \ - 'git cat-file blob branch:newdir/exec.sh >actual && - test_cmp expect actual' +test_expect_success 'D: verify file6' ' + echo "$file6_data" >expect && + git cat-file blob branch:newdir/exec.sh >actual && + test_cmp expect actual +' ### ### series E ### -cat >input <<INPUT_END -commit refs/heads/branch -author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> Tue Feb 6 11:22:18 2007 -0500 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> Tue Feb 6 12:35:02 2007 -0500 -data <<COMMIT -RFC 2822 type date -COMMIT +test_expect_success 'E: rfc2822 date, --date-format=raw' ' + cat >input <<-INPUT_END && + commit refs/heads/branch + author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> Tue Feb 6 11:22:18 2007 -0500 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> Tue Feb 6 12:35:02 2007 -0500 + data <<COMMIT + RFC 2822 type date + COMMIT -from refs/heads/branch^0 + from refs/heads/branch^0 -INPUT_END -test_expect_success 'E: rfc2822 date, --date-format=raw' ' - test_must_fail git fast-import --date-format=raw <input + INPUT_END + + test_must_fail git fast-import --date-format=raw <input +' +test_expect_success 'E: rfc2822 date, --date-format=rfc2822' ' + git fast-import --date-format=rfc2822 <input ' -test_expect_success \ - 'E: rfc2822 date, --date-format=rfc2822' \ - 'git fast-import --date-format=rfc2822 <input' test_expect_success 'E: verify pack' ' verify_packs ' -cat >expect <<EOF -author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> 1170778938 -0500 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1170783302 -0500 +test_expect_success 'E: verify commit' ' + cat >expect <<-EOF && + author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> 1170778938 -0500 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1170783302 -0500 -RFC 2822 type date -EOF -test_expect_success \ - 'E: verify commit' \ - 'git cat-file commit branch | sed 1,2d >actual && - test_cmp expect actual' + RFC 2822 type date + EOF + git cat-file commit branch | sed 1,2d >actual && + test_cmp expect actual +' ### ### series F ### -old_branch=`git rev-parse --verify branch^0` -test_tick -cat >input <<INPUT_END -commit refs/heads/branch -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -losing things already? -COMMIT - -from refs/heads/branch~1 - -reset refs/heads/other -from refs/heads/branch - -INPUT_END -test_expect_success \ - 'F: non-fast-forward update skips' \ - 'if git fast-import <input - then - echo BAD gfi did not fail - return 1 - else - if test $old_branch = `git rev-parse --verify branch^0` - then - : branch unaffected and failure returned - return 0 - else - echo BAD gfi changed branch $old_branch - return 1 - fi - fi - ' +test_expect_success 'F: non-fast-forward update skips' ' + old_branch=$(git rev-parse --verify branch^0) && + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/branch + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + losing things already? + COMMIT + + from refs/heads/branch~1 + + reset refs/heads/other + from refs/heads/branch + + INPUT_END + + test_must_fail git fast-import <input && + # branch must remain unaffected + test $old_branch = $(git rev-parse --verify branch^0) +' test_expect_success 'F: verify pack' ' verify_packs ' -cat >expect <<EOF -tree `git rev-parse branch~1^{tree}` -parent `git rev-parse branch~1` -author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +test_expect_success 'F: verify other commit' ' + cat >expect <<-EOF && + tree $(git rev-parse branch~1^{tree}) + parent $(git rev-parse branch~1) + author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -losing things already? -EOF -test_expect_success \ - 'F: verify other commit' \ - 'git cat-file commit other >actual && - test_cmp expect actual' + losing things already? + EOF + git cat-file commit other >actual && + test_cmp expect actual +' ### ### series G ### -old_branch=`git rev-parse --verify branch^0` -test_tick -cat >input <<INPUT_END -commit refs/heads/branch -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -losing things already? -COMMIT +test_expect_success 'G: non-fast-forward update forced' ' + old_branch=$(git rev-parse --verify branch^0) && + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/branch + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + losing things already? + COMMIT -from refs/heads/branch~1 + from refs/heads/branch~1 -INPUT_END -test_expect_success \ - 'G: non-fast-forward update forced' \ - 'git fast-import --force <input' + INPUT_END + git fast-import --force <input +' test_expect_success 'G: verify pack' ' verify_packs ' -test_expect_success \ - 'G: branch changed, but logged' \ - 'test $old_branch != `git rev-parse --verify branch^0` && - test $old_branch = `git rev-parse --verify branch@{1}`' +test_expect_success 'G: branch changed, but logged' ' + test $old_branch != $(git rev-parse --verify branch^0) && + test $old_branch = $(git rev-parse --verify branch@{1}) +' ### ### series H ### -test_tick -cat >input <<INPUT_END -commit refs/heads/H -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -third -COMMIT - -from refs/heads/branch^0 -M 644 inline i-will-die -data <<EOF -this file will never exist. -EOF - -deleteall -M 644 inline h/e/l/lo -data <<EOF -$file5_data -EOF - -INPUT_END -test_expect_success \ - 'H: deletall, add 1' \ - 'git fast-import <input && - git whatchanged H' +test_expect_success 'H: deletall, add 1' ' + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/H + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + third + COMMIT + + from refs/heads/branch^0 + M 644 inline i-will-die + data <<EOF + this file will never exist. + EOF + + deleteall + M 644 inline h/e/l/lo + data <<EOF + $file5_data + EOF + + INPUT_END + git fast-import <input && + git whatchanged H +' test_expect_success 'H: verify pack' ' verify_packs ' -cat >expect <<EOF -:100755 000000 f1fb5da718392694d0076d677d6d0e364c79b0bc 0000000000000000000000000000000000000000 D file2/newf -:100644 000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 0000000000000000000000000000000000000000 D file2/oldf -:100755 000000 85df50785d62d3b05ab03d9cbf7e4a0b49449730 0000000000000000000000000000000000000000 D file4 -:100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 R100 newdir/interesting h/e/l/lo -:100755 000000 e74b7d465e52746be2b4bae983670711e6e66657 0000000000000000000000000000000000000000 D newdir/exec.sh -EOF -git diff-tree -M -r H^ H >actual -test_expect_success \ - 'H: validate old files removed, new files added' \ - 'compare_diff_raw expect actual' - -echo "$file5_data" >expect -test_expect_success \ - 'H: verify file' \ - 'git cat-file blob H:h/e/l/lo >actual && - test_cmp expect actual' +test_expect_success 'H: validate old files removed, new files added' ' + cat >expect <<-EOF && + :100755 000000 f1fb5da718392694d0076d677d6d0e364c79b0bc 0000000000000000000000000000000000000000 D file2/newf + :100644 000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 0000000000000000000000000000000000000000 D file2/oldf + :100755 000000 85df50785d62d3b05ab03d9cbf7e4a0b49449730 0000000000000000000000000000000000000000 D file4 + :100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 R100 newdir/interesting h/e/l/lo + :100755 000000 e74b7d465e52746be2b4bae983670711e6e66657 0000000000000000000000000000000000000000 D newdir/exec.sh + EOF + git diff-tree -M -r H^ H >actual && + compare_diff_raw expect actual +' + +test_expect_success 'H: verify file' ' + echo "$file5_data" >expect && + git cat-file blob H:h/e/l/lo >actual && + test_cmp expect actual +' ### ### series I ### -cat >input <<INPUT_END -commit refs/heads/export-boundary -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -we have a border. its only 40 characters wide. -COMMIT +test_expect_success 'I: export-pack-edges' ' + cat >input <<-INPUT_END && + commit refs/heads/export-boundary + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + we have a border. its only 40 characters wide. + COMMIT -from refs/heads/branch + from refs/heads/branch -INPUT_END -test_expect_success \ - 'I: export-pack-edges' \ - 'git fast-import --export-pack-edges=edges.list <input' + INPUT_END + git fast-import --export-pack-edges=edges.list <input +' -cat >expect <<EOF -.git/objects/pack/pack-.pack: `git rev-parse --verify export-boundary` -EOF -test_expect_success \ - 'I: verify edge list' \ - 'sed -e s/pack-.*pack/pack-.pack/ edges.list >actual && - test_cmp expect actual' +test_expect_success 'I: verify edge list' ' + cat >expect <<-EOF && + .git/objects/pack/pack-.pack: $(git rev-parse --verify export-boundary) + EOF + sed -e s/pack-.*pack/pack-.pack/ edges.list >actual && + test_cmp expect actual +' ### ### series J ### -cat >input <<INPUT_END -commit refs/heads/J -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -create J -COMMIT - -from refs/heads/branch - -reset refs/heads/J - -commit refs/heads/J -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -initialize J -COMMIT - -INPUT_END -test_expect_success \ - 'J: reset existing branch creates empty commit' \ - 'git fast-import <input' -test_expect_success \ - 'J: branch has 1 commit, empty tree' \ - 'test 1 = `git rev-list J | wc -l` && - test 0 = `git ls-tree J | wc -l`' - -cat >input <<INPUT_END -reset refs/heads/J2 - -tag wrong_tag -from refs/heads/J2 -data <<EOF -Tag branch that was reset. -EOF -INPUT_END -test_expect_success \ - 'J: tag must fail on empty branch' \ - 'test_must_fail git fast-import <input' +test_expect_success 'J: reset existing branch creates empty commit' ' + cat >input <<-INPUT_END && + commit refs/heads/J + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + create J + COMMIT + + from refs/heads/branch + + reset refs/heads/J + + commit refs/heads/J + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + initialize J + COMMIT + + INPUT_END + git fast-import <input +' +test_expect_success 'J: branch has 1 commit, empty tree' ' + test 1 = $(git rev-list J | wc -l) && + test 0 = $(git ls-tree J | wc -l) +' + +test_expect_success 'J: tag must fail on empty branch' ' + cat >input <<-INPUT_END && + reset refs/heads/J2 + + tag wrong_tag + from refs/heads/J2 + data <<EOF + Tag branch that was reset. + EOF + INPUT_END + test_must_fail git fast-import <input +' + ### ### series K ### -cat >input <<INPUT_END -commit refs/heads/K -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -create K -COMMIT +test_expect_success 'K: reinit branch with from' ' + cat >input <<-INPUT_END && + commit refs/heads/K + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + create K + COMMIT -from refs/heads/branch + from refs/heads/branch -commit refs/heads/K -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -redo K -COMMIT + commit refs/heads/K + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + redo K + COMMIT -from refs/heads/branch^1 + from refs/heads/branch^1 -INPUT_END -test_expect_success \ - 'K: reinit branch with from' \ - 'git fast-import <input' -test_expect_success \ - 'K: verify K^1 = branch^1' \ - 'test `git rev-parse --verify branch^1` \ - = `git rev-parse --verify K^1`' + INPUT_END + git fast-import <input +' +test_expect_success 'K: verify K^1 = branch^1' ' + test $(git rev-parse --verify branch^1) \ + = $(git rev-parse --verify K^1) +' ### ### series L ### -cat >input <<INPUT_END -blob -mark :1 -data <<EOF -some data -EOF - -blob -mark :2 -data <<EOF -other data -EOF - -commit refs/heads/L -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -create L -COMMIT - -M 644 :1 b. -M 644 :1 b/other -M 644 :1 ba - -commit refs/heads/L -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -update L -COMMIT - -M 644 :2 b. -M 644 :2 b/other -M 644 :2 ba -INPUT_END - -cat >expect <<EXPECT_END -:100644 100644 4268632... 55d3a52... M b. -:040000 040000 0ae5cac... 443c768... M b -:100644 100644 4268632... 55d3a52... M ba -EXPECT_END - -test_expect_success \ - 'L: verify internal tree sorting' \ - 'git fast-import <input && - git diff-tree --abbrev --raw L^ L >output && - test_cmp expect output' - -cat >input <<INPUT_END -blob -mark :1 -data <<EOF -the data -EOF - -commit refs/heads/L2 -committer C O Mitter <committer@example.com> 1112912473 -0700 -data <<COMMIT -init L2 -COMMIT -M 644 :1 a/b/c -M 644 :1 a/b/d -M 644 :1 a/e/f - -commit refs/heads/L2 -committer C O Mitter <committer@example.com> 1112912473 -0700 -data <<COMMIT -update L2 -COMMIT -C a g -C a/e g/b -M 644 :1 g/b/h -INPUT_END - -cat <<EOF >expect -g/b/f -g/b/h -EOF - -test_expect_success \ - 'L: nested tree copy does not corrupt deltas' \ - 'git fast-import <input && +test_expect_success 'L: verify internal tree sorting' ' + cat >input <<-INPUT_END && + blob + mark :1 + data <<EOF + some data + EOF + + blob + mark :2 + data <<EOF + other data + EOF + + commit refs/heads/L + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + create L + COMMIT + + M 644 :1 b. + M 644 :1 b/other + M 644 :1 ba + + commit refs/heads/L + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + update L + COMMIT + + M 644 :2 b. + M 644 :2 b/other + M 644 :2 ba + INPUT_END + + cat >expect <<-EXPECT_END && + :100644 100644 4268632... 55d3a52... M b. + :040000 040000 0ae5cac... 443c768... M b + :100644 100644 4268632... 55d3a52... M ba + EXPECT_END + + git fast-import <input && + GIT_PRINT_SHA1_ELLIPSIS="yes" git diff-tree --abbrev --raw L^ L >output && + test_cmp expect output +' + +test_expect_success 'L: nested tree copy does not corrupt deltas' ' + cat >input <<-INPUT_END && + blob + mark :1 + data <<EOF + the data + EOF + + commit refs/heads/L2 + committer C O Mitter <committer@example.com> 1112912473 -0700 + data <<COMMIT + init L2 + COMMIT + M 644 :1 a/b/c + M 644 :1 a/b/d + M 644 :1 a/e/f + + commit refs/heads/L2 + committer C O Mitter <committer@example.com> 1112912473 -0700 + data <<COMMIT + update L2 + COMMIT + C a g + C a/e g/b + M 644 :1 g/b/h + INPUT_END + + cat >expect <<-\EOF && + g/b/f + g/b/h + EOF + + test_when_finished "git update-ref -d refs/heads/L2" && + git fast-import <input && git ls-tree L2 g/b/ >tmp && cat tmp | cut -f 2 >actual && test_cmp expect actual && - git fsck `git rev-parse L2`' - -git update-ref -d refs/heads/L2 + git fsck $(git rev-parse L2) +' ### ### series M ### -test_tick -cat >input <<INPUT_END -commit refs/heads/M1 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -file rename -COMMIT - -from refs/heads/branch^0 -R file2/newf file2/n.e.w.f - -INPUT_END - -cat >expect <<EOF -:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100 file2/newf file2/n.e.w.f -EOF -test_expect_success \ - 'M: rename file in same subdirectory' \ - 'git fast-import <input && - git diff-tree -M -r M1^ M1 >actual && - compare_diff_raw expect actual' - -cat >input <<INPUT_END -commit refs/heads/M2 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -file rename -COMMIT - -from refs/heads/branch^0 -R file2/newf i/am/new/to/you - -INPUT_END - -cat >expect <<EOF -:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100 file2/newf i/am/new/to/you -EOF -test_expect_success \ - 'M: rename file to new subdirectory' \ - 'git fast-import <input && - git diff-tree -M -r M2^ M2 >actual && - compare_diff_raw expect actual' - -cat >input <<INPUT_END -commit refs/heads/M3 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -file rename -COMMIT - -from refs/heads/M2^0 -R i other/sub - -INPUT_END - -cat >expect <<EOF -:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100 i/am/new/to/you other/sub/am/new/to/you -EOF -test_expect_success \ - 'M: rename subdirectory to new subdirectory' \ - 'git fast-import <input && - git diff-tree -M -r M3^ M3 >actual && - compare_diff_raw expect actual' - -cat >input <<INPUT_END -commit refs/heads/M4 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -rename root -COMMIT - -from refs/heads/M2^0 -R "" sub - -INPUT_END - -cat >expect <<EOF -:100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 R100 file2/oldf sub/file2/oldf -:100755 100755 85df50785d62d3b05ab03d9cbf7e4a0b49449730 85df50785d62d3b05ab03d9cbf7e4a0b49449730 R100 file4 sub/file4 -:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100 i/am/new/to/you sub/i/am/new/to/you -:100755 100755 e74b7d465e52746be2b4bae983670711e6e66657 e74b7d465e52746be2b4bae983670711e6e66657 R100 newdir/exec.sh sub/newdir/exec.sh -:100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 R100 newdir/interesting sub/newdir/interesting -EOF -test_expect_success \ - 'M: rename root to subdirectory' \ - 'git fast-import <input && - git diff-tree -M -r M4^ M4 >actual && - cat actual && - compare_diff_raw expect actual' +test_expect_success 'M: rename file in same subdirectory' ' + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/M1 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + file rename + COMMIT + + from refs/heads/branch^0 + R file2/newf file2/n.e.w.f + + INPUT_END + + cat >expect <<-EOF && + :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100 file2/newf file2/n.e.w.f + EOF + git fast-import <input && + git diff-tree -M -r M1^ M1 >actual && + compare_diff_raw expect actual +' + +test_expect_success 'M: rename file to new subdirectory' ' + cat >input <<-INPUT_END && + commit refs/heads/M2 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + file rename + COMMIT + + from refs/heads/branch^0 + R file2/newf i/am/new/to/you + + INPUT_END + + cat >expect <<-EOF && + :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100 file2/newf i/am/new/to/you + EOF + git fast-import <input && + git diff-tree -M -r M2^ M2 >actual && + compare_diff_raw expect actual +' + +test_expect_success 'M: rename subdirectory to new subdirectory' ' + cat >input <<-INPUT_END && + commit refs/heads/M3 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + file rename + COMMIT + + from refs/heads/M2^0 + R i other/sub + + INPUT_END + + cat >expect <<-EOF && + :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100 i/am/new/to/you other/sub/am/new/to/you + EOF + git fast-import <input && + git diff-tree -M -r M3^ M3 >actual && + compare_diff_raw expect actual +' + +test_expect_success 'M: rename root to subdirectory' ' + cat >input <<-INPUT_END && + commit refs/heads/M4 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + rename root + COMMIT + + from refs/heads/M2^0 + R "" sub + + INPUT_END + + cat >expect <<-EOF && + :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 R100 file2/oldf sub/file2/oldf + :100755 100755 85df50785d62d3b05ab03d9cbf7e4a0b49449730 85df50785d62d3b05ab03d9cbf7e4a0b49449730 R100 file4 sub/file4 + :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100 i/am/new/to/you sub/i/am/new/to/you + :100755 100755 e74b7d465e52746be2b4bae983670711e6e66657 e74b7d465e52746be2b4bae983670711e6e66657 R100 newdir/exec.sh sub/newdir/exec.sh + :100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 R100 newdir/interesting sub/newdir/interesting + EOF + git fast-import <input && + git diff-tree -M -r M4^ M4 >actual && + cat actual && + compare_diff_raw expect actual +' ### ### series N ### -test_tick -cat >input <<INPUT_END -commit refs/heads/N1 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -file copy -COMMIT - -from refs/heads/branch^0 -C file2/newf file2/n.e.w.f - -INPUT_END - -cat >expect <<EOF -:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf file2/n.e.w.f -EOF -test_expect_success \ - 'N: copy file in same subdirectory' \ - 'git fast-import <input && - git diff-tree -C --find-copies-harder -r N1^ N1 >actual && - compare_diff_raw expect actual' - -cat >input <<INPUT_END -commit refs/heads/N2 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -clean directory copy -COMMIT - -from refs/heads/branch^0 -C file2 file3 - -commit refs/heads/N2 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -modify directory copy -COMMIT - -M 644 inline file3/file5 -data <<EOF -$file5_data -EOF - -INPUT_END - -cat >expect <<EOF -:100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 C100 newdir/interesting file3/file5 -:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf file3/newf -:100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100 file2/oldf file3/oldf -EOF -test_expect_success \ - 'N: copy then modify subdirectory' \ - 'git fast-import <input && - git diff-tree -C --find-copies-harder -r N2^^ N2 >actual && - compare_diff_raw expect actual' - -cat >input <<INPUT_END -commit refs/heads/N3 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -dirty directory copy -COMMIT - -from refs/heads/branch^0 -M 644 inline file2/file5 -data <<EOF -$file5_data -EOF - -C file2 file3 -D file2/file5 - -INPUT_END - -test_expect_success \ - 'N: copy dirty subdirectory' \ - 'git fast-import <input && - test `git rev-parse N2^{tree}` = `git rev-parse N3^{tree}`' - -test_expect_success \ - 'N: copy directory by id' \ - 'cat >expect <<-\EOF && +test_expect_success 'N: copy file in same subdirectory' ' + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/N1 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + file copy + COMMIT + + from refs/heads/branch^0 + C file2/newf file2/n.e.w.f + + INPUT_END + + cat >expect <<-EOF && + :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf file2/n.e.w.f + EOF + git fast-import <input && + git diff-tree -C --find-copies-harder -r N1^ N1 >actual && + compare_diff_raw expect actual +' + +test_expect_success 'N: copy then modify subdirectory' ' + cat >input <<-INPUT_END && + commit refs/heads/N2 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + clean directory copy + COMMIT + + from refs/heads/branch^0 + C file2 file3 + + commit refs/heads/N2 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + modify directory copy + COMMIT + + M 644 inline file3/file5 + data <<EOF + $file5_data + EOF + + INPUT_END + + cat >expect <<-EOF && + :100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 C100 newdir/interesting file3/file5 :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf file3/newf :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100 file2/oldf file3/oldf EOF - subdir=$(git rev-parse refs/heads/branch^0:file2) && - cat >input <<-INPUT_END && + git fast-import <input && + git diff-tree -C --find-copies-harder -r N2^^ N2 >actual && + compare_diff_raw expect actual +' + +test_expect_success 'N: copy dirty subdirectory' ' + cat >input <<-INPUT_END && + commit refs/heads/N3 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + dirty directory copy + COMMIT + + from refs/heads/branch^0 + M 644 inline file2/file5 + data <<EOF + $file5_data + EOF + + C file2 file3 + D file2/file5 + + INPUT_END + + git fast-import <input && + test $(git rev-parse N2^{tree}) = $(git rev-parse N3^{tree}) +' + +test_expect_success 'N: copy directory by id' ' + cat >expect <<-\EOF && + :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf file3/newf + :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100 file2/oldf file3/oldf + EOF + subdir=$(git rev-parse refs/heads/branch^0:file2) && + cat >input <<-INPUT_END && commit refs/heads/N4 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1127,9 +1113,10 @@ test_expect_success \ from refs/heads/branch^0 M 040000 $subdir file3 INPUT_END - git fast-import <input && - git diff-tree -C --find-copies-harder -r N4^ N4 >actual && - compare_diff_raw expect actual' + git fast-import <input && + git diff-tree -C --find-copies-harder -r N4^ N4 >actual && + compare_diff_raw expect actual +' test_expect_success PIPE 'N: read and copy directory' ' cat >expect <<-\EOF && @@ -1202,14 +1189,13 @@ test_expect_success PIPE 'N: empty directory reads as missing' ' test_cmp expect actual ' -test_expect_success \ - 'N: copy root directory by tree hash' \ - 'cat >expect <<-\EOF && +test_expect_success 'N: copy root directory by tree hash' ' + cat >expect <<-\EOF && :100755 000000 f1fb5da718392694d0076d677d6d0e364c79b0bc 0000000000000000000000000000000000000000 D file3/newf :100644 000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 0000000000000000000000000000000000000000 D file3/oldf EOF - root=$(git rev-parse refs/heads/branch^0^{tree}) && - cat >input <<-INPUT_END && + root=$(git rev-parse refs/heads/branch^0^{tree}) && + cat >input <<-INPUT_END && commit refs/heads/N6 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1219,20 +1205,20 @@ test_expect_success \ from refs/heads/branch^0 M 040000 $root "" INPUT_END - git fast-import <input && - git diff-tree -C --find-copies-harder -r N4 N6 >actual && - compare_diff_raw expect actual' + git fast-import <input && + git diff-tree -C --find-copies-harder -r N4 N6 >actual && + compare_diff_raw expect actual +' -test_expect_success \ - 'N: copy root by path' \ - 'cat >expect <<-\EOF && +test_expect_success 'N: copy root by path' ' + cat >expect <<-\EOF && :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf oldroot/file2/newf :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100 file2/oldf oldroot/file2/oldf :100755 100755 85df50785d62d3b05ab03d9cbf7e4a0b49449730 85df50785d62d3b05ab03d9cbf7e4a0b49449730 C100 file4 oldroot/file4 :100755 100755 e74b7d465e52746be2b4bae983670711e6e66657 e74b7d465e52746be2b4bae983670711e6e66657 C100 newdir/exec.sh oldroot/newdir/exec.sh :100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 C100 newdir/interesting oldroot/newdir/interesting EOF - cat >input <<-INPUT_END && + cat >input <<-INPUT_END && commit refs/heads/N-copy-root-path committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1242,21 +1228,21 @@ test_expect_success \ from refs/heads/branch^0 C "" oldroot INPUT_END - git fast-import <input && - git diff-tree -C --find-copies-harder -r branch N-copy-root-path >actual && - compare_diff_raw expect actual' + git fast-import <input && + git diff-tree -C --find-copies-harder -r branch N-copy-root-path >actual && + compare_diff_raw expect actual +' -test_expect_success \ - 'N: delete directory by copying' \ - 'cat >expect <<-\EOF && +test_expect_success 'N: delete directory by copying' ' + cat >expect <<-\EOF && OBJID :100644 000000 OBJID OBJID D foo/bar/qux OBJID :000000 100644 OBJID OBJID A foo/bar/baz :000000 100644 OBJID OBJID A foo/bar/qux EOF - empty_tree=$(git mktree </dev/null) && - cat >input <<-INPUT_END && + empty_tree=$(git mktree </dev/null) && + cat >input <<-INPUT_END && commit refs/heads/N-delete committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1282,21 +1268,21 @@ test_expect_success \ M 040000 $empty_tree foo/bar/qux INPUT_END - git fast-import <input && - git rev-list N-delete | + git fast-import <input && + git rev-list N-delete | git diff-tree -r --stdin --root --always | sed -e "s/$_x40/OBJID/g" >actual && - test_cmp expect actual' + test_cmp expect actual +' -test_expect_success \ - 'N: modify copied tree' \ - 'cat >expect <<-\EOF && +test_expect_success 'N: modify copied tree' ' + cat >expect <<-\EOF && :100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 C100 newdir/interesting file3/file5 :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf file3/newf :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100 file2/oldf file3/oldf EOF - subdir=$(git rev-parse refs/heads/branch^0:file2) && - cat >input <<-INPUT_END && + subdir=$(git rev-parse refs/heads/branch^0:file2) && + cat >input <<-INPUT_END && commit refs/heads/N5 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1317,14 +1303,14 @@ test_expect_success \ $file5_data EOF INPUT_END - git fast-import <input && - git diff-tree -C --find-copies-harder -r N5^^ N5 >actual && - compare_diff_raw expect actual' + git fast-import <input && + git diff-tree -C --find-copies-harder -r N5^^ N5 >actual && + compare_diff_raw expect actual +' -test_expect_success \ - 'N: reject foo/ syntax' \ - 'subdir=$(git rev-parse refs/heads/branch^0:file2) && - test_must_fail git fast-import <<-INPUT_END +test_expect_success 'N: reject foo/ syntax' ' + subdir=$(git rev-parse refs/heads/branch^0:file2) && + test_must_fail git fast-import <<-INPUT_END commit refs/heads/N5B committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1333,11 +1319,11 @@ test_expect_success \ from refs/heads/branch^0 M 040000 $subdir file3/ - INPUT_END' + INPUT_END +' -test_expect_success \ - 'N: reject foo/ syntax in copy source' \ - 'test_must_fail git fast-import <<-INPUT_END +test_expect_success 'N: reject foo/ syntax in copy source' ' + test_must_fail git fast-import <<-INPUT_END commit refs/heads/N5C committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1346,11 +1332,11 @@ test_expect_success \ from refs/heads/branch^0 C file2/ file3 - INPUT_END' + INPUT_END +' -test_expect_success \ - 'N: reject foo/ syntax in rename source' \ - 'test_must_fail git fast-import <<-INPUT_END +test_expect_success 'N: reject foo/ syntax in rename source' ' + test_must_fail git fast-import <<-INPUT_END commit refs/heads/N5D committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1359,11 +1345,11 @@ test_expect_success \ from refs/heads/branch^0 R file2/ file3 - INPUT_END' + INPUT_END +' -test_expect_success \ - 'N: reject foo/ syntax in ls argument' \ - 'test_must_fail git fast-import <<-INPUT_END +test_expect_success 'N: reject foo/ syntax in ls argument' ' + test_must_fail git fast-import <<-INPUT_END commit refs/heads/N5E committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1372,13 +1358,13 @@ test_expect_success \ from refs/heads/branch^0 ls "file2/" - INPUT_END' + INPUT_END +' -test_expect_success \ - 'N: copy to root by id and modify' \ - 'echo "hello, world" >expect.foo && - echo hello >expect.bar && - git fast-import <<-SETUP_END && +test_expect_success 'N: copy to root by id and modify' ' + echo "hello, world" >expect.foo && + echo hello >expect.bar && + git fast-import <<-SETUP_END && commit refs/heads/N7 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1392,8 +1378,8 @@ test_expect_success \ EOF SETUP_END - tree=$(git rev-parse --verify N7:) && - git fast-import <<-INPUT_END && + tree=$(git rev-parse --verify N7:) && + git fast-import <<-INPUT_END && commit refs/heads/N8 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1406,15 +1392,15 @@ test_expect_success \ hello, world EOF INPUT_END - git show N8:foo/foo >actual.foo && - git show N8:foo/bar >actual.bar && - test_cmp expect.foo actual.foo && - test_cmp expect.bar actual.bar' + git show N8:foo/foo >actual.foo && + git show N8:foo/bar >actual.bar && + test_cmp expect.foo actual.foo && + test_cmp expect.bar actual.bar +' -test_expect_success \ - 'N: extract subtree' \ - 'branch=$(git rev-parse --verify refs/heads/branch^{tree}) && - cat >input <<-INPUT_END && +test_expect_success 'N: extract subtree' ' + branch=$(git rev-parse --verify refs/heads/branch^{tree}) && + cat >input <<-INPUT_END && commit refs/heads/N9 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1424,14 +1410,14 @@ test_expect_success \ M 040000 $branch "" C "newdir" "" INPUT_END - git fast-import <input && - git diff --exit-code branch:newdir N9' + git fast-import <input && + git diff --exit-code branch:newdir N9 +' -test_expect_success \ - 'N: modify subtree, extract it, and modify again' \ - 'echo hello >expect.baz && - echo hello, world >expect.qux && - git fast-import <<-SETUP_END && +test_expect_success 'N: modify subtree, extract it, and modify again' ' + echo hello >expect.baz && + echo hello, world >expect.qux && + git fast-import <<-SETUP_END && commit refs/heads/N10 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1445,8 +1431,8 @@ test_expect_success \ EOF SETUP_END - tree=$(git rev-parse --verify N10:) && - git fast-import <<-INPUT_END && + tree=$(git rev-parse --verify N10:) && + git fast-import <<-INPUT_END && commit refs/heads/N11 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1461,676 +1447,692 @@ test_expect_success \ R "foo" "" C "bar/qux" "bar/quux" INPUT_END - git show N11:bar/baz >actual.baz && - git show N11:bar/qux >actual.qux && - git show N11:bar/quux >actual.quux && - test_cmp expect.baz actual.baz && - test_cmp expect.qux actual.qux && - test_cmp expect.qux actual.quux' + git show N11:bar/baz >actual.baz && + git show N11:bar/qux >actual.qux && + git show N11:bar/quux >actual.quux && + test_cmp expect.baz actual.baz && + test_cmp expect.qux actual.qux && + test_cmp expect.qux actual.quux' ### ### series O ### -cat >input <<INPUT_END -#we will -commit refs/heads/O1 -# -- ignore all of this text -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -# $GIT_COMMITTER_NAME has inserted here for his benefit. -data <<COMMIT -dirty directory copy -COMMIT - -# don't forget the import blank line! -# -# yes, we started from our usual base of branch^0. -# i like branch^0. -from refs/heads/branch^0 -# and we need to reuse file2/file5 from N3 above. -M 644 inline file2/file5 -# otherwise the tree will be different -data <<EOF -$file5_data -EOF - -# don't forget to copy file2 to file3 -C file2 file3 -# -# or to delete file5 from file2. -D file2/file5 -# are we done yet? - -INPUT_END - -test_expect_success \ - 'O: comments are all skipped' \ - 'git fast-import <input && - test `git rev-parse N3` = `git rev-parse O1`' - -cat >input <<INPUT_END -commit refs/heads/O2 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -dirty directory copy -COMMIT -from refs/heads/branch^0 -M 644 inline file2/file5 -data <<EOF -$file5_data -EOF -C file2 file3 -D file2/file5 - -INPUT_END - -test_expect_success \ - 'O: blank lines not necessary after data commands' \ - 'git fast-import <input && - test `git rev-parse N3` = `git rev-parse O2`' - -test_expect_success \ - 'O: repack before next test' \ - 'git repack -a -d' - -cat >input <<INPUT_END -commit refs/heads/O3 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -zstring -COMMIT -commit refs/heads/O3 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -zof -COMMIT -checkpoint -commit refs/heads/O3 -mark :5 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -zempty -COMMIT -checkpoint -commit refs/heads/O3 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -zcommits -COMMIT -reset refs/tags/O3-2nd -from :5 -reset refs/tags/O3-3rd -from :5 -INPUT_END - -cat >expect <<INPUT_END -string -of -empty -commits -INPUT_END -test_expect_success \ - 'O: blank lines not necessary after other commands' \ - 'git fast-import <input && - test 8 = `find .git/objects/pack -type f | wc -l` && - test `git rev-parse refs/tags/O3-2nd` = `git rev-parse O3^` && - git log --reverse --pretty=oneline O3 | sed s/^.*z// >actual && - test_cmp expect actual' - -cat >input <<INPUT_END -commit refs/heads/O4 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -zstring -COMMIT -commit refs/heads/O4 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -zof -COMMIT -progress Two commits down, 2 to go! -commit refs/heads/O4 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -zempty -COMMIT -progress Three commits down, 1 to go! -commit refs/heads/O4 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -zcommits -COMMIT -progress I'm done! -INPUT_END -test_expect_success \ - 'O: progress outputs as requested by input' \ - 'git fast-import <input >actual && - grep "progress " <input >expect && - test_cmp expect actual' +test_expect_success 'O: comments are all skipped' ' + cat >input <<-INPUT_END && + #we will + commit refs/heads/O1 + # -- ignore all of this text + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + # $GIT_COMMITTER_NAME has inserted here for his benefit. + data <<COMMIT + dirty directory copy + COMMIT + + # do not forget the import blank line! + # + # yes, we started from our usual base of branch^0. + # i like branch^0. + from refs/heads/branch^0 + # and we need to reuse file2/file5 from N3 above. + M 644 inline file2/file5 + # otherwise the tree will be different + data <<EOF + $file5_data + EOF + + # do not forget to copy file2 to file3 + C file2 file3 + # + # or to delete file5 from file2. + D file2/file5 + # are we done yet? + + INPUT_END + + git fast-import <input && + test $(git rev-parse N3) = $(git rev-parse O1) +' + +test_expect_success 'O: blank lines not necessary after data commands' ' + cat >input <<-INPUT_END && + commit refs/heads/O2 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + dirty directory copy + COMMIT + from refs/heads/branch^0 + M 644 inline file2/file5 + data <<EOF + $file5_data + EOF + C file2 file3 + D file2/file5 + + INPUT_END + + git fast-import <input && + test $(git rev-parse N3) = $(git rev-parse O2) +' + +test_expect_success 'O: repack before next test' ' + git repack -a -d +' + +test_expect_success 'O: blank lines not necessary after other commands' ' + cat >input <<-INPUT_END && + commit refs/heads/O3 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + zstring + COMMIT + commit refs/heads/O3 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + zof + COMMIT + checkpoint + commit refs/heads/O3 + mark :5 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + zempty + COMMIT + checkpoint + commit refs/heads/O3 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + zcommits + COMMIT + reset refs/tags/O3-2nd + from :5 + reset refs/tags/O3-3rd + from :5 + INPUT_END + + cat >expect <<-INPUT_END && + string + of + empty + commits + INPUT_END + + git fast-import <input && + test 8 = $(find .git/objects/pack -type f | wc -l) && + test $(git rev-parse refs/tags/O3-2nd) = $(git rev-parse O3^) && + git log --reverse --pretty=oneline O3 | sed s/^.*z// >actual && + test_cmp expect actual +' + +test_expect_success 'O: progress outputs as requested by input' ' + cat >input <<-INPUT_END && + commit refs/heads/O4 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + zstring + COMMIT + commit refs/heads/O4 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + zof + COMMIT + progress Two commits down, 2 to go! + commit refs/heads/O4 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + zempty + COMMIT + progress Three commits down, 1 to go! + commit refs/heads/O4 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + zcommits + COMMIT + progress done! + INPUT_END + git fast-import <input >actual && + grep "progress " <input >expect && + test_cmp expect actual +' ### ### series P (gitlinks) ### -cat >input <<INPUT_END -blob -mark :1 -data 10 -test file - -reset refs/heads/sub -commit refs/heads/sub -mark :2 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data 12 -sub_initial -M 100644 :1 file - -blob -mark :3 -data <<DATAEND -[submodule "sub"] - path = sub - url = "`pwd`/sub" -DATAEND - -commit refs/heads/subuse1 -mark :4 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data 8 -initial -from refs/heads/master -M 100644 :3 .gitmodules -M 160000 :2 sub - -blob -mark :5 -data 20 -test file -more data - -commit refs/heads/sub -mark :6 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data 11 -sub_second -from :2 -M 100644 :5 file - -commit refs/heads/subuse1 -mark :7 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data 7 -second -from :4 -M 160000 :6 sub - -INPUT_END - -test_expect_success \ - 'P: superproject & submodule mix' \ - 'git fast-import <input && - git checkout subuse1 && - rm -rf sub && mkdir sub && (cd sub && - git init && - git fetch --update-head-ok .. refs/heads/sub:refs/heads/master && - git checkout master) && - git submodule init && - git submodule update' - -SUBLAST=$(git rev-parse --verify sub) -SUBPREV=$(git rev-parse --verify sub^) - -cat >input <<INPUT_END -blob -mark :1 -data <<DATAEND -[submodule "sub"] - path = sub - url = "`pwd`/sub" -DATAEND - -commit refs/heads/subuse2 -mark :2 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data 8 -initial -from refs/heads/master -M 100644 :1 .gitmodules -M 160000 $SUBPREV sub - -commit refs/heads/subuse2 -mark :3 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data 7 -second -from :2 -M 160000 $SUBLAST sub - -INPUT_END - -test_expect_success \ - 'P: verbatim SHA gitlinks' \ - 'git branch -D sub && - git gc && git prune && - git fast-import <input && - test $(git rev-parse --verify subuse2) = $(git rev-parse --verify subuse1)' - -test_tick -cat >input <<INPUT_END -commit refs/heads/subuse3 -mark :1 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -corrupt -COMMIT - -from refs/heads/subuse2 -M 160000 inline sub -data <<DATA -$SUBPREV -DATA - -INPUT_END +test_expect_success 'P: superproject & submodule mix' ' + cat >input <<-INPUT_END && + blob + mark :1 + data 10 + test file -test_expect_success 'P: fail on inline gitlink' ' - test_must_fail git fast-import <input' + reset refs/heads/sub + commit refs/heads/sub + mark :2 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data 12 + sub_initial + M 100644 :1 file + + blob + mark :3 + data <<DATAEND + [submodule "sub"] + path = sub + url = "$(pwd)/sub" + DATAEND + + commit refs/heads/subuse1 + mark :4 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data 8 + initial + from refs/heads/master + M 100644 :3 .gitmodules + M 160000 :2 sub -test_tick -cat >input <<INPUT_END -blob -mark :1 -data <<DATA -$SUBPREV -DATA + blob + mark :5 + data 20 + test file + more data -commit refs/heads/subuse3 -mark :2 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -corrupt -COMMIT + commit refs/heads/sub + mark :6 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data 11 + sub_second + from :2 + M 100644 :5 file -from refs/heads/subuse2 -M 160000 :1 sub + commit refs/heads/subuse1 + mark :7 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data 7 + second + from :4 + M 160000 :6 sub -INPUT_END + INPUT_END + + git fast-import <input && + git checkout subuse1 && + rm -rf sub && + mkdir sub && + ( + cd sub && + git init && + git fetch --update-head-ok .. refs/heads/sub:refs/heads/master && + git checkout master + ) && + git submodule init && + git submodule update +' + +test_expect_success 'P: verbatim SHA gitlinks' ' + SUBLAST=$(git rev-parse --verify sub) && + SUBPREV=$(git rev-parse --verify sub^) && + + cat >input <<-INPUT_END && + blob + mark :1 + data <<DATAEND + [submodule "sub"] + path = sub + url = "$(pwd)/sub" + DATAEND + + commit refs/heads/subuse2 + mark :2 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data 8 + initial + from refs/heads/master + M 100644 :1 .gitmodules + M 160000 $SUBPREV sub + + commit refs/heads/subuse2 + mark :3 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data 7 + second + from :2 + M 160000 $SUBLAST sub + + INPUT_END + + git branch -D sub && + git gc && + git prune && + git fast-import <input && + test $(git rev-parse --verify subuse2) = $(git rev-parse --verify subuse1) +' + +test_expect_success 'P: fail on inline gitlink' ' + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/subuse3 + mark :1 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + corrupt + COMMIT + + from refs/heads/subuse2 + M 160000 inline sub + data <<DATA + $SUBPREV + DATA + + INPUT_END + + test_must_fail git fast-import <input +' test_expect_success 'P: fail on blob mark in gitlink' ' - test_must_fail git fast-import <input' + test_tick && + cat >input <<-INPUT_END && + blob + mark :1 + data <<DATA + $SUBPREV + DATA + + commit refs/heads/subuse3 + mark :2 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + corrupt + COMMIT + + from refs/heads/subuse2 + M 160000 :1 sub + + INPUT_END + + test_must_fail git fast-import <input +' ### ### series Q (notes) ### -note1_data="The first note for the first commit" -note2_data="The first note for the second commit" -note3_data="The first note for the third commit" -note1b_data="The second note for the first commit" -note1c_data="The third note for the first commit" -note2b_data="The second note for the second commit" - -test_tick -cat >input <<INPUT_END -blob -mark :2 -data <<EOF -$file2_data -EOF - -commit refs/heads/notes-test -mark :3 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -first (:3) -COMMIT - -M 644 :2 file2 - -blob -mark :4 -data $file4_len -$file4_data -commit refs/heads/notes-test -mark :5 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -second (:5) -COMMIT - -M 644 :4 file4 - -commit refs/heads/notes-test -mark :6 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -third (:6) -COMMIT - -M 644 inline file5 -data <<EOF -$file5_data -EOF - -M 755 inline file6 -data <<EOF -$file6_data -EOF - -blob -mark :7 -data <<EOF -$note1_data -EOF - -blob -mark :8 -data <<EOF -$note2_data -EOF - -commit refs/notes/foobar -mark :9 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -notes (:9) -COMMIT - -N :7 :3 -N :8 :5 -N inline :6 -data <<EOF -$note3_data -EOF - -commit refs/notes/foobar -mark :10 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -notes (:10) -COMMIT - -N inline :3 -data <<EOF -$note1b_data -EOF - -commit refs/notes/foobar2 -mark :11 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -notes (:11) -COMMIT - -N inline :3 -data <<EOF -$note1c_data -EOF - -commit refs/notes/foobar -mark :12 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -notes (:12) -COMMIT - -deleteall -N inline :5 -data <<EOF -$note2b_data -EOF - -INPUT_END - -test_expect_success \ - 'Q: commit notes' \ - 'git fast-import <input && - git whatchanged notes-test' +test_expect_success 'Q: commit notes' ' + note1_data="The first note for the first commit" && + note2_data="The first note for the second commit" && + note3_data="The first note for the third commit" && + note1b_data="The second note for the first commit" && + note1c_data="The third note for the first commit" && + note2b_data="The second note for the second commit" && + + test_tick && + cat >input <<-INPUT_END && + blob + mark :2 + data <<EOF + $file2_data + EOF + + commit refs/heads/notes-test + mark :3 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + first (:3) + COMMIT + + M 644 :2 file2 + + blob + mark :4 + data $file4_len + $file4_data + commit refs/heads/notes-test + mark :5 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + second (:5) + COMMIT + + M 644 :4 file4 + + commit refs/heads/notes-test + mark :6 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + third (:6) + COMMIT + + M 644 inline file5 + data <<EOF + $file5_data + EOF + + M 755 inline file6 + data <<EOF + $file6_data + EOF + + blob + mark :7 + data <<EOF + $note1_data + EOF + + blob + mark :8 + data <<EOF + $note2_data + EOF + + commit refs/notes/foobar + mark :9 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + notes (:9) + COMMIT + + N :7 :3 + N :8 :5 + N inline :6 + data <<EOF + $note3_data + EOF + + commit refs/notes/foobar + mark :10 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + notes (:10) + COMMIT + + N inline :3 + data <<EOF + $note1b_data + EOF + + commit refs/notes/foobar2 + mark :11 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + notes (:11) + COMMIT + + N inline :3 + data <<EOF + $note1c_data + EOF + + commit refs/notes/foobar + mark :12 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + notes (:12) + COMMIT + + deleteall + N inline :5 + data <<EOF + $note2b_data + EOF + + INPUT_END + + git fast-import <input && + git whatchanged notes-test +' test_expect_success 'Q: verify pack' ' verify_packs ' -commit1=$(git rev-parse notes-test~2) -commit2=$(git rev-parse notes-test^) -commit3=$(git rev-parse notes-test) - -cat >expect <<EOF -author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - -first (:3) -EOF -test_expect_success \ - 'Q: verify first commit' \ - 'git cat-file commit notes-test~2 | sed 1d >actual && - test_cmp expect actual' - -cat >expect <<EOF -parent $commit1 -author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - -second (:5) -EOF -test_expect_success \ - 'Q: verify second commit' \ - 'git cat-file commit notes-test^ | sed 1d >actual && - test_cmp expect actual' - -cat >expect <<EOF -parent $commit2 -author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - -third (:6) -EOF -test_expect_success \ - 'Q: verify third commit' \ - 'git cat-file commit notes-test | sed 1d >actual && - test_cmp expect actual' - -cat >expect <<EOF -author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - -notes (:9) -EOF -test_expect_success \ - 'Q: verify first notes commit' \ - 'git cat-file commit refs/notes/foobar~2 | sed 1d >actual && - test_cmp expect actual' - -cat >expect.unsorted <<EOF -100644 blob $commit1 -100644 blob $commit2 -100644 blob $commit3 -EOF -cat expect.unsorted | sort >expect -test_expect_success \ - 'Q: verify first notes tree' \ - 'git cat-file -p refs/notes/foobar~2^{tree} | sed "s/ [0-9a-f]* / /" >actual && - test_cmp expect actual' - -echo "$note1_data" >expect -test_expect_success \ - 'Q: verify first note for first commit' \ - 'git cat-file blob refs/notes/foobar~2:$commit1 >actual && test_cmp expect actual' - -echo "$note2_data" >expect -test_expect_success \ - 'Q: verify first note for second commit' \ - 'git cat-file blob refs/notes/foobar~2:$commit2 >actual && test_cmp expect actual' - -echo "$note3_data" >expect -test_expect_success \ - 'Q: verify first note for third commit' \ - 'git cat-file blob refs/notes/foobar~2:$commit3 >actual && test_cmp expect actual' - -cat >expect <<EOF -parent `git rev-parse --verify refs/notes/foobar~2` -author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - -notes (:10) -EOF -test_expect_success \ - 'Q: verify second notes commit' \ - 'git cat-file commit refs/notes/foobar^ | sed 1d >actual && - test_cmp expect actual' - -cat >expect.unsorted <<EOF -100644 blob $commit1 -100644 blob $commit2 -100644 blob $commit3 -EOF -cat expect.unsorted | sort >expect -test_expect_success \ - 'Q: verify second notes tree' \ - 'git cat-file -p refs/notes/foobar^^{tree} | sed "s/ [0-9a-f]* / /" >actual && - test_cmp expect actual' - -echo "$note1b_data" >expect -test_expect_success \ - 'Q: verify second note for first commit' \ - 'git cat-file blob refs/notes/foobar^:$commit1 >actual && test_cmp expect actual' - -echo "$note2_data" >expect -test_expect_success \ - 'Q: verify first note for second commit' \ - 'git cat-file blob refs/notes/foobar^:$commit2 >actual && test_cmp expect actual' - -echo "$note3_data" >expect -test_expect_success \ - 'Q: verify first note for third commit' \ - 'git cat-file blob refs/notes/foobar^:$commit3 >actual && test_cmp expect actual' - -cat >expect <<EOF -author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - -notes (:11) -EOF -test_expect_success \ - 'Q: verify third notes commit' \ - 'git cat-file commit refs/notes/foobar2 | sed 1d >actual && - test_cmp expect actual' - -cat >expect.unsorted <<EOF -100644 blob $commit1 -EOF -cat expect.unsorted | sort >expect -test_expect_success \ - 'Q: verify third notes tree' \ - 'git cat-file -p refs/notes/foobar2^{tree} | sed "s/ [0-9a-f]* / /" >actual && - test_cmp expect actual' - -echo "$note1c_data" >expect -test_expect_success \ - 'Q: verify third note for first commit' \ - 'git cat-file blob refs/notes/foobar2:$commit1 >actual && test_cmp expect actual' - -cat >expect <<EOF -parent `git rev-parse --verify refs/notes/foobar^` -author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - -notes (:12) -EOF -test_expect_success \ - 'Q: verify fourth notes commit' \ - 'git cat-file commit refs/notes/foobar | sed 1d >actual && - test_cmp expect actual' - -cat >expect.unsorted <<EOF -100644 blob $commit2 -EOF -cat expect.unsorted | sort >expect -test_expect_success \ - 'Q: verify fourth notes tree' \ - 'git cat-file -p refs/notes/foobar^{tree} | sed "s/ [0-9a-f]* / /" >actual && - test_cmp expect actual' - -echo "$note2b_data" >expect -test_expect_success \ - 'Q: verify second note for second commit' \ - 'git cat-file blob refs/notes/foobar:$commit2 >actual && test_cmp expect actual' - -cat >input <<EOF -reset refs/heads/Q0 - -commit refs/heads/note-Q0 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -Note for an empty branch. -COMMIT - -N inline refs/heads/Q0 -data <<NOTE -some note -NOTE -EOF -test_expect_success \ - 'Q: deny note on empty branch' \ - 'test_must_fail git fast-import <input' +test_expect_success 'Q: verify first commit' ' + commit1=$(git rev-parse notes-test~2) && + commit2=$(git rev-parse notes-test^) && + commit3=$(git rev-parse notes-test) && + + cat >expect <<-EOF && + author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + + first (:3) + EOF + git cat-file commit notes-test~2 | sed 1d >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify second commit' ' + cat >expect <<-EOF && + parent $commit1 + author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + + second (:5) + EOF + git cat-file commit notes-test^ | sed 1d >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify third commit' ' + cat >expect <<-EOF && + parent $commit2 + author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + + third (:6) + EOF + git cat-file commit notes-test | sed 1d >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify first notes commit' ' + cat >expect <<-EOF && + author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + + notes (:9) + EOF + git cat-file commit refs/notes/foobar~2 | sed 1d >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify first notes tree' ' + cat >expect.unsorted <<-EOF && + 100644 blob $commit1 + 100644 blob $commit2 + 100644 blob $commit3 + EOF + cat expect.unsorted | sort >expect && + git cat-file -p refs/notes/foobar~2^{tree} | sed "s/ [0-9a-f]* / /" >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify first note for first commit' ' + echo "$note1_data" >expect && + git cat-file blob refs/notes/foobar~2:$commit1 >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify first note for second commit' ' + echo "$note2_data" >expect && + git cat-file blob refs/notes/foobar~2:$commit2 >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify first note for third commit' ' + echo "$note3_data" >expect && + git cat-file blob refs/notes/foobar~2:$commit3 >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify second notes commit' ' + cat >expect <<-EOF && + parent $(git rev-parse --verify refs/notes/foobar~2) + author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + + notes (:10) + EOF + git cat-file commit refs/notes/foobar^ | sed 1d >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify second notes tree' ' + cat >expect.unsorted <<-EOF && + 100644 blob $commit1 + 100644 blob $commit2 + 100644 blob $commit3 + EOF + cat expect.unsorted | sort >expect && + git cat-file -p refs/notes/foobar^^{tree} | sed "s/ [0-9a-f]* / /" >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify second note for first commit' ' + echo "$note1b_data" >expect && + git cat-file blob refs/notes/foobar^:$commit1 >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify first note for second commit' ' + echo "$note2_data" >expect && + git cat-file blob refs/notes/foobar^:$commit2 >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify first note for third commit' ' + echo "$note3_data" >expect && + git cat-file blob refs/notes/foobar^:$commit3 >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify third notes commit' ' + cat >expect <<-EOF && + author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + + notes (:11) + EOF + git cat-file commit refs/notes/foobar2 | sed 1d >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify third notes tree' ' + cat >expect.unsorted <<-EOF && + 100644 blob $commit1 + EOF + cat expect.unsorted | sort >expect && + git cat-file -p refs/notes/foobar2^{tree} | sed "s/ [0-9a-f]* / /" >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify third note for first commit' ' + echo "$note1c_data" >expect && + git cat-file blob refs/notes/foobar2:$commit1 >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify fourth notes commit' ' + cat >expect <<-EOF && + parent $(git rev-parse --verify refs/notes/foobar^) + author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + + notes (:12) + EOF + git cat-file commit refs/notes/foobar | sed 1d >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify fourth notes tree' ' + cat >expect.unsorted <<-EOF && + 100644 blob $commit2 + EOF + cat expect.unsorted | sort >expect && + git cat-file -p refs/notes/foobar^{tree} | sed "s/ [0-9a-f]* / /" >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify second note for second commit' ' + echo "$note2b_data" >expect && + git cat-file blob refs/notes/foobar:$commit2 >actual && + test_cmp expect actual +' + +test_expect_success 'Q: deny note on empty branch' ' + cat >input <<-EOF && + reset refs/heads/Q0 + + commit refs/heads/note-Q0 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + Note for an empty branch. + COMMIT + + N inline refs/heads/Q0 + data <<NOTE + some note + NOTE + EOF + test_must_fail git fast-import <input +' ### ### series R (feature and option) ### -cat >input <<EOF -feature no-such-feature-exists -EOF - test_expect_success 'R: abort on unsupported feature' ' + cat >input <<-EOF && + feature no-such-feature-exists + EOF + test_must_fail git fast-import <input ' -cat >input <<EOF -feature date-format=now -EOF - test_expect_success 'R: supported feature is accepted' ' + cat >input <<-EOF && + feature date-format=now + EOF + git fast-import <input ' -cat >input << EOF -blob -data 3 -hi -feature date-format=now -EOF - test_expect_success 'R: abort on receiving feature after data command' ' + cat >input <<-EOF && + blob + data 3 + hi + feature date-format=now + EOF + test_must_fail git fast-import <input ' -cat >input << EOF -feature import-marks=git.marks -feature import-marks=git2.marks -EOF - test_expect_success 'R: only one import-marks feature allowed per stream' ' + cat >input <<-EOF && + feature import-marks=git.marks + feature import-marks=git2.marks + EOF + test_must_fail git fast-import <input ' -cat >input << EOF -feature export-marks=git.marks -blob -mark :1 -data 3 -hi +test_expect_success 'R: export-marks feature results in a marks file being created' ' + cat >input <<-EOF && + feature export-marks=git.marks + blob + mark :1 + data 3 + hi -EOF + EOF -test_expect_success \ - 'R: export-marks feature results in a marks file being created' \ - 'cat input | git fast-import && - grep :1 git.marks' + cat input | git fast-import && + grep :1 git.marks +' -test_expect_success \ - 'R: export-marks options can be overridden by commandline options' \ - 'cat input | git fast-import --export-marks=other.marks && - grep :1 other.marks' +test_expect_success 'R: export-marks options can be overridden by commandline options' ' + cat input | git fast-import --export-marks=other.marks && + grep :1 other.marks +' test_expect_success 'R: catch typo in marks file name' ' test_must_fail git fast-import --import-marks=nonexistent.marks </dev/null && @@ -2234,62 +2236,62 @@ test_expect_success 'R: feature import-marks-if-exists' ' test_cmp expect io.marks ' -cat >input << EOF -feature import-marks=marks.out -feature export-marks=marks.new -EOF - -test_expect_success \ - 'R: import to output marks works without any content' \ - 'cat input | git fast-import && - test_cmp marks.out marks.new' +test_expect_success 'R: import to output marks works without any content' ' + cat >input <<-EOF && + feature import-marks=marks.out + feature export-marks=marks.new + EOF -cat >input <<EOF -feature import-marks=nonexistent.marks -feature export-marks=marks.new -EOF + cat input | git fast-import && + test_cmp marks.out marks.new +' -test_expect_success \ - 'R: import marks prefers commandline marks file over the stream' \ - 'cat input | git fast-import --import-marks=marks.out && - test_cmp marks.out marks.new' +test_expect_success 'R: import marks prefers commandline marks file over the stream' ' + cat >input <<-EOF && + feature import-marks=nonexistent.marks + feature export-marks=marks.new + EOF + cat input | git fast-import --import-marks=marks.out && + test_cmp marks.out marks.new +' -cat >input <<EOF -feature import-marks=nonexistent.marks -feature export-marks=combined.marks -EOF test_expect_success 'R: multiple --import-marks= should be honoured' ' - head -n2 marks.out > one.marks && - tail -n +3 marks.out > two.marks && - git fast-import --import-marks=one.marks --import-marks=two.marks <input && - test_cmp marks.out combined.marks -' + cat >input <<-EOF && + feature import-marks=nonexistent.marks + feature export-marks=combined.marks + EOF -cat >input <<EOF -feature relative-marks -feature import-marks=relative.in -feature export-marks=relative.out -EOF + head -n2 marks.out > one.marks && + tail -n +3 marks.out > two.marks && + git fast-import --import-marks=one.marks --import-marks=two.marks <input && + test_cmp marks.out combined.marks +' test_expect_success 'R: feature relative-marks should be honoured' ' - mkdir -p .git/info/fast-import/ && - cp marks.new .git/info/fast-import/relative.in && - git fast-import <input && - test_cmp marks.new .git/info/fast-import/relative.out -' + cat >input <<-EOF && + feature relative-marks + feature import-marks=relative.in + feature export-marks=relative.out + EOF -cat >input <<EOF -feature relative-marks -feature import-marks=relative.in -feature no-relative-marks -feature export-marks=non-relative.out -EOF + mkdir -p .git/info/fast-import/ && + cp marks.new .git/info/fast-import/relative.in && + git fast-import <input && + test_cmp marks.new .git/info/fast-import/relative.out +' test_expect_success 'R: feature no-relative-marks should be honoured' ' - git fast-import <input && - test_cmp marks.new non-relative.out + cat >input <<-EOF && + feature relative-marks + feature import-marks=relative.in + feature no-relative-marks + feature export-marks=non-relative.out + EOF + + git fast-import <input && + test_cmp marks.new non-relative.out ' test_expect_success 'R: feature ls supported' ' @@ -2330,15 +2332,28 @@ test_expect_success !MINGW 'R: in-stream cat-blob-fd not respected' ' cat-blob $blob EOF test_cmp expect actual.3 && - test_cmp empty actual.1 && + test_must_be_empty actual.1 && git fast-import 3>actual.3 >actual.1 <<-EOF && option cat-blob-fd=3 cat-blob $blob EOF - test_cmp empty actual.3 && + test_must_be_empty actual.3 && test_cmp expect actual.1 ' +test_expect_success !MINGW 'R: print mark for new blob' ' + echo "effluentish" | git hash-object --stdin >expect && + git fast-import --cat-blob-fd=6 6>actual <<-\EOF && + blob + mark :1 + data <<BLOB_END + effluentish + BLOB_END + get-mark :1 + EOF + test_cmp expect actual +' + test_expect_success !MINGW 'R: print new blob' ' blob=$(echo "yep yep yep" | git hash-object --stdin) && cat >expect <<-EOF && @@ -2449,7 +2464,7 @@ test_expect_success PIPE 'R: copy using cat-file' ' read blob_id type size <&3 && echo "$blob_id $type $size" >response && - head_c $size >blob <&3 && + test_copy_bytes $size >blob <&3 && read newline <&3 && cat <<-EOF && @@ -2492,7 +2507,7 @@ test_expect_success PIPE 'R: print blob mid-commit' ' EOF read blob_id type size <&3 && - head_c $size >actual <&3 && + test_copy_bytes $size >actual <&3 && read newline <&3 && echo @@ -2527,7 +2542,7 @@ test_expect_success PIPE 'R: print staged blob within commit' ' echo "cat-blob $to_get" && read blob_id type size <&3 && - head_c $size >actual <&3 && + test_copy_bytes $size >actual <&3 && read newline <&3 && echo deleteall @@ -2536,17 +2551,17 @@ test_expect_success PIPE 'R: print staged blob within commit' ' test_cmp expect actual ' -cat >input << EOF -option git quiet -blob -data 3 -hi +test_expect_success 'R: quiet option results in no stats being output' ' + cat >input <<-EOF && + option git quiet + blob + data 3 + hi -EOF + EOF -test_expect_success 'R: quiet option results in no stats being output' ' - cat input | git fast-import 2> output && - test_cmp empty output + cat input | git fast-import 2> output && + test_must_be_empty output ' test_expect_success 'R: feature done means terminating "done" is mandatory' ' @@ -2591,16 +2606,16 @@ test_expect_success 'R: terminating "done" within commit' ' test_cmp expect actual ' -cat >input <<EOF -option git non-existing-option -EOF - test_expect_success 'R: die on unknown option' ' - test_must_fail git fast-import <input + cat >input <<-EOF && + option git non-existing-option + EOF + + test_must_fail git fast-import <input ' test_expect_success 'R: unknown commandline options are rejected' '\ - test_must_fail git fast-import --non-existing-option < /dev/null + test_must_fail git fast-import --non-existing-option < /dev/null ' test_expect_success 'R: die on invalid option argument' ' @@ -2611,41 +2626,57 @@ test_expect_success 'R: die on invalid option argument' ' test_must_fail git fast-import --depth="5 elephants" </dev/null ' -cat >input <<EOF -option non-existing-vcs non-existing-option -EOF - test_expect_success 'R: ignore non-git options' ' - git fast-import <input + cat >input <<-EOF && + option non-existing-vcs non-existing-option + EOF + + git fast-import <input +' + +test_expect_success 'R: corrupt lines do not mess marks file' ' + rm -f io.marks && + blob=$(echo hi | git hash-object --stdin) && + cat >expect <<-EOF && + :3 0000000000000000000000000000000000000000 + :1 $blob + :2 $blob + EOF + cp expect io.marks && + test_must_fail git fast-import --import-marks=io.marks --export-marks=io.marks <<-\EOF && + + EOF + test_cmp expect io.marks ' ## ## R: very large blobs ## -blobsize=$((2*1024*1024 + 53)) -test-genrandom bar $blobsize >expect -cat >input <<INPUT_END -commit refs/heads/big-file -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -R - big file -COMMIT - -M 644 inline big1 -data $blobsize -INPUT_END -cat expect >>input -cat >>input <<INPUT_END -M 644 inline big2 -data $blobsize -INPUT_END -cat expect >>input -echo >>input - -test_expect_success \ - 'R: blob bigger than threshold' \ - 'test_create_repo R && - git --git-dir=R/.git fast-import --big-file-threshold=1 <input' +test_expect_success 'R: blob bigger than threshold' ' + blobsize=$((2*1024*1024 + 53)) && + test-tool genrandom bar $blobsize >expect && + cat >input <<-INPUT_END && + commit refs/heads/big-file + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + R - big file + COMMIT + + M 644 inline big1 + data $blobsize + INPUT_END + cat expect >>input && + cat >>input <<-INPUT_END && + M 644 inline big2 + data $blobsize + INPUT_END + cat expect >>input && + echo >>input && + + test_create_repo R && + git --git-dir=R/.git config fastimport.unpackLimit 0 && + git --git-dir=R/.git fast-import --big-file-threshold=1 <input +' test_expect_success 'R: verify created pack' ' ( @@ -2654,17 +2685,18 @@ test_expect_success 'R: verify created pack' ' ) ' -test_expect_success \ - 'R: verify written objects' \ - 'git --git-dir=R/.git cat-file blob big-file:big1 >actual && - test_cmp_bin expect actual && - a=$(git --git-dir=R/.git rev-parse big-file:big1) && - b=$(git --git-dir=R/.git rev-parse big-file:big2) && - test $a = $b' -test_expect_success \ - 'R: blob appears only once' \ - 'n=$(grep $a verify | wc -l) && - test 1 = $n' +test_expect_success 'R: verify written objects' ' + git --git-dir=R/.git cat-file blob big-file:big1 >actual && + test_cmp_bin expect actual && + a=$(git --git-dir=R/.git rev-parse big-file:big1) && + b=$(git --git-dir=R/.git rev-parse big-file:big2) && + test $a = $b +' + +test_expect_success 'R: blob appears only once' ' + n=$(grep $a verify | wc -l) && + test 1 = $n +' ### ### series S @@ -2697,46 +2729,46 @@ test_expect_success \ # # Invalid dataref .. # -test_tick - -cat >input <<INPUT_END -commit refs/heads/S -mark :301 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -commit 1 -COMMIT -M 100644 inline hello.c -data <<BLOB -blob 1 -BLOB - -commit refs/heads/S -mark :302 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -commit 2 -COMMIT -from :301 -M 100644 inline hello.c -data <<BLOB -blob 2 -BLOB - -blob -mark :403 -data <<BLOB -blob 3 -BLOB - -blob -mark :202 -data <<BLOB -note 2 -BLOB -INPUT_END - test_expect_success 'S: initialize for S tests' ' + test_tick && + + cat >input <<-INPUT_END && + commit refs/heads/S + mark :301 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + commit 1 + COMMIT + M 100644 inline hello.c + data <<BLOB + blob 1 + BLOB + + commit refs/heads/S + mark :302 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + commit 2 + COMMIT + from :301 + M 100644 inline hello.c + data <<BLOB + blob 2 + BLOB + + blob + mark :403 + data <<BLOB + blob 3 + BLOB + + blob + mark :202 + data <<BLOB + note 2 + BLOB + INPUT_END + git fast-import --export-marks=marks <input ' @@ -2790,7 +2822,7 @@ test_expect_success 'S: filemodify with garbage after sha1 must fail' ' # # notemodify, three ways to say dataref # -test_expect_success 'S: notemodify with garabge after mark dataref must fail' ' +test_expect_success 'S: notemodify with garbage after mark dataref must fail' ' test_must_fail git fast-import --import-marks=marks <<-EOF 2>err && commit refs/heads/S committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE @@ -2988,104 +3020,246 @@ test_expect_success 'T: empty reset doesnt delete branch' ' ### series U (filedelete) ### -cat >input <<INPUT_END -commit refs/heads/U -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -test setup -COMMIT -M 100644 inline hello.c -data <<BLOB -blob 1 -BLOB -M 100644 inline good/night.txt -data <<BLOB -sleep well -BLOB -M 100644 inline good/bye.txt -data <<BLOB -au revoir -BLOB - -INPUT_END - test_expect_success 'U: initialize for U tests' ' + cat >input <<-INPUT_END && + commit refs/heads/U + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + test setup + COMMIT + M 100644 inline hello.c + data <<BLOB + blob 1 + BLOB + M 100644 inline good/night.txt + data <<BLOB + sleep well + BLOB + M 100644 inline good/bye.txt + data <<BLOB + au revoir + BLOB + + INPUT_END + git fast-import <input ' -cat >input <<INPUT_END -commit refs/heads/U -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -delete good/night.txt -COMMIT -from refs/heads/U^0 -D good/night.txt +test_expect_success 'U: filedelete file succeeds' ' + cat >input <<-INPUT_END && + commit refs/heads/U + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + delete good/night.txt + COMMIT + from refs/heads/U^0 + D good/night.txt -INPUT_END + INPUT_END -test_expect_success 'U: filedelete file succeeds' ' git fast-import <input ' -cat >expect <<EOF -:100644 000000 2907ebb4bf85d91bf0716bb3bd8a68ef48d6da76 0000000000000000000000000000000000000000 D good/night.txt -EOF +test_expect_success 'U: validate file delete result' ' + cat >expect <<-EOF && + :100644 000000 2907ebb4bf85d91bf0716bb3bd8a68ef48d6da76 0000000000000000000000000000000000000000 D good/night.txt + EOF -git diff-tree -M -r U^1 U >actual + git diff-tree -M -r U^1 U >actual && -test_expect_success 'U: validate file delete result' ' compare_diff_raw expect actual ' -cat >input <<INPUT_END -commit refs/heads/U -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -delete good dir -COMMIT -from refs/heads/U^0 -D good +test_expect_success 'U: filedelete directory succeeds' ' + cat >input <<-INPUT_END && + commit refs/heads/U + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + delete good dir + COMMIT + from refs/heads/U^0 + D good -INPUT_END + INPUT_END -test_expect_success 'U: filedelete directory succeeds' ' git fast-import <input ' -cat >expect <<EOF -:100644 000000 69cb75792f55123d8389c156b0b41c2ff00ed507 0000000000000000000000000000000000000000 D good/bye.txt -EOF +test_expect_success 'U: validate directory delete result' ' + cat >expect <<-EOF && + :100644 000000 69cb75792f55123d8389c156b0b41c2ff00ed507 0000000000000000000000000000000000000000 D good/bye.txt + EOF -git diff-tree -M -r U^1 U >actual + git diff-tree -M -r U^1 U >actual && -test_expect_success 'U: validate directory delete result' ' compare_diff_raw expect actual ' -cat >input <<INPUT_END -commit refs/heads/U -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -must succeed -COMMIT -from refs/heads/U^0 -D "" +test_expect_success 'U: filedelete root succeeds' ' + cat >input <<-INPUT_END && + commit refs/heads/U + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + must succeed + COMMIT + from refs/heads/U^0 + D "" -INPUT_END + INPUT_END -test_expect_success 'U: filedelete root succeeds' ' - git fast-import <input + git fast-import <input ' -cat >expect <<EOF -:100644 000000 c18147dc648481eeb65dc5e66628429a64843327 0000000000000000000000000000000000000000 D hello.c -EOF +test_expect_success 'U: validate root delete result' ' + cat >expect <<-EOF && + :100644 000000 c18147dc648481eeb65dc5e66628429a64843327 0000000000000000000000000000000000000000 D hello.c + EOF -git diff-tree -M -r U^1 U >actual + git diff-tree -M -r U^1 U >actual && -test_expect_success 'U: validate root delete result' ' compare_diff_raw expect actual ' +### +### series V (checkpoint) +### + +# The commands in input_file should not produce any output on the file +# descriptor set with --cat-blob-fd (or stdout if unspecified). +# +# To make sure you're observing the side effects of checkpoint *before* +# fast-import terminates (and thus writes out its state), check that the +# fast-import process is still running using background_import_still_running +# *after* evaluating the test conditions. +background_import_then_checkpoint () { + options=$1 + input_file=$2 + + mkfifo V.input + exec 8<>V.input + rm V.input + + mkfifo V.output + exec 9<>V.output + rm V.output + + git fast-import $options <&8 >&9 & + echo $! >V.pid + # We don't mind if fast-import has already died by the time the test + # ends. + test_when_finished "exec 8>&-; exec 9>&-; kill $(cat V.pid) || true" + + # Start in the background to ensure we adhere strictly to (blocking) + # pipes writing sequence. We want to assume that the write below could + # block, e.g. if fast-import blocks writing its own output to &9 + # because there is no reader on &9 yet. + ( + cat "$input_file" + echo "checkpoint" + echo "progress checkpoint" + ) >&8 & + + error=1 ;# assume the worst + while read output <&9 + do + if test "$output" = "progress checkpoint" + then + error=0 + break + fi + # otherwise ignore cruft + echo >&2 "cruft: $output" + done + + if test $error -eq 1 + then + false + fi +} + +background_import_still_running () { + if ! kill -0 "$(cat V.pid)" + then + echo >&2 "background fast-import terminated too early" + false + fi +} + +test_expect_success PIPE 'V: checkpoint helper does not get stuck with extra output' ' + cat >input <<-INPUT_END && + progress foo + progress bar + + INPUT_END + + background_import_then_checkpoint "" input && + background_import_still_running +' + +test_expect_success PIPE 'V: checkpoint updates refs after reset' ' + cat >input <<-\INPUT_END && + reset refs/heads/V + from refs/heads/U + + INPUT_END + + background_import_then_checkpoint "" input && + test "$(git rev-parse --verify V)" = "$(git rev-parse --verify U)" && + background_import_still_running +' + +test_expect_success PIPE 'V: checkpoint updates refs and marks after commit' ' + cat >input <<-INPUT_END && + commit refs/heads/V + mark :1 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data 0 + from refs/heads/U + + INPUT_END + + background_import_then_checkpoint "--export-marks=marks.actual" input && + + echo ":1 $(git rev-parse --verify V)" >marks.expected && + + test "$(git rev-parse --verify V^)" = "$(git rev-parse --verify U)" && + test_cmp marks.expected marks.actual && + background_import_still_running +' + +# Re-create the exact same commit, but on a different branch: no new object is +# created in the database, but the refs and marks still need to be updated. +test_expect_success PIPE 'V: checkpoint updates refs and marks after commit (no new objects)' ' + cat >input <<-INPUT_END && + commit refs/heads/V2 + mark :2 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data 0 + from refs/heads/U + + INPUT_END + + background_import_then_checkpoint "--export-marks=marks.actual" input && + + echo ":2 $(git rev-parse --verify V2)" >marks.expected && + + test "$(git rev-parse --verify V2)" = "$(git rev-parse --verify V)" && + test_cmp marks.expected marks.actual && + background_import_still_running +' + +test_expect_success PIPE 'V: checkpoint updates tags after tag' ' + cat >input <<-INPUT_END && + tag Vtag + from refs/heads/V + tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data 0 + + INPUT_END + + background_import_then_checkpoint "" input && + git show-ref -d Vtag && + background_import_still_running +' + test_done diff --git a/t/t9301-fast-import-notes.sh b/t/t9301-fast-import-notes.sh index 83acf68bc3..dadc70b7d5 100755 --- a/t/t9301-fast-import-notes.sh +++ b/t/t9301-fast-import-notes.sh @@ -483,6 +483,48 @@ test_expect_success 'verify that lots of notes trigger a fanout scheme' ' ' +# Create another notes tree from the one above +SP=" " +cat >>input <<INPUT_END +commit refs/heads/other_commits +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +commit #$(($num_commit + 1)) +COMMIT + +from refs/heads/many_commits +M 644 inline file +data <<EOF +file contents in commit #$(($num_commit + 1)) +EOF + +commit refs/notes/other_notes +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +data <<COMMIT +committing one more note on a tree imported from a previous notes tree +COMMIT + +M 040000 $(git log --no-walk --format=%T refs/notes/many_notes)$SP +N inline :$(($num_commit + 1)) +data <<EOF +note for commit #$(($num_commit + 1)) +EOF +INPUT_END + +test_expect_success 'verify that importing a notes tree respects the fanout scheme' ' + git fast-import <input && + + # None of the entries in the top-level notes tree should be a full SHA1 + git ls-tree --name-only refs/notes/other_notes | + while read path + do + if test $(expr length "$path") -ge 40 + then + return 1 + fi + done +' + cat >>expect_non-note1 << EOF This is not a note, but rather a regular file residing in a notes tree EOF diff --git a/t/t9302-fast-import-unpack-limit.sh b/t/t9302-fast-import-unpack-limit.sh new file mode 100755 index 0000000000..a04de14677 --- /dev/null +++ b/t/t9302-fast-import-unpack-limit.sh @@ -0,0 +1,105 @@ +#!/bin/sh +test_description='test git fast-import unpack limit' +. ./test-lib.sh + +test_expect_success 'create loose objects on import' ' + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/master + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + initial + COMMIT + + done + INPUT_END + + git -c fastimport.unpackLimit=2 fast-import --done <input && + git fsck --no-progress && + test $(find .git/objects/?? -type f | wc -l) -eq 2 && + test $(find .git/objects/pack -type f | wc -l) -eq 0 +' + +test_expect_success 'bigger packs are preserved' ' + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/master + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + incremental should create a pack + COMMIT + from refs/heads/master^0 + + commit refs/heads/branch + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + branch + COMMIT + + done + INPUT_END + + git -c fastimport.unpackLimit=2 fast-import --done <input && + git fsck --no-progress && + test $(find .git/objects/?? -type f | wc -l) -eq 2 && + test $(find .git/objects/pack -type f | wc -l) -eq 2 +' + +test_expect_success 'lookups after checkpoint works' ' + hello_id=$(echo hello | git hash-object --stdin -t blob) && + id="$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE" && + before=$(git rev-parse refs/heads/master^0) && + ( + cat <<-INPUT_END && + blob + mark :1 + data 6 + hello + + commit refs/heads/master + mark :2 + committer $id + data <<COMMIT + checkpoint after this + COMMIT + from refs/heads/master^0 + M 100644 :1 hello + + # pre-checkpoint + cat-blob :1 + cat-blob $hello_id + checkpoint + # post-checkpoint + cat-blob :1 + cat-blob $hello_id + INPUT_END + + n=0 && + from=$before && + while test x"$from" = x"$before" + do + if test $n -gt 30 + then + echo >&2 "checkpoint did not update branch" + exit 1 + else + n=$(($n + 1)) + fi && + sleep 1 && + from=$(git rev-parse refs/heads/master^0) + done && + cat <<-INPUT_END && + commit refs/heads/master + committer $id + data <<COMMIT + make sure from "unpacked sha1 reference" works, too + COMMIT + from $from + INPUT_END + echo done + ) | git -c fastimport.unpackLimit=100 fast-import --done && + test $(find .git/objects/?? -type f | wc -l) -eq 6 && + test $(find .git/objects/pack -type f | wc -l) -eq 2 +' + +test_done diff --git a/t/t9303-fast-import-compression.sh b/t/t9303-fast-import-compression.sh new file mode 100755 index 0000000000..856219f46a --- /dev/null +++ b/t/t9303-fast-import-compression.sh @@ -0,0 +1,67 @@ +#!/bin/sh + +test_description='compression setting of fast-import utility' +. ./test-lib.sh + +# This should be moved to test-lib.sh together with the +# copy in t0021 after both topics have graduated to 'master'. +file_size () { + perl -e 'print -s $ARGV[0]' "$1" +} + +import_large () { + ( + echo blob + echo "data <<EOD" + printf "%2000000s\n" "$*" + echo EOD + ) | git "$@" fast-import +} + +while read expect config +do + test_expect_success "fast-import (packed) with $config" ' + test_when_finished "rm -f .git/objects/pack/pack-*.*" && + test_when_finished "rm -rf .git/objects/??" && + import_large -c fastimport.unpacklimit=0 $config && + sz=$(file_size .git/objects/pack/pack-*.pack) && + case "$expect" in + small) test "$sz" -le 100000 ;; + large) test "$sz" -ge 100000 ;; + esac + ' +done <<\EOF +large -c core.compression=0 +small -c core.compression=9 +large -c core.compression=0 -c pack.compression=0 +large -c core.compression=9 -c pack.compression=0 +small -c core.compression=0 -c pack.compression=9 +small -c core.compression=9 -c pack.compression=9 +large -c pack.compression=0 +small -c pack.compression=9 +EOF + +while read expect config +do + test_expect_success "fast-import (loose) with $config" ' + test_when_finished "rm -f .git/objects/pack/pack-*.*" && + test_when_finished "rm -rf .git/objects/??" && + import_large -c fastimport.unpacklimit=9 $config && + sz=$(file_size .git/objects/??/????*) && + case "$expect" in + small) test "$sz" -le 100000 ;; + large) test "$sz" -ge 100000 ;; + esac + ' +done <<\EOF +large -c core.compression=0 +small -c core.compression=9 +large -c core.compression=0 -c core.loosecompression=0 +large -c core.compression=9 -c core.loosecompression=0 +small -c core.compression=0 -c core.loosecompression=9 +small -c core.compression=9 -c core.loosecompression=9 +large -c core.loosecompression=0 +small -c core.loosecompression=9 +EOF + +test_done diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index 66c8b0a371..d5679ffb87 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -43,20 +43,20 @@ test_expect_success 'fast-export | fast-import' ' MUSS=$(git rev-parse --verify muss) && mkdir new && git --git-dir=new/.git init && - git fast-export --all | + git fast-export --all >actual && (cd new && git fast-import && test $MASTER = $(git rev-parse --verify refs/heads/master) && test $REIN = $(git rev-parse --verify refs/tags/rein) && test $WER = $(git rev-parse --verify refs/heads/wer) && - test $MUSS = $(git rev-parse --verify refs/tags/muss)) + test $MUSS = $(git rev-parse --verify refs/tags/muss)) <actual ' test_expect_success 'fast-export master~2..master' ' - git fast-export master~2..master | - sed "s/master/partial/" | + git fast-export master~2..master >actual && + sed "s/master/partial/" actual | (cd new && git fast-import && test $MASTER != $(git rev-parse --verify refs/heads/partial) && @@ -70,15 +70,16 @@ test_expect_success 'iso-8859-1' ' git config i18n.commitencoding ISO8859-1 && # use author and committer name in ISO-8859-1 to match it. - . "$TEST_DIRECTORY"/t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && test_tick && echo rosten >file && git commit -s -m den file && - git fast-export wer^..wer | - sed "s/wer/i18n/" | + git fast-export wer^..wer >iso8859-1.fi && + sed "s/wer/i18n/" iso8859-1.fi | (cd new && git fast-import && - git cat-file commit i18n | grep "Ãéà óú") + git cat-file commit i18n >actual && + grep "Ãéà óú" actual) ' test_expect_success 'import/export-marks' ' @@ -87,20 +88,14 @@ test_expect_success 'import/export-marks' ' git fast-export --export-marks=tmp-marks HEAD && test -s tmp-marks && test_line_count = 3 tmp-marks && - test $( - git fast-export --import-marks=tmp-marks\ - --export-marks=tmp-marks HEAD | - grep ^commit | - wc -l) \ - -eq 0 && + git fast-export --import-marks=tmp-marks \ + --export-marks=tmp-marks HEAD >actual && + test $(grep ^commit actual | wc -l) -eq 0 && echo change > file && git commit -m "last commit" file && - test $( - git fast-export --import-marks=tmp-marks \ - --export-marks=tmp-marks HEAD | - grep ^commit\ | - wc -l) \ - -eq 1 && + git fast-export --import-marks=tmp-marks \ + --export-marks=tmp-marks HEAD >actual && + test $(grep ^commit\ actual | wc -l) -eq 1 && test_line_count = 4 tmp-marks ' @@ -163,7 +158,7 @@ test_expect_success 'setup submodule' ' git add file && git commit -m sub_initial ) && - git submodule add "`pwd`/sub" sub && + git submodule add "$(pwd)/sub" sub && git commit -m initial && test_tick && ( @@ -184,7 +179,7 @@ test_expect_success 'submodule fast-export | fast-import' ' rm -rf new && mkdir new && git --git-dir=new/.git init && - git fast-export --signed-tags=strip --all | + git fast-export --signed-tags=strip --all >actual && (cd new && git fast-import && test "$SUBENT1" = "$(git ls-tree refs/heads/master^ sub)" && @@ -192,7 +187,7 @@ test_expect_success 'submodule fast-export | fast-import' ' git checkout master && git submodule init && git submodule update && - cmp sub/file ../sub/file) + cmp sub/file ../sub/file) <actual ' @@ -234,7 +229,7 @@ test_expect_success 'fast-export -C -C | fast-import' ' mkdir new && git --git-dir=new/.git init && git fast-export -C -C --signed-tags=strip --all > output && - grep "^C file6 file7\$" output && + grep "^C file2 file4\$" output && cat output | (cd new && git fast-import && @@ -367,17 +362,19 @@ test_expect_success 'path limiting with import-marks does not lose unmodified fi echo more content >> file && test_tick && git commit -mnext file && - git fast-export --import-marks=marks simple -- file file0 | grep file0 + git fast-export --import-marks=marks simple -- file file0 >actual && + grep file0 actual ' test_expect_success 'full-tree re-shows unmodified files' ' git checkout -f simple && - test $(git fast-export --full-tree simple | grep -c file0) -eq 3 + git fast-export --full-tree simple >actual && + test $(grep -c file0 actual) -eq 3 ' test_expect_success 'set-up a few more tags for tag export tests' ' git checkout -f master && - HEAD_TREE=`git show -s --pretty=raw HEAD | grep tree | sed "s/tree //"` && + HEAD_TREE=$(git show -s --pretty=raw HEAD | grep tree | sed "s/tree //") && git tag tree_tag -m "tagging a tree" $HEAD_TREE && git tag -a tree_tag-obj -m "tagging a tree" $HEAD_TREE && git tag tag-obj_tag -m "tagging a tag" tree_tag-obj && @@ -422,7 +419,7 @@ test_expect_success 'directory becomes symlink' ' test_expect_success 'fast-export quotes pathnames' ' git init crazy-paths && (cd crazy-paths && - blob=`echo foo | git hash-object -w --stdin` && + blob=$(echo foo | git hash-object -w --stdin) && git update-index --add \ --cacheinfo 100644 $blob "$(printf "path with\\nnewline")" \ --cacheinfo 100644 $blob "path with \"quote\"" \ @@ -505,8 +502,8 @@ test_expect_success 'refs are updated even if no commits need to be exported' ' ' test_expect_success 'use refspec' ' - git fast-export --refspec refs/heads/master:refs/heads/foobar master | \ - grep "^commit " | sort | uniq > actual && + git fast-export --refspec refs/heads/master:refs/heads/foobar master >actual2 && + grep "^commit " actual2 | sort | uniq >actual && echo "commit refs/heads/foobar" > expected && test_cmp expected actual ' @@ -522,4 +519,23 @@ test_expect_success 'delete refspec' ' test_cmp expected actual ' +test_expect_success 'when using -C, do not declare copy when source of copy is also modified' ' + test_create_repo src && + echo a_line >src/file.txt && + git -C src add file.txt && + git -C src commit -m 1st_commit && + + cp src/file.txt src/file2.txt && + echo another_line >>src/file.txt && + git -C src add file.txt file2.txt && + git -C src commit -m 2nd_commit && + + test_create_repo dst && + git -C src fast-export --all -C >actual && + git -C dst fast-import <actual && + git -C src show >expected && + git -C dst show >actual && + test_cmp expected actual +' + test_done diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index 6146c3fec2..06742748e9 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -25,11 +25,11 @@ perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || { test_done } -WORKDIR=$(pwd) -SERVERDIR=$(pwd)/gitcvs.git +WORKDIR=$PWD +SERVERDIR=$PWD/gitcvs.git git_config="$SERVERDIR/config" CVSROOT=":fork:$SERVERDIR" -CVSWORK="$(pwd)/cvswork" +CVSWORK="$PWD/cvswork" CVS_SERVER=git-cvsserver export CVSROOT CVS_SERVER @@ -45,7 +45,8 @@ test_expect_success 'setup' ' touch secondrootfile && git add secondrootfile && git commit -m "second root") && - git pull secondroot master && + git fetch secondroot master && + git merge --allow-unrelated-histories FETCH_HEAD && git clone -q --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 && GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true && GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" && @@ -446,12 +447,10 @@ test_expect_success 'cvs update (-p)' ' git push gitcvs.git >/dev/null && cd cvswork && GIT_CONFIG="$git_config" cvs update && - rm -f failures && for i in merge no-lf empty really-empty; do - GIT_CONFIG="$git_config" cvs update -p "$i" >$i.out - test_cmp $i.out ../$i >>failures 2>&1 - done && - test -z "$(cat failures)" + GIT_CONFIG="$git_config" cvs update -p "$i" >$i.out && + test_cmp $i.out ../$i || return 1 + done ' cd "$WORKDIR" @@ -587,4 +586,52 @@ test_expect_success 'cvs annotate' ' test_cmp ../expect ../actual ' +#------------ +# running via git-shell +#------------ + +cd "$WORKDIR" + +test_expect_success 'create remote-cvs helper' ' + write_script remote-cvs <<-\EOF + exec git shell -c "cvs server" + EOF +' + +test_expect_success 'cvs server does not run with vanilla git-shell' ' + ( + cd cvswork && + CVS_SERVER=$WORKDIR/remote-cvs && + export CVS_SERVER && + test_must_fail cvs log merge + ) +' + +test_expect_success 'configure git shell to run cvs server' ' + mkdir "$HOME"/git-shell-commands && + + write_script "$HOME"/git-shell-commands/cvs <<-\EOF && + if ! test $# = 1 && test "$1" = "server" + then + echo >&2 "git-cvsserver only handles \"server\"" + exit 1 + fi + exec git cvsserver server + EOF + + # Should not be used, but part of the recommended setup + write_script "$HOME"/git-shell-commands/no-interactive-login <<-\EOF + echo Interactive login forbidden + EOF +' + +test_expect_success 'cvs server can run with recommended config' ' + ( + cd cvswork && + CVS_SERVER=$WORKDIR/remote-cvs && + export CVS_SERVER && + cvs log merge + ) +' + test_done diff --git a/t/t9401-git-cvsserver-crlf.sh b/t/t9401-git-cvsserver-crlf.sh index 5a4ed28e49..84787eee9a 100755 --- a/t/t9401-git-cvsserver-crlf.sh +++ b/t/t9401-git-cvsserver-crlf.sh @@ -74,11 +74,11 @@ perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || { } unset GIT_DIR GIT_CONFIG -WORKDIR=$(pwd) -SERVERDIR=$(pwd)/gitcvs.git +WORKDIR=$PWD +SERVERDIR=$PWD/gitcvs.git git_config="$SERVERDIR/config" CVSROOT=":fork:$SERVERDIR" -CVSWORK="$(pwd)/cvswork" +CVSWORK="$PWD/cvswork" CVS_SERVER=git-cvsserver export CVSROOT CVS_SERVER @@ -154,7 +154,7 @@ test_expect_success 'adding files' ' echo "more text" > src.c && GIT_CONFIG="$git_config" cvs -Q add src.c >cvs.log 2>&1 && marked_as . src.c "" && - echo "psuedo-binary" > temp.bin + echo "pseudo-binary" > temp.bin ) && GIT_CONFIG="$git_config" cvs -Q add subdir/temp.bin >cvs.log 2>&1 && marked_as subdir temp.bin "-kb" && diff --git a/t/t9402-git-cvsserver-refs.sh b/t/t9402-git-cvsserver-refs.sh index d00df08731..cf31ace667 100755 --- a/t/t9402-git-cvsserver-refs.sh +++ b/t/t9402-git-cvsserver-refs.sh @@ -82,11 +82,11 @@ perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || { } unset GIT_DIR GIT_CONFIG -WORKDIR=$(pwd) -SERVERDIR=$(pwd)/gitcvs.git +WORKDIR=$PWD +SERVERDIR=$PWD/gitcvs.git git_config="$SERVERDIR/config" CVSROOT=":fork:$SERVERDIR" -CVSWORK="$(pwd)/cvswork" +CVSWORK="$PWD/cvswork" CVS_SERVER=git-cvsserver export CVSROOT CVS_SERVER @@ -455,20 +455,20 @@ test_expect_success 'cvs up -r $(git rev-parse v1)' ' ' test_expect_success 'cvs diff -r v1 -u' ' - ( cd cvswork && cvs -f diff -r v1 -u ) >cvsDiff.out 2>cvs.log && + ( cd cvswork && cvs -f diff -r v1 -u >../cvsDiff.out 2>../cvs.log ) && test_must_be_empty cvsDiff.out && test_must_be_empty cvs.log ' test_expect_success 'cvs diff -N -r v2 -u' ' - ( cd cvswork && ! cvs -f diff -N -r v2 -u ) >cvsDiff.out 2>cvs.log && + ( cd cvswork && ! cvs -f diff -N -r v2 -u >../cvsDiff.out 2>../cvs.log ) && test_must_be_empty cvs.log && test -s cvsDiff.out && check_diff cvsDiff.out v2 v1 >check_diff.out 2>&1 ' test_expect_success 'cvs diff -N -r v2 -r v1.2' ' - ( cd cvswork && ! cvs -f diff -N -r v2 -r v1.2 -u ) >cvsDiff.out 2>cvs.log && + ( cd cvswork && ! cvs -f diff -N -r v2 -r v1.2 -u >../cvsDiff.out 2>../cvs.log ) && test_must_be_empty cvs.log && test -s cvsDiff.out && check_diff cvsDiff.out v2 v1.2 >check_diff.out 2>&1 @@ -487,7 +487,7 @@ test_expect_success 'apply early [cvswork3] diff to b3' ' ' test_expect_success 'check [cvswork3] diff' ' - ( cd cvswork3 && ! cvs -f diff -N -u ) >"$WORKDIR/cvsDiff.out" 2>cvs.log && + ( cd cvswork3 && ! cvs -f diff -N -u >"$WORKDIR/cvsDiff.out" 2>../cvs.log ) && test_must_be_empty cvs.log && test -s cvsDiff.out && test $(grep Index: cvsDiff.out | wc -l) = 3 && diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index e94b2f147a..cc8d463e01 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -519,7 +519,7 @@ test_expect_success \ test_expect_success \ 'encode(commit): utf8' \ - '. "$TEST_DIRECTORY"/t3901-utf8.txt && + '. "$TEST_DIRECTORY"/t3901/utf8.txt && test_when_finished "GIT_AUTHOR_NAME=\"A U Thor\"" && test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" && echo "UTF-8" >> file && @@ -529,7 +529,7 @@ test_expect_success \ test_expect_success \ 'encode(commit): iso-8859-1' \ - '. "$TEST_DIRECTORY"/t3901-8859-1.txt && + '. "$TEST_DIRECTORY"/t3901/8859-1.txt && test_when_finished "GIT_AUTHOR_NAME=\"A U Thor\"" && test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" && echo "ISO-8859-1" >> file && @@ -709,6 +709,14 @@ test_expect_success HIGHLIGHT \ git commit -m "Add test.sh" && gitweb_run "p=.git;a=blob;f=test.sh"' +test_expect_success HIGHLIGHT \ + 'syntax highlighting (highlighter language autodetection)' \ + 'git config gitweb.highlight yes && + echo "#!/usr/bin/perl" > test && + git add test && + git commit -m "Add test" && + gitweb_run "p=.git;a=blob;f=test"' + # ---------------------------------------------------------------------- # forks of projects diff --git a/t/t9501-gitweb-standalone-http-status.sh b/t/t9501-gitweb-standalone-http-status.sh index d3a5bac754..2a0ffed870 100755 --- a/t/t9501-gitweb-standalone-http-status.sh +++ b/t/t9501-gitweb-standalone-http-status.sh @@ -100,14 +100,14 @@ test_expect_success 'snapshots: bad tree-ish id (tagged object)' ' echo object > tag-object && git add tag-object && test_tick && git commit -m "Object to be tagged" && - git tag tagged-object `git hash-object tag-object` && + git tag tagged-object $(git hash-object tag-object) && gitweb_run "p=.git;a=snapshot;h=tagged-object;sf=tgz" && grep "400 - Object is not a tree-ish" gitweb.output ' test_debug 'cat gitweb.output' test_expect_success 'snapshots: good object id' ' - ID=`git rev-parse --verify HEAD` && + ID=$(git rev-parse --verify HEAD) && gitweb_run "p=.git;a=snapshot;h=$ID;sf=tgz" && grep "Status: 200 OK" gitweb.output ' @@ -173,7 +173,7 @@ test_expect_success DATE_PARSER 'modification: snapshot if-modified-since (unmod test_debug 'cat gitweb.headers' test_expect_success DATE_PARSER 'modification: tree snapshot' ' - ID=`git rev-parse --verify HEAD^{tree}` && + ID=$(git rev-parse --verify HEAD^{tree}) && HTTP_IF_MODIFIED_SINCE="Wed, 6 Apr 2005 22:14:13 +0000" && export HTTP_IF_MODIFIED_SINCE && test_when_finished "unset HTTP_IF_MODIFIED_SINCE" && diff --git a/t/t9600-cvsimport.sh b/t/t9600-cvsimport.sh index 4c384ff023..804ce3850f 100755 --- a/t/t9600-cvsimport.sh +++ b/t/t9600-cvsimport.sh @@ -3,6 +3,11 @@ test_description='git cvsimport basic tests' . ./lib-cvs.sh +if ! test_have_prereq NOT_ROOT; then + skip_all='When cvs is compiled with CVS_BADROOT commits as root fail' + test_done +fi + test_expect_success PERL 'setup cvsroot environment' ' CVSROOT=$(pwd)/cvsroot && export CVSROOT diff --git a/t/t9700/test.pl b/t/t9700/test.pl index 1140767b50..34cd01366f 100755 --- a/t/t9700/test.pl +++ b/t/t9700/test.pl @@ -17,6 +17,12 @@ BEGIN { use Cwd; use File::Basename; +sub adjust_dirsep { + my $path = shift; + $path =~ s{\\}{/}g; + return $path; +} + BEGIN { use_ok('Git') } # set up @@ -33,7 +39,7 @@ is($r->config_int("test.int"), 2048, "config_int: integer"); is($r->config_int("test.nonexistent"), undef, "config_int: nonexistent"); ok($r->config_bool("test.booltrue"), "config_bool: true"); ok(!$r->config_bool("test.boolfalse"), "config_bool: false"); -is($r->config_path("test.path"), $r->config("test.pathexpanded"), +is(adjust_dirsep($r->config_path("test.path")), $r->config("test.pathexpanded"), "config_path: ~/foo expansion"); is_deeply([$r->config_path("test.pathmulti")], ["foo", "bar"], "config_path: multiple values"); @@ -127,6 +133,13 @@ close TEMPFILE3; unlink $tmpfile3; chdir($abs_repo_dir); +# unquoting paths +is(Git::unquote_path('abc'), 'abc', 'unquote unquoted path'); +is(Git::unquote_path('"abc def"'), 'abc def', 'unquote simple quoted path'); +is(Git::unquote_path('"abc\"\\\\ \a\b\t\n\v\f\r\001\040"'), + "abc\"\\ \x07\x08\x09\x0a\x0b\x0c\x0d\x01 ", + 'unquote escape sequences'); + printf "1..%d\n", Test::More->builder->current_test; my $is_passing = eval { Test::More->is_passing }; diff --git a/t/t9800-git-p4-basic.sh b/t/t9800-git-p4-basic.sh index 5b562122a1..4849edc4ef 100755 --- a/t/t9800-git-p4-basic.sh +++ b/t/t9800-git-p4-basic.sh @@ -131,6 +131,64 @@ test_expect_success 'clone two dirs, @all, conflicting files' ' ) ' +test_expect_success 'clone two dirs, each edited by submit, single git commit' ' + ( + cd "$cli" && + echo sub1/f4 >sub1/f4 && + p4 add sub1/f4 && + echo sub2/f4 >sub2/f4 && + p4 add sub2/f4 && + p4 submit -d "sub1/f4 and sub2/f4" + ) && + git p4 clone --dest="$git" //depot/sub1@all //depot/sub2@all && + test_when_finished cleanup_git && + ( + cd "$git" && + git ls-files >lines && + test_line_count = 4 lines && + git log --oneline p4/master >lines && + test_line_count = 5 lines + ) +' + +revision_ranges="2000/01/01,#head \ + 1,2080/01/01 \ + 2000/01/01,2080/01/01 \ + 2000/01/01,1000 \ + 1,1000" + +test_expect_success 'clone using non-numeric revision ranges' ' + test_when_finished cleanup_git && + for r in $revision_ranges + do + rm -fr "$git" && + test ! -d "$git" && + git p4 clone --dest="$git" //depot@$r && + ( + cd "$git" && + git ls-files >lines && + test_line_count = 8 lines + ) + done +' + +test_expect_success 'clone with date range, excluding some changes' ' + test_when_finished cleanup_git && + before=$(date +%Y/%m/%d:%H:%M:%S) && + sleep 2 && + ( + cd "$cli" && + :>date_range_test && + p4 add date_range_test && + p4 submit -d "Adding file" + ) && + git p4 clone --dest="$git" //depot@1,$before && + ( + cd "$git" && + test_path_is_missing date_range_test + ) +' + test_expect_success 'exit when p4 fails to produce marshaled output' ' mkdir badp4dir && test_when_finished "rm badp4dir/p4 && rmdir badp4dir" && @@ -203,6 +261,42 @@ test_expect_success 'unresolvable host in P4PORT should display error' ' ) ' +test_expect_success 'submit from detached head' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + git checkout p4/master && + >detached_head_test && + git add detached_head_test && + git commit -m "add detached_head" && + git config git-p4.skipSubmitEdit true && + git p4 submit && + git p4 rebase && + git log p4/master | grep detached_head + ) +' + +test_expect_success 'submit from worktree' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + git worktree add ../worktree-test + ) && + ( + cd "$git/../worktree-test" && + test_commit "worktree-commit" && + git config git-p4.skipSubmitEdit true && + git p4 submit + ) && + ( + cd "$cli" && + p4 sync && + test_path_is_file worktree-commit.t + ) +' + test_expect_success 'kill p4d' ' kill_p4d ' diff --git a/t/t9801-git-p4-branch.sh b/t/t9801-git-p4-branch.sh index 0aafd03334..6a86d6996b 100755 --- a/t/t9801-git-p4-branch.sh +++ b/t/t9801-git-p4-branch.sh @@ -300,7 +300,7 @@ test_expect_success 'git p4 clone complex branches' ' test_path_is_file file2 && test_path_is_file file3 && ! grep update file2 && - test_path_is_missing .git/git-p4-tmp + test_must_fail git show-ref --verify refs/git-p4-tmp ) ' @@ -352,7 +352,7 @@ test_expect_success 'git p4 sync changes to two branches in the same changelist' test_path_is_file file2 && test_path_is_file file3 && ! grep update file2 && - test_path_is_missing .git/git-p4-tmp + test_must_fail git show-ref --verify refs/git-p4-tmp ) ' diff --git a/t/t9802-git-p4-filetype.sh b/t/t9802-git-p4-filetype.sh index 66d3fc91a7..1fc9b33aeb 100755 --- a/t/t9802-git-p4-filetype.sh +++ b/t/t9802-git-p4-filetype.sh @@ -223,12 +223,12 @@ build_gendouble() { import sys import struct - s = struct.pack(">LL18s", + s = struct.pack(b">LL18s", 0x00051607, # AppleDouble 0x00020000, # version 2 - "" # pad to 26 bytes + b"" # pad to 26 bytes ) - sys.stdout.write(s) + getattr(sys.stdout, 'buffer', sys.stdout).write(s) EOF } @@ -237,7 +237,7 @@ test_expect_success 'ignore apple' ' build_gendouble && ( cd "$cli" && - test-genrandom apple 1024 >double.png && + test-tool genrandom apple 1024 >double.png && "$PYTHON_PATH" "$TRASH_DIRECTORY/gendouble.py" >%double.png && p4 add -t apple double.png && p4 submit -d appledouble diff --git a/t/t9803-git-p4-shell-metachars.sh b/t/t9803-git-p4-shell-metachars.sh index d950c7d665..d5c3675100 100755 --- a/t/t9803-git-p4-shell-metachars.sh +++ b/t/t9803-git-p4-shell-metachars.sh @@ -28,7 +28,7 @@ test_expect_success 'shell metachars in filenames' ' echo f2 >"file with spaces" && git add "file with spaces" && git commit -m "add files" && - P4EDITOR="test-chmtime +5" git p4 submit + P4EDITOR="test-tool chmtime +5" git p4 submit ) && ( cd "$cli" && @@ -47,7 +47,7 @@ test_expect_success 'deleting with shell metachars' ' git rm foo\$bar && git rm file\ with\ spaces && git commit -m "remove files" && - P4EDITOR="test-chmtime +5" git p4 submit + P4EDITOR="test-tool chmtime +5" git p4 submit ) && ( cd "$cli" && diff --git a/t/t9806-git-p4-options.sh b/t/t9806-git-p4-options.sh index 254d428b73..1ab76c4246 100755 --- a/t/t9806-git-p4-options.sh +++ b/t/t9806-git-p4-options.sh @@ -269,6 +269,38 @@ test_expect_success 'submit works with two branches' ' ) ' +test_expect_success 'use --git-dir option and GIT_DIR' ' + test_when_finished cleanup_git && + git p4 clone //depot --destination="$git" && + ( + cd "$git" && + git config git-p4.skipSubmitEdit true && + test_commit first-change && + git p4 submit --git-dir "$git" + ) && + ( + cd "$cli" && + p4 sync && + test_path_is_file first-change.t && + echo "cli_file" >cli_file.t && + p4 add cli_file.t && + p4 submit -d "cli change" + ) && + (git --git-dir "$git" p4 sync) && + (cd "$git" && git checkout -q p4/master) && + test_path_is_file "$git"/cli_file.t && + ( + cd "$cli" && + echo "cli_file2" >cli_file2.t && + p4 add cli_file2.t && + p4 submit -d "cli change2" + ) && + (GIT_DIR="$git" git p4 sync) && + (cd "$git" && git checkout -q p4/master) && + test_path_is_file "$git"/cli_file2.t +' + + test_expect_success 'kill p4d' ' kill_p4d ' diff --git a/t/t9807-git-p4-submit.sh b/t/t9807-git-p4-submit.sh index 1f74a88385..71cae2874d 100755 --- a/t/t9807-git-p4-submit.sh +++ b/t/t9807-git-p4-submit.sh @@ -139,6 +139,22 @@ test_expect_success 'submit with master branch name from argv' ' ) ' +test_expect_success 'allow submit from branch with same revision but different name' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + test_commit "file8" && + git checkout -b branch1 && + git checkout -b branch2 && + git config git-p4.skipSubmitEdit true && + git config git-p4.allowSubmit "branch1" && + test_must_fail git p4 submit && + git checkout branch1 && + git p4 submit + ) +' + # # Basic submit tests, the five handled cases # @@ -389,7 +405,7 @@ test_expect_success 'description with Jobs section and bogus following text' ' ( cd "$cli" && p4 revert desc6 && - rm desc6 + rm -f desc6 ) ' @@ -413,6 +429,79 @@ test_expect_success 'submit --prepare-p4-only' ' ) ' +test_expect_success 'submit --shelve' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$cli" && + p4 revert ... && + cd "$git" && + git config git-p4.skipSubmitEdit true && + test_commit "shelveme1" && + git p4 submit --origin=HEAD^ && + + echo 654321 >shelveme2.t && + echo 123456 >>shelveme1.t && + git add shelveme* && + git commit -m"shelvetest" && + git p4 submit --shelve --origin=HEAD^ && + + test_path_is_file shelveme1.t && + test_path_is_file shelveme2.t + ) && + ( + cd "$cli" && + change=$(p4 -G changes -s shelved -m 1 //depot/... | \ + marshal_dump change) && + p4 describe -S $change | grep shelveme2 && + p4 describe -S $change | grep 123456 && + test_path_is_file shelveme1.t && + test_path_is_missing shelveme2.t + ) +' + +make_shelved_cl() { + test_commit "$1" >/dev/null && + git p4 submit --origin HEAD^ --shelve >/dev/null && + p4 -G changes -s shelved -m 1 | marshal_dump change +} + +# Update existing shelved changelists + +test_expect_success 'submit --update-shelve' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$cli" && + p4 revert ... && + cd "$git" && + git config git-p4.skipSubmitEdit true && + shelved_cl0=$(make_shelved_cl "shelved-change-0") && + echo shelved_cl0=$shelved_cl0 && + shelved_cl1=$(make_shelved_cl "shelved-change-1") && + + echo "updating shelved change lists $shelved_cl0 and $shelved_cl1" && + + echo "updated-line" >>shelf.t && + echo added-file.t >added-file.t && + git add shelf.t added-file.t && + git rm -f shelved-change-1.t && + git commit --amend -C HEAD && + git show --stat HEAD && + git p4 submit -v --origin HEAD~2 --update-shelve $shelved_cl0 --update-shelve $shelved_cl1 && + echo "done git p4 submit" + ) && + ( + cd "$cli" && + change=$(p4 -G changes -s shelved -m 1 //depot/... | \ + marshal_dump change) && + p4 unshelve -c $change -s $change && + grep -q updated-line shelf.t && + p4 describe -S $change | grep added-file.t && + test_path_is_missing shelved-change-1.t + ) +' + test_expect_success 'kill p4d' ' kill_p4d ' diff --git a/t/t9811-git-p4-label-import.sh b/t/t9811-git-p4-label-import.sh index 095238fffe..decb66ba30 100755 --- a/t/t9811-git-p4-label-import.sh +++ b/t/t9811-git-p4-label-import.sh @@ -214,6 +214,51 @@ test_expect_success 'use git config to enable import/export of tags' ' ) ' +p4_head_revision() { + p4 changes -m 1 "$@" | awk '{print $2}' +} + +# Importing a label that references a P4 commit that +# has not been seen. The presence of a label on a commit +# we haven't seen should not cause git-p4 to fail. It should +# merely skip that label, and still import other labels. +test_expect_success 'importing labels with missing revisions' ' + test_when_finished cleanup_git && + ( + rm -fr "$cli" "$git" && + mkdir "$cli" && + P4CLIENT=missing-revision && + client_view "//depot/missing-revision/... //missing-revision/..." && + cd "$cli" && + >f1 && p4 add f1 && p4 submit -d "start" && + + p4 tag -l TAG_S0 ... && + + >f2 && p4 add f2 && p4 submit -d "second" && + + startrev=$(p4_head_revision //depot/missing-revision/...) && + + >f3 && p4 add f3 && p4 submit -d "third" && + + p4 edit f2 && date >f2 && p4 submit -d "change" f2 && + + endrev=$(p4_head_revision //depot/missing-revision/...) && + + p4 tag -l TAG_S1 ... && + + # we should skip TAG_S0 since it is before our startpoint, + # but pick up TAG_S1. + + git p4 clone --dest="$git" --import-labels -v \ + //depot/missing-revision/...@$startrev,$endrev && + ( + cd "$git" && + git rev-parse TAG_S1 && + ! git rev-parse TAG_S0 + ) + ) +' + test_expect_success 'kill p4d' ' kill_p4d diff --git a/t/t9813-git-p4-preserve-users.sh b/t/t9813-git-p4-preserve-users.sh index 0fe2312807..783c6ad165 100755 --- a/t/t9813-git-p4-preserve-users.sh +++ b/t/t9813-git-p4-preserve-users.sh @@ -53,7 +53,7 @@ test_expect_success 'preserve users' ' git commit --author "Alice <alice@example.com>" -m "a change by alice" file1 && git commit --author "Bob <bob@example.com>" -m "a change by bob" file2 && git config git-p4.skipSubmitEditCheck true && - P4EDITOR="test-chmtime +5" P4USER=alice P4PASSWD=secret && + P4EDITOR="test-tool chmtime +5" P4USER=alice P4PASSWD=secret && export P4EDITOR P4USER P4PASSWD && git p4 commit --preserve-user && p4_check_commit_author file1 alice && @@ -71,7 +71,7 @@ test_expect_success 'refuse to preserve users without perms' ' git config git-p4.skipSubmitEditCheck true && echo "username-noperms: a change by alice" >>file1 && git commit --author "Alice <alice@example.com>" -m "perms: a change by alice" file1 && - P4EDITOR="test-chmtime +5" P4USER=bob P4PASSWD=secret && + P4EDITOR="test-tool chmtime +5" P4USER=bob P4PASSWD=secret && export P4EDITOR P4USER P4PASSWD && test_must_fail git p4 commit --preserve-user && ! git diff --exit-code HEAD..p4/master @@ -89,7 +89,7 @@ test_expect_success 'preserve user where author is unknown to p4' ' git commit --author "Bob <bob@example.com>" -m "preserve: a change by bob" file1 && echo "username-unknown: a change by charlie" >>file1 && git commit --author "Charlie <charlie@example.com>" -m "preserve: a change by charlie" file1 && - P4EDITOR="test-chmtime +5" P4USER=alice P4PASSWD=secret && + P4EDITOR="test-tool chmtime +5" P4USER=alice P4PASSWD=secret && export P4EDITOR P4USER P4PASSWD && test_must_fail git p4 commit --preserve-user && ! git diff --exit-code HEAD..p4/master && @@ -118,21 +118,21 @@ test_expect_success 'not preserving user with mixed authorship' ' make_change_by_user usernamefile3 Derek derek@example.com && P4EDITOR=cat P4USER=alice P4PASSWD=secret && export P4EDITOR P4USER P4PASSWD && - git p4 commit |\ - grep "git author derek@example.com does not match" && + git p4 commit >actual && + grep "git author derek@example.com does not match" actual && make_change_by_user usernamefile3 Charlie charlie@example.com && - git p4 commit |\ - grep "git author charlie@example.com does not match" && + git p4 commit >actual && + grep "git author charlie@example.com does not match" actual && make_change_by_user usernamefile3 alice alice@example.com && - git p4 commit |\ - test_must_fail grep "git author.*does not match" && + git p4 commit >actual && + ! grep "git author.*does not match" actual && git config git-p4.skipUserNameCheck true && make_change_by_user usernamefile3 Charlie charlie@example.com && - git p4 commit |\ - test_must_fail grep "git author.*does not match" && + git p4 commit >actual && + ! grep "git author.*does not match" actual && p4_check_commit_author usernamefile3 alice ) diff --git a/t/t9814-git-p4-rename.sh b/t/t9814-git-p4-rename.sh index c89992cf95..e7e0268e98 100755 --- a/t/t9814-git-p4-rename.sh +++ b/t/t9814-git-p4-rename.sh @@ -141,7 +141,7 @@ test_expect_success 'detect copies' ' git diff-tree -r -C HEAD && git p4 submit && p4 filelog //depot/file8 && - p4 filelog //depot/file8 | test_must_fail grep -q "branch from" && + ! p4 filelog //depot/file8 | grep -q "branch from" && echo "file9" >>file2 && git commit -a -m "Differentiate file2" && @@ -154,7 +154,7 @@ test_expect_success 'detect copies' ' git config git-p4.detectCopies true && git p4 submit && p4 filelog //depot/file9 && - p4 filelog //depot/file9 | test_must_fail grep -q "branch from" && + ! p4 filelog //depot/file9 | grep -q "branch from" && echo "file10" >>file2 && git commit -a -m "Differentiate file2" && @@ -202,7 +202,7 @@ test_expect_success 'detect copies' ' git config git-p4.detectCopies $(($level + 2)) && git p4 submit && p4 filelog //depot/file12 && - p4 filelog //depot/file12 | test_must_fail grep -q "branch from" && + ! p4 filelog //depot/file12 | grep -q "branch from" && echo "file13" >>file2 && git commit -a -m "Differentiate file2" && diff --git a/t/t9815-git-p4-submit-fail.sh b/t/t9815-git-p4-submit-fail.sh index 4cff6a760f..37b42d03a2 100755 --- a/t/t9815-git-p4-submit-fail.sh +++ b/t/t9815-git-p4-submit-fail.sh @@ -417,11 +417,8 @@ test_expect_success 'cleanup chmod after submit cancel' ' ! p4 fstat -T action text && test_path_is_file text+x && ! p4 fstat -T action text+x && - if test_have_prereq !CYGWIN - then - stat --format=%A text | egrep ^-r-- && - stat --format=%A text+x | egrep ^-r-x - fi + ls -l text | egrep ^-r-- && + ls -l text+x | egrep ^-r-x ) ' diff --git a/t/t9818-git-p4-block.sh b/t/t9818-git-p4-block.sh index 153b20afbd..8840a183ac 100755 --- a/t/t9818-git-p4-block.sh +++ b/t/t9818-git-p4-block.sh @@ -8,18 +8,34 @@ test_expect_success 'start p4d' ' start_p4d ' -test_expect_success 'Create a repo with ~100 changes' ' +create_restricted_group() { + p4 group -i <<-EOF + Group: restricted + MaxResults: 7 + MaxScanRows: 40 + Users: author + EOF +} + +test_expect_success 'Create group with limited maxrows' ' + create_restricted_group +' + +test_expect_success 'Create a repo with many changes' ' ( - cd "$cli" && + client_view "//depot/included/... //client/included/..." \ + "//depot/excluded/... //client/excluded/..." && + mkdir -p "$cli/included" "$cli/excluded" && + cd "$cli/included" && >file.txt && p4 add file.txt && p4 submit -d "Add file.txt" && - for i in $(test_seq 0 9) + for i in $(test_seq 0 5) do >outer$i.txt && p4 add outer$i.txt && p4 submit -d "Adding outer$i.txt" && - for j in $(test_seq 0 9) + for j in $(test_seq 0 5) do p4 edit file.txt && echo $i$j >file.txt && @@ -29,34 +45,99 @@ test_expect_success 'Create a repo with ~100 changes' ' ) ' +test_expect_success 'Default user cannot fetch changes' ' + ! p4 changes -m 1 //depot/... +' + test_expect_success 'Clone the repo' ' - git p4 clone --dest="$git" --changes-block-size=10 --verbose //depot@all + git p4 clone --dest="$git" --changes-block-size=7 --verbose //depot/included@all ' test_expect_success 'All files are present' ' echo file.txt >expected && test_write_lines outer0.txt outer1.txt outer2.txt outer3.txt outer4.txt >>expected && - test_write_lines outer5.txt outer6.txt outer7.txt outer8.txt outer9.txt >>expected && + test_write_lines outer5.txt >>expected && ls "$git" >current && test_cmp expected current ' test_expect_success 'file.txt is correct' ' - echo 99 >expected && + echo 55 >expected && test_cmp expected "$git/file.txt" ' test_expect_success 'Correct number of commits' ' (cd "$git" && git log --oneline) >log && - test_line_count = 111 log + wc -l log && + test_line_count = 43 log ' test_expect_success 'Previous version of file.txt is correct' ' (cd "$git" && git checkout HEAD^^) && - echo 97 >expected && + echo 53 >expected && test_cmp expected "$git/file.txt" ' +# Test git-p4 sync, with some files outside the client specification. + +p4_add_file() { + (cd "$cli" && + >$1 && + p4 add $1 && + p4 submit -d "Added file $1" $1 + ) +} + +test_expect_success 'Add some more files' ' + for i in $(test_seq 0 10) + do + p4_add_file "included/x$i" && + p4_add_file "excluded/x$i" + done && + for i in $(test_seq 0 10) + do + p4_add_file "excluded/y$i" + done +' + +# This should pick up the 10 new files in "included", but not be confused +# by the additional files in "excluded" +test_expect_success 'Syncing files' ' + ( + cd "$git" && + git p4 sync --changes-block-size=7 && + git checkout p4/master && + ls -l x* > log && + test_line_count = 11 log + ) +' + +# Handling of multiple depot paths: +# git p4 clone //depot/pathA //depot/pathB +# +test_expect_success 'Create a repo with multiple depot paths' ' + client_view "//depot/pathA/... //client/pathA/..." \ + "//depot/pathB/... //client/pathB/..." && + mkdir -p "$cli/pathA" "$cli/pathB" && + for p in pathA pathB + do + for i in $(test_seq 1 10) + do + p4_add_file "$p/file$p$i" + done + done +' + +test_expect_success 'Clone repo with multiple depot paths' ' + ( + cd "$git" && + git p4 clone --changes-block-size=4 //depot/pathA@all //depot/pathB@all \ + --destination=dest && + ls -1 dest >log && + test_line_count = 20 log + ) +' + test_expect_success 'kill p4d' ' kill_p4d ' diff --git a/t/t9819-git-p4-case-folding.sh b/t/t9819-git-p4-case-folding.sh index 78f1d0f92d..d808c008c1 100755 --- a/t/t9819-git-p4-case-folding.sh +++ b/t/t9819-git-p4-case-folding.sh @@ -4,6 +4,12 @@ test_description='interaction with P4 case-folding' . ./lib-git-p4.sh +if test_have_prereq CASE_INSENSITIVE_FS +then + skip_all='skipping P4 case-folding tests; case insensitive file system detected' + test_done +fi + test_expect_success 'start p4d with case folding enabled' ' start_p4d -C1 ' diff --git a/t/t9820-git-p4-editor-handling.sh b/t/t9820-git-p4-editor-handling.sh index 6dc6df032e..3c22f74bd4 100755 --- a/t/t9820-git-p4-editor-handling.sh +++ b/t/t9820-git-p4-editor-handling.sh @@ -26,7 +26,7 @@ test_expect_success 'EDITOR with options' ' cd "$git" && echo change >file1 && git commit -m "change" file1 && - P4EDITOR=": >\"$git/touched\" && test-chmtime +5" git p4 submit && + P4EDITOR=": >\"$git/touched\" && test-tool chmtime +5" git p4 submit && test_path_is_file "$git/touched" ) ' diff --git a/t/t9821-git-p4-path-variations.sh b/t/t9821-git-p4-path-variations.sh new file mode 100755 index 0000000000..81e46acfa8 --- /dev/null +++ b/t/t9821-git-p4-path-variations.sh @@ -0,0 +1,200 @@ +#!/bin/sh + +test_description='Clone repositories with path case variations' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d with case folding enabled' ' + start_p4d -C1 +' + +test_expect_success 'Create a repo with path case variations' ' + client_view "//depot/... //client/..." && + ( + cd "$cli" && + + mkdir -p Path/to && + >Path/to/File2.txt && + p4 add Path/to/File2.txt && + p4 submit -d "Add file2" && + rm -rf Path && + + mkdir -p path/TO && + >path/TO/file1.txt && + p4 add path/TO/file1.txt && + p4 submit -d "Add file1" && + rm -rf path && + + mkdir -p path/to && + >path/to/file3.txt && + p4 add path/to/file3.txt && + p4 submit -d "Add file3" && + rm -rf path && + + mkdir -p x-outside-spec && + >x-outside-spec/file4.txt && + p4 add x-outside-spec/file4.txt && + p4 submit -d "Add file4" && + rm -rf x-outside-spec + ) +' + +test_expect_success 'Clone root' ' + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config core.ignorecase false && + git p4 clone --use-client-spec --destination="$git" //depot && + # This method is used instead of "test -f" to ensure the case is + # checked even if the test is executed on case-insensitive file systems. + # All files are there as expected although the path cases look random. + cat >expect <<-\EOF && + Path/to/File2.txt + path/TO/file1.txt + path/to/file3.txt + x-outside-spec/file4.txt + EOF + git ls-files >actual && + test_cmp expect actual + ) +' + +test_expect_success 'Clone root (ignorecase)' ' + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config core.ignorecase true && + git p4 clone --use-client-spec --destination="$git" //depot && + # This method is used instead of "test -f" to ensure the case is + # checked even if the test is executed on case-insensitive file systems. + # All files are there as expected although the path cases look random. + cat >expect <<-\EOF && + path/TO/File2.txt + path/TO/file1.txt + path/TO/file3.txt + x-outside-spec/file4.txt + EOF + git ls-files >actual && + test_cmp expect actual + ) +' + +test_expect_success 'Clone root and ignore one file' ' + client_view \ + "//depot/... //client/..." \ + "-//depot/path/TO/file1.txt //client/path/TO/file1.txt" && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config core.ignorecase false && + git p4 clone --use-client-spec --destination="$git" //depot && + # We ignore one file in the client spec and all path cases change from + # "TO" to "to"! + cat >expect <<-\EOF && + Path/to/File2.txt + path/to/file3.txt + x-outside-spec/file4.txt + EOF + git ls-files >actual && + test_cmp expect actual + ) +' + +test_expect_success 'Clone root and ignore one file (ignorecase)' ' + client_view \ + "//depot/... //client/..." \ + "-//depot/path/TO/file1.txt //client/path/TO/file1.txt" && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config core.ignorecase true && + git p4 clone --use-client-spec --destination="$git" //depot && + # We ignore one file in the client spec and all path cases change from + # "TO" to "to"! + cat >expect <<-\EOF && + Path/to/File2.txt + Path/to/file3.txt + x-outside-spec/file4.txt + EOF + git ls-files >actual && + test_cmp expect actual + ) +' + +test_expect_success 'Clone path' ' + client_view "//depot/Path/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config core.ignorecase false && + git p4 clone --use-client-spec --destination="$git" //depot && + cat >expect <<-\EOF && + to/File2.txt + EOF + git ls-files >actual && + test_cmp expect actual + ) +' + +test_expect_success 'Clone path (ignorecase)' ' + client_view "//depot/Path/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config core.ignorecase true && + git p4 clone --use-client-spec --destination="$git" //depot && + cat >expect <<-\EOF && + TO/File2.txt + TO/file1.txt + TO/file3.txt + EOF + git ls-files >actual && + test_cmp expect actual + ) +' + +# It looks like P4 determines the path case based on the first file in +# lexicographical order. Please note the lower case "to" directory for all +# files triggered through the addition of "File0.txt". +test_expect_success 'Add a new file and clone path with new file (ignorecase)' ' + client_view "//depot/... //client/..." && + ( + cd "$cli" && + mkdir -p Path/to && + >Path/to/File0.txt && + p4 add Path/to/File0.txt && + p4 submit -d "Add file" && + rm -rf Path + ) && + + client_view "//depot/Path/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config core.ignorecase true && + git p4 clone --use-client-spec --destination="$git" //depot && + cat >expect <<-\EOF && + to/File0.txt + to/File2.txt + to/file1.txt + to/file3.txt + EOF + git ls-files >actual && + test_cmp expect actual + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9822-git-p4-path-encoding.sh b/t/t9822-git-p4-path-encoding.sh new file mode 100755 index 0000000000..c78477c19b --- /dev/null +++ b/t/t9822-git-p4-path-encoding.sh @@ -0,0 +1,74 @@ +#!/bin/sh + +test_description='Clone repositories with non ASCII paths' + +. ./lib-git-p4.sh + +UTF8_ESCAPED="a-\303\244_o-\303\266_u-\303\274.txt" +ISO8859_ESCAPED="a-\344_o-\366_u-\374.txt" + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'Create a repo containing iso8859-1 encoded paths' ' + ( + cd "$cli" && + ISO8859="$(printf "$ISO8859_ESCAPED")" && + echo content123 >"$ISO8859" && + p4 add "$ISO8859" && + p4 submit -d "test commit" + ) +' + +test_expect_failure 'Clone auto-detects depot with iso8859-1 paths' ' + git p4 clone --destination="$git" //depot && + test_when_finished cleanup_git && + ( + cd "$git" && + UTF8="$(printf "$UTF8_ESCAPED")" && + echo "$UTF8" >expect && + git -c core.quotepath=false ls-files >actual && + test_cmp expect actual + ) +' + +test_expect_success 'Clone repo containing iso8859-1 encoded paths with git-p4.pathEncoding' ' + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config git-p4.pathEncoding iso8859-1 && + git p4 clone --use-client-spec --destination="$git" //depot && + UTF8="$(printf "$UTF8_ESCAPED")" && + echo "$UTF8" >expect && + git -c core.quotepath=false ls-files >actual && + test_cmp expect actual && + + echo content123 >expect && + cat "$UTF8" >actual && + test_cmp expect actual + ) +' + +test_expect_success 'Delete iso8859-1 encoded paths and clone' ' + ( + cd "$cli" && + ISO8859="$(printf "$ISO8859_ESCAPED")" && + p4 delete "$ISO8859" && + p4 submit -d "remove file" + ) && + git p4 clone --destination="$git" //depot@all && + test_when_finished cleanup_git && + ( + cd "$git" && + git -c core.quotepath=false ls-files >actual && + test_must_be_empty actual + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9823-git-p4-mock-lfs.sh b/t/t9823-git-p4-mock-lfs.sh new file mode 100755 index 0000000000..1f2dc369bf --- /dev/null +++ b/t/t9823-git-p4-mock-lfs.sh @@ -0,0 +1,192 @@ +#!/bin/sh + +test_description='Clone repositories and store files in Mock LFS' + +. ./lib-git-p4.sh + +test_file_is_not_in_mock_lfs () { + FILE="$1" && + CONTENT="$2" && + echo "$CONTENT" >expect_content && + test_path_is_file "$FILE" && + test_cmp expect_content "$FILE" +} + +test_file_is_in_mock_lfs () { + FILE="$1" && + CONTENT="$2" && + LOCAL_STORAGE=".git/mock-storage/local/$CONTENT" && + SERVER_STORAGE=".git/mock-storage/remote/$CONTENT" && + echo "pointer-$CONTENT" >expect_pointer && + echo "$CONTENT" >expect_content && + test_path_is_file "$FILE" && + test_path_is_file "$LOCAL_STORAGE" && + test_path_is_file "$SERVER_STORAGE" && + test_cmp expect_pointer "$FILE" && + test_cmp expect_content "$LOCAL_STORAGE" && + test_cmp expect_content "$SERVER_STORAGE" +} + +test_file_is_deleted_in_mock_lfs () { + FILE="$1" && + CONTENT="$2" && + LOCAL_STORAGE=".git/mock-storage/local/$CONTENT" && + SERVER_STORAGE=".git/mock-storage/remote/$CONTENT" && + echo "pointer-$CONTENT" >expect_pointer && + echo "$CONTENT" >expect_content && + test_path_is_missing "$FILE" && + test_path_is_file "$LOCAL_STORAGE" && + test_path_is_file "$SERVER_STORAGE" && + test_cmp expect_content "$LOCAL_STORAGE" && + test_cmp expect_content "$SERVER_STORAGE" +} + +test_file_count_in_dir () { + DIR="$1" && + EXPECTED_COUNT="$2" && + find "$DIR" -type f >actual && + test_line_count = $EXPECTED_COUNT actual +} + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'Create repo with binary files' ' + client_view "//depot/... //client/..." && + ( + cd "$cli" && + + echo "content 1 txt 23 bytes" >file1.txt && + p4 add file1.txt && + echo "content 2-3 bin 25 bytes" >file2.dat && + p4 add file2.dat && + p4 submit -d "Add text and binary file" && + + mkdir "path with spaces" && + echo "content 2-3 bin 25 bytes" >"path with spaces/file3.bin" && + p4 add "path with spaces/file3.bin" && + p4 submit -d "Add another binary file with same content and spaces in path" && + + echo "content 4 bin 26 bytes XX" >file4.bin && + p4 add file4.bin && + p4 submit -d "Add another binary file with different content" + ) +' + +test_expect_success 'Store files in Mock LFS based on size (>24 bytes)' ' + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config git-p4.useClientSpec true && + git config git-p4.largeFileSystem MockLFS && + git config git-p4.largeFileThreshold 24 && + git config git-p4.largeFilePush True && + git p4 clone --destination="$git" //depot@all && + + test_file_is_not_in_mock_lfs file1.txt "content 1 txt 23 bytes" && + test_file_is_in_mock_lfs file2.dat "content 2-3 bin 25 bytes" && + test_file_is_in_mock_lfs "path with spaces/file3.bin" "content 2-3 bin 25 bytes" && + test_file_is_in_mock_lfs file4.bin "content 4 bin 26 bytes XX" && + + test_file_count_in_dir ".git/mock-storage/local" 2 && + test_file_count_in_dir ".git/mock-storage/remote" 2 + ) +' + +test_expect_success 'Store files in Mock LFS based on extension (dat)' ' + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config git-p4.useClientSpec true && + git config git-p4.largeFileSystem MockLFS && + git config git-p4.largeFileExtensions dat && + git config git-p4.largeFilePush True && + git p4 clone --destination="$git" //depot@all && + + test_file_is_not_in_mock_lfs file1.txt "content 1 txt 23 bytes" && + test_file_is_in_mock_lfs file2.dat "content 2-3 bin 25 bytes" && + test_file_is_not_in_mock_lfs "path with spaces/file3.bin" "content 2-3 bin 25 bytes" && + test_file_is_not_in_mock_lfs file4.bin "content 4 bin 26 bytes XX" && + + test_file_count_in_dir ".git/mock-storage/local" 1 && + test_file_count_in_dir ".git/mock-storage/remote" 1 + ) +' + +test_expect_success 'Store files in Mock LFS based on extension (dat) and use git p4 sync and no client spec' ' + test_when_finished cleanup_git && + ( + cd "$git" && + git init && + git config git-p4.useClientSpec true && + git config git-p4.largeFileSystem MockLFS && + git config git-p4.largeFileExtensions dat && + git config git-p4.largeFilePush True && + git p4 sync //depot && + git checkout p4/master && + + test_file_is_not_in_mock_lfs file1.txt "content 1 txt 23 bytes" && + test_file_is_in_mock_lfs file2.dat "content 2-3 bin 25 bytes" && + test_file_is_not_in_mock_lfs "path with spaces/file3.bin" "content 2-3 bin 25 bytes" && + test_file_is_not_in_mock_lfs file4.bin "content 4 bin 26 bytes XX" && + + test_file_count_in_dir ".git/mock-storage/local" 1 && + test_file_count_in_dir ".git/mock-storage/remote" 1 + ) +' + +test_expect_success 'Remove file from repo and store files in Mock LFS based on size (>24 bytes)' ' + client_view "//depot/... //client/..." && + ( + cd "$cli" && + p4 delete file4.bin && + p4 submit -d "Remove file" + ) && + + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config git-p4.useClientSpec true && + git config git-p4.largeFileSystem MockLFS && + git config git-p4.largeFileThreshold 24 && + git config git-p4.largeFilePush True && + git p4 clone --destination="$git" //depot@all && + + test_file_is_not_in_mock_lfs file1.txt "content 1 txt 23 bytes" && + test_file_is_in_mock_lfs file2.dat "content 2-3 bin 25 bytes" && + test_file_is_in_mock_lfs "path with spaces/file3.bin" "content 2-3 bin 25 bytes" && + test_file_is_deleted_in_mock_lfs file4.bin "content 4 bin 26 bytes XX" && + + test_file_count_in_dir ".git/mock-storage/local" 2 && + test_file_count_in_dir ".git/mock-storage/remote" 2 + ) +' + +test_expect_success 'Run git p4 submit in repo configured with large file system' ' + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config git-p4.useClientSpec true && + git config git-p4.largeFileSystem MockLFS && + git config git-p4.largeFileThreshold 24 && + git config git-p4.largeFilePush True && + git p4 clone --destination="$git" //depot@all && + + test_must_fail git p4 submit + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9824-git-p4-git-lfs.sh b/t/t9824-git-p4-git-lfs.sh new file mode 100755 index 0000000000..ed80ca858c --- /dev/null +++ b/t/t9824-git-p4-git-lfs.sh @@ -0,0 +1,294 @@ +#!/bin/sh + +test_description='Clone repositories and store files in Git LFS' + +. ./lib-git-p4.sh + +git lfs help >/dev/null 2>&1 || { + skip_all='skipping git p4 Git LFS tests; Git LFS not found' + test_done +} + +test_file_in_lfs () { + FILE="$1" && + SIZE="$2" && + EXPECTED_CONTENT="$3" && + sed -n '1,1 p' "$FILE" | grep "^version " && + sed -n '2,2 p' "$FILE" | grep "^oid " && + sed -n '3,3 p' "$FILE" | grep "^size " && + test_line_count = 3 "$FILE" && + cat "$FILE" | grep "size $SIZE" && + HASH=$(cat "$FILE" | grep "oid sha256:" | sed -e "s/oid sha256://g") && + LFS_FILE=".git/lfs/objects/$(echo "$HASH" | cut -c1-2)/$(echo "$HASH" | cut -c3-4)/$HASH" && + echo $EXPECTED_CONTENT >expect && + test_path_is_file "$FILE" && + test_path_is_file "$LFS_FILE" && + test_cmp expect "$LFS_FILE" +} + +test_file_count_in_dir () { + DIR="$1" && + EXPECTED_COUNT="$2" && + find "$DIR" -type f >actual && + test_line_count = $EXPECTED_COUNT actual +} + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'Create repo with binary files' ' + client_view "//depot/... //client/..." && + ( + cd "$cli" && + + >file0.dat && + p4 add file0.dat && + echo "content 1 txt 23 bytes" >file1.txt && + p4 add file1.txt && + echo "content 2-3 bin 25 bytes" >file2.dat && + p4 add file2.dat && + p4 submit -d "Add text and binary file" && + + mkdir "path with spaces" && + echo "content 2-3 bin 25 bytes" >"path with spaces/file3.bin" && + p4 add "path with spaces/file3.bin" && + p4 submit -d "Add another binary file with same content and spaces in path" && + + echo "content 4 bin 26 bytes XX" >file4.bin && + p4 add file4.bin && + p4 submit -d "Add another binary file with different content" + ) +' + +test_expect_success 'Store files in LFS based on size (>24 bytes)' ' + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config git-p4.useClientSpec true && + git config git-p4.largeFileSystem GitLFS && + git config git-p4.largeFileThreshold 24 && + git p4 clone --destination="$git" //depot@all && + + test_file_in_lfs file2.dat 25 "content 2-3 bin 25 bytes" && + test_file_in_lfs "path with spaces/file3.bin" 25 "content 2-3 bin 25 bytes" && + test_file_in_lfs file4.bin 26 "content 4 bin 26 bytes XX" && + + test_file_count_in_dir ".git/lfs/objects" 2 && + + cat >expect <<-\EOF && + + # + # Git LFS (see https://git-lfs.github.com/) + # + /file2.dat filter=lfs diff=lfs merge=lfs -text + /file4.bin filter=lfs diff=lfs merge=lfs -text + /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs diff=lfs merge=lfs -text + EOF + test_path_is_file .gitattributes && + test_cmp expect .gitattributes + ) +' + +test_expect_success 'Store files in LFS based on size (>25 bytes)' ' + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config git-p4.useClientSpec true && + git config git-p4.largeFileSystem GitLFS && + git config git-p4.largeFileThreshold 25 && + git p4 clone --destination="$git" //depot@all && + + test_file_in_lfs file4.bin 26 "content 4 bin 26 bytes XX" && + test_file_count_in_dir ".git/lfs/objects" 1 && + + cat >expect <<-\EOF && + + # + # Git LFS (see https://git-lfs.github.com/) + # + /file4.bin filter=lfs diff=lfs merge=lfs -text + EOF + test_path_is_file .gitattributes && + test_cmp expect .gitattributes + ) +' + +test_expect_success 'Store files in LFS based on extension (dat)' ' + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config git-p4.useClientSpec true && + git config git-p4.largeFileSystem GitLFS && + git config git-p4.largeFileExtensions dat && + git p4 clone --destination="$git" //depot@all && + + test_file_in_lfs file2.dat 25 "content 2-3 bin 25 bytes" && + test_file_count_in_dir ".git/lfs/objects" 1 && + + cat >expect <<-\EOF && + + # + # Git LFS (see https://git-lfs.github.com/) + # + *.dat filter=lfs diff=lfs merge=lfs -text + EOF + test_path_is_file .gitattributes && + test_cmp expect .gitattributes + ) +' + +test_expect_success 'Store files in LFS based on size (>25 bytes) and extension (dat)' ' + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config git-p4.useClientSpec true && + git config git-p4.largeFileSystem GitLFS && + git config git-p4.largeFileExtensions dat && + git config git-p4.largeFileThreshold 25 && + git p4 clone --destination="$git" //depot@all && + + test_file_in_lfs file2.dat 25 "content 2-3 bin 25 bytes" && + test_file_in_lfs file4.bin 26 "content 4 bin 26 bytes XX" && + test_file_count_in_dir ".git/lfs/objects" 2 && + + cat >expect <<-\EOF && + + # + # Git LFS (see https://git-lfs.github.com/) + # + *.dat filter=lfs diff=lfs merge=lfs -text + /file4.bin filter=lfs diff=lfs merge=lfs -text + EOF + test_path_is_file .gitattributes && + test_cmp expect .gitattributes + ) +' + +test_expect_success 'Remove file from repo and store files in LFS based on size (>24 bytes)' ' + client_view "//depot/... //client/..." && + ( + cd "$cli" && + p4 delete file4.bin && + p4 submit -d "Remove file" + ) && + + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config git-p4.useClientSpec true && + git config git-p4.largeFileSystem GitLFS && + git config git-p4.largeFileThreshold 24 && + git p4 clone --destination="$git" //depot@all && + + test_file_in_lfs file2.dat 25 "content 2-3 bin 25 bytes" && + test_file_in_lfs "path with spaces/file3.bin" 25 "content 2-3 bin 25 bytes" && + test_path_is_missing file4.bin && + test_file_count_in_dir ".git/lfs/objects" 2 && + + cat >expect <<-\EOF && + + # + # Git LFS (see https://git-lfs.github.com/) + # + /file2.dat filter=lfs diff=lfs merge=lfs -text + /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs diff=lfs merge=lfs -text + EOF + test_path_is_file .gitattributes && + test_cmp expect .gitattributes + ) +' + +test_expect_success 'Add .gitattributes and store files in LFS based on size (>24 bytes)' ' + client_view "//depot/... //client/..." && + ( + cd "$cli" && + echo "*.txt text" >.gitattributes && + p4 add .gitattributes && + p4 submit -d "Add .gitattributes" + ) && + + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config git-p4.useClientSpec true && + git config git-p4.largeFileSystem GitLFS && + git config git-p4.largeFileThreshold 24 && + git p4 clone --destination="$git" //depot@all && + + test_file_in_lfs file2.dat 25 "content 2-3 bin 25 bytes" && + test_file_in_lfs "path with spaces/file3.bin" 25 "content 2-3 bin 25 bytes" && + test_path_is_missing file4.bin && + test_file_count_in_dir ".git/lfs/objects" 2 && + + cat >expect <<-\EOF && + *.txt text + + # + # Git LFS (see https://git-lfs.github.com/) + # + /file2.dat filter=lfs diff=lfs merge=lfs -text + /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs diff=lfs merge=lfs -text + EOF + test_path_is_file .gitattributes && + test_cmp expect .gitattributes + ) +' + +test_expect_success 'Add big files to repo and store files in LFS based on compressed size (>28 bytes)' ' + client_view "//depot/... //client/..." && + ( + cd "$cli" && + echo "content 5 bin 40 bytes XXXXXXXXXXXXXXXX" >file5.bin && + p4 add file5.bin && + p4 submit -d "Add file with small footprint after compression" && + + echo "content 6 bin 39 bytes XXXXXYYYYYZZZZZ" >file6.bin && + p4 add file6.bin && + p4 submit -d "Add file with large footprint after compression" + ) && + + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config git-p4.useClientSpec true && + git config git-p4.largeFileSystem GitLFS && + git config git-p4.largeFileCompressedThreshold 28 && + # We only import HEAD here ("@all" is missing!) + git p4 clone --destination="$git" //depot && + + test_file_in_lfs file6.bin 39 "content 6 bin 39 bytes XXXXXYYYYYZZZZZ" && + test_file_count_in_dir ".git/lfs/objects" 1 && + + cat >expect <<-\EOF && + *.txt text + + # + # Git LFS (see https://git-lfs.github.com/) + # + /file6.bin filter=lfs diff=lfs merge=lfs -text + EOF + test_path_is_file .gitattributes && + test_cmp expect .gitattributes + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9825-git-p4-handle-utf16-without-bom.sh b/t/t9825-git-p4-handle-utf16-without-bom.sh new file mode 100755 index 0000000000..1551845dc1 --- /dev/null +++ b/t/t9825-git-p4-handle-utf16-without-bom.sh @@ -0,0 +1,50 @@ +#!/bin/sh + +test_description='git p4 handling of UTF-16 files without BOM' + +. ./lib-git-p4.sh + +UTF16="\227\000\227\000" + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'init depot with UTF-16 encoded file and artificially remove BOM' ' + ( + cd "$cli" && + printf "$UTF16" >file1 && + p4 add -t utf16 file1 && + p4 submit -d "file1" + ) && + + ( + cd db && + p4d -jc && + # P4D automatically adds a BOM. Remove it here to make the file invalid. + sed -e "\$d" depot/file1,v >depot/file1,v.new && + mv depot/file1,v.new depot/file1,v && + printf "@$UTF16@" >>depot/file1,v && + p4d -jrF checkpoint.1 + ) +' + +test_expect_success 'clone depot with invalid UTF-16 file in verbose mode' ' + git p4 clone --dest="$git" --verbose //depot && + test_when_finished cleanup_git && + ( + cd "$git" && + printf "$UTF16" >expect && + test_cmp_bin expect file1 + ) +' + +test_expect_failure 'clone depot with invalid UTF-16 file in non-verbose mode' ' + git p4 clone --dest="$git" //depot +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9826-git-p4-keep-empty-commits.sh b/t/t9826-git-p4-keep-empty-commits.sh new file mode 100755 index 0000000000..fa8b9daf1f --- /dev/null +++ b/t/t9826-git-p4-keep-empty-commits.sh @@ -0,0 +1,134 @@ +#!/bin/sh + +test_description='Clone repositories and keep empty commits' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'Create a repo' ' + client_view "//depot/... //client/..." && + ( + cd "$cli" && + + mkdir -p subdir && + + >subdir/file1.txt && + p4 add subdir/file1.txt && + p4 submit -d "Add file 1" && + + >file2.txt && + p4 add file2.txt && + p4 submit -d "Add file 2" && + + >subdir/file3.txt && + p4 add subdir/file3.txt && + p4 submit -d "Add file 3" && + + >file4.txt && + p4 add file4.txt && + p4 submit -d "Add file 4" && + + p4 delete subdir/file3.txt && + p4 submit -d "Remove file 3" && + + p4 delete file4.txt && + p4 submit -d "Remove file 4" + ) +' + +test_expect_success 'Clone repo root path with all history' ' + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git p4 clone --use-client-spec --destination="$git" //depot@all && + cat >expect <<-\EOF && + Remove file 4 + [git-p4: depot-paths = "//depot/": change = 6] + + Remove file 3 + [git-p4: depot-paths = "//depot/": change = 5] + + Add file 4 + [git-p4: depot-paths = "//depot/": change = 4] + + Add file 3 + [git-p4: depot-paths = "//depot/": change = 3] + + Add file 2 + [git-p4: depot-paths = "//depot/": change = 2] + + Add file 1 + [git-p4: depot-paths = "//depot/": change = 1] + + EOF + git log --format=%B >actual && + test_cmp expect actual + ) +' + +test_expect_success 'Clone repo subdir with all history but keep empty commits' ' + client_view "//depot/subdir/... //client/subdir/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config git-p4.keepEmptyCommits true && + git p4 clone --use-client-spec --destination="$git" //depot@all && + cat >expect <<-\EOF && + Remove file 4 + [git-p4: depot-paths = "//depot/": change = 6] + + Remove file 3 + [git-p4: depot-paths = "//depot/": change = 5] + + Add file 4 + [git-p4: depot-paths = "//depot/": change = 4] + + Add file 3 + [git-p4: depot-paths = "//depot/": change = 3] + + Add file 2 + [git-p4: depot-paths = "//depot/": change = 2] + + Add file 1 + [git-p4: depot-paths = "//depot/": change = 1] + + EOF + git log --format=%B >actual && + test_cmp expect actual + ) +' + +test_expect_success 'Clone repo subdir with all history' ' + client_view "//depot/subdir/... //client/subdir/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git p4 clone --use-client-spec --destination="$git" --verbose //depot@all && + cat >expect <<-\EOF && + Remove file 3 + [git-p4: depot-paths = "//depot/": change = 5] + + Add file 3 + [git-p4: depot-paths = "//depot/": change = 3] + + Add file 1 + [git-p4: depot-paths = "//depot/": change = 1] + + EOF + git log --format=%B >actual && + test_cmp expect actual + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9827-git-p4-change-filetype.sh b/t/t9827-git-p4-change-filetype.sh new file mode 100755 index 0000000000..7433998f47 --- /dev/null +++ b/t/t9827-git-p4-change-filetype.sh @@ -0,0 +1,66 @@ +#!/bin/sh + +test_description='git p4 support for file type change' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'create files' ' + ( + cd "$cli" && + p4 client -o | sed "/LineEnd/s/:.*/:unix/" | p4 client -i && + cat >file1 <<-EOF && + text without any funny substitution business + EOF + cat >file2 <<-EOF && + second file whose type will change + EOF + p4 add file1 file2 && + p4 submit -d "add files" + ) +' + +test_expect_success SYMLINKS 'change file to symbolic link' ' + git p4 clone --dest="$git" //depot@all && + test_when_finished cleanup_git && + ( + cd "$git" && + git config git-p4.skipSubmitEdit true && + + rm file2 && + ln -s file1 file2 && + git add file2 && + git commit -m "symlink file1 to file2" && + git p4 submit && + p4 filelog -m 1 //depot/file2 >filelog && + grep "(symlink)" filelog + ) +' + +test_expect_success SYMLINKS 'change symbolic link to file' ' + git p4 clone --dest="$git" //depot@all && + test_when_finished cleanup_git && + ( + cd "$git" && + git config git-p4.skipSubmitEdit true && + + rm file2 && + cat >file2 <<-EOF && + This is another content for the second file. + EOF + git add file2 && + git commit -m "re-write file2" && + git p4 submit && + p4 filelog -m 1 //depot/file2 >filelog && + grep "(text)" filelog + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9828-git-p4-map-user.sh b/t/t9828-git-p4-map-user.sh new file mode 100755 index 0000000000..e20395c89f --- /dev/null +++ b/t/t9828-git-p4-map-user.sh @@ -0,0 +1,61 @@ +#!/bin/sh + +test_description='Clone repositories and map users' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'Create a repo with different users' ' + client_view "//depot/... //client/..." && + ( + cd "$cli" && + + >author.txt && + p4 add author.txt && + p4 submit -d "Add file author\\n" && + + P4USER=mmax && + >max.txt && + p4 add max.txt && + p4 submit -d "Add file max" && + + P4USER=eri && + >moritz.txt && + p4 add moritz.txt && + p4 submit -d "Add file moritz" && + + P4USER=no && + >nobody.txt && + p4 add nobody.txt && + p4 submit -d "Add file nobody" + ) +' + +test_expect_success 'Clone repo root path with all history' ' + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config --add git-p4.mapUser "mmax = Max Musterman <max@example.com> " && + git config --add git-p4.mapUser " eri=Erika Musterman <erika@example.com>" && + git p4 clone --use-client-spec --destination="$git" //depot@all && + cat >expect <<-\EOF && + no <no@client> + Erika Musterman <erika@example.com> + Max Musterman <max@example.com> + Dr. author <author@example.com> + EOF + git log --format="%an <%ae>" >actual && + test_cmp expect actual + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9829-git-p4-jobs.sh b/t/t9829-git-p4-jobs.sh new file mode 100755 index 0000000000..971aeeea1f --- /dev/null +++ b/t/t9829-git-p4-jobs.sh @@ -0,0 +1,99 @@ +#!/bin/sh + +test_description='git p4 retrieve job info' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'add p4 jobs' ' + ( + p4_add_job TESTJOB-A && + p4_add_job TESTJOB-B + ) +' + +test_expect_success 'add p4 files' ' + client_view "//depot/... //client/..." && + ( + cd "$cli" && + >file1 && + p4 add file1 && + p4 submit -d "Add file 1" + ) +' + +test_expect_success 'check log message of changelist with no jobs' ' + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git p4 clone --use-client-spec --destination="$git" //depot@all && + cat >expect <<-\EOF && + Add file 1 + [git-p4: depot-paths = "//depot/": change = 1] + + EOF + git log --format=%B >actual && + test_cmp expect actual + ) +' + +test_expect_success 'add TESTJOB-A to change 1' ' + ( + cd "$cli" && + p4 fix -c 1 TESTJOB-A + ) +' + +test_expect_success 'check log message of changelist with one job' ' + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git p4 clone --use-client-spec --destination="$git" //depot@all && + cat >expect <<-\EOF && + Add file 1 + Jobs: TESTJOB-A + [git-p4: depot-paths = "//depot/": change = 1] + + EOF + git log --format=%B >actual && + test_cmp expect actual + ) +' + +test_expect_success 'add TESTJOB-B to change 1' ' + ( + cd "$cli" && + p4 fix -c 1 TESTJOB-B + ) +' + +test_expect_success 'check log message of changelist with more jobs' ' + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git p4 clone --use-client-spec --destination="$git" //depot@all && + cat >expect <<-\EOF && + Add file 1 + Jobs: TESTJOB-A TESTJOB-B + [git-p4: depot-paths = "//depot/": change = 1] + + EOF + git log --format=%B >actual && + test_cmp expect actual + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9830-git-p4-symlink-dir.sh b/t/t9830-git-p4-symlink-dir.sh new file mode 100755 index 0000000000..3dc528bb1e --- /dev/null +++ b/t/t9830-git-p4-symlink-dir.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +test_description='git p4 symlinked directories' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'symlinked directory' ' + ( + cd "$cli" && + : >first_file.t && + p4 add first_file.t && + p4 submit -d "first change" + ) && + git p4 clone --dest "$git" //depot && + ( + cd "$git" && + mkdir -p some/sub/directory && + mkdir -p other/subdir2 && + : > other/subdir2/file.t && + (cd some/sub/directory && ln -s ../../../other/subdir2 .) && + git add some other && + git commit -m "symlinks" && + git config git-p4.skipSubmitEdit true && + git p4 submit -v + ) && + ( + cd "$cli" && + p4 sync && + test -L some/sub/directory/subdir2 + test_path_is_file some/sub/directory/subdir2/file.t + ) + +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9831-git-p4-triggers.sh b/t/t9831-git-p4-triggers.sh new file mode 100755 index 0000000000..bbcf14c664 --- /dev/null +++ b/t/t9831-git-p4-triggers.sh @@ -0,0 +1,103 @@ +#!/bin/sh + +test_description='git p4 with server triggers' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'init depot' ' + ( + cd "$cli" && + echo file1 >file1 && + p4 add file1 && + p4 submit -d "change 1" + echo file2 >file2 && + p4 add file2 && + p4 submit -d "change 2" + ) +' + +test_expect_success 'clone with extra info lines from verbose p4 trigger' ' + test_when_finished cleanup_git && + ( + p4 triggers -i <<-EOF + Triggers: p4triggertest-command command pre-user-change "echo verbose trigger" + EOF + ) && + ( + p4 change -o | grep -s "verbose trigger" + ) && + git p4 clone --dest="$git" //depot/@all && + ( + p4 triggers -i <<-EOF + Triggers: + EOF + ) +' + +test_expect_success 'import with extra info lines from verbose p4 trigger' ' + test_when_finished cleanup_git && + ( + cd "$cli" && + echo file3 >file3 && + p4 add file3 && + p4 submit -d "change 3" + ) && + ( + p4 triggers -i <<-EOF + Triggers: p4triggertest-command command pre-user-describe "echo verbose trigger" + EOF + ) && + ( + p4 describe 1 | grep -s "verbose trigger" + ) && + git p4 clone --dest="$git" //depot/@all && + ( + cd "$git" && + git p4 sync + )&& + ( + p4 triggers -i <<-EOF + Triggers: + EOF + ) +' + +test_expect_success 'submit description with extra info lines from verbose p4 change trigger' ' + test_when_finished cleanup_git && + ( + p4 triggers -i <<-EOF + Triggers: p4triggertest-command command pre-user-change "echo verbose trigger" + EOF + ) && + ( + p4 change -o | grep -s "verbose trigger" + ) && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + git config git-p4.skipSubmitEdit true && + echo file4 >file4 && + git add file4 && + git commit -m file4 && + git p4 submit + ) && + ( + p4 triggers -i <<-EOF + Triggers: + EOF + ) && + ( + cd "$cli" && + test_path_is_file file4 + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9901-git-web--browse.sh b/t/t9901-git-web--browse.sh index b0a6bad8dd..de7152f827 100755 --- a/t/t9901-git-web--browse.sh +++ b/t/t9901-git-web--browse.sh @@ -43,7 +43,7 @@ test_expect_success \ echo fake: "$@" EOF chmod +x "fake browser" && - git config browser.w3m.path "`pwd`/fake browser" && + git config browser.w3m.path "$(pwd)/fake browser" && test_web_browse w3m http://example.com/foo ' diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 2ba62fbc17..1b34caa1e1 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -98,7 +98,7 @@ test_gitcomp () { local -a COMPREPLY && sed -e 's/Z$//' >expected && - cur="$1" && + local cur="$1" && shift && __gitcomp "$@" && print_comp && @@ -113,7 +113,7 @@ test_gitcomp_nl () { local -a COMPREPLY && sed -e 's/Z$//' >expected && - cur="$1" && + local cur="$1" && shift && __gitcomp_nl "$@" && print_comp && @@ -124,145 +124,296 @@ invalid_variable_name='${foo.bar}' actual="$TRASH_DIRECTORY/actual" -test_expect_success 'setup for __gitdir tests' ' +if test_have_prereq MINGW +then + ROOT="$(pwd -W)" +else + ROOT="$(pwd)" +fi + +test_expect_success 'setup for __git_find_repo_path/__gitdir tests' ' mkdir -p subdir/subsubdir && + mkdir -p non-repo && git init otherrepo ' -test_expect_success '__gitdir - from command line (through $__git_dir)' ' - echo "$TRASH_DIRECTORY/otherrepo/.git" >expected && +test_expect_success '__git_find_repo_path - from command line (through $__git_dir)' ' + echo "$ROOT/otherrepo/.git" >expected && ( - __git_dir="$TRASH_DIRECTORY/otherrepo/.git" && - __gitdir >"$actual" + __git_dir="$ROOT/otherrepo/.git" && + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" ) && test_cmp expected "$actual" ' -test_expect_success '__gitdir - repo as argument' ' - echo "otherrepo/.git" >expected && - __gitdir "otherrepo" >"$actual" && - test_cmp expected "$actual" -' - -test_expect_success '__gitdir - remote as argument' ' - echo "remote" >expected && - __gitdir "remote" >"$actual" && - test_cmp expected "$actual" -' - -test_expect_success '__gitdir - .git directory in cwd' ' +test_expect_success '__git_find_repo_path - .git directory in cwd' ' echo ".git" >expected && - __gitdir >"$actual" && + ( + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" + ) && test_cmp expected "$actual" ' -test_expect_success '__gitdir - .git directory in parent' ' - echo "$(pwd -P)/.git" >expected && +test_expect_success '__git_find_repo_path - .git directory in parent' ' + echo "$ROOT/.git" >expected && ( cd subdir/subsubdir && - __gitdir >"$actual" + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" ) && test_cmp expected "$actual" ' -test_expect_success '__gitdir - cwd is a .git directory' ' +test_expect_success '__git_find_repo_path - cwd is a .git directory' ' echo "." >expected && ( cd .git && - __gitdir >"$actual" + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" ) && test_cmp expected "$actual" ' -test_expect_success '__gitdir - parent is a .git directory' ' - echo "$(pwd -P)/.git" >expected && +test_expect_success '__git_find_repo_path - parent is a .git directory' ' + echo "$ROOT/.git" >expected && ( cd .git/refs/heads && - __gitdir >"$actual" + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" ) && test_cmp expected "$actual" ' -test_expect_success '__gitdir - $GIT_DIR set while .git directory in cwd' ' - echo "$TRASH_DIRECTORY/otherrepo/.git" >expected && +test_expect_success '__git_find_repo_path - $GIT_DIR set while .git directory in cwd' ' + echo "$ROOT/otherrepo/.git" >expected && ( - GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" && + GIT_DIR="$ROOT/otherrepo/.git" && export GIT_DIR && - __gitdir >"$actual" + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" ) && test_cmp expected "$actual" ' -test_expect_success '__gitdir - $GIT_DIR set while .git directory in parent' ' - echo "$TRASH_DIRECTORY/otherrepo/.git" >expected && +test_expect_success '__git_find_repo_path - $GIT_DIR set while .git directory in parent' ' + echo "$ROOT/otherrepo/.git" >expected && ( - GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" && + GIT_DIR="$ROOT/otherrepo/.git" && export GIT_DIR && cd subdir && - __gitdir >"$actual" + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_find_repo_path - from command line while "git -C"' ' + echo "$ROOT/.git" >expected && + ( + __git_dir="$ROOT/.git" && + __git_C_args=(-C otherrepo) && + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_find_repo_path - relative dir from command line and "git -C"' ' + echo "$ROOT/otherrepo/.git" >expected && + ( + cd subdir && + __git_dir="otherrepo/.git" && + __git_C_args=(-C ..) && + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" ) && test_cmp expected "$actual" ' -test_expect_success '__gitdir - non-existing $GIT_DIR' ' +test_expect_success '__git_find_repo_path - $GIT_DIR set while "git -C"' ' + echo "$ROOT/.git" >expected && ( - GIT_DIR="$TRASH_DIRECTORY/non-existing" && + GIT_DIR="$ROOT/.git" && export GIT_DIR && - test_must_fail __gitdir - ) + __git_C_args=(-C otherrepo) && + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" + ) && + test_cmp expected "$actual" ' -function pwd_P_W () { - if test_have_prereq MINGW - then - pwd -W - else - pwd -P - fi -} +test_expect_success '__git_find_repo_path - relative dir in $GIT_DIR and "git -C"' ' + echo "$ROOT/otherrepo/.git" >expected && + ( + cd subdir && + GIT_DIR="otherrepo/.git" && + export GIT_DIR && + __git_C_args=(-C ..) && + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_find_repo_path - "git -C" while .git directory in cwd' ' + echo "$ROOT/otherrepo/.git" >expected && + ( + __git_C_args=(-C otherrepo) && + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" + ) && + test_cmp expected "$actual" +' -test_expect_success '__gitdir - gitfile in cwd' ' - echo "$(pwd_P_W)/otherrepo/.git" >expected && - echo "gitdir: $(pwd_P_W)/otherrepo/.git" >subdir/.git && +test_expect_success '__git_find_repo_path - "git -C" while cwd is a .git directory' ' + echo "$ROOT/otherrepo/.git" >expected && + ( + cd .git && + __git_C_args=(-C .. -C otherrepo) && + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_find_repo_path - "git -C" while .git directory in parent' ' + echo "$ROOT/otherrepo/.git" >expected && + ( + cd subdir && + __git_C_args=(-C .. -C otherrepo) && + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_find_repo_path - non-existing path in "git -C"' ' + ( + __git_C_args=(-C non-existing) && + test_must_fail __git_find_repo_path && + printf "$__git_repo_path" >"$actual" + ) && + test_must_be_empty "$actual" +' + +test_expect_success '__git_find_repo_path - non-existing path in $__git_dir' ' + ( + __git_dir="non-existing" && + test_must_fail __git_find_repo_path && + printf "$__git_repo_path" >"$actual" + ) && + test_must_be_empty "$actual" +' + +test_expect_success '__git_find_repo_path - non-existing $GIT_DIR' ' + ( + GIT_DIR="$ROOT/non-existing" && + export GIT_DIR && + test_must_fail __git_find_repo_path && + printf "$__git_repo_path" >"$actual" + ) && + test_must_be_empty "$actual" +' + +test_expect_success '__git_find_repo_path - gitfile in cwd' ' + echo "$ROOT/otherrepo/.git" >expected && + echo "gitdir: $ROOT/otherrepo/.git" >subdir/.git && test_when_finished "rm -f subdir/.git" && ( cd subdir && - __gitdir >"$actual" + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" ) && test_cmp expected "$actual" ' -test_expect_success '__gitdir - gitfile in parent' ' - echo "$(pwd_P_W)/otherrepo/.git" >expected && - echo "gitdir: $(pwd_P_W)/otherrepo/.git" >subdir/.git && +test_expect_success '__git_find_repo_path - gitfile in parent' ' + echo "$ROOT/otherrepo/.git" >expected && + echo "gitdir: $ROOT/otherrepo/.git" >subdir/.git && test_when_finished "rm -f subdir/.git" && ( cd subdir/subsubdir && - __gitdir >"$actual" + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" ) && test_cmp expected "$actual" ' -test_expect_success SYMLINKS '__gitdir - resulting path avoids symlinks' ' - echo "$(pwd -P)/otherrepo/.git" >expected && +test_expect_success SYMLINKS '__git_find_repo_path - resulting path avoids symlinks' ' + echo "$ROOT/otherrepo/.git" >expected && mkdir otherrepo/dir && test_when_finished "rm -rf otherrepo/dir" && ln -s otherrepo/dir link && test_when_finished "rm -f link" && ( cd link && - __gitdir >"$actual" + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" ) && test_cmp expected "$actual" ' -test_expect_success '__gitdir - not a git repository' ' +test_expect_success '__git_find_repo_path - not a git repository' ' ( - cd subdir/subsubdir && - GIT_CEILING_DIRECTORIES="$TRASH_DIRECTORY" && + cd non-repo && + GIT_CEILING_DIRECTORIES="$ROOT" && export GIT_CEILING_DIRECTORIES && - test_must_fail __gitdir - ) + test_must_fail __git_find_repo_path && + printf "$__git_repo_path" >"$actual" + ) && + test_must_be_empty "$actual" +' + +test_expect_success '__gitdir - finds repo' ' + echo "$ROOT/.git" >expected && + ( + cd subdir/subsubdir && + __gitdir >"$actual" + ) && + test_cmp expected "$actual" +' + + +test_expect_success '__gitdir - returns error when cant find repo' ' + ( + __git_dir="non-existing" && + test_must_fail __gitdir >"$actual" + ) && + test_must_be_empty "$actual" +' + +test_expect_success '__gitdir - repo as argument' ' + echo "otherrepo/.git" >expected && + ( + __gitdir "otherrepo" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__gitdir - remote as argument' ' + echo "remote" >expected && + ( + __gitdir "remote" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__gitcomp_direct - puts everything into COMPREPLY as-is' ' + sed -e "s/Z$//g" >expected <<-EOF && + with-trailing-space Z + without-trailing-spaceZ + --option Z + --option=Z + $invalid_variable_name Z + EOF + ( + cur=should_be_ignored && + __gitcomp_direct "$(cat expected)" && + print_comp + ) && + test_cmp expected out ' test_expect_success '__gitcomp - trailing space - options' ' @@ -366,10 +517,657 @@ test_expect_success '__git_remotes - list remotes from $GIT_DIR/remotes and from git remote add remote_in_config_1 git://remote_1 && test_when_finished "git remote remove remote_in_config_2" && git remote add remote_in_config_2 git://remote_2 && - __git_remotes >actual && + ( + __git_remotes >actual + ) && test_cmp expect actual ' +test_expect_success '__git_is_configured_remote' ' + test_when_finished "git remote remove remote_1" && + git remote add remote_1 git://remote_1 && + test_when_finished "git remote remove remote_2" && + git remote add remote_2 git://remote_2 && + ( + verbose __git_is_configured_remote remote_2 && + test_must_fail __git_is_configured_remote non-existent + ) +' + +test_expect_success 'setup for ref completion' ' + git commit --allow-empty -m initial && + git branch matching-branch && + git tag matching-tag && + ( + cd otherrepo && + git commit --allow-empty -m initial && + git branch -m master master-in-other && + git branch branch-in-other && + git tag tag-in-other + ) && + git remote add other "$ROOT/otherrepo/.git" && + git fetch --no-tags other && + rm -f .git/FETCH_HEAD && + git init thirdrepo +' + +test_expect_success '__git_refs - simple' ' + cat >expected <<-EOF && + HEAD + master + matching-branch + other/branch-in-other + other/master-in-other + matching-tag + EOF + ( + cur= && + __git_refs >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - full refs' ' + cat >expected <<-EOF && + refs/heads/master + refs/heads/matching-branch + refs/remotes/other/branch-in-other + refs/remotes/other/master-in-other + refs/tags/matching-tag + EOF + ( + cur=refs/heads/ && + __git_refs >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - repo given on the command line' ' + cat >expected <<-EOF && + HEAD + branch-in-other + master-in-other + tag-in-other + EOF + ( + __git_dir="$ROOT/otherrepo/.git" && + cur= && + __git_refs >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - remote on local file system' ' + cat >expected <<-EOF && + HEAD + branch-in-other + master-in-other + tag-in-other + EOF + ( + cur= && + __git_refs otherrepo >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - remote on local file system - full refs' ' + cat >expected <<-EOF && + refs/heads/branch-in-other + refs/heads/master-in-other + refs/tags/tag-in-other + EOF + ( + cur=refs/ && + __git_refs otherrepo >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - configured remote' ' + cat >expected <<-EOF && + HEAD + branch-in-other + master-in-other + EOF + ( + cur= && + __git_refs other >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - configured remote - full refs' ' + cat >expected <<-EOF && + HEAD + refs/heads/branch-in-other + refs/heads/master-in-other + refs/tags/tag-in-other + EOF + ( + cur=refs/ && + __git_refs other >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - configured remote - repo given on the command line' ' + cat >expected <<-EOF && + HEAD + branch-in-other + master-in-other + EOF + ( + cd thirdrepo && + __git_dir="$ROOT/.git" && + cur= && + __git_refs other >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - configured remote - full refs - repo given on the command line' ' + cat >expected <<-EOF && + HEAD + refs/heads/branch-in-other + refs/heads/master-in-other + refs/tags/tag-in-other + EOF + ( + cd thirdrepo && + __git_dir="$ROOT/.git" && + cur=refs/ && + __git_refs other >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - configured remote - remote name matches a directory' ' + cat >expected <<-EOF && + HEAD + branch-in-other + master-in-other + EOF + mkdir other && + test_when_finished "rm -rf other" && + ( + cur= && + __git_refs other >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - URL remote' ' + cat >expected <<-EOF && + HEAD + branch-in-other + master-in-other + tag-in-other + EOF + ( + cur= && + __git_refs "file://$ROOT/otherrepo/.git" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - URL remote - full refs' ' + cat >expected <<-EOF && + HEAD + refs/heads/branch-in-other + refs/heads/master-in-other + refs/tags/tag-in-other + EOF + ( + cur=refs/ && + __git_refs "file://$ROOT/otherrepo/.git" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - non-existing remote' ' + ( + cur= && + __git_refs non-existing >"$actual" + ) && + test_must_be_empty "$actual" +' + +test_expect_success '__git_refs - non-existing remote - full refs' ' + ( + cur=refs/ && + __git_refs non-existing >"$actual" + ) && + test_must_be_empty "$actual" +' + +test_expect_success '__git_refs - non-existing URL remote' ' + ( + cur= && + __git_refs "file://$ROOT/non-existing" >"$actual" + ) && + test_must_be_empty "$actual" +' + +test_expect_success '__git_refs - non-existing URL remote - full refs' ' + ( + cur=refs/ && + __git_refs "file://$ROOT/non-existing" >"$actual" + ) && + test_must_be_empty "$actual" +' + +test_expect_success '__git_refs - not in a git repository' ' + ( + GIT_CEILING_DIRECTORIES="$ROOT" && + export GIT_CEILING_DIRECTORIES && + cd subdir && + cur= && + __git_refs >"$actual" + ) && + test_must_be_empty "$actual" +' + +test_expect_success '__git_refs - unique remote branches for git checkout DWIMery' ' + cat >expected <<-EOF && + HEAD + master + matching-branch + other/ambiguous + other/branch-in-other + other/master-in-other + remote/ambiguous + remote/branch-in-remote + matching-tag + branch-in-other + branch-in-remote + master-in-other + EOF + for remote_ref in refs/remotes/other/ambiguous \ + refs/remotes/remote/ambiguous \ + refs/remotes/remote/branch-in-remote + do + git update-ref $remote_ref master && + test_when_finished "git update-ref -d $remote_ref" + done && + ( + cur= && + __git_refs "" 1 >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - after --opt=' ' + cat >expected <<-EOF && + HEAD + master + matching-branch + other/branch-in-other + other/master-in-other + matching-tag + EOF + ( + cur="--opt=" && + __git_refs "" "" "" "" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - after --opt= - full refs' ' + cat >expected <<-EOF && + refs/heads/master + refs/heads/matching-branch + refs/remotes/other/branch-in-other + refs/remotes/other/master-in-other + refs/tags/matching-tag + EOF + ( + cur="--opt=refs/" && + __git_refs "" "" "" refs/ >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git refs - exluding refs' ' + cat >expected <<-EOF && + ^HEAD + ^master + ^matching-branch + ^other/branch-in-other + ^other/master-in-other + ^matching-tag + EOF + ( + cur=^ && + __git_refs >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git refs - exluding full refs' ' + cat >expected <<-EOF && + ^refs/heads/master + ^refs/heads/matching-branch + ^refs/remotes/other/branch-in-other + ^refs/remotes/other/master-in-other + ^refs/tags/matching-tag + EOF + ( + cur=^refs/ && + __git_refs >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'setup for filtering matching refs' ' + git branch matching/branch && + git tag matching/tag && + git -C otherrepo branch matching/branch-in-other && + git fetch --no-tags other && + rm -f .git/FETCH_HEAD +' + +test_expect_success '__git_refs - dont filter refs unless told so' ' + cat >expected <<-EOF && + HEAD + master + matching-branch + matching/branch + other/branch-in-other + other/master-in-other + other/matching/branch-in-other + matching-tag + matching/tag + EOF + ( + cur=master && + __git_refs >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - only matching refs' ' + cat >expected <<-EOF && + matching-branch + matching/branch + matching-tag + matching/tag + EOF + ( + cur=mat && + __git_refs "" "" "" "$cur" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - only matching refs - full refs' ' + cat >expected <<-EOF && + refs/heads/matching-branch + refs/heads/matching/branch + EOF + ( + cur=refs/heads/mat && + __git_refs "" "" "" "$cur" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - only matching refs - remote on local file system' ' + cat >expected <<-EOF && + master-in-other + matching/branch-in-other + EOF + ( + cur=ma && + __git_refs otherrepo "" "" "$cur" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - only matching refs - configured remote' ' + cat >expected <<-EOF && + master-in-other + matching/branch-in-other + EOF + ( + cur=ma && + __git_refs other "" "" "$cur" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - only matching refs - remote - full refs' ' + cat >expected <<-EOF && + refs/heads/master-in-other + refs/heads/matching/branch-in-other + EOF + ( + cur=refs/heads/ma && + __git_refs other "" "" "$cur" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_refs - only matching refs - checkout DWIMery' ' + cat >expected <<-EOF && + matching-branch + matching/branch + matching-tag + matching/tag + matching/branch-in-other + EOF + for remote_ref in refs/remotes/other/ambiguous \ + refs/remotes/remote/ambiguous \ + refs/remotes/remote/branch-in-remote + do + git update-ref $remote_ref master && + test_when_finished "git update-ref -d $remote_ref" + done && + ( + cur=mat && + __git_refs "" 1 "" "$cur" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'teardown after filtering matching refs' ' + git branch -d matching/branch && + git tag -d matching/tag && + git update-ref -d refs/remotes/other/matching/branch-in-other && + git -C otherrepo branch -D matching/branch-in-other +' + +test_expect_success '__git_refs - for-each-ref format specifiers in prefix' ' + cat >expected <<-EOF && + evil-%%-%42-%(refname)..master + EOF + ( + cur="evil-%%-%42-%(refname)..mas" && + __git_refs "" "" "evil-%%-%42-%(refname).." mas >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_complete_refs - simple' ' + sed -e "s/Z$//" >expected <<-EOF && + HEAD Z + master Z + matching-branch Z + other/branch-in-other Z + other/master-in-other Z + matching-tag Z + EOF + ( + cur= && + __git_complete_refs && + print_comp + ) && + test_cmp expected out +' + +test_expect_success '__git_complete_refs - matching' ' + sed -e "s/Z$//" >expected <<-EOF && + matching-branch Z + matching-tag Z + EOF + ( + cur=mat && + __git_complete_refs && + print_comp + ) && + test_cmp expected out +' + +test_expect_success '__git_complete_refs - remote' ' + sed -e "s/Z$//" >expected <<-EOF && + HEAD Z + branch-in-other Z + master-in-other Z + EOF + ( + cur= + __git_complete_refs --remote=other && + print_comp + ) && + test_cmp expected out +' + +test_expect_success '__git_complete_refs - track' ' + sed -e "s/Z$//" >expected <<-EOF && + HEAD Z + master Z + matching-branch Z + other/branch-in-other Z + other/master-in-other Z + matching-tag Z + branch-in-other Z + master-in-other Z + EOF + ( + cur= + __git_complete_refs --track && + print_comp + ) && + test_cmp expected out +' + +test_expect_success '__git_complete_refs - current word' ' + sed -e "s/Z$//" >expected <<-EOF && + matching-branch Z + matching-tag Z + EOF + ( + cur="--option=mat" && + __git_complete_refs --cur="${cur#*=}" && + print_comp + ) && + test_cmp expected out +' + +test_expect_success '__git_complete_refs - prefix' ' + sed -e "s/Z$//" >expected <<-EOF && + v1.0..matching-branch Z + v1.0..matching-tag Z + EOF + ( + cur=v1.0..mat && + __git_complete_refs --pfx=v1.0.. --cur=mat && + print_comp + ) && + test_cmp expected out +' + +test_expect_success '__git_complete_refs - suffix' ' + cat >expected <<-EOF && + HEAD. + master. + matching-branch. + other/branch-in-other. + other/master-in-other. + matching-tag. + EOF + ( + cur= && + __git_complete_refs --sfx=. && + print_comp + ) && + test_cmp expected out +' + +test_expect_success '__git_complete_fetch_refspecs - simple' ' + sed -e "s/Z$//" >expected <<-EOF && + HEAD:HEAD Z + branch-in-other:branch-in-other Z + master-in-other:master-in-other Z + EOF + ( + cur= && + __git_complete_fetch_refspecs other && + print_comp + ) && + test_cmp expected out +' + +test_expect_success '__git_complete_fetch_refspecs - matching' ' + sed -e "s/Z$//" >expected <<-EOF && + branch-in-other:branch-in-other Z + EOF + ( + cur=br && + __git_complete_fetch_refspecs other "" br && + print_comp + ) && + test_cmp expected out +' + +test_expect_success '__git_complete_fetch_refspecs - prefix' ' + sed -e "s/Z$//" >expected <<-EOF && + +HEAD:HEAD Z + +branch-in-other:branch-in-other Z + +master-in-other:master-in-other Z + EOF + ( + cur="+" && + __git_complete_fetch_refspecs other "+" "" && + print_comp + ) && + test_cmp expected out +' + +test_expect_success '__git_complete_fetch_refspecs - fully qualified' ' + sed -e "s/Z$//" >expected <<-EOF && + refs/heads/branch-in-other:refs/heads/branch-in-other Z + refs/heads/master-in-other:refs/heads/master-in-other Z + refs/tags/tag-in-other:refs/tags/tag-in-other Z + EOF + ( + cur=refs/ && + __git_complete_fetch_refspecs other "" refs/ && + print_comp + ) && + test_cmp expected out +' + +test_expect_success '__git_complete_fetch_refspecs - fully qualified & prefix' ' + sed -e "s/Z$//" >expected <<-EOF && + +refs/heads/branch-in-other:refs/heads/branch-in-other Z + +refs/heads/master-in-other:refs/heads/master-in-other Z + +refs/tags/tag-in-other:refs/tags/tag-in-other Z + EOF + ( + cur=+refs/ && + __git_complete_fetch_refspecs other + refs/ && + print_comp + ) && + test_cmp expected out +' + +test_expect_success 'teardown after ref completion' ' + git branch -d matching-branch && + git tag -d matching-tag && + git remote remove other +' + test_expect_success '__git_get_config_variables' ' cat >expect <<-EOF && name-1 @@ -439,14 +1237,20 @@ test_expect_success 'double dash "git" itself' ' test_expect_success 'double dash "git checkout"' ' test_completion "git checkout --" <<-\EOF --quiet Z + --detach Z + --track Z + --orphan=Z --ours Z --theirs Z - --track Z - --no-track Z --merge Z - --conflict= - --orphan Z + --conflict=Z --patch Z + --ignore-skip-worktree-bits Z + --ignore-other-worktrees Z + --recurse-submodules Z + --progress Z + --no-track Z + --no-recurse-submodules Z EOF ' @@ -480,7 +1284,12 @@ test_expect_success 'general options plus command' ' test_completion "git --namespace=foo check" "checkout " && test_completion "git --paginate check" "checkout " && test_completion "git --info-path check" "checkout " && - test_completion "git --no-replace-objects check" "checkout " + test_completion "git --no-replace-objects check" "checkout " && + test_completion "git --git-dir some/path check" "checkout " && + test_completion "git -c conf.var=value check" "checkout " && + test_completion "git -C some/path check" "checkout " && + test_completion "git --work-tree some/path check" "checkout " && + test_completion "git --namespace name/space check" "checkout " ' test_expect_success 'git --help completion' ' @@ -488,10 +1297,10 @@ test_expect_success 'git --help completion' ' test_completion "git --help core" "core-tutorial " ' -test_expect_success 'setup for ref completion' ' +test_expect_success 'setup for integration tests' ' echo content >file1 && echo more >file2 && - git add . && + git add file1 file2 && git commit -m one && git branch mybranch && git tag mytag @@ -505,6 +1314,12 @@ test_expect_success 'checkout completes ref names' ' EOF ' +test_expect_success 'git -C <path> checkout uses the right repo' ' + test_completion "git -C subdir -C subsubdir -C .. -C ../otherrepo checkout b" <<-\EOF + branch-in-other Z + EOF +' + test_expect_success 'show completes all refs' ' test_completion "git show m" <<-\EOF master Z @@ -522,7 +1337,7 @@ test_expect_success '<ref>: completes paths' ' test_expect_success 'complete tree filename with spaces' ' echo content >"name with spaces" && - git add . && + git add "name with spaces" && git commit -m spaces && test_completion "git show HEAD:nam" <<-\EOF name with spaces Z @@ -531,7 +1346,7 @@ test_expect_success 'complete tree filename with spaces' ' test_expect_success 'complete tree filename with metacharacters' ' echo content >"name with \${meta}" && - git add . && + git add "name with \${meta}" && git commit -m meta && test_completion "git show HEAD:nam" <<-\EOF name with ${meta} Z @@ -639,6 +1454,12 @@ test_expect_success 'completion used <cmd> completion for alias: !f() { : git <c EOF ' +test_expect_success 'completion without explicit _git_xxx function' ' + test_completion "git version --" <<-\EOF + --build-options Z + EOF +' + test_expect_failure 'complete with tilde expansion' ' git init tmp && cd tmp && test_when_finished "cd .. && rm -rf tmp" && @@ -648,4 +1469,69 @@ test_expect_failure 'complete with tilde expansion' ' test_completion "git add ~/tmp/" "~/tmp/file" ' +test_expect_success 'setup other remote for remote reference completion' ' + git remote add other otherrepo && + git fetch other +' + +for flag in -d --delete +do + test_expect_success "__git_complete_remote_or_refspec - push $flag other" ' + sed -e "s/Z$//" >expected <<-EOF && + master-in-other Z + EOF + ( + words=(git push '$flag' other ma) && + cword=${#words[@]} cur=${words[cword-1]} && + __git_complete_remote_or_refspec && + print_comp + ) && + test_cmp expected out + ' + + test_expect_failure "__git_complete_remote_or_refspec - push other $flag" ' + sed -e "s/Z$//" >expected <<-EOF && + master-in-other Z + EOF + ( + words=(git push other '$flag' ma) && + cword=${#words[@]} cur=${words[cword-1]} && + __git_complete_remote_or_refspec && + print_comp + ) && + test_cmp expected out + ' +done + +test_expect_success 'sourcing the completion script clears cached commands' ' + __git_compute_all_commands && + verbose test -n "$__git_all_commands" && + . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && + verbose test -z "$__git_all_commands" +' + +test_expect_success 'sourcing the completion script clears cached porcelain commands' ' + __git_compute_porcelain_commands && + verbose test -n "$__git_porcelain_commands" && + . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && + verbose test -z "$__git_porcelain_commands" +' + +test_expect_success !GETTEXT_POISON 'sourcing the completion script clears cached merge strategies' ' + __git_compute_merge_strategies && + verbose test -n "$__git_merge_strategies" && + . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && + verbose test -z "$__git_merge_strategies" +' + +test_expect_success 'sourcing the completion script clears cached --options' ' + __gitcomp_builtin checkout && + verbose test -n "$__gitcomp_builtin_checkout" && + __gitcomp_builtin notes_edit && + verbose test -n "$__gitcomp_builtin_notes_edit" && + . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && + verbose test -z "$__gitcomp_builtin_checkout" && + verbose test -z "$__gitcomp_builtin_notes_edit" +' + test_done diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh index 49d58e6726..8f5c811dd7 100755 --- a/t/t9903-bash-prompt.sh +++ b/t/t9903-bash-prompt.sh @@ -67,7 +67,7 @@ repo_with_newline='repo with newline' -if mkdir "$repo_with_newline" 2>/dev/null +if test_have_prereq !MINGW && mkdir "$repo_with_newline" 2>/dev/null then test_set_prereq FUNNYNAMES else @@ -107,7 +107,7 @@ test_expect_success 'prompt - describe detached head - contains' ' ' test_expect_success 'prompt - describe detached head - branch' ' - printf " ((b1~1))" >expected && + printf " ((tags/t2~1))" >expected && git checkout b1^ && test_when_finished "git checkout master" && ( @@ -177,7 +177,7 @@ test_expect_success 'prompt - interactive rebase' ' git checkout b1 && test_when_finished "git checkout master" && git rebase -i HEAD^ && - test_when_finished "git rebase --abort" + test_when_finished "git rebase --abort" && __git_ps1 >"$actual" && test_cmp expected "$actual" ' @@ -273,11 +273,36 @@ test_expect_success 'prompt - dirty status indicator - dirty index and worktree' test_cmp expected "$actual" ' -test_expect_success 'prompt - dirty status indicator - before root commit' ' - printf " (master #)" >expected && +test_expect_success 'prompt - dirty status indicator - orphan branch - clean' ' + printf " (orphan #)" >expected && + test_when_finished "git checkout master" && + git checkout --orphan orphan && + git reset --hard && + ( + GIT_PS1_SHOWDIRTYSTATE=y && + __git_ps1 >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - dirty status indicator - orphan branch - dirty index' ' + printf " (orphan +)" >expected && + test_when_finished "git checkout master" && + git checkout --orphan orphan && + ( + GIT_PS1_SHOWDIRTYSTATE=y && + __git_ps1 >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - dirty status indicator - orphan branch - dirty index and worktree' ' + printf " (orphan *+)" >expected && + test_when_finished "git checkout master" && + git checkout --orphan orphan && + >file && ( GIT_PS1_SHOWDIRTYSTATE=y && - cd otherrepo && __git_ps1 >"$actual" ) && test_cmp expected "$actual" @@ -397,6 +422,31 @@ test_expect_success 'prompt - untracked files status indicator - untracked files test_cmp expected "$actual" ' +test_expect_success 'prompt - untracked files status indicator - empty untracked dir' ' + printf " (master)" >expected && + mkdir otherrepo/untracked-dir && + test_when_finished "rm -rf otherrepo/untracked-dir" && + ( + GIT_PS1_SHOWUNTRACKEDFILES=y && + cd otherrepo && + __git_ps1 >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - untracked files status indicator - non-empty untracked dir' ' + printf " (master %%)" >expected && + mkdir otherrepo/untracked-dir && + test_when_finished "rm -rf otherrepo/untracked-dir" && + >otherrepo/untracked-dir/untracked-file && + ( + GIT_PS1_SHOWUNTRACKEDFILES=y && + cd otherrepo && + __git_ps1 >"$actual" + ) && + test_cmp expected "$actual" +' + test_expect_success 'prompt - untracked files status indicator - untracked files outside cwd' ' printf " (master %%)" >expected && ( @@ -685,22 +735,12 @@ test_expect_success 'prompt - hide if pwd ignored - env var set, config unset, p test_cmp expected "$actual" ' -test_expect_success 'prompt - hide if pwd ignored - inside gitdir (stdout)' ' +test_expect_success 'prompt - hide if pwd ignored - inside gitdir' ' printf " (GIT_DIR!)" >expected && ( GIT_PS1_HIDE_IF_PWD_IGNORED=y && cd .git && - __git_ps1 >"$actual" 2>/dev/null - ) && - test_cmp expected "$actual" -' - -test_expect_success 'prompt - hide if pwd ignored - inside gitdir (stderr)' ' - printf "" >expected && - ( - GIT_PS1_HIDE_IF_PWD_IGNORED=y && - cd .git && - __git_ps1 >/dev/null 2>"$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index e8d3c0fdbc..7d620bf2a9 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -42,6 +42,7 @@ test_decode_color () { function name(n) { if (n == 0) return "RESET"; if (n == 1) return "BOLD"; + if (n == 7) return "REVERSE"; if (n == 30) return "BLACK"; if (n == 31) return "RED"; if (n == 32) return "GREEN"; @@ -81,6 +82,10 @@ test_decode_color () { ' } +lf_to_nul () { + perl -pe 'y/\012/\000/' +} + nul_to_q () { perl -pe 'y/\000/Q/' } @@ -132,29 +137,37 @@ test_tick () { export GIT_COMMITTER_DATE GIT_AUTHOR_DATE } -# Stop execution and start a shell. This is useful for debugging tests and -# only makes sense together with "-v". +# Stop execution and start a shell. This is useful for debugging tests. # # Be sure to remove all invocations of this command before submitting. test_pause () { - if test "$verbose" = t; then - "$SHELL_PATH" <&6 >&3 2>&4 - else - error >&5 "test_pause requires --verbose" - fi + "$SHELL_PATH" <&6 >&5 2>&7 +} + +# Wrap git in gdb. Adding this to a command can make it easier to +# understand what is going on in a failing test. +# +# Example: "debug git checkout master". +debug () { + GIT_TEST_GDB=1 "$@" <&6 >&5 2>&7 } -# Call test_commit with the arguments "<message> [<file> [<contents> [<tag>]]]" +# Call test_commit with the arguments +# [-C <directory>] <message> [<file> [<contents> [<tag>]]]" # # This will commit a file with the given contents and the given commit # message, and tag the resulting commit with the given tag name. # # <file>, <contents>, and <tag> all default to <message>. +# +# If the first argument is "-C", the second argument is used as a path for +# the git invocations. test_commit () { notick= && signoff= && + indir= && while test $# != 0 do case "$1" in @@ -164,21 +177,26 @@ test_commit () { --signoff) signoff="$1" ;; + -C) + indir="$2" + shift + ;; *) break ;; esac shift done && + indir=${indir:+"$indir"/} && file=${2:-"$1.t"} && - echo "${3-$1}" > "$file" && - git add "$file" && + echo "${3-$1}" > "$indir$file" && + git ${indir:+ -C "$indir"} add "$file" && if test -z "$notick" then test_tick fi && - git commit $signoff -m "$1" && - git tag "${4:-$1}" + git ${indir:+ -C "$indir"} commit $signoff -m "$1" && + git ${indir:+ -C "$indir"} tag "${4:-$1}" } # Call test_merge with the arguments "<message> <commit>", where <commit> @@ -199,9 +217,21 @@ test_chmod () { git update-index --add "--chmod=$@" } +# Get the modebits from a file. +test_modebits () { + ls -l "$1" | sed -e 's|^\(..........\).*|\1|' +} + # Unset a configuration variable, but don't fail if it doesn't exist. test_unconfig () { - git config --unset-all "$@" + config_dir= + if test "$1" = -C + then + shift + config_dir=$1 + shift + fi + git ${config_dir:+-C "$config_dir"} config --unset-all "$@" config_status=$? case "$config_status" in 5) # ok, nothing to unset @@ -213,8 +243,15 @@ test_unconfig () { # Set git config, automatically unsetting it after the test is over. test_config () { - test_when_finished "test_unconfig '$1'" && - git config "$@" + config_dir= + if test "$1" = -C + then + shift + config_dir=$1 + shift + fi + test_when_finished "test_unconfig ${config_dir:+-C '$config_dir'} '$1'" && + git ${config_dir:+-C "$config_dir"} config "$@" } test_config_global () { @@ -547,6 +584,21 @@ test_line_count () { fi } +# Returns success if a comma separated string of keywords ($1) contains a +# given keyword ($2). +# Examples: +# `list_contains "foo,bar" bar` returns 0 +# `list_contains "foo" bar` returns 1 + +list_contains () { + case ",$1," in + *,$2,*) + return 0 + ;; + esac + return 1 +} + # This is not among top-level (test_expect_success | test_expect_failure) # but is a prefix that can be used in the test script, like: # @@ -558,25 +610,49 @@ test_line_count () { # # Writing this as "! git checkout ../outerspace" is wrong, because # the failure could be due to a segv. We want a controlled failure. +# +# Accepts the following options: +# +# ok=<signal-name>[,<...>]: +# Don't treat an exit caused by the given signal as error. +# Multiple signals can be specified as a comma separated list. +# Currently recognized signal names are: sigpipe, success. +# (Don't use 'success', use 'test_might_fail' instead.) test_must_fail () { - "$@" + case "$1" in + ok=*) + _test_ok=${1#ok=} + shift + ;; + *) + _test_ok= + ;; + esac + "$@" 2>&7 exit_code=$? - if test $exit_code = 0; then - echo >&2 "test_must_fail: command succeeded: $*" + if test $exit_code -eq 0 && ! list_contains "$_test_ok" success + then + echo >&4 "test_must_fail: command succeeded: $*" return 1 - elif test $exit_code -gt 129 && test $exit_code -le 192; then - echo >&2 "test_must_fail: died by signal: $*" + elif test_match_signal 13 $exit_code && list_contains "$_test_ok" sigpipe + then + return 0 + elif test $exit_code -gt 129 && test $exit_code -le 192 + then + echo >&4 "test_must_fail: died by signal $(($exit_code - 128)): $*" return 1 - elif test $exit_code = 127; then - echo >&2 "test_must_fail: command not found: $*" + elif test $exit_code -eq 127 + then + echo >&4 "test_must_fail: command not found: $*" return 1 - elif test $exit_code = 126; then - echo >&2 "test_must_fail: valgrind error: $*" + elif test $exit_code -eq 126 + then + echo >&4 "test_must_fail: valgrind error: $*" return 1 fi return 0 -} +} 7>&2 2>&4 # Similar to test_must_fail, but tolerates success, too. This is # meant to be used in contexts like: @@ -588,19 +664,12 @@ test_must_fail () { # # Writing "git config --unset all.configuration || :" would be wrong, # because we want to notice if it fails due to segv. +# +# Accepts the same options as test_must_fail. test_might_fail () { - "$@" - exit_code=$? - if test $exit_code -gt 129 && test $exit_code -le 192; then - echo >&2 "test_might_fail: died by signal: $*" - return 1 - elif test $exit_code = 127; then - echo >&2 "test_might_fail: command not found: $*" - return 1 - fi - return 0 -} + test_must_fail ok=success "$@" 2>&7 +} 7>&2 2>&4 # Similar to test_must_fail and test_might_fail, but check that a # given command exited with a given exit code. Meant to be used as: @@ -612,16 +681,16 @@ test_might_fail () { test_expect_code () { want_code=$1 shift - "$@" + "$@" 2>&7 exit_code=$? if test $exit_code = $want_code then return 0 fi - echo >&2 "test_expect_code: command exited with $exit_code, we wanted $want_code $*" + echo >&4 "test_expect_code: command exited with $exit_code, we wanted $want_code $*" return 1 -} +} 7>&2 2>&4 # test_cmp is a helper function to compare actual and expected output. # You can use it like: @@ -646,12 +715,66 @@ test_cmp_bin() { cmp "$@" } +# Use this instead of test_cmp to compare files that contain expected and +# actual output from git commands that can be translated. When running +# under GETTEXT_POISON this pretends that the command produced expected +# results. +test_i18ncmp () { + test -n "$GETTEXT_POISON" || test_cmp "$@" +} + +# Use this instead of "grep expected-string actual" to see if the +# output from a git command that can be translated either contains an +# expected string, or does not contain an unwanted one. When running +# under GETTEXT_POISON this pretends that the command produced expected +# results. +test_i18ngrep () { + eval "last_arg=\${$#}" + + test -f "$last_arg" || + error "bug in the test script: test_i18ngrep requires a file" \ + "to read as the last parameter" + + if test $# -lt 2 || + { test "x!" = "x$1" && test $# -lt 3 ; } + then + error "bug in the test script: too few parameters to test_i18ngrep" + fi + + if test -n "$GETTEXT_POISON" + then + # pretend success + return 0 + fi + + if test "x!" = "x$1" + then + shift + ! grep "$@" && return 0 + + echo >&4 "error: '! grep $@' did find a match in:" + else + grep "$@" && return 0 + + echo >&4 "error: 'grep $@' didn't find a match in:" + fi + + if test -s "$last_arg" + then + cat >&4 "$last_arg" + else + echo >&4 "<File '$last_arg' is empty>" + fi + + return 1 +} + # Call any command "$@" but be more verbose about its # failure. This is handy for commands like "test" which do # not output anything when they fail. verbose () { "$@" && return 0 - echo >&2 "command failed: $(git rev-parse --sq-quote "$@")" + echo >&4 "command failed: $(git rev-parse --sq-quote "$@")" return 1 } @@ -659,6 +782,7 @@ verbose () { # otherwise. test_must_be_empty () { + test_path_is_file "$1" && if test -s "$1" then echo "'$1' is not empty, it contains:" @@ -674,20 +798,13 @@ test_cmp_rev () { test_cmp expect.rev actual.rev } -# Print a sequence of numbers or letters in increasing order. This is -# similar to GNU seq(1), but the latter might not be available -# everywhere (and does not do letters). It may be used like: +# Print a sequence of integers in increasing order, either with +# two arguments (start and end): +# +# test_seq 1 5 -- outputs 1 2 3 4 5 one line at a time # -# for i in $(test_seq 100) -# do -# for j in $(test_seq 10 20) -# do -# for k in $(test_seq a z) -# do -# echo $i-$j-$k -# done -# done -# done +# or with one argument (end), in which case it starts counting +# from 1. test_seq () { case $# in @@ -695,7 +812,12 @@ test_seq () { 2) ;; *) error "bug in the test script: not 1 or 2 parameters to test_seq" ;; esac - perl -le 'print for $ARGV[0]..$ARGV[1]' -- "$@" + test_seq_counter__=$1 + while test "$test_seq_counter__" -le "$2" + do + echo "$test_seq_counter__" + test_seq_counter__=$(( $test_seq_counter__ + 1 )) + done } # This function can be used to schedule some commands to be run @@ -722,6 +844,11 @@ test_seq () { # what went wrong. test_when_finished () { + # We cannot detect when we are in a subshell in general, but by + # doing so on Bash is better than nothing (the test will + # silently pass on other shells). + test "${BASH_SUBSHELL-0}" = 0 || + error "bug in test script: test_when_finished does nothing in a subshell" test_cleanup="{ $* } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup" } @@ -766,8 +893,8 @@ test_write_lines () { } perl () { - command "$PERL_PATH" "$@" -} + command "$PERL_PATH" "$@" 2>&7 +} 7>&2 2>&4 # Is the value one of the various ways to spell a boolean true/false? test_normalize_bool () { @@ -892,3 +1019,103 @@ mingw_read_file_strip_cr_ () { eval "$1=\$$1\$line" done } + +# Like "env FOO=BAR some-program", but run inside a subshell, which means +# it also works for shell functions (though those functions cannot impact +# the environment outside of the test_env invocation). +test_env () { + ( + while test $# -gt 0 + do + case "$1" in + *=*) + eval "${1%%=*}=\${1#*=}" + eval "export ${1%%=*}" + shift + ;; + *) + "$@" 2>&7 + exit + ;; + esac + done + ) +} 7>&2 2>&4 + +# Returns true if the numeric exit code in "$2" represents the expected signal +# in "$1". Signals should be given numerically. +test_match_signal () { + if test "$2" = "$((128 + $1))" + then + # POSIX + return 0 + elif test "$2" = "$((256 + $1))" + then + # ksh + return 0 + fi + return 1 +} + +# Read up to "$1" bytes (or to EOF) from stdin and write them to stdout. +test_copy_bytes () { + perl -e ' + my $len = $ARGV[1]; + while ($len > 0) { + my $s; + my $nread = sysread(STDIN, $s, $len); + die "cannot read: $!" unless defined($nread); + last unless $nread; + print $s; + $len -= $nread; + } + ' - "$1" +} + +# run "$@" inside a non-git directory +nongit () { + test -d non-repo || + mkdir non-repo || + return 1 + + ( + GIT_CEILING_DIRECTORIES=$(pwd) && + export GIT_CEILING_DIRECTORIES && + cd non-repo && + "$@" 2>&7 + ) +} 7>&2 2>&4 + +# convert stdin to pktline representation; note that empty input becomes an +# empty packet, not a flush packet (for that you can just print 0000 yourself). +packetize() { + cat >packetize.tmp && + len=$(wc -c <packetize.tmp) && + printf '%04x%s' "$(($len + 4))" && + cat packetize.tmp && + rm -f packetize.tmp +} + +# Parse the input as a series of pktlines, writing the result to stdout. +# Sideband markers are removed automatically, and the output is routed to +# stderr if appropriate. +# +# NUL bytes are converted to "\\0" for ease of parsing with text tools. +depacketize () { + perl -e ' + while (read(STDIN, $len, 4) == 4) { + if ($len eq "0000") { + print "FLUSH\n"; + } else { + read(STDIN, $buf, hex($len) - 4); + $buf =~ s/\0/\\0/g; + if ($buf =~ s/^[\x2\x3]//) { + print STDERR $buf; + } else { + $buf =~ s/^\x1//; + print $buf; + } + } + } + ' +} diff --git a/t/test-lib.sh b/t/test-lib.sh index 39da9c2d99..ea2bbaaa7a 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -15,9 +15,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see http://www.gnu.org/licenses/ . -# Keep the original TERM for say_color -ORIGINAL_TERM=$TERM - # Test the binaries we have just built. The tests are kept in # t/ subdirectory and are run in 'trash directory' subdirectory. if test -z "$TEST_DIRECTORY" @@ -39,6 +36,19 @@ then fi GIT_BUILD_DIR="$TEST_DIRECTORY"/.. +# If we were built with ASAN, it may complain about leaks +# of program-lifetime variables. Disable it by default to lower +# the noise level. This needs to happen at the start of the script, +# before we even do our "did we build git yet" check (since we don't +# want that one to complain to stderr). +: ${ASAN_OPTIONS=detect_leaks=0:abort_on_error=1} +export ASAN_OPTIONS + +# If LSAN is in effect we _do_ want leak checking, but we still +# want to abort so that we notice the problems. +: ${LSAN_OPTIONS=abort_on_error=1} +export LSAN_OPTIONS + ################################################################ # It appears that people try to run tests without building... "$GIT_BUILD_DIR/git" >/dev/null @@ -57,23 +67,33 @@ case "$GIT_TEST_TEE_STARTED, $* " in done,*) # do not redirect again ;; -*' --tee '*|*' --va'*) +*' --tee '*|*' --va'*|*' --verbose-log '*) mkdir -p "$TEST_OUTPUT_DIRECTORY/test-results" BASE="$TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" .sh)" - (GIT_TEST_TEE_STARTED=done ${SHELL_PATH} "$0" "$@" 2>&1; - echo $? > $BASE.exit) | tee $BASE.out - test "$(cat $BASE.exit)" = 0 + + # Make this filename available to the sub-process in case it is using + # --verbose-log. + GIT_TEST_TEE_OUTPUT_FILE=$BASE.out + export GIT_TEST_TEE_OUTPUT_FILE + + # Truncate before calling "tee -a" to get rid of the results + # from any previous runs. + >"$GIT_TEST_TEE_OUTPUT_FILE" + + (GIT_TEST_TEE_STARTED=done ${TEST_SHELL_PATH} "$0" "$@" 2>&1; + echo $? >"$BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE" + test "$(cat "$BASE.exit")" = 0 exit ;; esac # For repeatability, reset the environment to known value. +# TERM is sanitized below, after saving color control sequences. LANG=C LC_ALL=C PAGER=cat TZ=UTC -TERM=dumb -export LANG LC_ALL PAGER TERM TZ +export LANG LC_ALL PAGER TZ EDITOR=: # A call to "unset" with no arguments causes at least Solaris 10 # /usr/xpg4/bin/sh and /bin/ksh to bail out. So keep the unsets @@ -84,7 +104,6 @@ unset VISUAL EMAIL LANGUAGE COLUMNS $("$PERL_PATH" -e ' my $ok = join("|", qw( TRACE DEBUG - USE_LOOKUP TEST .*_TEST PROVE @@ -92,10 +111,12 @@ unset VISUAL EMAIL LANGUAGE COLUMNS $("$PERL_PATH" -e ' UNZIP PERF_ CURL_VERBOSE + TRACE_CURL )); my @vars = grep(/^GIT_/ && !/^GIT_($ok)/o, @env); print join("\n", @vars); ') +unset XDG_CACHE_HOME unset XDG_CONFIG_HOME unset GITPERLLIB GIT_AUTHOR_EMAIL=author@example.com @@ -140,9 +161,6 @@ else } fi -: ${ASAN_OPTIONS=detect_leaks=0} -export ASAN_OPTIONS - # Protect ourselves from common misconfiguration to export # CDPATH into the environment unset CDPATH @@ -158,13 +176,17 @@ esac # Convenience # -# A regexp to match 5 and 40 hexdigits +# A regexp to match 5, 35 and 40 hexdigits _x05='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' -_x40="$_x05$_x05$_x05$_x05$_x05$_x05$_x05$_x05" +_x35="$_x05$_x05$_x05$_x05$_x05$_x05$_x05" +_x40="$_x35$_x05" # Zero SHA-1 _z40=0000000000000000000000000000000000000000 +EMPTY_TREE=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +EMPTY_BLOB=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 + # Line feed LF=' ' @@ -173,7 +195,7 @@ LF=' # when case-folding filenames u200c=$(printf '\342\200\214') -export _x05 _x40 _z40 LF u200c +export _x05 _x35 _x40 _z40 LF u200c EMPTY_TREE EMPTY_BLOB # Each test should start with something like this, after copyright notices: # @@ -181,8 +203,14 @@ export _x05 _x40 _z40 LF u200c # This test checks if command xyzzy does the right thing... # ' # . ./test-lib.sh +test "x$TERM" != "xdumb" && ( + test -t 1 && + tput bold >/dev/null 2>&1 && + tput setaf 1 >/dev/null 2>&1 && + tput sgr0 >/dev/null 2>&1 + ) && + color=t -unset color while test "$#" -ne 0 do case "$1" in @@ -199,13 +227,13 @@ do } run_list=$1; shift ;; --run=*) - run_list=$(expr "z$1" : 'z[^=]*=\(.*\)'); shift ;; + run_list=${1#--*=}; shift ;; -h|--h|--he|--hel|--help) help=t; shift ;; -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) verbose=t; shift ;; --verbose-only=*) - verbose_only=$(expr "z$1" : 'z[^=]*=\(.*\)') + verbose_only=${1#--*=} shift ;; -q|--q|--qu|--qui|--quie|--quiet) # Ignore --quiet under a TAP::Harness. Saying how many tests @@ -219,15 +247,15 @@ do valgrind=memcheck shift ;; --valgrind=*) - valgrind=$(expr "z$1" : 'z[^=]*=\(.*\)') + valgrind=${1#--*=} shift ;; --valgrind-only=*) - valgrind_only=$(expr "z$1" : 'z[^=]*=\(.*\)') + valgrind_only=${1#--*=} shift ;; --tee) shift ;; # was handled already --root=*) - root=$(expr "z$1" : 'z[^=]*=\(.*\)') + root=${1#--*=} shift ;; --chain-lint) GIT_TEST_CHAIN_LINT=1 @@ -236,8 +264,27 @@ do GIT_TEST_CHAIN_LINT=0 shift ;; -x) - trace=t - verbose=t + # Some test scripts can't be reliably traced with '-x', + # unless the test is run with a Bash version supporting + # BASH_XTRACEFD (introduced in Bash v4.1). Check whether + # this test is marked as such, and ignore '-x' if it + # isn't executed with a suitable Bash version. + if test -z "$test_untraceable" || { + test -n "$BASH_VERSION" && { + test ${BASH_VERSINFO[0]} -gt 4 || { + test ${BASH_VERSINFO[0]} -eq 4 && + test ${BASH_VERSINFO[1]} -ge 1 + } + } + } + then + trace=t + else + echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" + fi + shift ;; + --verbose-log) + verbose_log=t shift ;; *) echo "error: unknown test option '$1'" >&2; exit 1 ;; @@ -250,9 +297,52 @@ then test -z "$verbose" && verbose_only="$valgrind_only" elif test -n "$valgrind" then + test -z "$verbose_log" && verbose=t +fi + +if test -n "$trace" && test -z "$verbose_log" +then verbose=t fi +if test -n "$color" +then + # Save the color control sequences now rather than run tput + # each time say_color() is called. This is done for two + # reasons: + # * TERM will be changed to dumb + # * HOME will be changed to a temporary directory and tput + # might need to read ~/.terminfo from the original HOME + # directory to get the control sequences + # Note: This approach assumes the control sequences don't end + # in a newline for any terminal of interest (command + # substitutions strip trailing newlines). Given that most + # (all?) terminals in common use are related to ECMA-48, this + # shouldn't be a problem. + say_color_error=$(tput bold; tput setaf 1) # bold red + say_color_skip=$(tput setaf 4) # blue + say_color_warn=$(tput setaf 3) # brown/yellow + say_color_pass=$(tput setaf 2) # green + say_color_info=$(tput setaf 6) # cyan + say_color_reset=$(tput sgr0) + say_color_="" # no formatting for normal text + say_color () { + test -z "$1" && test -n "$quiet" && return + eval "say_color_color=\$say_color_$1" + shift + printf "%s\\n" "$say_color_color$*$say_color_reset" + } +else + say_color() { + test -z "$1" && test -n "$quiet" && return + shift + printf "%s\n" "$*" + } +fi + +TERM=dumb +export TERM + error () { say_color error "error: $*" GIT_EXIT_OK=t @@ -263,6 +353,16 @@ say () { say_color info "$*" } +if test -n "$HARNESS_ACTIVE" +then + if test "$verbose" = t || test -n "$verbose_only" + then + printf 'Bail out! %s\n' \ + 'verbose mode forbidden under TAP harness; try --verbose-log' + exit 1 + fi +fi + test "${test_description}" != "" || error "Test script did not set test_description." @@ -274,13 +374,30 @@ fi exec 5>&1 exec 6<&0 -if test "$verbose" = "t" +exec 7>&2 +if test "$verbose_log" = "t" +then + exec 3>>"$GIT_TEST_TEE_OUTPUT_FILE" 4>&3 +elif test "$verbose" = "t" then exec 4>&2 3>&1 else exec 4>/dev/null 3>/dev/null fi +# Send any "-x" output directly to stderr to avoid polluting tests +# which capture stderr. We can do this unconditionally since it +# has no effect if tracing isn't turned on. +# +# Note that this sets up the trace fd as soon as we assign the variable, so it +# must come after the creation of descriptor 4 above. Likewise, we must never +# unset this, as it has the side effect of closing descriptor 4, which we +# use to show verbose tests to the user. +# +# Note also that we don't need or want to export it. The tracing is local to +# this shell, and we would not want to influence any shells we exec. +BASH_XTRACEFD=4 + test_failure=0 test_count=0 test_fixed=0 @@ -490,6 +607,12 @@ maybe_setup_valgrind () { fi } +want_trace () { + test "$trace" = t && { + test "$verbose" = t || test "$verbose_log" = t + } +} + # This is a separate function because some tests use # "return" to end a test_expect_success block early # (and we want to make sure we run any cleanup like @@ -497,31 +620,45 @@ maybe_setup_valgrind () { test_eval_inner_ () { # Do not add anything extra (including LF) after '$*' eval " - test \"$trace\" = t && set -x + want_trace && set -x $*" } test_eval_ () { - # We run this block with stderr redirected to avoid extra cruft - # during a "-x" trace. Once in "set -x" mode, we cannot prevent + # If "-x" tracing is in effect, then we want to avoid polluting stderr + # with non-test commands. But once in "set -x" mode, we cannot prevent # the shell from printing the "set +x" to turn it off (nor the saving # of $? before that). But we can make sure that the output goes to # /dev/null. # - # The test itself is run with stderr put back to &4 (so either to - # /dev/null, or to the original stderr if --verbose was used). + # There are a few subtleties here: + # + # - we have to redirect descriptor 4 in addition to 2, to cover + # BASH_XTRACEFD + # + # - the actual eval has to come before the redirection block (since + # it needs to see descriptor 4 to set up its stderr) + # + # - likewise, any error message we print must be outside the block to + # access descriptor 4 + # + # - checking $? has to come immediately after the eval, but it must + # be _inside_ the block to avoid polluting the "set -x" output + # + + test_eval_inner_ "$@" </dev/null >&3 2>&4 { - test_eval_inner_ "$@" </dev/null >&3 2>&4 test_eval_ret_=$? - if test "$trace" = t + if want_trace then set +x - if test "$test_eval_ret_" != 0 - then - say_color error >&4 "error: last command exited with \$?=$test_eval_ret_" - fi fi - } 2>/dev/null + } 2>/dev/null 4>&2 + + if test "$test_eval_ret_" != 0 && want_trace + then + say_color error >&4 "error: last command exited with \$?=$test_eval_ret_" + fi return $test_eval_ret_ } @@ -530,12 +667,17 @@ test_run_ () { expecting_failure=$2 if test "${GIT_TEST_CHAIN_LINT:-1}" != 0; then + # turn off tracing for this test-eval, as it simply creates + # confusing noise in the "-x" output + trace_tmp=$trace + trace= # 117 is magic because it is unlikely to match the exit # code of other programs - test_eval_ "(exit 117) && $1" - if test "$?" != 117; then - error "bug in the test script: broken &&-chain: $1" + if test "OK-117" != "$(test_eval_ "(exit 117) && $1${LF}${LF}echo OK-\$?" 3>&1)" + then + error "bug in the test script: broken &&-chain or run-away HERE-DOC: $1" fi + trace=$trace_tmp fi setup_malloc_check @@ -621,9 +763,9 @@ test_done () { test_results_dir="$TEST_OUTPUT_DIRECTORY/test-results" mkdir -p "$test_results_dir" base=${0##*/} - test_results_path="$test_results_dir/${base%.sh}-$$.counts" + test_results_path="$test_results_dir/${base%.sh}.counts" - cat >>"$test_results_path" <<-EOF + cat >"$test_results_path" <<-EOF total $test_count success $test_success fixed $test_fixed @@ -651,26 +793,36 @@ test_done () { fi case "$test_failure" in 0) - # Maybe print SKIP message - if test -n "$skip_all" && test $test_count -gt 0 - then - error "Can't use skip_all after running some tests" - fi - test -z "$skip_all" || skip_all=" # SKIP $skip_all" - if test $test_external_has_tap -eq 0 then if test $test_remaining -gt 0 then say_color pass "# passed all $msg" fi - say "1..$test_count$skip_all" + + # Maybe print SKIP message + test -z "$skip_all" || skip_all="# SKIP $skip_all" + case "$test_count" in + 0) + say "1..$test_count${skip_all:+ $skip_all}" + ;; + *) + test -z "$skip_all" || + say_color warn "$skip_all" + say "1..$test_count" + ;; + esac fi - test -d "$remove_trash" && - cd "$(dirname "$remove_trash")" && - rm -rf "$(basename "$remove_trash")" + if test -z "$debug" + then + test -d "$TRASH_DIRECTORY" || + error "Tests passed but trash directory already removed before test cleanup; aborting" + cd "$TRASH_DIRECTORY/.." && + rm -fr "$TRASH_DIRECTORY" || + error "Tests passed but test cleanup failed; aborting" + fi test_at_end_hook_ exit 0 ;; @@ -716,7 +868,14 @@ then return; base=$(basename "$1") - symlink_target=$GIT_BUILD_DIR/$base + case "$base" in + test-*) + symlink_target="$GIT_BUILD_DIR/t/helper/$base" + ;; + *) + symlink_target="$GIT_BUILD_DIR/$base" + ;; + esac # do not override scripts if test -x "$symlink_target" && test ! -d "$symlink_target" && @@ -735,7 +894,7 @@ then # override all git executables in TEST_DIRECTORY/.. GIT_VALGRIND=$TEST_DIRECTORY/valgrind mkdir -p "$GIT_VALGRIND"/bin - for file in $GIT_BUILD_DIR/git* $GIT_BUILD_DIR/test-* + for file in $GIT_BUILD_DIR/git* $GIT_BUILD_DIR/t/helper/test-* do make_valgrind_symlink $file done @@ -798,16 +957,16 @@ then fi fi -GITPERLLIB="$GIT_BUILD_DIR"/perl/blib/lib:"$GIT_BUILD_DIR"/perl/blib/arch/auto/Git +GITPERLLIB="$GIT_BUILD_DIR"/perl/build/lib export GITPERLLIB test -d "$GIT_BUILD_DIR"/templates/blt || { error "You haven't built things yet, have you?" } -if ! test -x "$GIT_BUILD_DIR"/test-chmtime +if ! test -x "$GIT_BUILD_DIR"/t/helper/test-tool then - echo >&2 'You need to build test-chmtime:' - echo >&2 'Run "make test-chmtime" in the source (toplevel) directory' + echo >&2 'You need to build test-tool:' + echo >&2 'Run "make t/helper/test-tool" in the source (toplevel) directory' exit 1 fi @@ -818,7 +977,6 @@ case "$TRASH_DIRECTORY" in /*) ;; # absolute path is good *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;; esac -test ! -z "$debug" || remove_trash=$TRASH_DIRECTORY rm -fr "$TRASH_DIRECTORY" || { GIT_EXIT_OK=t echo >&5 "FATAL: Cannot prepare test area" @@ -829,52 +987,6 @@ HOME="$TRASH_DIRECTORY" GNUPGHOME="$HOME/gnupg-home-not-used" export HOME GNUPGHOME -# run the tput tests *after* changing HOME (in case ncurses needs -# ~/.terminfo for $TERM) -test -n "${color+set}" || test "x$ORIGINAL_TERM" != "xdumb" && ( - TERM=$ORIGINAL_TERM && - export TERM && - test -t 1 && - tput bold >/dev/null 2>&1 && - tput setaf 1 >/dev/null 2>&1 && - tput sgr0 >/dev/null 2>&1 - ) && - color=t - -if test -n "$color" -then - say_color () { - ( - TERM=$ORIGINAL_TERM - export TERM - case "$1" in - error) - tput bold; tput setaf 1;; # bold red - skip) - tput setaf 4;; # blue - warn) - tput setaf 3;; # brown/yellow - pass) - tput setaf 2;; # green - info) - tput setaf 6;; # cyan - *) - test -n "$quiet" && return;; - esac - shift - printf "%s" "$*" - tput sgr0 - echo - ) - } -else - say_color() { - test -z "$1" && test -n "$quiet" && return - shift - printf "%s\n" "$*" - } -fi - if test -z "$TEST_NO_CREATE_REPO" then test_create_repo "$TRASH_DIRECTORY" @@ -903,14 +1015,17 @@ yes () { y="$*" fi - while echo "$y" + i=0 + while test $i -lt 99 do - : + echo "$y" + i=$(($i+1)) done } # Fix some commands on Windows -case $(uname -s) in +uname_s=$(uname -s) +case $uname_s in *MINGW*) # Windows has its own (incompatible) sort and find sort () { @@ -919,9 +1034,6 @@ case $(uname -s) in find () { /usr/bin/find "$@" } - sum () { - md5sum "$@" - } # git sees Windows-style pwd pwd () { builtin pwd -W @@ -951,8 +1063,11 @@ esac ( COLUMNS=1 && test $COLUMNS = 1 ) && test_set_prereq COLUMNS_CAN_BE_1 test -z "$NO_PERL" && test_set_prereq PERL +test -z "$NO_PTHREADS" && test_set_prereq PTHREADS test -z "$NO_PYTHON" && test_set_prereq PYTHON -test -n "$USE_LIBPCRE" && test_set_prereq LIBPCRE +test -n "$USE_LIBPCRE1$USE_LIBPCRE2" && test_set_prereq PCRE +test -n "$USE_LIBPCRE1" && test_set_prereq LIBPCRE1 +test -n "$USE_LIBPCRE2" && test_set_prereq LIBPCRE2 test -z "$NO_GETTEXT" && test_set_prereq GETTEXT # Can we rely on git's output in the C locale? @@ -965,42 +1080,10 @@ else test_set_prereq C_LOCALE_OUTPUT fi -# Use this instead of test_cmp to compare files that contain expected and -# actual output from git commands that can be translated. When running -# under GETTEXT_POISON this pretends that the command produced expected -# results. -test_i18ncmp () { - test -n "$GETTEXT_POISON" || test_cmp "$@" -} - -# Use this instead of "grep expected-string actual" to see if the -# output from a git command that can be translated either contains an -# expected string, or does not contain an unwanted one. When running -# under GETTEXT_POISON this pretends that the command produced expected -# results. -test_i18ngrep () { - if test -n "$GETTEXT_POISON" - then - : # pretend success - elif test "x!" = "x$1" - then - shift - ! grep "$@" - else - grep "$@" - fi -} - test_lazy_prereq PIPE ' # test whether the filesystem supports FIFOs - case $(uname -s) in - CYGWIN*) - false - ;; - *) - rm -f testfifo && mkfifo testfifo - ;; - esac + test_have_prereq !MINGW,!CYGWIN && + rm -f testfifo && mkfifo testfifo ' test_lazy_prereq SYMLINKS ' @@ -1041,6 +1124,10 @@ test_lazy_prereq EXPENSIVE ' test -n "$GIT_TEST_LONG" ' +test_lazy_prereq EXPENSIVE_ON_WINDOWS ' + test_have_prereq EXPENSIVE || test_have_prereq !MINGW,!CYGWIN +' + test_lazy_prereq USR_BIN_TIME ' test -x /usr/bin/time ' @@ -1050,20 +1137,32 @@ test_lazy_prereq NOT_ROOT ' test "$uid" != 0 ' -# On a filesystem that lacks SANITY, a file can be deleted even if -# the containing directory doesn't have write permissions, or a file -# can be accessed even if the containing directory doesn't have read -# or execute permissions, causing our tests that validate that Git -# works sensibly in such situations. +test_lazy_prereq JGIT ' + type jgit +' + +# SANITY is about "can you correctly predict what the filesystem would +# do by only looking at the permission bits of the files and +# directories?" A typical example of !SANITY is running the test +# suite as root, where a test may expect "chmod -r file && cat file" +# to fail because file is supposed to be unreadable after a successful +# chmod. In an environment (i.e. combination of what filesystem is +# being used and who is running the tests) that lacks SANITY, you may +# be able to delete or create a file when the containing directory +# doesn't have write permissions, or access a file even if the +# containing directory doesn't have read or execute permissions. + test_lazy_prereq SANITY ' mkdir SANETESTD.1 SANETESTD.2 && chmod +w SANETESTD.1 SANETESTD.2 && >SANETESTD.1/x 2>SANETESTD.2/x && chmod -w SANETESTD.1 && + chmod -r SANETESTD.1/x && chmod -rx SANETESTD.2 || error "bug in test sript: cannot prepare SANETESTD" + ! test -r SANETESTD.1/x && ! rm SANETESTD.1/x && ! test -f SANETESTD.2/x status=$? @@ -1073,6 +1172,7 @@ test_lazy_prereq SANITY ' return $status ' +test FreeBSD != $uname_s || GIT_UNZIP=${GIT_UNZIP:-/usr/local/bin/unzip} GIT_UNZIP=${GIT_UNZIP:-unzip} test_lazy_prereq UNZIP ' "$GIT_UNZIP" -v @@ -1083,4 +1183,32 @@ run_with_limited_cmdline () { (ulimit -s 128 && "$@") } -test_lazy_prereq CMDLINE_LIMIT 'run_with_limited_cmdline true' +test_lazy_prereq CMDLINE_LIMIT ' + test_have_prereq !MINGW,!CYGWIN && + run_with_limited_cmdline true +' + +run_with_limited_stack () { + (ulimit -s 128 && "$@") +} + +test_lazy_prereq ULIMIT_STACK_SIZE ' + test_have_prereq !MINGW,!CYGWIN && + run_with_limited_stack true +' + +build_option () { + git version --build-options | + sed -ne "s/^$1: //p" +} + +test_lazy_prereq LONG_IS_64BIT ' + test 8 -le "$(build_option sizeof-long)" +' + +test_lazy_prereq TIME_IS_64BIT 'test-tool date is64bit' +test_lazy_prereq TIME_T_IS_64BIT 'test-tool date time_t-is64bit' + +test_lazy_prereq CURL ' + curl --version +' diff --git a/t/test-terminal.perl b/t/test-terminal.perl index 1fb373f25b..46bf618479 100755 --- a/t/test-terminal.perl +++ b/t/test-terminal.perl @@ -5,15 +5,17 @@ use warnings; use IO::Pty; use File::Copy; -# Run @$argv in the background with stdio redirected to $out and $err. +# Run @$argv in the background with stdio redirected to $in, $out and $err. sub start_child { - my ($argv, $out, $err) = @_; + my ($argv, $in, $out, $err) = @_; my $pid = fork; if (not defined $pid) { die "fork failed: $!" } elsif ($pid == 0) { + open STDIN, "<&", $in; open STDOUT, ">&", $out; open STDERR, ">&", $err; + close $in; close $out; exec(@$argv) or die "cannot exec '$argv->[0]': $!" } @@ -49,6 +51,17 @@ sub xsendfile { copy($in, $out, 4096) or $!{EIO} or die "cannot copy from child: $!"; } +sub copy_stdin { + my ($in) = @_; + my $pid = fork; + if (!$pid) { + xsendfile($in, \*STDIN); + exit 0; + } + close($in); + return $pid; +} + sub copy_stdio { my ($out, $err) = @_; my $pid = fork; @@ -67,14 +80,26 @@ sub copy_stdio { if ($#ARGV < 1) { die "usage: test-terminal program args"; } +$ENV{TERM} = 'vt100'; +my $master_in = new IO::Pty; my $master_out = new IO::Pty; my $master_err = new IO::Pty; +$master_in->set_raw(); $master_out->set_raw(); $master_err->set_raw(); +$master_in->slave->set_raw(); $master_out->slave->set_raw(); $master_err->slave->set_raw(); -my $pid = start_child(\@ARGV, $master_out->slave, $master_err->slave); +my $pid = start_child(\@ARGV, $master_in->slave, $master_out->slave, $master_err->slave); +close $master_in->slave; close $master_out->slave; close $master_err->slave; +my $in_pid = copy_stdin($master_in); copy_stdio($master_out, $master_err); -exit(finish_child($pid)); +my $ret = finish_child($pid); +# If the child process terminates before our copy_stdin() process is able to +# write all of its data to $master_in, the copy_stdin() process could stall. +# Send SIGTERM to it to ensure it terminates. +kill 'TERM', $in_pid; +finish_child($in_pid); +exit($ret); diff --git a/t/valgrind/valgrind.sh b/t/valgrind/valgrind.sh index 42153036dc..669ebaf68b 100755 --- a/t/valgrind/valgrind.sh +++ b/t/valgrind/valgrind.sh @@ -1,11 +1,19 @@ #!/bin/sh base=$(basename "$0") +case "$base" in +test-*) + program="$GIT_VALGRIND/../../t/helper/$base" + ;; +*) + program="$GIT_VALGRIND/../../$base" + ;; +esac TOOL_OPTIONS='--leak-check=no' test -z "$GIT_VALGRIND_ENABLED" && -exec "$GIT_VALGRIND"/../../"$base" "$@" +exec "$program" "$@" case "$GIT_VALGRIND_MODE" in memcheck-fast) @@ -29,4 +37,4 @@ exec valgrind -q --error-exitcode=126 \ --log-fd=4 \ --input-fd=4 \ $GIT_VALGRIND_OPTIONS \ - "$GIT_VALGRIND"/../../"$base" "$@" + "$program" "$@" |