diff options
Diffstat (limited to 't')
537 files changed, 35700 insertions, 6144 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 d613935f14..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,6 +24,7 @@ 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)) @@ -35,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)' @@ -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 in non-bash shells, - 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:: @@ -265,12 +266,12 @@ 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 +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' @@ -292,6 +293,28 @@ and know what setup is needed for it. Or when you want to run everything up to a certain test. +Running tests with special setups +--------------------------------- + +The whole test suite could be run to test some special features +that cannot be easily covered by a few specific test cases. These +could be enabled by running the test suite with correct GIT_TEST_ +environment set. + +GIT_TEST_SPLIT_INDEX=<boolean> forces split-index mode on the whole +test suite. Accept any boolean values that are accepted by git-config. + +GIT_TEST_FULL_IN_PACK_ARRAY=<boolean> exercises the uncommon +pack-objects code path where there are more than 1024 packs even if +the actual number of packs in repository is below this limit. Accept +any boolean values that are accepted by git-config. + +GIT_TEST_OE_SIZE=<n> exercises the uncommon pack-objects code path +where we do not cache object size in memory and read it from existing +packs on demand. This normally only happens when the object size is +over 2GB. This variable forces the code path on any object larger than +<n> bytes. + Naming Tests ------------ @@ -332,13 +355,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) @@ -455,6 +475,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 @@ -471,13 +507,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. @@ -611,9 +647,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 @@ -656,7 +694,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 @@ -664,17 +702,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. @@ -801,11 +854,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. @@ -815,6 +880,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/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/diff-lib.sh b/t/diff-lib.sh index c211dc40ee..2de880f7a5 100644 --- a/t/diff-lib.sh +++ b/t/diff-lib.sh @@ -1,6 +1,6 @@ : -sanitize_diff_raw='/^:/s/ '"\($_x40\)"' '"\($_x40\)"' \([A-Z]\)[0-9]* / \1 \2 \3# /' +sanitize_diff_raw='/^:/s/ '"\($OID_REGEX\)"' '"\($OID_REGEX\)"' \([A-Z]\)[0-9]* / \1 \2 \3# /' compare_diff_raw () { # When heuristics are improved, the score numbers would change. # Ignore them while comparing. @@ -12,7 +12,7 @@ compare_diff_raw () { test_cmp .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2 } -sanitize_diff_raw_z='/^:/s/ '"$_x40"' '"$_x40"' \([A-Z]\)[0-9]*$/ X X \1#/' +sanitize_diff_raw_z='/^:/s/ '"$OID_REGEX"' '"$OID_REGEX"' \([A-Z]\)[0-9]*$/ X X \1#/' compare_diff_raw_z () { # When heuristics are improved, the score numbers would change. # Ignore them while comparing. 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 index d6e8b36798..2bad28af92 100644 --- a/t/helper/.gitignore +++ b/t/helper/.gitignore @@ -1,33 +1,5 @@ -/test-chmtime -/test-ctype -/test-config -/test-date -/test-delta -/test-dump-cache-tree -/test-dump-split-index -/test-dump-untracked-cache -/test-fake-ssh -/test-scrap-cache-tree -/test-genrandom -/test-hashmap -/test-index-version -/test-line-buffer -/test-match-trees -/test-mergesort -/test-mktemp -/test-parse-options -/test-path-utils -/test-prio-queue -/test-read-cache -/test-regex -/test-revision-walking -/test-run-command -/test-sha1 -/test-sha1-array -/test-sigchain -/test-string-list -/test-submodule-config -/test-subprocess -/test-svn-fe -/test-urlmatch-normalization -/test-wildmatch +* +!*.sh +!*.[ch] +!*.gitignore + diff --git a/t/helper/test-chmtime.c b/t/helper/test-chmtime.c index e760256406..aa22af48c2 100644 --- a/t/helper/test-chmtime.c +++ b/t/helper/test-chmtime.c @@ -5,32 +5,43 @@ * * The mtime can be changed to an absolute value: * - * test-chmtime =<seconds> file... + * test-tool chmtime =<seconds> file... * * Relative to the current time as returned by time(3): * - * test-chmtime =+<seconds> (or =-<seconds>) file... + * test-tool chmtime =+<seconds> (or =-<seconds>) file... * * Or relative to the current mtime of the file: * - * test-chmtime <seconds> file... - * test-chmtime +<seconds> (or -<seconds>) 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: + * To print the mtime and the file name use --verbose and set + * the file mtime offset to 0: * - * test-chmtime -v +0 file + * test-tool chmtime -v +0 file + * + * To print only the mtime use --get: + * + * test-tool chmtime --get file * * To set the mtime to current time: * - * test-chmtime =+0 file + * test-tool chmtime =+0 file + * + * To set the file mtime offset to +1 and print the new value: + * + * test-tool chmtime --get +1 file * */ +#include "test-tool.h" #include "git-compat-util.h" #include <utime.h> -static const char usage_str[] = "-v|--verbose (+|=|=+|=-|-)<seconds> <file>..."; +static const char usage_str[] = + "(-v|--verbose|-g|--get) (+|=|=+|=-|-)<seconds> <file>..."; static int timespec_arg(const char *arg, long int *set_time, int *set_eq) { @@ -46,7 +57,6 @@ static int timespec_arg(const char *arg, long int *set_time, int *set_eq) } *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) { @@ -56,9 +66,10 @@ static int timespec_arg(const char *arg, long int *set_time, int *set_eq) return 1; } -int cmd_main(int argc, const char **argv) +int cmd__chmtime(int argc, const char **argv) { static int verbose; + static int get; int i = 1; /* no mtime change by default */ @@ -68,18 +79,34 @@ int cmd_main(int argc, const char **argv) if (argc < 3) goto usage; - if (strcmp(argv[i], "--verbose") == 0 || strcmp(argv[i], "-v") == 0) { + if (strcmp(argv[i], "--get") == 0 || strcmp(argv[i], "-g") == 0) { + get = 1; + ++i; + } else if (strcmp(argv[i], "--verbose") == 0 || strcmp(argv[i], "-v") == 0) { verbose = 1; ++i; } - if (timespec_arg(argv[i], &set_time, &set_eq)) + + if (i == argc) { + goto usage; + } + + if (timespec_arg(argv[i], &set_time, &set_eq)) { ++i; - else + } else { + if (get == 0) { + fprintf(stderr, "Not a base-10 integer: %s\n", argv[i] + 1); + goto usage; + } + } + + if (i == argc) goto usage; for (; i < argc; i++) { struct stat sb; struct utimbuf utb; + uintmax_t mtime; if (stat(argv[i], &sb) < 0) { fprintf(stderr, "Failed to stat %s: %s\n", @@ -99,8 +126,10 @@ int cmd_main(int argc, const char **argv) 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; + mtime = utb.modtime < 0 ? 0: utb.modtime; + if (get) { + printf("%"PRIuMAX"\n", mtime); + } else if (verbose) { printf("%"PRIuMAX"\t%s\n", mtime, argv[i]); } diff --git a/t/helper/test-config.c b/t/helper/test-config.c index 83a4f2ab86..214003d5b2 100644 --- a/t/helper/test-config.c +++ b/t/helper/test-config.c @@ -1,4 +1,6 @@ +#include "test-tool.h" #include "cache.h" +#include "config.h" #include "string-list.h" /* @@ -31,7 +33,7 @@ * Examples: * * To print the value with highest priority for key "foo.bAr Baz.rock": - * test-config get_value "foo.bAr Baz.rock" + * test-tool config get_value "foo.bAr Baz.rock" * */ @@ -66,13 +68,28 @@ static int iterate_cb(const char *var, const char *value, void *data) return 0; } -int cmd_main(int argc, const char **argv) +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); diff --git a/t/helper/test-ctype.c b/t/helper/test-ctype.c index bb72c47df5..92c4c2313e 100644 --- a/t/helper/test-ctype.c +++ b/t/helper/test-ctype.c @@ -1,3 +1,4 @@ +#include "test-tool.h" #include "cache.h" static int rc; @@ -28,7 +29,7 @@ static int is_in(const char *s, int ch) #define LOWER "abcdefghijklmnopqrstuvwxyz" #define UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -int cmd_main(int argc, const char **argv) +int cmd__ctype(int argc, const char **argv) { TEST_CLASS(isdigit, DIGIT); TEST_CLASS(isspace, " \n\r\t"); diff --git a/t/helper/test-date.c b/t/helper/test-date.c index 506054bcd5..a0837371ab 100644 --- a/t/helper/test-date.c +++ b/t/helper/test-date.c @@ -1,10 +1,14 @@ +#include "test-tool.h" #include "cache.h" static const char *usage_msg = "\n" -" test-date relative [time_t]...\n" -" test-date show:<format> [time_t]...\n" -" test-date parse [date]...\n" -" test-date approxidate [date]...\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) { @@ -25,14 +29,14 @@ static void show_dates(const char **argv, const char *format) parse_date_format(format, &mode); for (; *argv; argv++) { char *arg; - time_t t; + 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 = strtol(*argv, &arg, 10); + t = parse_timestamp(*argv, &arg, 10); while (*arg == ' ') arg++; tz = atoi(arg); @@ -46,12 +50,12 @@ static void parse_dates(const char **argv, struct timeval *now) struct strbuf result = STRBUF_INIT; for (; *argv; argv++) { - unsigned long t; + timestamp_t t; int tz; strbuf_reset(&result); parse_date(*argv, &result); - if (sscanf(result.buf, "%lu %d", &t, &tz) == 2) + if (sscanf(result.buf, "%"PRItime" %d", &t, &tz) == 2) printf("%s -> %s\n", *argv, show_date(t, tz, DATE_MODE(ISO8601))); else @@ -63,13 +67,22 @@ static void parse_dates(const char **argv, struct timeval *now) static void parse_approxidate(const char **argv, struct timeval *now) { for (; *argv; argv++) { - time_t t; + timestamp_t t; t = approxidate_relative(*argv, now); printf("%s -> %s\n", *argv, show_date(t, 0, DATE_MODE(ISO8601))); } } -int cmd_main(int argc, const char **argv) +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; @@ -93,6 +106,12 @@ int cmd_main(int argc, const char **argv) 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 index 59937dc1be..34c7259248 100644 --- a/t/helper/test-delta.c +++ b/t/helper/test-delta.c @@ -8,14 +8,15 @@ * 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-delta (-d|-p) <from_file> <data_file> <out_file>"; + "test-tool delta (-d|-p) <from_file> <data_file> <out_file>"; -int cmd_main(int argc, const char **argv) +int cmd__delta(int argc, const char **argv) { int fd; struct stat st; @@ -69,7 +70,7 @@ int cmd_main(int argc, const char **argv) } fd = open (argv[4], O_WRONLY|O_CREAT|O_TRUNC, 0666); - if (fd < 0 || write_in_full(fd, out_buf, out_size) != out_size) { + if (fd < 0 || write_in_full(fd, out_buf, out_size) < 0) { perror(argv[4]); return 1; } diff --git a/t/helper/test-drop-caches.c b/t/helper/test-drop-caches.c new file mode 100644 index 0000000000..d6bcfddf13 --- /dev/null +++ b/t/helper/test-drop-caches.c @@ -0,0 +1,157 @@ +#include "test-tool.h" +#include "git-compat-util.h" + +#if defined(GIT_WINDOWS_NATIVE) +#include "lazyload.h" + +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; + DECLARE_PROC_ADDR(ntdll.dll, DWORD, 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); + + if (!INIT_PROC_ADDR(NtSetSystemInformation)) + return error("Could not find NtSetSystemInformation() function"); + + 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); + + 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 index 7af116d49e..98a4891f1d 100644 --- a/t/helper/test-dump-cache-tree.c +++ b/t/helper/test-dump-cache-tree.c @@ -1,3 +1,4 @@ +#include "test-tool.h" #include "cache.h" #include "tree.h" #include "cache-tree.h" @@ -10,7 +11,7 @@ static void dump_one(struct cache_tree *it, const char *pfx, const char *x) "invalid", x, pfx, it->subtree_nr); else printf("%s %s%s (%d entries, %d subtrees)\n", - sha1_to_hex(it->sha1), x, pfx, + oid_to_hex(&it->oid), x, pfx, it->entry_count, it->subtree_nr); } @@ -32,7 +33,7 @@ static int dump_cache_tree(struct cache_tree *it, } else { dump_one(it, pfx, ""); - if (hashcmp(it->sha1, ref->sha1) || + 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 */ @@ -54,7 +55,7 @@ static int dump_cache_tree(struct cache_tree *it, return errs; } -int cmd_main(int ac, const char **av) +int cmd__dump_cache_tree(int ac, const char **av) { struct index_state istate; struct cache_tree *another = cache_tree(); 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 index e44430b699..63c689d6ee 100644 --- a/t/helper/test-dump-split-index.c +++ b/t/helper/test-dump-split-index.c @@ -1,3 +1,4 @@ +#include "test-tool.h" #include "cache.h" #include "split-index.h" #include "ewah/ewok.h" @@ -7,19 +8,19 @@ static void show_bit(size_t pos, void *data) printf(" %d", (int)pos); } -int cmd_main(int ac, const char **av) +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)); + printf("own %s\n", oid_to_hex(&the_index.oid)); 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)); + printf("base %s\n", oid_to_hex(&si->base_oid)); 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, diff --git a/t/helper/test-dump-untracked-cache.c b/t/helper/test-dump-untracked-cache.c index f752532ffb..bd92fb305a 100644 --- a/t/helper/test-dump-untracked-cache.c +++ b/t/helper/test-dump-untracked-cache.c @@ -23,7 +23,7 @@ static void dump(struct untracked_cache_dir *ucd, struct strbuf *base) len = base->len; strbuf_addf(base, "%s/", ucd->name); printf("%s %s", base->buf, - sha1_to_hex(ucd->exclude_sha1)); + oid_to_hex(&ucd->exclude_oid)); if (ucd->recurse) fputs(" recurse", stdout); if (ucd->check_only) @@ -54,8 +54,8 @@ int cmd_main(int ac, const char **av) printf("no untracked cache\n"); return 0; } - printf("info/exclude %s\n", sha1_to_hex(uc->ss_info_exclude.sha1)); - printf("core.excludesfile %s\n", sha1_to_hex(uc->ss_excludes_file.sha1)); + 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) diff --git a/t/helper/test-example-decorate.c b/t/helper/test-example-decorate.c new file mode 100644 index 0000000000..a20a6161e4 --- /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) + BUG("when adding a brand-new object, NULL should be returned"); + ret = add_decoration(&n, two, NULL); + if (ret) + 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) + BUG("when readding an already existing object, existing decoration should be returned"); + ret = add_decoration(&n, two, &decoration_b); + if (ret) + 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) + BUG("lookup should return added declaration"); + ret = lookup_decoration(&n, two); + if (ret != &decoration_b) + BUG("lookup should return added declaration"); + three = lookup_unknown_object(three_oid.hash); + ret = lookup_decoration(&n, three); + if (ret) + 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) + BUG("should have 2 objects"); + + return 0; +} diff --git a/t/helper/test-genrandom.c b/t/helper/test-genrandom.c index 8d11d22d98..99b8dc1e2d 100644 --- a/t/helper/test-genrandom.c +++ b/t/helper/test-genrandom.c @@ -4,9 +4,10 @@ * Copyright (C) 2007 by Nicolas Pitre, licensed under the GPL version 2. */ +#include "test-tool.h" #include "git-compat-util.h" -int cmd_main(int argc, const char **argv) +int cmd__genrandom(int argc, const char **argv) { unsigned long count, next = 0; unsigned char *c; diff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c index 7aa9440e27..23d2b172fe 100644 --- a/t/helper/test-hashmap.c +++ b/t/helper/test-hashmap.c @@ -1,5 +1,7 @@ +#include "test-tool.h" #include "git-compat-util.h" #include "hashmap.h" +#include "strbuf.h" struct test_entry { @@ -13,23 +15,28 @@ static const char *get_value(const struct test_entry *e) return e->key + strlen(e->key) + 1; } -static int test_entry_cmp(const struct test_entry *e1, - const struct test_entry *e2, const char* key) +static int test_entry_cmp(const void *cmp_data, + const void *entry, + const void *entry_or_key, + const void *keydata) { - return strcmp(e1->key, key ? key : e2->key); + 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 int test_entry_cmp_icase(const struct test_entry *e1, - const struct test_entry *e2, const char* key) +static struct test_entry *alloc_test_entry(unsigned int hash, + char *key, char *value) { - return strcasecmp(e1->key, key ? key : e2->key); -} - -static struct test_entry *alloc_test_entry(int hash, char *key, int klen, - char *value, int vlen) -{ - struct test_entry *entry = malloc(sizeof(struct test_entry) + klen - + vlen + 2); + 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); @@ -71,7 +78,7 @@ static unsigned int hash(unsigned int method, unsigned int i, const char *key) /* * Test performance of hashmap.[ch] - * Usage: time echo "perfhashmap method rounds" | test-hashmap + * Usage: time echo "perfhashmap method rounds" | test-tool hashmap */ static void perf_hashmap(unsigned int method, unsigned int rounds) { @@ -81,18 +88,18 @@ static void perf_hashmap(unsigned int method, unsigned int rounds) unsigned int *hashes; unsigned int i, j; - entries = malloc(TEST_SIZE * sizeof(struct test_entry *)); - hashes = malloc(TEST_SIZE * sizeof(int)); + ALLOC_ARRAY(entries, TEST_SIZE); + ALLOC_ARRAY(hashes, TEST_SIZE); for (i = 0; i < TEST_SIZE; i++) { - snprintf(buf, sizeof(buf), "%i", i); - entries[i] = alloc_test_entry(0, buf, strlen(buf), "", 0); + 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, (hashmap_cmp_fn) test_entry_cmp, 0); + hashmap_init(&map, test_entry_cmp, NULL, 0); /* add entries */ for (i = 0; i < TEST_SIZE; i++) { @@ -104,7 +111,7 @@ static void perf_hashmap(unsigned int method, unsigned int rounds) } } else { /* test map lookups */ - hashmap_init(&map, (hashmap_cmp_fn) test_entry_cmp, 0); + hashmap_init(&map, test_entry_cmp, NULL, 0); /* fill the map (sparsely if specified) */ j = (method & TEST_SPARSE) ? TEST_SIZE / 10 : TEST_SIZE; @@ -138,56 +145,53 @@ static void perf_hashmap(unsigned int method, unsigned int rounds) * * perfhashmap method rounds -> test hashmap.[ch] performance */ -int cmd_main(int argc, const char **argv) +int cmd__hashmap(int argc, const char **argv) { - char line[1024]; + struct strbuf line = STRBUF_INIT; struct hashmap map; int icase; /* init hash map */ icase = argc > 1 && !strcmp("ignorecase", argv[1]); - hashmap_init(&map, (hashmap_cmp_fn) (icase ? test_entry_cmp_icase - : test_entry_cmp), 0); + hashmap_init(&map, test_entry_cmp, &icase, 0); /* process commands from stdin */ - while (fgets(line, sizeof(line), stdin)) { + while (strbuf_getline(&line, stdin) != EOF) { char *cmd, *p1 = NULL, *p2 = NULL; - int l1 = 0, l2 = 0, hash = 0; + unsigned int hash = 0; struct test_entry *entry; /* break line into command and up to two parameters */ - cmd = strtok(line, DELIM); + cmd = strtok(line.buf, DELIM); /* ignore empty lines */ if (!cmd || *cmd == '#') continue; p1 = strtok(NULL, DELIM); if (p1) { - l1 = strlen(p1); hash = icase ? strihash(p1) : strhash(p1); p2 = strtok(NULL, DELIM); - if (p2) - l2 = strlen(p2); } - if (!strcmp("hash", cmd) && l1) { + if (!strcmp("hash", cmd) && p1) { /* print results of different hash functions */ - printf("%u %u %u %u\n", strhash(p1), memhash(p1, l1), - strihash(p1), memihash(p1, l1)); + printf("%u %u %u %u\n", + strhash(p1), memhash(p1, strlen(p1)), + strihash(p1), memihash(p1, strlen(p1))); - } else if (!strcmp("add", cmd) && l1 && l2) { + } else if (!strcmp("add", cmd) && p1 && p2) { /* create entry with key = p1, value = p2 */ - entry = alloc_test_entry(hash, p1, l1, p2, l2); + entry = alloc_test_entry(hash, p1, p2); /* add to hashmap */ hashmap_add(&map, entry); - } else if (!strcmp("put", cmd) && l1 && l2) { + } else if (!strcmp("put", cmd) && p1 && p2) { /* create entry with key = p1, value = p2 */ - entry = alloc_test_entry(hash, p1, l1, p2, l2); + entry = alloc_test_entry(hash, p1, p2); /* add / replace entry */ entry = hashmap_put(&map, entry); @@ -196,7 +200,7 @@ int cmd_main(int argc, const char **argv) puts(entry ? get_value(entry) : "NULL"); free(entry); - } else if (!strcmp("get", cmd) && l1) { + } else if (!strcmp("get", cmd) && p1) { /* lookup entry in hashmap */ entry = hashmap_get_from_hash(&map, hash, p1); @@ -209,7 +213,7 @@ int cmd_main(int argc, const char **argv) entry = hashmap_get_next(&map, entry); } - } else if (!strcmp("remove", cmd) && l1) { + } else if (!strcmp("remove", cmd) && p1) { /* setup static key */ struct hashmap_entry key; @@ -232,9 +236,10 @@ int cmd_main(int argc, const char **argv) } else if (!strcmp("size", cmd)) { /* print table sizes */ - printf("%u %u\n", map.tablesize, map.size); + printf("%u %u\n", map.tablesize, + hashmap_get_size(&map)); - } else if (!strcmp("intern", cmd) && l1) { + } else if (!strcmp("intern", cmd) && p1) { /* test that strintern works */ const char *i1 = strintern(p1); @@ -248,7 +253,7 @@ int cmd_main(int argc, const char **argv) else printf("%s\n", i1); - } else if (!strcmp("perfhashmap", cmd) && l1 && l2) { + } else if (!strcmp("perfhashmap", cmd) && p1 && p2) { perf_hashmap(atoi(p1), atoi(p2)); @@ -259,6 +264,7 @@ int cmd_main(int argc, const char **argv) } } + 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 index f569f6b7ef..fcd10968cc 100644 --- a/t/helper/test-index-version.c +++ b/t/helper/test-index-version.c @@ -1,6 +1,7 @@ +#include "test-tool.h" #include "cache.h" -int cmd_main(int argc, const char **argv) +int cmd__index_version(int argc, const char **argv) { struct cache_header hdr; int version; 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 index 81575fe2ab..078dd7e29d 100644 --- a/t/helper/test-line-buffer.c +++ b/t/helper/test-line-buffer.c @@ -17,27 +17,17 @@ static uint32_t strtouint32(const char *s) static void handle_command(const char *command, const char *arg, struct line_buffer *buf) { - switch (*command) { - case 'b': - 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); - return; - } - case 'c': - if (starts_with(command, "copy ")) { - buffer_copy_bytes(buf, strtouint32(arg)); - return; - } - case 's': - if (starts_with(command, "skip ")) { - buffer_skip_bytes(buf, strtouint32(arg)); - return; - } - default: + 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); } } diff --git a/t/helper/test-match-trees.c b/t/helper/test-match-trees.c index e939502863..96857f26ac 100644 --- a/t/helper/test-match-trees.c +++ b/t/helper/test-match-trees.c @@ -1,7 +1,8 @@ +#include "test-tool.h" #include "cache.h" #include "tree.h" -int cmd_main(int ac, const char **av) +int cmd__match_trees(int ac, const char **av) { struct object_id hash1, hash2, shifted; struct tree *one, *two; @@ -12,10 +13,10 @@ int cmd_main(int ac, const char **av) 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.hash); + one = parse_tree_indirect(&hash1); if (!one) die("not a tree-ish %s", av[1]); - two = parse_tree_indirect(hash2.hash); + two = parse_tree_indirect(&hash2); if (!two) die("not a tree-ish %s", av[2]); diff --git a/t/helper/test-mergesort.c b/t/helper/test-mergesort.c index 335cf6b626..c5cffaa4b7 100644 --- a/t/helper/test-mergesort.c +++ b/t/helper/test-mergesort.c @@ -1,3 +1,4 @@ +#include "test-tool.h" #include "cache.h" #include "mergesort.h" @@ -22,7 +23,7 @@ static int compare_strings(const void *a, const void *b) return strcmp(x->text, y->text); } -int cmd_main(int argc, const char **argv) +int cmd__mergesort(int argc, const char **argv) { struct line *line, *p = NULL, *lines = NULL; struct strbuf sb = STRBUF_INIT; diff --git a/t/helper/test-mktemp.c b/t/helper/test-mktemp.c index 89d9b2f7be..2290688940 100644 --- a/t/helper/test-mktemp.c +++ b/t/helper/test-mktemp.c @@ -1,9 +1,10 @@ /* * test-mktemp.c: code to exercise the creation of temporary files */ +#include "test-tool.h" #include "git-compat-util.h" -int cmd_main(int argc, const char **argv) +int cmd__mktemp(int argc, const char **argv) { if (argc != 2) usage("Expected 1 parameter defining the temporary file template"); 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 index a01430c24b..630c76d127 100644 --- a/t/helper/test-parse-options.c +++ b/t/helper/test-parse-options.c @@ -5,7 +5,7 @@ static int boolean = 0; static int integer = 0; static unsigned long magnitude = 0; -static unsigned long timestamp; +static timestamp_t timestamp; static int abbrev = 7; static int verbose = -1; /* unspecified */ static int dry_run = 0, quiet = 0; @@ -99,6 +99,8 @@ 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; @@ -161,7 +163,7 @@ int cmd_main(int argc, const char **argv) show(&expect, &ret, "boolean: %d", boolean); show(&expect, &ret, "integer: %d", integer); show(&expect, &ret, "magnitude: %lu", magnitude); - show(&expect, &ret, "timestamp: %lu", timestamp); + 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); diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c index 1ebe0f750c..ae091d9b3e 100644 --- a/t/helper/test-path-utils.c +++ b/t/helper/test-path-utils.c @@ -1,5 +1,7 @@ +#include "test-tool.h" #include "cache.h" #include "string-list.h" +#include "utf8.h" /* * A "string_list_each_func_t" function that normalizes an entry from @@ -38,6 +40,20 @@ struct test_data { 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) { @@ -156,7 +172,12 @@ static struct test_data dirname_data[] = { { NULL, NULL } }; -int cmd_main(int argc, const char **argv) +static int is_dotgitmodules(const char *path) +{ + return is_hfs_dotgitmodules(path) || is_ntfs_dotgitmodules(path); +} + +int cmd__path_utils(int argc, const char **argv) { if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) { char *buf = xmallocz(strlen(argv[2])); @@ -251,10 +272,24 @@ int cmd_main(int argc, const char **argv) } if (argc == 2 && !strcmp(argv[1], "basename")) - return test_function(basename_data, basename, argv[1]); + return test_function(basename_data, posix_basename, argv[1]); if (argc == 2 && !strcmp(argv[1], "dirname")) - return test_function(dirname_data, dirname, argv[1]); + return test_function(dirname_data, posix_dirname, argv[1]); + + if (argc > 2 && !strcmp(argv[1], "is_dotgitmodules")) { + int res = 0, expect = 1, i; + for (i = 2; i < argc; i++) + if (!strcmp("--not", argv[i])) + expect = !expect; + else if (expect != is_dotgitmodules(argv[i])) + res = error("'%s' is %s.gitmodules", argv[i], + expect ? "not " : ""); + else + fprintf(stderr, "ok: '%s' is %s.gitmodules\n", + argv[i], expect ? "" : "not "); + return !!res; + } fprintf(stderr, "%s: unknown function name: %s\n", argv[0], argv[1] ? argv[1] : "(there was none)"); diff --git a/t/helper/test-pkt-line.c b/t/helper/test-pkt-line.c new file mode 100644 index 0000000000..0f19e53c75 --- /dev/null +++ b/t/helper/test-pkt-line.c @@ -0,0 +1,64 @@ +#include "pkt-line.h" + +static void pack_line(const char *line) +{ + if (!strcmp(line, "0000") || !strcmp(line, "0000\n")) + packet_flush(1); + else if (!strcmp(line, "0001") || !strcmp(line, "0001\n")) + packet_delim(1); + else + packet_write_fmt(1, "%s", line); +} + +static void pack(int argc, const char **argv) +{ + if (argc) { /* read from argv */ + int i; + for (i = 0; i < argc; i++) + pack_line(argv[i]); + } else { /* read from stdin */ + char line[LARGE_PACKET_MAX]; + while (fgets(line, sizeof(line), stdin)) { + pack_line(line); + } + } +} + +static void unpack(void) +{ + struct packet_reader reader; + packet_reader_init(&reader, 0, NULL, 0, + PACKET_READ_GENTLE_ON_EOF | + PACKET_READ_CHOMP_NEWLINE); + + while (packet_reader_read(&reader) != PACKET_READ_EOF) { + switch (reader.status) { + case PACKET_READ_EOF: + break; + case PACKET_READ_NORMAL: + printf("%s\n", reader.line); + break; + case PACKET_READ_FLUSH: + printf("0000\n"); + break; + case PACKET_READ_DELIM: + printf("0001\n"); + break; + } + } +} + +int cmd_main(int argc, const char **argv) +{ + if (argc < 2) + die("too few arguments"); + + if (!strcmp(argv[1], "pack")) + pack(argc - 2, argv + 2); + else if (!strcmp(argv[1], "unpack")) + unpack(); + else + die("invalid argument '%s'", argv[1]); + + return 0; +} diff --git a/t/helper/test-prio-queue.c b/t/helper/test-prio-queue.c index ae58fff359..9807b649b1 100644 --- a/t/helper/test-prio-queue.c +++ b/t/helper/test-prio-queue.c @@ -1,3 +1,4 @@ +#include "test-tool.h" #include "cache.h" #include "prio-queue.h" @@ -16,7 +17,7 @@ static void show(int *v) free(v); } -int cmd_main(int argc, const char **argv) +int cmd__prio_queue(int argc, const char **argv) { struct prio_queue pq = { intcmp }; diff --git a/t/helper/test-read-cache.c b/t/helper/test-read-cache.c index 2a7990efc3..d674c88ba0 100644 --- a/t/helper/test-read-cache.c +++ b/t/helper/test-read-cache.c @@ -1,10 +1,12 @@ +#include "test-tool.h" #include "cache.h" -int cmd_main(int argc, const char **argv) +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(); diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c new file mode 100644 index 0000000000..e9e0541276 --- /dev/null +++ b/t/helper/test-ref-store.c @@ -0,0 +1,299 @@ +#include "test-tool.h" +#include "cache.h" +#include "refs.h" +#include "worktree.h" +#include "object-store.h" +#include "repository.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(the_repository); + } 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 index b5ea8a97c5..10284cc56f 100644 --- a/t/helper/test-regex.c +++ b/t/helper/test-regex.c @@ -1,3 +1,4 @@ +#include "test-tool.h" #include "git-compat-util.h" #include "gettext.h" @@ -36,7 +37,7 @@ static int test_regex_bug(void) return 0; } -int cmd_main(int argc, const char **argv) +int cmd__regex(int argc, const char **argv) { const char *pat; const char *str; @@ -47,8 +48,8 @@ int cmd_main(int argc, const char **argv) if (argc == 2 && !strcmp(argv[1], "--bug")) return test_regex_bug(); else if (argc < 3) - usage("test-regex --bug\n" - "test-regex <pattern> <string> [<options>]"); + usage("test-tool regex --bug\n" + "test-tool regex <pattern> <string> [<options>]"); argv++; pat = *argv++; diff --git a/t/helper/test-revision-walking.c b/t/helper/test-revision-walking.c index b8e6fe1d00..4f8bc75821 100644 --- a/t/helper/test-revision-walking.c +++ b/t/helper/test-revision-walking.c @@ -8,6 +8,7 @@ * published by the Free Software Foundation. */ +#include "test-tool.h" #include "cache.h" #include "commit.h" #include "diff.h" @@ -45,7 +46,7 @@ static int run_revision_walk(void) return got_revision; } -int cmd_main(int argc, const char **argv) +int cmd__revision_walking(int argc, const char **argv) { if (argc < 2) return 1; diff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c index d24d157379..2cc93bb69c 100644 --- a/t/helper/test-run-command.c +++ b/t/helper/test-run-command.c @@ -8,6 +8,7 @@ * published by the Free Software Foundation. */ +#include "test-tool.h" #include "git-compat-util.h" #include "run-command.h" #include "argv-array.h" @@ -49,13 +50,22 @@ static int task_finished(int result, return 1; } -int cmd_main(int argc, const char **argv) +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")) { diff --git a/t/helper/test-scrap-cache-tree.c b/t/helper/test-scrap-cache-tree.c index 27fe0405b8..393f1604ff 100644 --- a/t/helper/test-scrap-cache-tree.c +++ b/t/helper/test-scrap-cache-tree.c @@ -1,14 +1,15 @@ +#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_main(int ac, const char **av) +int cmd__scrap_cache_tree(int ac, const char **av) { + struct lock_file index_lock = LOCK_INIT; + setup_git_directory(); - hold_locked_index(&index_lock, 1); + hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); if (read_cache() < 0) die("unable to read index file"); active_cache_tree = NULL; diff --git a/t/helper/test-sha1-array.c b/t/helper/test-sha1-array.c index f7a53c4ad6..ad5e69f9d3 100644 --- a/t/helper/test-sha1-array.c +++ b/t/helper/test-sha1-array.c @@ -1,33 +1,34 @@ +#include "test-tool.h" #include "cache.h" #include "sha1-array.h" -static int print_sha1(const unsigned char sha1[20], void *data) +static int print_oid(const struct object_id *oid, void *data) { - puts(sha1_to_hex(sha1)); + puts(oid_to_hex(oid)); return 0; } -int cmd_main(int argc, const char **argv) +int cmd__sha1_array(int argc, const char **argv) { - struct sha1_array array = SHA1_ARRAY_INIT; + struct oid_array array = OID_ARRAY_INIT; struct strbuf line = STRBUF_INIT; while (strbuf_getline(&line, stdin) != EOF) { const char *arg; - unsigned char sha1[20]; + struct object_id oid; if (skip_prefix(line.buf, "append ", &arg)) { - if (get_sha1_hex(arg, sha1)) + if (get_oid_hex(arg, &oid)) die("not a hexadecimal SHA1: %s", arg); - sha1_array_append(&array, sha1); + oid_array_append(&array, &oid); } else if (skip_prefix(line.buf, "lookup ", &arg)) { - if (get_sha1_hex(arg, sha1)) + if (get_oid_hex(arg, &oid)) die("not a hexadecimal SHA1: %s", arg); - printf("%d\n", sha1_array_lookup(&array, sha1)); + printf("%d\n", oid_array_lookup(&array, &oid)); } else if (!strcmp(line.buf, "clear")) - sha1_array_clear(&array); + oid_array_clear(&array); else if (!strcmp(line.buf, "for_each_unique")) - sha1_array_for_each_unique(&array, print_sha1, NULL); + oid_array_for_each_unique(&array, print_oid, NULL); else die("unknown command: %s", line.buf); } diff --git a/t/helper/test-sha1.c b/t/helper/test-sha1.c index a1c13f54ec..1ba0675c75 100644 --- a/t/helper/test-sha1.c +++ b/t/helper/test-sha1.c @@ -1,6 +1,7 @@ +#include "test-tool.h" #include "cache.h" -int cmd_main(int ac, const char **av) +int cmd__sha1(int ac, const char **av) { git_SHA_CTX ctx; unsigned char sha1[20]; diff --git a/t/helper/test-sha1.sh b/t/helper/test-sha1.sh index 750b95a0a1..84594885c7 100755 --- a/t/helper/test-sha1.sh +++ b/t/helper/test-sha1.sh @@ -1,7 +1,7 @@ #!/bin/sh dd if=/dev/zero bs=1048576 count=100 2>/dev/null | -/usr/bin/time t/helper/test-sha1 >/dev/null +/usr/bin/time t/helper/test-tool sha1 >/dev/null while read expect cnt pfx do @@ -11,7 +11,7 @@ do test -z "$pfx" || echo "$pfx" dd if=/dev/zero bs=1048576 count=$cnt 2>/dev/null | perl -pe 'y/\000/g/' - } | ./t/helper/test-sha1 $cnt + } | ./t/helper/test-tool sha1 $cnt ) if test "$expect" = "$actual" then diff --git a/t/helper/test-sigchain.c b/t/helper/test-sigchain.c index b71edbd442..77ac5bc33f 100644 --- a/t/helper/test-sigchain.c +++ b/t/helper/test-sigchain.c @@ -1,3 +1,4 @@ +#include "test-tool.h" #include "cache.h" #include "sigchain.h" @@ -13,7 +14,7 @@ X(two) X(three) #undef X -int cmd_main(int argc, const char **argv) { +int cmd__sigchain(int argc, const char **argv) { sigchain_push(SIGTERM, one); sigchain_push(SIGTERM, two); sigchain_push(SIGTERM, three); 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 index 4a68967bd1..2123dda85b 100644 --- a/t/helper/test-string-list.c +++ b/t/helper/test-string-list.c @@ -1,3 +1,4 @@ +#include "test-tool.h" #include "cache.h" #include "string-list.h" @@ -41,7 +42,7 @@ static int prefix_cb(struct string_list_item *item, void *cb_data) return starts_with(item->string, prefix); } -int cmd_main(int argc, const char **argv) +int cmd__string_list(int argc, const char **argv) { if (argc == 5 && !strcmp(argv[1], "split")) { struct string_list list = STRING_LIST_INIT_DUP; @@ -97,6 +98,31 @@ int cmd_main(int argc, const char **argv) 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 index 2f144d539a..e2692746df 100644 --- a/t/helper/test-submodule-config.c +++ b/t/helper/test-submodule-config.c @@ -1,4 +1,6 @@ +#include "test-tool.h" #include "cache.h" +#include "config.h" #include "submodule-config.h" #include "submodule.h" @@ -9,12 +11,7 @@ static void die_usage(int argc, const char **argv, const char *msg) exit(1); } -static int git_test_config(const char *var, const char *value, void *cb) -{ - return parse_submodule_config_option(var, value); -} - -int cmd_main(int argc, const char **argv) +int cmd__submodule_config(int argc, const char **argv) { const char **arg = argv; int my_argc = argc; @@ -36,11 +33,9 @@ int cmd_main(int argc, const char **argv) die_usage(argc, argv, "Wrong number of arguments."); setup_git_directory(); - gitmodules_config(); - git_config(git_test_config, NULL); while (*arg) { - unsigned char commit_sha1[20]; + struct object_id commit_oid; const struct submodule *submodule; const char *commit; const char *path_or_name; @@ -49,14 +44,16 @@ int cmd_main(int argc, const char **argv) path_or_name = arg[1]; if (commit[0] == '\0') - hashclr(commit_sha1); - else if (get_sha1(commit, commit_sha1) < 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_sha1, path_or_name); + submodule = submodule_from_name(the_repository, + &commit_oid, path_or_name); } else - submodule = submodule_from_path(commit_sha1, path_or_name); + submodule = submodule_from_path(the_repository, + &commit_oid, path_or_name); if (!submodule) die_usage(argc, argv, "Submodule not found."); @@ -70,7 +67,7 @@ int cmd_main(int argc, const char **argv) arg += 2; } - submodule_free(); + submodule_free(the_repository); return 0; } diff --git a/t/helper/test-subprocess.c b/t/helper/test-subprocess.c index 30c5765bfc..92b69de635 100644 --- a/t/helper/test-subprocess.c +++ b/t/helper/test-subprocess.c @@ -1,7 +1,8 @@ +#include "test-tool.h" #include "cache.h" #include "run-command.h" -int cmd_main(int argc, const char **argv) +int cmd__subprocess(int argc, const char **argv) { struct child_process cp = CHILD_PROCESS_INIT; int nogit = 0; diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c new file mode 100644 index 0000000000..805a45de9c --- /dev/null +++ b/t/helper/test-tool.c @@ -0,0 +1,63 @@ +#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; + + BUG_exit_code = 99; + 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 index 49b6e836be..8f4d67e646 100644 --- a/t/helper/test-urlmatch-normalization.c +++ b/t/helper/test-urlmatch-normalization.c @@ -1,9 +1,10 @@ +#include "test-tool.h" #include "git-compat-util.h" #include "urlmatch.h" -int cmd_main(int argc, const char **argv) +int cmd__urlmatch_normalization(int argc, const char **argv) { - const char usage[] = "test-urlmatch-normalization [-p | -l] <url1> | <url1> <url2>"; + const char usage[] = "test-tool urlmatch-normalization [-p | -l] <url1> | <url1> <url2>"; char *url1, *url2; int opt_p = 0, opt_l = 0; diff --git a/t/helper/test-wildmatch.c b/t/helper/test-wildmatch.c index 52be876fed..2c103d1824 100644 --- a/t/helper/test-wildmatch.c +++ b/t/helper/test-wildmatch.c @@ -1,6 +1,7 @@ +#include "test-tool.h" #include "cache.h" -int cmd_main(int argc, const char **argv) +int cmd__wildmatch(int argc, const char **argv) { int i; for (i = 2; i < argc; i++) { @@ -11,11 +12,13 @@ int cmd_main(int argc, const char **argv) argv[i] += 3; } if (!strcmp(argv[1], "wildmatch")) - return !!wildmatch(argv[3], argv[2], WM_PATHNAME, NULL); + return !!wildmatch(argv[3], argv[2], WM_PATHNAME); else if (!strcmp(argv[1], "iwildmatch")) - return !!wildmatch(argv[3], argv[2], WM_PATHNAME | WM_CASEFOLD, NULL); + return !!wildmatch(argv[3], argv[2], WM_PATHNAME | WM_CASEFOLD); else if (!strcmp(argv[1], "pathmatch")) - return !!wildmatch(argv[3], argv[2], 0, NULL); + 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..8837717d36 --- /dev/null +++ b/t/helper/test-write-cache.c @@ -0,0 +1,20 @@ +#include "test-tool.h" +#include "cache.h" +#include "lockfile.h" + +int cmd__write_cache(int argc, const char **argv) +{ + struct lock_file index_lock = LOCK_INIT; + int i, cnt = 1; + if (argc == 2) + cnt = strtol(argv[1], NULL, 0); + setup_git_directory(); + read_cache(); + for (i = 0; i < cnt; i++) { + hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); + if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) + die("unable to write index file"); + } + + 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-diff-alternative.sh b/t/lib-diff-alternative.sh index 8b4dbf22d2..8d1e408bb5 100644 --- a/t/lib-diff-alternative.sh +++ b/t/lib-diff-alternative.sh @@ -59,9 +59,11 @@ int main(int argc, char **argv) } EOF - cat >expect <<\EOF + file1=$(git rev-parse --short $(git hash-object file1)) + file2=$(git rev-parse --short $(git hash-object file2)) + cat >expect <<EOF diff --git a/file1 b/file2 -index 6faa5a3..e3af329 100644 +index $file1..$file2 100644 --- a/file1 +++ b/file2 @@ -1,26 +1,25 @@ @@ -136,9 +138,11 @@ e f EOF - cat >expect <<\EOF + uniq1=$(git rev-parse --short $(git hash-object uniq1)) + uniq2=$(git rev-parse --short $(git hash-object uniq2)) + cat >expect <<EOF diff --git a/uniq1 b/uniq2 -index b414108..0fdf397 100644 +index $uniq1..$uniq2 100644 --- a/uniq1 +++ b/uniq2 @@ -1,6 +1,6 @@ diff --git a/t/lib-git-daemon.sh b/t/lib-git-daemon.sh index f9cbd47931..edbea2d986 100644 --- a/t/lib-git-daemon.sh +++ b/t/lib-git-daemon.sh @@ -32,7 +32,8 @@ 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" @@ -46,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" @@ -89,3 +99,25 @@ stop_git_daemon() { 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 54fd5a6ca0..c27599474c 100644 --- a/t/lib-git-p4.sh +++ b/t/lib-git-p4.sh @@ -39,7 +39,7 @@ native_path () { then path=$(cygpath --windows "$path") else - path=$(test-path-utils real_path "$path") + path=$(test-tool path-utils real_path "$path") fi && echo "$path" } diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index 688313ed5c..a8130f9119 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -17,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 @@ -49,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 @@ -110,18 +110,20 @@ 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 () { diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh index ec2aa8f687..a5d3b2cbaa 100755 --- a/t/lib-gpg.sh +++ b/t/lib-gpg.sh @@ -31,6 +31,7 @@ then 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/apache.conf b/t/lib-httpd/apache.conf index 69174c6e31..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 @@ -76,6 +79,8 @@ 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/ @@ -133,6 +138,15 @@ 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 diff --git a/t/lib-pack.sh b/t/lib-pack.sh index 7509846571..c4d907a450 100644 --- a/t/lib-pack.sh +++ b/t/lib-pack.sh @@ -79,13 +79,25 @@ pack_obj () { ;; esac + # If it's not a delta, we can convince pack-objects to generate a pack + # with just our entry, and then strip off the header (12 bytes) and + # trailer (20 bytes). + if test -z "$2" + then + echo "$1" | git pack-objects --stdout >pack_obj.tmp && + size=$(wc -c <pack_obj.tmp) && + dd if=pack_obj.tmp bs=1 count=$((size - 20 - 12)) skip=12 && + rm -f pack_obj.tmp + return + fi + echo >&2 "BUG: don't know how to print $1${2:+ (from $2)}" return 1 } # 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 index b0917d93e6..83babe57d9 100644 --- a/t/lib-proto-disable.sh +++ b/t/lib-proto-disable.sh @@ -1,15 +1,12 @@ # Test routines for checking protocol disabling. -# 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 clone/fetch/push with GIT_ALLOW_PROTOCOL whitelist +test_whitelist () { desc=$1 proto=$2 url=$3 - test_expect_success "clone $1 (enabled)" ' + test_expect_success "clone $desc (enabled)" ' rm -rf tmp.git && ( GIT_ALLOW_PROTOCOL=$proto && @@ -18,7 +15,7 @@ test_proto () { ) ' - test_expect_success "fetch $1 (enabled)" ' + test_expect_success "fetch $desc (enabled)" ' ( cd tmp.git && GIT_ALLOW_PROTOCOL=$proto && @@ -27,7 +24,7 @@ test_proto () { ) ' - test_expect_success "push $1 (enabled)" ' + test_expect_success "push $desc (enabled)" ' ( cd tmp.git && GIT_ALLOW_PROTOCOL=$proto && @@ -36,7 +33,7 @@ test_proto () { ) ' - test_expect_success "push $1 (disabled)" ' + test_expect_success "push $desc (disabled)" ' ( cd tmp.git && GIT_ALLOW_PROTOCOL=none && @@ -45,7 +42,7 @@ test_proto () { ) ' - test_expect_success "fetch $1 (disabled)" ' + test_expect_success "fetch $desc (disabled)" ' ( cd tmp.git && GIT_ALLOW_PROTOCOL=none && @@ -54,7 +51,7 @@ test_proto () { ) ' - test_expect_success "clone $1 (disabled)" ' + test_expect_success "clone $desc (disabled)" ' rm -rf tmp.git && ( GIT_ALLOW_PROTOCOL=none && @@ -62,6 +59,133 @@ test_proto () { 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 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-t6000.sh b/t/lib-t6000.sh index 3f2d873fec..b0ed4767e3 100644 --- a/t/lib-t6000.sh +++ b/t/lib-t6000.sh @@ -4,11 +4,11 @@ mkdir -p .git/refs/tags >sed.script -# Answer the sha1 has associated with the tag. The tag must exist in .git/refs/tags +# Answer the sha1 has associated with the tag. The tag must exist under refs/tags tag () { _tag=$1 - test -f ".git/refs/tags/$_tag" || error "tag: \"$_tag\" does not exist" - cat ".git/refs/tags/$_tag" + git rev-parse --verify "refs/tags/$_tag" || + error "tag: \"$_tag\" does not exist" } # Generate a commit using the text specified to make it unique and the tree 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 49ea4349be..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 diff --git a/t/perf/aggregate.perl b/t/perf/aggregate.perl index 924b19dab4..bc865160e7 100755 --- a/t/perf/aggregate.perl +++ b/t/perf/aggregate.perl @@ -1,8 +1,10 @@ #!/usr/bin/perl -use lib '../../perl/blib/lib'; +use lib '../../perl/build/lib'; use strict; use warnings; +use JSON; +use Getopt::Long; use Git; sub get_times { @@ -35,7 +37,31 @@ sub format_times { return $out; } -my (@dirs, %dirnames, %dirabbrevs, %prefixes, @tests); +sub usage { + print <<EOT; +./aggregate.perl [options] [--] [<dir_or_rev>...] [--] [<test_script>...] > + + Options: + --codespeed * Format output for Codespeed + --reponame <str> * Send given reponame to codespeed + --sort-by <str> * Sort output (only "regression" criteria is supported) + --subsection <str> * Use results from given subsection + +EOT + exit(1); +} + +my (@dirs, %dirnames, %dirabbrevs, %prefixes, @tests, + $codespeed, $sortby, $subsection, $reponame); + +Getopt::Long::Configure qw/ require_order /; + +my $rc = GetOptions("codespeed" => \$codespeed, + "reponame=s" => \$reponame, + "sort-by=s" => \$sortby, + "subsection=s" => \$subsection); +usage() unless $rc; + while (scalar @ARGV) { my $arg = $ARGV[0]; my $dir; @@ -69,12 +95,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; @@ -88,19 +126,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 (@_) { @@ -116,52 +148,168 @@ 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") { + print "Only 'regression' is supported as '--sort-by' argument\n"; + usage(); + } + + 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/bisect_regression b/t/perf/bisect_regression new file mode 100755 index 0000000000..a94d9955d0 --- /dev/null +++ b/t/perf/bisect_regression @@ -0,0 +1,73 @@ +#!/bin/sh + +# Read a line coming from `./aggregate.perl --sort-by regression ...` +# and automatically bisect to find the commit responsible for the +# performance regression. +# +# Lines from `./aggregate.perl --sort-by regression ...` look like: +# +# +100.0% p7821-grep-engines-fixed.1 0.04(0.10+0.03) 0.08(0.11+0.08) v2.14.3 v2.15.1 +# +33.3% p7820-grep-engines.1 0.03(0.08+0.02) 0.04(0.08+0.02) v2.14.3 v2.15.1 +# + +die () { + echo >&2 "error: $*" + exit 1 +} + +while [ $# -gt 0 ]; do + arg="$1" + case "$arg" in + --help) + echo "usage: $0 [--config file] [--subsection subsection]" + exit 0 + ;; + --config) + shift + GIT_PERF_CONFIG_FILE=$(cd "$(dirname "$1")"; pwd)/$(basename "$1") + export GIT_PERF_CONFIG_FILE + shift ;; + --subsection) + shift + GIT_PERF_SUBSECTION="$1" + export GIT_PERF_SUBSECTION + shift ;; + --*) + die "unrecognised option: '$arg'" ;; + *) + die "unknown argument '$arg'" + ;; + esac +done + +read -r regression subtest oldtime newtime oldrev newrev + +test_script=$(echo "$subtest" | sed -e 's/\(.*\)\.[0-9]*$/\1.sh/') +test_number=$(echo "$subtest" | sed -e 's/.*\.\([0-9]*\)$/\1/') + +# oldtime and newtime are decimal number, not integers + +oldtime=$(echo "$oldtime" | sed -e 's/^\([0-9]\+\.[0-9]\+\).*$/\1/') +newtime=$(echo "$newtime" | sed -e 's/^\([0-9]\+\.[0-9]\+\).*$/\1/') + +test $(echo "$newtime" "$oldtime" | awk '{ print ($1 > $2) }') = 1 || + die "New time '$newtime' shoud be greater than old time '$oldtime'" + +tmpdir=$(mktemp -d -t bisect_regression_XXXXXX) || die "Failed to create temp directory" +echo "$oldtime" >"$tmpdir/oldtime" || die "Failed to write to '$tmpdir/oldtime'" +echo "$newtime" >"$tmpdir/newtime" || die "Failed to write to '$tmpdir/newtime'" + +# Bisecting must be performed from the top level directory (even with --no-checkout) +( + toplevel_dir=$(git rev-parse --show-toplevel) || die "Failed to find top level directory" + cd "$toplevel_dir" || die "Failed to cd into top level directory '$toplevel_dir'" + + git bisect start --no-checkout "$newrev" "$oldrev" || die "Failed to start bisecting" + + git bisect run t/perf/bisect_run_script "$test_script" "$test_number" "$tmpdir" + res="$?" + + git bisect reset + + exit "$res" +) diff --git a/t/perf/bisect_run_script b/t/perf/bisect_run_script new file mode 100755 index 0000000000..3ebaf15521 --- /dev/null +++ b/t/perf/bisect_run_script @@ -0,0 +1,53 @@ +#!/bin/sh + +script="$1" +test_number="$2" +info_dir="$3" + +# This aborts the bisection immediately +die () { + echo >&2 "error: $*" + exit 255 +} + +bisect_head=$(git rev-parse --verify BISECT_HEAD) || die "Failed to find BISECT_HEAD ref" + +script_number=$(echo "$script" | sed -e "s/^p\([0-9]*\).*\$/\1/") || die "Failed to get script number for '$script'" + +oldtime=$(cat "$info_dir/oldtime") || die "Failed to access '$info_dir/oldtime'" +newtime=$(cat "$info_dir/newtime") || die "Failed to access '$info_dir/newtime'" + +cd t/perf || die "Failed to cd into 't/perf'" + +result_file="$info_dir/perf_${script_number}_${bisect_head}_results.txt" + +GIT_PERF_DIRS_OR_REVS="$bisect_head" +export GIT_PERF_DIRS_OR_REVS + +# Don't use codespeed +GIT_PERF_CODESPEED_OUTPUT= +GIT_PERF_SEND_TO_CODESPEED= +export GIT_PERF_CODESPEED_OUTPUT +export GIT_PERF_SEND_TO_CODESPEED + +./run "$script" >"$result_file" 2>&1 || die "Failed to run perf test '$script'" + +rtime=$(sed -n "s/^$script_number\.$test_number:.*\([0-9]\+\.[0-9]\+\)(.*).*\$/\1/p" "$result_file") + +echo "newtime: $newtime" +echo "rtime: $rtime" +echo "oldtime: $oldtime" + +# Compare ($newtime - $rtime) with ($rtime - $oldtime) +# Times are decimal number, not integers + +if test $(echo "$newtime" "$rtime" "$oldtime" | awk '{ print ($1 - $2 > $2 - $3) }') = 1 +then + # Current commit is considered "good/old" + echo "$rtime" >"$info_dir/oldtime" + exit 0 +else + # Current commit is considered "bad/new" + echo "$rtime" >"$info_dir/newtime" + exit 1 +fi 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/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 index b3e7d525d2..ce271ca4c1 100755 --- a/t/perf/p3400-rebase.sh +++ b/t/perf/p3400-rebase.sh @@ -5,7 +5,7 @@ test_description='Tests rebase performance' test_perf_default_repo -test_expect_success 'setup' ' +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 && @@ -33,4 +33,24 @@ test_perf 'rebase on top of a lot of unrelated changes' ' 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/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 b7ff68d4fa..392bcc0e51 100755 --- a/t/perf/p4211-line-log.sh +++ b/t/perf/p4211-line-log.sh @@ -31,4 +31,12 @@ 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/p5550-fetch-tags.sh b/t/perf/p5550-fetch-tags.sh index a5dc39f86a..d0e0e019ea 100755 --- a/t/perf/p5550-fetch-tags.sh +++ b/t/perf/p5550-fetch-tags.sh @@ -20,6 +20,7 @@ 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. @@ -44,26 +45,6 @@ create_tags () { git update-ref --stdin } -# 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"; - } - ' "$@" | - git fast-import && - - git cat-file --batch-all-objects --batch-check='%(objectname)' | - while read sha1 - do - echo $sha1 | git pack-objects .git/objects/pack/pack - done -} - test_expect_success 'create parent and child' ' git init parent && git -C parent commit --allow-empty -m base && @@ -84,9 +65,7 @@ test_expect_success 'populate parent tags' ' test_expect_success 'create child packs' ' ( cd child && - git config gc.auto 0 && - git config gc.autopacklimit 0 && - create_packs 500 + setup_many_packs ) ' 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 index 15ee5d1d53..b029586ccb 100755 --- a/t/perf/p7000-filter-branch.sh +++ b/t/perf/p7000-filter-branch.sh @@ -16,4 +16,9 @@ test_perf 'noop filter' ' 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/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 46f08ee087..e4c343a6b7 100644 --- a/t/perf/perf-lib.sh +++ b/t/perf/perf-lib.sh @@ -56,12 +56,10 @@ 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 @@ -78,12 +76,16 @@ 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="$(git -C "$source" 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" ( @@ -102,15 +104,29 @@ test_perf_create_repo_from () { ) && ( cd "$repo" && - git init -q && { - test_have_prereq SYMLINKS || - git config core.symlinks false - } && - mv .git/hooks .git/hooks-disabled 2>/dev/null + "$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" } 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 e8adedadfd..9aaa733c77 100755 --- a/t/perf/run +++ b/t/perf/run @@ -1,17 +1,35 @@ #!/bin/sh -case "$1" in - --help) - echo "usage: $0 [other_git_tree...] [--] [test_scripts]" - exit 0 - ;; -esac - die () { echo >&2 "error: $*" exit 1 } +while [ $# -gt 0 ]; do + arg="$1" + case "$arg" in + --) + break ;; + --help) + echo "usage: $0 [--config file] [--subsection subsec] [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 ;; + --subsection) + shift + GIT_PERF_SUBSECTION="$1" + export GIT_PERF_SUBSECTION + shift ;; + --*) + die "unrecognised option: '$arg'" ;; + *) + break ;; + esac +done + run_one_dir () { if test $# -eq 0; then set -- p????-*.sh @@ -24,12 +42,15 @@ 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 + name="$2" for config in config.mak config.mak.autogen config.status do if test -e "../../$config" @@ -37,8 +58,16 @@ build_git_rev () { cp "../../$config" "build/$rev/" fi done - (cd build/$rev && make $GIT_PERF_MAKE_OPTS) || - die "failed to build revision '$mydir'" + 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 () { @@ -56,13 +85,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 "$@" @@ -75,14 +107,118 @@ 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 + if test -n "$GIT_PERF_SUBSECTION" + then + if test -n "$GIT_PERF_CONFIG_FILE" + then + die "no subsections are defined in config file '$GIT_PERF_CONFIG_FILE'" + else + die "subsection '$GIT_PERF_SUBSECTION' defined without a config file" + fi + fi + ( + run_subsection "$@" + ) +elif test -n "$GIT_PERF_SUBSECTION" +then + egrep "^$GIT_PERF_SUBSECTION\$" test-results/run_subsections.names >/dev/null || + die "subsection '$GIT_PERF_SUBSECTION' not found in '$GIT_PERF_CONFIG_FILE'" + + egrep "^$GIT_PERF_SUBSECTION\$" test-results/run_subsections.names | while read -r subsec + do + ( + GIT_PERF_SUBSECTION="$subsec" + export GIT_PERF_SUBSECTION + echo "======== Run for subsection '$GIT_PERF_SUBSECTION' ========" + run_subsection "$@" + ) + done +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 1aa5093f36..af61d083b4 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. @@ -814,7 +839,7 @@ test_expect_success 'writing tree out with git write-tree' ' ' # we know the shape and contents of the tree and know the object ID for it. -test_expect_success 'validate object ID of a known tree' ' +test_expect_success SHA1 'validate object ID of a known tree' ' test "$tree" = 7bb943559a305bdd6bdee2cef6e5df2413c3d30a ' @@ -857,7 +882,7 @@ test_expect_success 'showing stage with git ls-files --stage' ' git ls-files --stage >current ' -test_expect_success 'validate git ls-files output for a known tree' ' +test_expect_success SHA1 'validate git ls-files output for a known tree' ' cat >expected <<-\EOF && 100644 f87290f8eb2cbbea7857214459a0739927eab154 0 path0 120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0 path0sym @@ -875,7 +900,7 @@ test_expect_success 'writing tree out with git write-tree' ' tree=$(git write-tree) ' -test_expect_success 'validate object ID for a known tree' ' +test_expect_success SHA1 'validate object ID for a known tree' ' test "$tree" = 087704a96baf1c2d1c869a8b084481e121c88b5b ' @@ -883,7 +908,7 @@ test_expect_success 'showing tree with git ls-tree' ' git ls-tree $tree >current ' -test_expect_success 'git ls-tree output for a known tree' ' +test_expect_success SHA1 'git ls-tree output for a known tree' ' cat >expected <<-\EOF && 100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym @@ -899,7 +924,7 @@ test_expect_success 'showing tree with git ls-tree -r' ' git ls-tree -r $tree >current ' -test_expect_success 'git ls-tree -r output for a known tree' ' +test_expect_success SHA1 'git ls-tree -r output for a known tree' ' cat >expected <<-\EOF && 100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym @@ -918,7 +943,7 @@ test_expect_success 'showing tree with git ls-tree -r -t' ' git ls-tree -r -t $tree >current ' -test_expect_success 'git ls-tree -r output for a known tree' ' +test_expect_success SHA1 'git ls-tree -r output for a known tree' ' cat >expected <<-\EOF && 100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym @@ -939,7 +964,7 @@ test_expect_success 'writing partial tree out with git write-tree --prefix' ' ptree=$(git write-tree --prefix=path3) ' -test_expect_success 'validate object ID for a known tree' ' +test_expect_success SHA1 'validate object ID for a known tree' ' test "$ptree" = 21ae8269cacbe57ae09138dcc3a2887f904d02b3 ' @@ -947,7 +972,7 @@ test_expect_success 'writing partial tree out with git write-tree --prefix' ' ptree=$(git write-tree --prefix=path3/subp3) ' -test_expect_success 'validate object ID for a known tree' ' +test_expect_success SHA1 'validate object ID for a known tree' ' test "$ptree" = 3c5e5399f3a333eddecce7a9b9465b63f65f51e2 ' @@ -981,7 +1006,7 @@ test_expect_success 'git read-tree followed by write-tree should be idempotent' test "$newtree" = "$tree" ' -test_expect_success 'validate git diff-files output for a know cache/work tree state' ' +test_expect_success SHA1 'validate git diff-files output for a know cache/work tree state' ' cat >expected <<\EOF && :100644 100644 f87290f8eb2cbbea7857214459a0739927eab154 0000000000000000000000000000000000000000 M path0 :120000 120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0000000000000000000000000000000000000000 M path0sym @@ -1008,21 +1033,21 @@ test_expect_success 'no diff after checkout and git update-index --refresh' ' ################################################################ P=087704a96baf1c2d1c869a8b084481e121c88b5b -test_expect_success 'git commit-tree records the correct tree in a commit' ' +test_expect_success SHA1 'git commit-tree records the correct tree in a commit' ' commit0=$(echo NO | git commit-tree $P) && tree=$(git show --pretty=raw $commit0 | sed -n -e "s/^tree //p" -e "/^author /q") && test "z$tree" = "z$P" ' -test_expect_success 'git commit-tree records the correct parent in a commit' ' +test_expect_success SHA1 'git commit-tree records the correct parent in a commit' ' commit1=$(echo NO | git commit-tree $P -p $commit0) && parent=$(git show --pretty=raw $commit1 | sed -n -e "s/^parent //p" -e "/^author /q") && test "z$commit0" = "z$parent" ' -test_expect_success 'git commit-tree omits duplicated parent in a commit' ' +test_expect_success SHA1 'git commit-tree omits duplicated parent in a commit' ' commit2=$(echo NO | git commit-tree $P -p $commit0 -p $commit0) && parent=$(git show --pretty=raw $commit2 | sed -n -e "s/^parent //p" -e "/^author /q" | diff --git a/t/t0001-init.sh b/t/t0001-init.sh index b8fc588b19..c413bff9cf 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -258,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 && @@ -312,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 ) ' @@ -410,4 +453,16 @@ test_expect_success 're-init from a linked worktree' ' ) ' +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 9670e8cbe6..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' ' 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 46042f1f13..4c214bd11c 100755 --- a/t/t0005-signals.sh +++ b/t/t0005-signals.sh @@ -10,7 +10,7 @@ one EOF test_expect_success 'sigchain works' ' - { test-sigchain >actual; ret=$?; } && + { 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 @@ -24,7 +24,7 @@ test_expect_success 'sigchain works' ' 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 ' @@ -36,7 +36,7 @@ large_git () { } test_expect_success 'create blob' ' - test-genrandom foo 16384 >file && + test-tool genrandom foo 16384 >file && git add file ' diff --git a/t/t0006-date.sh b/t/t0006-date.sh index c0c910867d..64ff86df8e 100755 --- a/t/t0006-date.sh +++ b/t/t0006-date.sh @@ -10,7 +10,7 @@ check_relative() { t=$(($TEST_DATE_NOW - $1)) echo "$t -> $2" >expect test_expect_${3:-success} "relative date ($2)" " - test-date relative $t >actual && + test-tool date relative $t >actual && test_i18ncmp expect actual " } @@ -31,9 +31,11 @@ check_show () { format=$1 time=$2 expect=$3 - test_expect_success $4 "show date ($format:$time)" ' + prereqs=$4 + zone=$5 + test_expect_success $prereqs "show date ($format:$time)" ' echo "$time -> $expect" >expect && - test-date show:$format "$time" >actual && + TZ=${zone:-$TZ} test-tool date show:"$format" "$time" >actual && test_cmp expect actual ' } @@ -51,15 +53,25 @@ 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" LONG_IS_64BIT -check_show iso-local "$FUTURE" "2152-06-19 22:24:56 +0000" LONG_IS_64BIT +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 " } @@ -80,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 d27f438bf4..c03f155a35 100755 --- a/t/t0008-ignores.sh +++ b/t/t0008-ignores.sh @@ -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" ' ############################################################################ @@ -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 index 8faba2e8bc..bc27df7f38 100755 --- a/t/t0012-help.sh +++ b/t/t0012-help.sh @@ -25,6 +25,15 @@ test_expect_success "setup" ' EOF ' +# make sure to exercise these code paths, the output is a bit tricky +# to verify +test_expect_success 'basic help commands' ' + git help >/dev/null && + git help -a >/dev/null && + git help -g >/dev/null && + git help -av >/dev/null +' + test_expect_success "works for commands and guides by default" ' configure_help && git help status && @@ -49,4 +58,31 @@ test_expect_success "--help does not work for guides" " test_i18ncmp expect actual " +test_expect_success 'git help' ' + git help >help.output && + test_i18ngrep "^ clone " help.output && + test_i18ngrep "^ add " help.output && + test_i18ngrep "^ log " help.output && + test_i18ngrep "^ commit " help.output && + test_i18ngrep "^ fetch " help.output +' +test_expect_success 'git help -g' ' + git help -g >help.output && + test_i18ngrep "^ attributes " help.output && + test_i18ngrep "^ everyday " help.output && + test_i18ngrep "^ tutorial " help.output +' + +test_expect_success 'generate builtin list' ' + git --list-cmds=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 71350e0657..5f056982a5 100755 --- a/t/t0020-crlf.sh +++ b/t/t0020-crlf.sh @@ -98,6 +98,16 @@ test_expect_success 'safecrlf: git diff demotes safecrlf=true to warn' ' ' +test_expect_success 'safecrlf: no warning with safecrlf=false' ' + git config core.autocrlf input && + git config core.safecrlf false && + + for w in I am all CRLF; do echo $w; done | append_cr >allcrlf && + git add allcrlf 2>err && + test_must_be_empty err +' + + test_expect_success 'switch off autocrlf, safecrlf, reset HEAD' ' git config core.autocrlf false && git config core.safecrlf false && diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh index 161f560446..9479a4aaab 100755 --- a/t/t0021-conversion.sh +++ b/t/t0021-conversion.sh @@ -19,7 +19,7 @@ write_script rot13-filter.pl "$PERL_PATH" \ generate_random_characters () { LEN=$1 NAME=$2 - test-genrandom some-seed $LEN | + test-tool genrandom some-seed $LEN | perl -pe "s/./chr((ord($&) % 26) + ord('a'))/sge" >"$TEST_ROOT/$NAME" } @@ -28,7 +28,7 @@ file_size () { } filter_git () { - rm -f rot13-filter.log && + rm -f *.log && git "$@" } @@ -42,10 +42,10 @@ test_cmp_count () { for FILE in "$expect" "$actual" do sort "$FILE" | uniq -c | - sed -e "s/^ *[0-9][0-9]*[ ]*IN: /x IN: /" >"$FILE.tmp" && - mv "$FILE.tmp" "$FILE" || return + sed -e "s/^ *[0-9][0-9]*[ ]*IN: /x IN: /" >"$FILE.tmp" done && - test_cmp "$expect" "$actual" + test_cmp "$expect.tmp" "$actual.tmp" && + rm "$expect.tmp" "$actual.tmp" } # Compare two files but exclude all `clean` invocations because Git can @@ -56,10 +56,10 @@ test_cmp_exclude_clean () { actual=$2 for FILE in "$expect" "$actual" do - grep -v "IN: clean" "$FILE" >"$FILE.tmp" && - mv "$FILE.tmp" "$FILE" + grep -v "IN: clean" "$FILE" >"$FILE.tmp" done && - test_cmp "$expect" "$actual" + 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 @@ -267,7 +267,7 @@ test_expect_success 'filtering large input to small output should use little mem ' 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 && test_config filter.epipe.clean "echo xyzzy" && git add big && @@ -342,7 +342,7 @@ test_expect_success 'diff does not reuse worktree files that need cleaning' ' ' test_expect_success PERL 'required process filter should filter data' ' - test_config_global filter.protocol.process "rot13-filter.pl clean smudge" && + 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 && @@ -375,7 +375,7 @@ test_expect_success PERL 'required process filter should filter data' ' IN: clean testsubdir/test3 '\''sq'\'',\$x=.r $S3 [OK] -- OUT: $S3 . [OK] STOP EOF - test_cmp_count expected.log rot13-filter.log && + test_cmp_count expected.log debug.log && git commit -m "test commit 2" && rm -f test2.r "testsubdir/test3 '\''sq'\'',\$x=.r" && @@ -388,7 +388,7 @@ test_expect_success PERL 'required process filter should filter data' ' IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $S3 [OK] -- OUT: $S3 . [OK] STOP EOF - test_cmp_exclude_clean expected.log rot13-filter.log && + test_cmp_exclude_clean expected.log debug.log && filter_git checkout --quiet --no-progress empty-branch && cat >expected.log <<-EOF && @@ -397,7 +397,7 @@ test_expect_success PERL 'required process filter should filter data' ' IN: clean test.r $S [OK] -- OUT: $S . [OK] STOP EOF - test_cmp_exclude_clean expected.log rot13-filter.log && + test_cmp_exclude_clean expected.log debug.log && filter_git checkout --quiet --no-progress master && cat >expected.log <<-EOF && @@ -409,7 +409,7 @@ test_expect_success PERL 'required process filter should filter data' ' IN: smudge testsubdir/test3 '\''sq'\'',\$x=.r $S3 [OK] -- OUT: $S3 . [OK] STOP EOF - test_cmp_exclude_clean expected.log rot13-filter.log && + 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 && @@ -419,7 +419,7 @@ test_expect_success PERL 'required process filter should filter data' ' 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 clean" && + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean" && test_config_global filter.protocol.required true && rm -rf repo && mkdir repo && @@ -439,12 +439,12 @@ test_expect_success PERL 'required process filter takes precedence' ' IN: clean test.r $S [OK] -- OUT: $S . [OK] STOP EOF - test_cmp_count expected.log rot13-filter.log + 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 clean" && + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean" && rm -rf repo && mkdir repo && ( @@ -462,7 +462,7 @@ test_expect_success PERL 'required process filter should be used only for "clean IN: clean test.r $S [OK] -- OUT: $S . [OK] STOP EOF - test_cmp_count expected.log rot13-filter.log && + test_cmp_count expected.log debug.log && rm test.r && @@ -474,12 +474,12 @@ test_expect_success PERL 'required process filter should be used only for "clean init handshake complete STOP EOF - test_cmp_exclude_clean expected.log rot13-filter.log + 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 clean smudge" && + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && test_config_global filter.protocol.required true && rm -rf repo && @@ -514,7 +514,7 @@ test_expect_success PERL 'required process filter should process multiple packet IN: clean 3pkt_2+1.file $(($S*2+1)) [OK] -- OUT: $(($S*2+1)) ... [OK] STOP EOF - test_cmp_count expected.log rot13-filter.log && + test_cmp_count expected.log debug.log && rm -f *.file && @@ -529,7 +529,7 @@ test_expect_success PERL 'required process filter should process multiple packet IN: smudge 3pkt_2+1.file $(($S*2+1)) [OK] -- OUT: $(($S*2+1)) ... [OK] STOP EOF - test_cmp_exclude_clean expected.log rot13-filter.log && + test_cmp_exclude_clean expected.log debug.log && for FILE in *.file do @@ -539,7 +539,7 @@ test_expect_success PERL 'required process filter should process multiple packet ' test_expect_success PERL 'required process filter with clean error should fail' ' - test_config_global filter.protocol.process "rot13-filter.pl clean smudge" && + 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 && @@ -558,7 +558,7 @@ test_expect_success PERL 'required process filter with clean error should fail' ' test_expect_success PERL 'process filter should restart after unexpected write failure' ' - test_config_global filter.protocol.process "rot13-filter.pl clean smudge" && + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && rm -rf repo && mkdir repo && ( @@ -579,7 +579,7 @@ test_expect_success PERL 'process filter should restart after unexpected write f git add . && rm -f *.r && - rm -f rot13-filter.log && + rm -f debug.log && git checkout --quiet --no-progress . 2>git-stderr.log && grep "smudge write error at" git-stderr.log && @@ -588,14 +588,14 @@ test_expect_success PERL 'process filter should restart after unexpected write f cat >expected.log <<-EOF && START init handshake complete - IN: smudge smudge-write-fail.r $SF [OK] -- OUT: $SF [WRITE FAIL] + 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 rot13-filter.log && + 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 && @@ -609,7 +609,7 @@ test_expect_success PERL 'process filter should restart after unexpected write f ' test_expect_success PERL 'process filter should not be restarted if it signals an error' ' - test_config_global filter.protocol.process "rot13-filter.pl clean smudge" && + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && rm -rf repo && mkdir repo && ( @@ -634,12 +634,12 @@ test_expect_success PERL 'process filter should not be restarted if it signals a cat >expected.log <<-EOF && START init handshake complete - IN: smudge error.r $SE [OK] -- OUT: 0 [ERROR] + 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 rot13-filter.log && + 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 && @@ -648,7 +648,7 @@ test_expect_success PERL 'process filter should not be restarted if it signals a ' test_expect_success PERL 'process filter abort stops processing of all further files' ' - test_config_global filter.protocol.process "rot13-filter.pl clean smudge" && + test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && rm -rf repo && mkdir repo && ( @@ -673,10 +673,10 @@ test_expect_success PERL 'process filter abort stops processing of all further f cat >expected.log <<-EOF && START init handshake complete - IN: smudge abort.r $SA [OK] -- OUT: 0 [ABORT] + IN: smudge abort.r $SA [OK] -- [ABORT] STOP EOF - test_cmp_exclude_clean expected.log rot13-filter.log && + test_cmp_exclude_clean expected.log debug.log && test_cmp "$TEST_ROOT/test.o" test.r && test_cmp "$TEST_ROOT/test2.o" test2.r && @@ -697,8 +697,124 @@ test_expect_success PERL 'invalid process filter must fail (and not hang!)' ' cp "$TEST_ROOT/test.o" test.r && test_must_fail git add . 2>git-stderr.log && - grep "does not support filter protocol version" 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 index 617f581e56..470107248e 100644 --- a/t/t0021/rot13-filter.pl +++ b/t/t0021/rot13-filter.pl @@ -2,8 +2,9 @@ # Example implementation for the Git filter protocol version 2 # See Documentation/gitattributes.txt, section "Filter Protocol" # -# The script takes the list of supported protocol capabilities as -# arguments ("clean", "smudge", etc). +# 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 @@ -17,16 +18,54 @@ # 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, ">>", "rot13-filter.log" or die "cannot open log file: $!"; +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; @@ -34,163 +73,154 @@ sub rot13 { return $str; } -sub packet_bin_read { - my $buffer; - my $bytes_read = read STDIN, $buffer, 4; - if ( $bytes_read == 0 ) { - # EOF - Git stopped talking to us! - print $debug "STOP\n"; - exit(); - } - elsif ( $bytes_read != 4 ) { - die "invalid packet: '$buffer'"; - } - my $pkt_size = hex($buffer); - if ( $pkt_size == 0 ) { - return ( 1, "" ); - } - elsif ( $pkt_size > 4 ) { - my $content_size = $pkt_size - 4; - $bytes_read = read STDIN, $buffer, $content_size; - if ( $bytes_read != $content_size ) { - die "invalid packet ($content_size bytes expected; $bytes_read bytes read)"; - } - return ( 0, $buffer ); - } - else { - die "invalid packet size: $pkt_size"; - } -} - -sub packet_txt_read { - my ( $res, $buf ) = packet_bin_read(); - unless ( $buf =~ s/\n$// ) { - die "A non-binary line MUST be terminated by an LF."; - } - return ( $res, $buf ); -} - -sub packet_bin_write { - my $buf = shift; - print STDOUT sprintf( "%04x", length($buf) + 4 ); - print STDOUT $buf; - STDOUT->flush(); -} - -sub packet_txt_write { - packet_bin_write( $_[0] . "\n" ); -} - -sub packet_flush { - print STDOUT sprintf( "%04x", 0 ); - STDOUT->flush(); -} - print $debug "START\n"; $debug->flush(); -( packet_txt_read() eq ( 0, "git-filter-client" ) ) || die "bad initialize"; -( packet_txt_read() eq ( 0, "version=2" ) ) || die "bad version"; -( packet_bin_read() eq ( 1, "" ) ) || die "bad version end"; +packet_initialize("git-filter", 2); -packet_txt_write("git-filter-server"); -packet_txt_write("version=2"); -packet_flush(); +my %remote_caps = packet_read_and_check_capabilities("clean", "smudge", "delay"); +packet_check_and_write_capabilities(\%remote_caps, @capabilities); -( packet_txt_read() eq ( 0, "capability=clean" ) ) || die "bad capability"; -( packet_txt_read() eq ( 0, "capability=smudge" ) ) || die "bad capability"; -( packet_bin_read() eq ( 1, "" ) ) || die "bad capability end"; - -foreach (@capabilities) { - packet_txt_write( "capability=" . $_ ); -} -packet_flush(); print $debug "init handshake complete\n"; $debug->flush(); while (1) { - my ($command) = packet_txt_read() =~ /^command=(.+)$/; + my ( $res, $command ) = packet_key_val_read("command"); + if ( $res == -1 ) { + print $debug "STOP\n"; + exit(); + } print $debug "IN: $command"; $debug->flush(); - my ($pathname) = packet_txt_read() =~ /^pathname=(.+)$/; - print $debug " $pathname"; - $debug->flush(); - - if ( $pathname eq "" ) { - die "bad pathname '$pathname'"; - } - - # Flush - packet_bin_read(); - - my $input = ""; - { - binmode(STDIN); - my $buffer; - my $done = 0; - while ( !$done ) { - ( $done, $buffer ) = packet_bin_read(); - $input .= $buffer; + 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"); + } + } } - print $debug " " . length($input) . " [OK] -- "; - $debug->flush(); - } - my $output; - if ( $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'"; - } - - print $debug "OUT: " . length($output) . " "; - $debug->flush(); - - 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"; + + print $debug " [OK]\n"; $debug->flush(); - packet_txt_write("status=abort"); - packet_flush(); - } - else { 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'"; + } - if ( $pathname eq "${command}-write-fail.r" ) { - print $debug "[WRITE FAIL]\n"; + ( $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(); - die "${command} write error"; } - 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 ); + 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"; } - else { - $output = ""; + + 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(); } - 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 d0bee08b2e..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 not 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 -z "$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 90db54c9f9..beb5927f77 100755 --- a/t/t0027-auto-crlf.sh +++ b/t/t0027-auto-crlf.sh @@ -4,12 +4,6 @@ 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" | tr -d 'Z' >"$2".actual && @@ -49,19 +43,31 @@ create_gitattributes () { } >.gitattributes } -create_NNO_files () { +# 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} + 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 + 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 @@ -75,7 +81,7 @@ check_warning () { *) echo >&2 "Illegal 1": "$1" ; return false ;; esac grep "will be replaced by" "$2" | sed -e "s/\(.*\) in [^ ]*$/\1/" | uniq >"$2".actual - test_cmp "$2".expect "$2".actual + test_i18ncmp "$2".expect "$2".actual } commit_check_warn () { @@ -142,6 +148,49 @@ commit_chk_wrnNNO () { ' } +# 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) @@ -321,7 +370,7 @@ test_expect_success 'setup master' ' echo >.gitattributes && git checkout -b master && git add .gitattributes && - git commit -m "add .gitattributes" "" && + 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 && @@ -329,8 +378,8 @@ test_expect_success 'setup master' ' 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_files CRLF_mix_LF CRLF_mix_LF CRLF_mix_LF CRLF_mix_LF CRLF_mix_LF && - git -c core.autocrlf=false add NNO_*.txt && + create_NNO_MIX_files && + git -c core.autocrlf=false add NNO_*.txt MIX_*.txt && git commit -m "mixed line endings" && test_tick ' @@ -391,6 +440,18 @@ test_expect_success 'commit files attr=crlf' ' commit_check_warn input "crlf" "LF_CRLF" "" "LF_CRLF" "LF_CRLF" "" ' +# 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 "" "" "" "" diff --git a/t/t0028-working-tree-encoding.sh b/t/t0028-working-tree-encoding.sh new file mode 100755 index 0000000000..12b8eb963a --- /dev/null +++ b/t/t0028-working-tree-encoding.sh @@ -0,0 +1,245 @@ +#!/bin/sh + +test_description='working-tree-encoding conversion via gitattributes' + +. ./test-lib.sh + +GIT_TRACE_WORKING_TREE_ENCODING=1 && export GIT_TRACE_WORKING_TREE_ENCODING + +test_expect_success 'setup test files' ' + git config core.eol lf && + + text="hallo there!\ncan you read me?" && + echo "*.utf16 text working-tree-encoding=utf-16" >.gitattributes && + printf "$text" >test.utf8.raw && + printf "$text" | iconv -f UTF-8 -t UTF-16 >test.utf16.raw && + printf "$text" | iconv -f UTF-8 -t UTF-32 >test.utf32.raw && + + # Line ending tests + printf "one\ntwo\nthree\n" >lf.utf8.raw && + printf "one\r\ntwo\r\nthree\r\n" >crlf.utf8.raw && + + # BOM tests + printf "\0a\0b\0c" >nobom.utf16be.raw && + printf "a\0b\0c\0" >nobom.utf16le.raw && + printf "\376\777\0a\0b\0c" >bebom.utf16be.raw && + printf "\777\376a\0b\0c\0" >lebom.utf16le.raw && + printf "\0\0\0a\0\0\0b\0\0\0c" >nobom.utf32be.raw && + printf "a\0\0\0b\0\0\0c\0\0\0" >nobom.utf32le.raw && + printf "\0\0\376\777\0\0\0a\0\0\0b\0\0\0c" >bebom.utf32be.raw && + printf "\777\376\0\0a\0\0\0b\0\0\0c\0\0\0" >lebom.utf32le.raw && + + # Add only UTF-16 file, we will add the UTF-32 file later + cp test.utf16.raw test.utf16 && + cp test.utf32.raw test.utf32 && + git add .gitattributes test.utf16 && + git commit -m initial +' + +test_expect_success 'ensure UTF-8 is stored in Git' ' + test_when_finished "rm -f test.utf16.git" && + + git cat-file -p :test.utf16 >test.utf16.git && + test_cmp_bin test.utf8.raw test.utf16.git +' + +test_expect_success 're-encode to UTF-16 on checkout' ' + test_when_finished "rm -f test.utf16.raw" && + + rm test.utf16 && + git checkout test.utf16 && + test_cmp_bin test.utf16.raw test.utf16 +' + +test_expect_success 'check $GIT_DIR/info/attributes support' ' + test_when_finished "rm -f test.utf32.git" && + test_when_finished "git reset --hard HEAD" && + + echo "*.utf32 text working-tree-encoding=utf-32" >.git/info/attributes && + git add test.utf32 && + + git cat-file -p :test.utf32 >test.utf32.git && + test_cmp_bin test.utf8.raw test.utf32.git +' + +for i in 16 32 +do + test_expect_success "check prohibited UTF-${i} BOM" ' + test_when_finished "git reset --hard HEAD" && + + echo "*.utf${i}be text working-tree-encoding=utf-${i}be" >>.gitattributes && + echo "*.utf${i}le text working-tree-encoding=utf-${i}LE" >>.gitattributes && + + # Here we add a UTF-16 (resp. UTF-32) files with BOM (big/little-endian) + # but we tell Git to treat it as UTF-16BE/UTF-16LE (resp. UTF-32). + # In these cases the BOM is prohibited. + cp bebom.utf${i}be.raw bebom.utf${i}be && + test_must_fail git add bebom.utf${i}be 2>err.out && + test_i18ngrep "fatal: BOM is prohibited .* utf-${i}be" err.out && + test_i18ngrep "use UTF-${i} as working-tree-encoding" err.out && + + cp lebom.utf${i}le.raw lebom.utf${i}be && + test_must_fail git add lebom.utf${i}be 2>err.out && + test_i18ngrep "fatal: BOM is prohibited .* utf-${i}be" err.out && + test_i18ngrep "use UTF-${i} as working-tree-encoding" err.out && + + cp bebom.utf${i}be.raw bebom.utf${i}le && + test_must_fail git add bebom.utf${i}le 2>err.out && + test_i18ngrep "fatal: BOM is prohibited .* utf-${i}LE" err.out && + test_i18ngrep "use UTF-${i} as working-tree-encoding" err.out && + + cp lebom.utf${i}le.raw lebom.utf${i}le && + test_must_fail git add lebom.utf${i}le 2>err.out && + test_i18ngrep "fatal: BOM is prohibited .* utf-${i}LE" err.out && + test_i18ngrep "use UTF-${i} as working-tree-encoding" err.out + ' + + test_expect_success "check required UTF-${i} BOM" ' + test_when_finished "git reset --hard HEAD" && + + echo "*.utf${i} text working-tree-encoding=utf-${i}" >>.gitattributes && + + cp nobom.utf${i}be.raw nobom.utf${i} && + test_must_fail git add nobom.utf${i} 2>err.out && + test_i18ngrep "fatal: BOM is required .* utf-${i}" err.out && + test_i18ngrep "use UTF-${i}BE or UTF-${i}LE" err.out && + + cp nobom.utf${i}le.raw nobom.utf${i} && + test_must_fail git add nobom.utf${i} 2>err.out && + test_i18ngrep "fatal: BOM is required .* utf-${i}" err.out && + test_i18ngrep "use UTF-${i}BE or UTF-${i}LE" err.out + ' + + test_expect_success "eol conversion for UTF-${i} encoded files on checkout" ' + test_when_finished "rm -f crlf.utf${i}.raw lf.utf${i}.raw" && + test_when_finished "git reset --hard HEAD^" && + + cat lf.utf8.raw | iconv -f UTF-8 -t UTF-${i} >lf.utf${i}.raw && + cat crlf.utf8.raw | iconv -f UTF-8 -t UTF-${i} >crlf.utf${i}.raw && + cp crlf.utf${i}.raw eol.utf${i} && + + cat >expectIndexLF <<-EOF && + i/lf w/-text attr/text eol.utf${i} + EOF + + git add eol.utf${i} && + git commit -m eol && + + # UTF-${i} with CRLF (Windows line endings) + rm eol.utf${i} && + git -c core.eol=crlf checkout eol.utf${i} && + test_cmp_bin crlf.utf${i}.raw eol.utf${i} && + + # Although the file has CRLF in the working tree, + # ensure LF in the index + git ls-files --eol eol.utf${i} >actual && + test_cmp expectIndexLF actual && + + # UTF-${i} with LF (Unix line endings) + rm eol.utf${i} && + git -c core.eol=lf checkout eol.utf${i} && + test_cmp_bin lf.utf${i}.raw eol.utf${i} && + + # The file LF in the working tree, ensure LF in the index + git ls-files --eol eol.utf${i} >actual && + test_cmp expectIndexLF actual + ' +done + +test_expect_success 'check unsupported encodings' ' + test_when_finished "git reset --hard HEAD" && + + echo "*.set text working-tree-encoding" >.gitattributes && + printf "set" >t.set && + test_must_fail git add t.set 2>err.out && + test_i18ngrep "true/false are no valid working-tree-encodings" err.out && + + echo "*.unset text -working-tree-encoding" >.gitattributes && + printf "unset" >t.unset && + git add t.unset && + + echo "*.empty text working-tree-encoding=" >.gitattributes && + printf "empty" >t.empty && + git add t.empty && + + echo "*.garbage text working-tree-encoding=garbage" >.gitattributes && + printf "garbage" >t.garbage && + test_must_fail git add t.garbage 2>err.out && + test_i18ngrep "failed to encode" err.out +' + +test_expect_success 'error if encoding round trip is not the same during refresh' ' + BEFORE_STATE=$(git rev-parse HEAD) && + test_when_finished "git reset --hard $BEFORE_STATE" && + + # Add and commit a UTF-16 file but skip the "working-tree-encoding" + # filter. Consequently, the in-repo representation is UTF-16 and not + # UTF-8. This simulates a Git version that has no working tree encoding + # support. + echo "*.utf16le text working-tree-encoding=utf-16le" >.gitattributes && + echo "hallo" >nonsense.utf16le && + TEST_HASH=$(git hash-object --no-filters -w nonsense.utf16le) && + git update-index --add --cacheinfo 100644 $TEST_HASH nonsense.utf16le && + COMMIT=$(git commit-tree -p $(git rev-parse HEAD) -m "plain commit" $(git write-tree)) && + git update-ref refs/heads/master $COMMIT && + + test_must_fail git checkout HEAD^ 2>err.out && + test_i18ngrep "error: .* overwritten by checkout:" err.out +' + +test_expect_success 'error if encoding garbage is already in Git' ' + BEFORE_STATE=$(git rev-parse HEAD) && + test_when_finished "git reset --hard $BEFORE_STATE" && + + # Skip the UTF-16 filter for the added file + # This simulates a Git version that has no checkoutEncoding support + cp nobom.utf16be.raw nonsense.utf16 && + TEST_HASH=$(git hash-object --no-filters -w nonsense.utf16) && + git update-index --add --cacheinfo 100644 $TEST_HASH nonsense.utf16 && + COMMIT=$(git commit-tree -p $(git rev-parse HEAD) -m "plain commit" $(git write-tree)) && + git update-ref refs/heads/master $COMMIT && + + git diff 2>err.out && + test_i18ngrep "error: BOM is required" err.out +' + +test_expect_success 'check roundtrip encoding' ' + test_when_finished "rm -f roundtrip.shift roundtrip.utf16" && + test_when_finished "git reset --hard HEAD" && + + text="hallo there!\nroundtrip test here!" && + printf "$text" | iconv -f UTF-8 -t SHIFT-JIS >roundtrip.shift && + printf "$text" | iconv -f UTF-8 -t UTF-16 >roundtrip.utf16 && + echo "*.shift text working-tree-encoding=SHIFT-JIS" >>.gitattributes && + + # SHIFT-JIS encoded files are round-trip checked by default... + GIT_TRACE=1 git add .gitattributes roundtrip.shift 2>&1 | + grep "Checking roundtrip encoding for SHIFT-JIS" && + git reset && + + # ... unless we overwrite the Git config! + ! GIT_TRACE=1 git -c core.checkRoundtripEncoding=garbage \ + add .gitattributes roundtrip.shift 2>&1 | + grep "Checking roundtrip encoding for SHIFT-JIS" && + git reset && + + # UTF-16 encoded files should not be round-trip checked by default... + ! GIT_TRACE=1 git add roundtrip.utf16 2>&1 | + grep "Checking roundtrip encoding for UTF-16" && + git reset && + + # ... unless we tell Git to check it! + GIT_TRACE=1 git -c core.checkRoundtripEncoding="UTF-16, UTF-32" \ + add roundtrip.utf16 2>&1 | + grep "Checking roundtrip encoding for utf-16" && + git reset && + + # ... unless we tell Git to check it! + # (here we also check that the casing of the encoding is irrelevant) + GIT_TRACE=1 git -c core.checkRoundtripEncoding="UTF-32, utf-16" \ + add roundtrip.utf16 2>&1 | + grep "Checking roundtrip encoding for utf-16" && + git reset +' + +test_done diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index 74d2cd76fe..04d474c84f 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -10,6 +10,8 @@ test_description='our own option parser' 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 @@ -289,7 +291,7 @@ test_expect_success 'OPT_CALLBACK() and OPT_BIT() work' ' test_expect_success 'OPT_CALLBACK() and callback errors work' ' 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 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 444b5a4df8..21a8b53132 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -8,15 +8,15 @@ 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() { @@ -37,7 +37,7 @@ 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 @@ -46,7 +46,7 @@ else # 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-path-utils print_path /)" in + case "$(test-tool path-utils print_path /)" in */) rootslash=1;; *) rootslash=0;; esac @@ -61,7 +61,7 @@ ancestor() { 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'" } @@ -70,13 +70,15 @@ ancestor() { case $(uname -s) in *MINGW*) ;; +*CYGWIN*) + ;; *) test_set_prereq POSIX ;; esac -test_expect_success basename 'test-path-utils basename' -test_expect_success dirname 'test-path-utils dirname' +test_expect_success basename 'test-tool path-utils basename' +test_expect_success dirname 'test-tool path-utils dirname' norm_path "" "" norm_path . "" @@ -155,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' ' @@ -207,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/ @@ -347,4 +349,90 @@ test_submodule_relative_url "(null)" "ssh://hostname:22/repo" "../subrepo" "ssh: 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_expect_success 'match .gitmodules' ' + test-tool path-utils is_dotgitmodules \ + .gitmodules \ + \ + .git${u200c}modules \ + \ + .Gitmodules \ + .gitmoduleS \ + \ + ".gitmodules " \ + ".gitmodules." \ + ".gitmodules " \ + ".gitmodules. " \ + ".gitmodules ." \ + ".gitmodules.." \ + ".gitmodules " \ + ".gitmodules. " \ + ".gitmodules . " \ + ".gitmodules ." \ + \ + ".Gitmodules " \ + ".Gitmodules." \ + ".Gitmodules " \ + ".Gitmodules. " \ + ".Gitmodules ." \ + ".Gitmodules.." \ + ".Gitmodules " \ + ".Gitmodules. " \ + ".Gitmodules . " \ + ".Gitmodules ." \ + \ + GITMOD~1 \ + gitmod~1 \ + GITMOD~2 \ + gitmod~3 \ + GITMOD~4 \ + \ + "GITMOD~1 " \ + "gitmod~2." \ + "GITMOD~3 " \ + "gitmod~4. " \ + "GITMOD~1 ." \ + "gitmod~2 " \ + "GITMOD~3. " \ + "gitmod~4 . " \ + \ + GI7EBA~1 \ + gi7eba~9 \ + \ + GI7EB~10 \ + GI7EB~11 \ + GI7EB~99 \ + GI7EB~10 \ + GI7E~100 \ + GI7E~101 \ + GI7E~999 \ + ~1000000 \ + ~9999999 \ + \ + --not \ + ".gitmodules x" \ + ".gitmodules .x" \ + \ + " .gitmodules" \ + \ + ..gitmodules \ + \ + gitmodules \ + \ + .gitmodule \ + \ + ".gitmodules x " \ + ".gitmodules .x" \ + \ + GI7EBA~ \ + GI7EBA~0 \ + GI7EBA~~1 \ + GI7EBA~X \ + Gx7EBA~1 \ + GI7EBX~1 \ + \ + GI7EB~1 \ + GI7EB~01 \ + GI7EB~1X +' + test_done diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh index 12228b4aa6..c887ed5b45 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 ' @@ -63,17 +104,17 @@ World EOF test_expect_success 'run_command runs in parallel with more jobs available than tasks' ' - test-run-command run-command-parallel 5 sh -c "printf \"%s\n%s\n\" Hello World" 2>actual && + 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-run-command run-command-parallel 4 sh -c "printf \"%s\n%s\n\" Hello World" 2>actual && + 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-run-command run-command-parallel 3 sh -c "printf \"%s\n%s\n\" Hello World" 2>actual && + test-tool run-command run-command-parallel 3 sh -c "printf \"%s\n%s\n\" Hello World" 2>actual && test_cmp expect actual ' @@ -87,7 +128,7 @@ asking for a quick stop EOF test_expect_success 'run_command is asked to abort gracefully' ' - test-run-command run-command-abort 3 false 2>actual && + test-tool run-command run-command-abort 3 false 2>actual && test_cmp expect actual ' @@ -96,8 +137,45 @@ no further jobs available EOF test_expect_success 'run_command outputs ' ' - test-run-command run-command-no-jobs 3 sh -c "printf \"%s\n%s\n\" Hello World" 2>actual && + 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 -e 's/.* run_command: //' -e '/trace: .*/d' >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 991ed2a48d..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 --bug + test-tool regex --bug ' test_done diff --git a/t/t0090-cache-tree.sh b/t/t0090-cache-tree.sh index adfd4f0b5e..0c61268fd2 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 && - sed "s/$_x40/SHA/" <actual >filtered && + test-tool dump-cache-tree | sed -e '/#(ref)/d' >actual && + sed "s/$OID_REGEX/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 ' @@ -246,9 +246,9 @@ test_expect_success 'switching trees does not invalidate shared index' ' git update-index --split-index && >split && git add split && - test-dump-split-index .git/index | grep -v ^own >before && + test-tool dump-split-index .git/index | grep -v ^own >before && git commit -m "as-is" && - test-dump-split-index .git/index | grep -v ^own >after && + test-tool dump-split-index .git/index | grep -v ^own >after && test_cmp before after ' 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/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 1d8d1f210b..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" diff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh new file mode 100755 index 0000000000..4984ca583d --- /dev/null +++ b/t/t0410-partial-clone.sh @@ -0,0 +1,351 @@ +#!/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 'extensions.partialclone without filter' ' + test_create_repo server && + git clone --filter="blob:none" "file://$(pwd)/server" client && + git -C client config --unset core.partialclonefilter && + git -C client fetch origin +' + +test_expect_success 'missing reflog object, but promised by a commit, 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}) && + 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..013c5a7bc3 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/ '"$OID_REGEX"' / 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..1057a96b24 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 @@ -30,7 +30,7 @@ read_tree_twoway () { compare_change () { sed -n >current \ -e '/^--- /d; /^+++ /d; /^@@ /d;' \ - -e 's/^\([-+][0-7][0-7][0-7][0-7][0-7][0-7]\) '"$_x40"' /\1 X /p' \ + -e 's/^\([-+][0-7][0-7][0-7][0-7][0-7][0-7]\) '"$OID_REGEX"' /\1 X /p' \ "$1" test_cmp expected current } @@ -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..9c05f5e1f5 100755 --- a/t/t1002-read-tree-m-u-2way.sh +++ b/t/t1002-read-tree-m-u-2way.sh @@ -16,7 +16,7 @@ compare_change () { -e '1{/^diff --git /d;}' \ -e '2{/^index /d;}' \ -e '/^--- /d; /^+++ /d; /^@@ /d;' \ - -e 's/^\(.[0-7][0-7][0-7][0-7][0-7][0-7]\) '"$_x40"' /\1 X /' "$1" + -e 's/^\(.[0-7][0-7][0-7][0-7][0-7][0-7]\) '"$OID_REGEX"' /\1 X /' "$1" test_cmp expected current } @@ -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 b19f332694..13dd510b2e 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -236,8 +236,8 @@ test_expect_success "--batch-check for an empty line" ' ' test_expect_success 'empty --batch-check notices missing object' ' - echo "$_z40 missing" >expect && - echo "$_z40" | git cat-file --batch-check="" >actual && + echo "$ZERO_OID missing" >expect && + echo "$ZERO_OID" | git cat-file --batch-check="" >actual && test_cmp expect actual ' @@ -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 && @@ -294,8 +294,8 @@ test_expect_success 'setup blobs which are likely to delta' ' test_expect_success 'confirm that neither loose blob is a delta' ' cat >expect <<-EOF && - $_z40 - $_z40 + $ZERO_OID + $ZERO_OID EOF git cat-file --batch-check="%(deltabase)" <blobs >actual && test_cmp expect actual diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index c5245c5cb4..a37753047e 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -9,13 +9,13 @@ echo_without_newline() { } test_blob_does_not_exist() { - test_expect_success 'blob does not exist in database' " + test_expect_success SHA1 'blob does not exist in database' " test_must_fail git cat-file blob $1 " } test_blob_exists() { - test_expect_success 'blob exists in database' " + test_expect_success SHA1 'blob exists in database' " git cat-file blob $1 " } @@ -73,19 +73,19 @@ test_expect_success "Can't use --path with --no-filters" ' push_repo -test_expect_success 'hash a file' ' +test_expect_success SHA1 'hash a file' ' test $hello_sha1 = $(git hash-object hello) ' test_blob_does_not_exist $hello_sha1 -test_expect_success 'hash from stdin' ' +test_expect_success SHA1 'hash from stdin' ' test $example_sha1 = $(git hash-object --stdin < example) ' test_blob_does_not_exist $example_sha1 -test_expect_success 'hash a file and write to database' ' +test_expect_success SHA1 'hash a file and write to database' ' test $hello_sha1 = $(git hash-object -w hello) ' @@ -134,6 +134,16 @@ test_expect_success 'gitattributes also work in a subdirectory' ' ) ' +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' ' nofilters_file1=$(git hash-object --no-filters file1) && test "$file0_sha" = "$nofilters_file1" && @@ -151,7 +161,7 @@ pop_repo for args in "-w --stdin" "--stdin -w"; do push_repo - test_expect_success "hash from stdin and write to database ($args)" ' + test_expect_success SHA1 "hash from stdin and write to database ($args)" ' test $example_sha1 = $(git hash-object $args < example) ' @@ -166,14 +176,14 @@ example" sha1s="$hello_sha1 $example_sha1" -test_expect_success "hash two files with names on stdin" ' +test_expect_success SHA1 "hash two files with names on stdin" ' test "$sha1s" = "$(echo_without_newline "$filenames" | git hash-object --stdin-paths)" ' for args in "-w --stdin-paths" "--stdin-paths -w"; do push_repo - test_expect_success "hash two files with names on stdin and write to database ($args)" ' + test_expect_success SHA1 "hash two files with names on stdin and write to database ($args)" ' test "$sha1s" = "$(echo_without_newline "$filenames" | git hash-object $args)" ' diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh index c167f606ca..0c6f48f302 100755 --- a/t/t1011-read-tree-sparse-checkout.sh +++ b/t/t1011-read-tree-sparse-checkout.sh @@ -15,8 +15,11 @@ test_description='sparse checkout tests . "$TEST_DIRECTORY"/lib-read-tree.sh test_expect_success 'setup' ' + test_commit init && + echo modified >>init.t && + cat >expected <<-EOF && - 100644 77f0ba1734ed79d12881f81b36ee134de6a3327b 0 init.t + 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 @@ -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 && diff --git a/t/t1012-read-tree-df.sh b/t/t1012-read-tree-df.sh index a6a04b6b90..57f0770df1 100755 --- a/t/t1012-read-tree-df.sh +++ b/t/t1012-read-tree-df.sh @@ -32,7 +32,7 @@ settree () { checkindex () { git ls-files -s | - sed "s|^[0-7][0-7]* $_x40 \([0-3]\) |\1 |" >current && + sed "s|^[0-7][0-7]* $OID_REGEX \([0-3]\) |\1 |" >current && cat >expect && test_cmp expect current } 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/t1050-large.sh b/t/t1050-large.sh index 6fd264cff0..f9eb143f43 100755 --- a/t/t1050-large.sh +++ b/t/t1050-large.sh @@ -103,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 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/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-config.sh index 923bfc5a26..03c223708e 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-config.sh @@ -108,6 +108,7 @@ bar = foo [beta] baz = multiple \ lines +foo = bar EOF test_expect_success 'unset with cont. lines' ' @@ -118,6 +119,7 @@ cat > expect <<\EOF [alpha] bar = foo [beta] +foo = bar EOF test_expect_success 'unset with cont. lines is correct' 'test_cmp expect .git/config' @@ -703,6 +705,12 @@ test_expect_success 'invalid unit' ' 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 @@ -734,7 +742,7 @@ test_expect_success bool ' do git config --bool --get bool.true$i >>result git config --bool --get bool.false$i >>result - done && + done && test_cmp expect result' test_expect_success 'invalid bool (--get)' ' @@ -895,6 +903,66 @@ 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 +' + +test_expect_success 'get --type=color' ' + rm .git/config && + git config foo.color "red" && + git config --get --type=color foo.color >actual.raw && + test_decode_color <actual.raw >actual && + echo "<RED>" >expect && + test_cmp expect actual +' + +cat >expect << EOF +[foo] + color = red +EOF + +test_expect_success 'set --type=color' ' + rm .git/config && + git config --type=color foo.color "red" && + test_cmp expect .git/config +' + +test_expect_success 'get --type=color barfs on non-color' ' + echo "[foo]bar=not-a-color" >.git/config && + test_must_fail git config --get --type=color foo.bar +' + +test_expect_success 'set --type=color barfs on non-color' ' + test_must_fail git config --type=color foo.color "not-a-color" 2>error && + test_i18ngrep "cannot parse color" error +' + cat > expect << EOF [quote] leading = " test" @@ -1069,6 +1137,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 && @@ -1097,10 +1172,95 @@ test_expect_success 'multiple git -c appends config' ' 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 && @@ -1177,8 +1337,113 @@ 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' ' +test_expect_success '--unset last key removes section (except if commented)' ' cat >.git/config <<-\EOF && # some generic comment on the configuration file itself # a comment specific to this "section" section. @@ -1192,13 +1457,86 @@ test_expect_failure 'unsetting the last key in a section removes header' ' cat >expect <<-\EOF && # some generic comment on the configuration file itself + # a comment specific to this "section" section. + [section] + # some intervening lines + # that should also be dropped + + # please be careful when you update the above variable EOF git config --unset section.key && - test_cmp expect .git/config + test_cmp expect .git/config && + + cat >.git/config <<-\EOF && + [section] + key = value + [next-section] + EOF + + cat >expect <<-\EOF && + [next-section] + EOF + + git config --unset section.key && + test_cmp expect .git/config && + + q_to_tab >.git/config <<-\EOF && + [one] + Qkey = "multiline \ + QQ# with comment" + [two] + key = true + EOF + git config --unset two.key && + ! grep two .git/config && + + q_to_tab >.git/config <<-\EOF && + [one] + Qkey = "multiline \ + QQ# with comment" + [one] + key = true + EOF + git config --unset-all one.key && + test_line_count = 0 .git/config && + + q_to_tab >.git/config <<-\EOF && + [one] + Qkey = true + Q# a comment not at the start + [two] + Qkey = true + EOF + git config --unset two.key && + grep two .git/config && + + q_to_tab >.git/config <<-\EOF && + [one] + Qkey = not [two "subsection"] + [two "subsection"] + [two "subsection"] + Qkey = true + [TWO "subsection"] + [one] + EOF + git config --unset two.subsection.key && + test "not [two subsection]" = "$(git config one.key)" && + test_line_count = 3 .git/config ' -test_expect_failure 'adding a key into an empty section reuses header' ' +test_expect_success '--unset-all removes section if empty & uncommented' ' + cat >.git/config <<-\EOF && + [section] + key = value1 + key = value2 + EOF + + git config --unset-all section.key && + test_line_count = 0 .git/config +' + +test_expect_success 'adding a key into an empty section reuses header' ' cat >.git/config <<-\EOF && [section] EOF @@ -1354,10 +1692,10 @@ test_expect_success '--show-origin stdin with file include' ' ' test_expect_success !MINGW '--show-origin blob' ' - cat >expect <<-\EOF && - blob:a9d9f9e555b5c6f07cbe09d3f06fe3df11e09c08 user.custom=true - EOF 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 ' @@ -1372,4 +1710,94 @@ test_expect_success !MINGW '--show-origin blob ref' ' 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 +' + +cat >.git/config <<-\EOF && +[core] +foo = true +number = 10 +big = 1M +EOF + +test_expect_success 'identical modern --type specifiers are allowed' ' + git config --type=int --type=int core.big >actual && + echo 1048576 >expect && + test_cmp expect actual +' + +test_expect_success 'identical legacy --type specifiers are allowed' ' + git config --int --int core.big >actual && + echo 1048576 >expect && + test_cmp expect actual +' + +test_expect_success 'identical mixed --type specifiers are allowed' ' + git config --int --type=int core.big >actual && + echo 1048576 >expect && + test_cmp expect actual +' + +test_expect_success 'non-identical modern --type specifiers are not allowed' ' + test_must_fail git config --type=int --type=bool core.big 2>error && + test_i18ngrep "only one type at a time" error +' + +test_expect_success 'non-identical legacy --type specifiers are not allowed' ' + test_must_fail git config --int --bool core.big 2>error && + test_i18ngrep "only one type at a time" error +' + +test_expect_success 'non-identical mixed --type specifiers are not allowed' ' + test_must_fail git config --type=int --bool core.big 2>error && + test_i18ngrep "only one type at a time" error +' + +test_expect_success '--type allows valid type specifiers' ' + echo "true" >expect && + git config --type=bool core.foo >actual && + test_cmp expect actual +' + +test_expect_success '--no-type unsets type specifiers' ' + echo "10" >expect && + git config --type=bool --no-type core.number >actual && + test_cmp expect actual +' + +test_expect_success 'unset type specifiers may be reset to conflicting ones' ' + echo 1048576 >expect && + git config --type=bool --no-type --type=int core.big >actual && + test_cmp expect actual +' + +test_expect_success '--type rejects unknown specifiers' ' + test_must_fail git config --type=nonsense core.foo 2>error && + test_i18ngrep "unrecognized --type argument" error +' + +test_expect_success '--replace-all does not invent newlines' ' + q_to_tab >.git/config <<-\EOF && + [abc]key + QkeepSection + [xyz] + Qkey = 1 + [abc] + Qkey = a + EOF + q_to_tab >expect <<-\EOF && + [abc] + QkeepSection + [xyz] + Qkey = 1 + [abc] + Qkey = b + EOF + git config --replace-all abc.key b && + test_cmp .git/config expect +' + test_done diff --git a/t/t1301-shared-repo.sh b/t/t1301-shared-repo.sh index 1312004f8c..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 ' @@ -177,7 +173,7 @@ test_expect_success POSIXPERM 'remote init does not use config from cwd' ' umask 0022 && git init --bare child.git && echo "-rw-r--r--" >expect && - modebits child.git/config >actual && + test_modebits child.git/config >actual && test_cmp expect actual ' @@ -187,7 +183,7 @@ test_expect_success POSIXPERM 're-init respects core.sharedrepository (local)' ' echo whatever >templates/foo && git init --template=templates && echo "-rw-rw-rw-" >expect && - modebits .git/foo >actual && + test_modebits .git/foo >actual && test_cmp expect actual ' @@ -198,7 +194,7 @@ test_expect_success POSIXPERM 're-init respects core.sharedrepository (remote)' test_path_is_missing child.git/foo && git init --bare --template=../templates child.git && echo "-rw-rw-rw-" >expect && - modebits child.git/foo >actual && + test_modebits child.git/foo >actual && test_cmp expect actual ' @@ -209,7 +205,7 @@ test_expect_success POSIXPERM 'template can set core.sharedrepository' ' cp .git/config templates/config && git init --bare --template=../templates child.git && echo "-rw-rw-rw-" >expect && - modebits child.git/HEAD >actual && + test_modebits child.git/HEAD >actual && test_cmp expect actual ' 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 eed31ffa30..37dc689d8c 100755 --- a/t/t1307-config-blob.sh +++ b/t/t1307-config-blob.sh @@ -73,4 +73,8 @@ test_expect_success 'can parse blob ending with CR' ' test_cmp expect actual ' +test_expect_success 'config --blob outside of a repository is an error' ' + test_must_fail nongit git config --blob=foo --list +' + test_done diff --git a/t/t1308-config-set.sh b/t/t1308-config-set.sh index ff50960cca..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 ' @@ -196,14 +207,14 @@ test_expect_success 'proper error on error in default config files' ' test_when_finished "mv .git/config.old .git/config" && echo "[" >>.git/config && echo "fatal: bad config line 34 in file .git/config" >expect && - test_expect_code 128 test-config get_value foo.bar 2>actual && + 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 line 1 in file syntax-error" >expect && - test_expect_code 128 test-config configset_get_value foo.bar syntax-error 2>actual && + test_expect_code 128 test-tool config configset_get_value foo.bar syntax-error 2>actual && test_i18ncmp expect actual ' @@ -215,7 +226,9 @@ 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' ' @@ -254,7 +267,7 @@ test_expect_success 'iteration shows correct origins' ' name= scope=cmdline EOF - GIT_CONFIG_PARAMETERS=$cmdline_config test-config iterate >actual && + GIT_CONFIG_PARAMETERS=$cmdline_config test-tool config iterate >actual && test_cmp expect actual ' 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/t1310-config-default.sh b/t/t1310-config-default.sh new file mode 100755 index 0000000000..6049d91708 --- /dev/null +++ b/t/t1310-config-default.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +test_description='Test git config in different settings (with --default)' + +. ./test-lib.sh + +test_expect_success 'uses --default when entry missing' ' + echo quux >expect && + git config -f config --default=quux core.foo >actual && + test_cmp expect actual +' + +test_expect_success 'does not use --default when entry present' ' + echo bar >expect && + git -c core.foo=bar config --default=baz core.foo >actual && + test_cmp expect actual +' + +test_expect_success 'canonicalizes --default with appropriate type' ' + echo true >expect && + git config -f config --default=yes --bool core.foo >actual && + test_cmp expect actual +' + +test_expect_success 'dies when --default cannot be parsed' ' + test_must_fail git config -f config --type=expiry-date --default=x --get \ + not.a.section 2>error && + test_i18ngrep "failed to format default config value" error +' + +test_expect_success 'does not allow --default without --get' ' + test_must_fail git config --default=quux --unset a.section >output 2>&1 && + test_i18ngrep "\-\-default is only applicable to" output +' + +test_done diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index d4fb977060..e1fd0f0ca8 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -6,74 +6,102 @@ test_description='Test git update-ref and basic ref logging' . ./test-lib.sh -Z=$_z40 +Z=$ZERO_OID -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 -outside=refs/foo - -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 + 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 ' -rm -f .git/$m test_expect_success 'update-ref does not create reflogs by default' ' test_when_finished "git update-ref -d $outside" && @@ -93,40 +121,96 @@ test_expect_success 'update-ref creates reflogs with --create-reflog' ' 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 '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' ' @@ -155,82 +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 +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)" \ - '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)" \ - '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 "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 @@ -244,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 @@ -331,20 +442,80 @@ $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") +' + +# Test adding and deleting pseudorefs + +test_expect_success 'given old value for missing pseudoref, do not create' ' + test_must_fail git update-ref PSEUDOREF $A $B 2>err && + test_path_is_missing .git/PSEUDOREF && + grep "could not read ref" err +' + +test_expect_success 'create pseudoref' ' + git update-ref PSEUDOREF $A && + test $A = $(cat .git/PSEUDOREF) +' + +test_expect_success 'overwrite pseudoref with no old value given' ' + git update-ref PSEUDOREF $B && + test $B = $(cat .git/PSEUDOREF) +' + +test_expect_success 'overwrite pseudoref with correct old value' ' + git update-ref PSEUDOREF $C $B && + test $C = $(cat .git/PSEUDOREF) +' + +test_expect_success 'do not overwrite pseudoref with wrong old value' ' + test_must_fail git update-ref PSEUDOREF $D $E 2>err && + test $C = $(cat .git/PSEUDOREF) && + grep "unexpected object ID" err +' + +test_expect_success 'delete pseudoref' ' + git update-ref -d PSEUDOREF && + test_path_is_missing .git/PSEUDOREF +' + +test_expect_success 'do not delete pseudoref with wrong old value' ' + git update-ref PSEUDOREF $A && + test_must_fail git update-ref -d PSEUDOREF $B 2>err && + test $A = $(cat .git/PSEUDOREF) && + grep "unexpected object ID" err +' + +test_expect_success 'delete pseudoref with correct old value' ' + git update-ref -d PSEUDOREF $A && + test_path_is_missing .git/PSEUDOREF +' + +test_expect_success 'create pseudoref with old OID zero' ' + git update-ref PSEUDOREF $A $Z && + test $A = $(cat .git/PSEUDOREF) +' + +test_expect_success 'do not overwrite pseudoref with old OID zero' ' + test_when_finished git update-ref -d PSEUDOREF && + test_must_fail git update-ref PSEUDOREF $B $Z 2>err && + test $A = $(cat .git/PSEUDOREF) && + grep "already exists" err +' + +# Test --stdin a=refs/heads/a b=refs/heads/b @@ -501,6 +672,7 @@ test_expect_success 'stdin does not create reflogs by default' ' ' 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 && @@ -1141,7 +1313,10 @@ 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' ' ( diff --git a/t/t1401-symbolic-ref.sh b/t/t1401-symbolic-ref.sh index eec3e90f9c..a4ebb0b65f 100755 --- a/t/t1401-symbolic-ref.sh +++ b/t/t1401-symbolic-ref.sh @@ -65,7 +65,7 @@ reset_to_sane test_expect_success 'symbolic-ref fails to delete real ref' ' echo "fatal: Cannot delete refs/heads/foo, not a symbolic ref" >expect && test_must_fail git symbolic-ref -d refs/heads/foo >actual 2>&1 && - test_path_is_file .git/refs/heads/foo && + git rev-parse --verify refs/heads/foo && test_cmp expect actual ' reset_to_sane @@ -129,11 +129,35 @@ test_expect_success 'symbolic-ref does not create ref d/f conflicts' ' test_must_fail git symbolic-ref refs/heads/df/conflict refs/heads/df ' -test_expect_success 'symbolic-ref handles existing pointer to invalid name' ' +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 0790edf60d..98e4a8613b 100755 --- a/t/t1402-check-ref-format.sh +++ b/t/t1402-check-ref-format.sh @@ -144,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 && @@ -161,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-errors.sh b/t/t1404-update-ref-errors.sh index c34ece48f5..3a887b5113 100755 --- a/t/t1404-update-ref-errors.sh +++ b/t/t1404-update-ref-errors.sh @@ -34,6 +34,81 @@ test_update_rejected () { 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 && @@ -188,6 +263,72 @@ test_expect_success 'empty directory should not fool 1-arg delete' ' 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' ' @@ -404,4 +545,77 @@ test_expect_success 'broken reference blocks indirect create' ' 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..4623ae15c4 --- /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 $ZERO_OID > .git/logs/PSEUDO-MAIN && + mkdir -p .git/logs/refs/bisect && + echo $ZERO_OID > .git/logs/refs/bisect/random && + + echo $ZERO_OID > .git/worktrees/wt/logs/PSEUDO-WT && + mkdir -p .git/worktrees/wt/logs/refs/bisect && + echo $ZERO_OID > .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/t1411-reflog-show.sh b/t/t1411-reflog-show.sh index 6ac7734d79..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 && 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 8937e25e49..c7878a60ed 100755 --- a/t/t1430-bad-ref-name.sh +++ b/t/t1430-bad-ref-name.sh @@ -122,7 +122,7 @@ test_expect_success 'push cannot create a badly named ref' ' ! 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 && @@ -331,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 ee7d4736db..91fd71444d 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")" } ' @@ -189,14 +189,16 @@ test_expect_success 'commit with NUL in header' ' ' test_expect_success 'tree object with duplicate entries' ' - test_when_finished "remove_object \$T" && + 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 @@ -220,6 +222,28 @@ test_expect_success 'unparseable tree object' ' 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 @@ -521,9 +545,21 @@ test_expect_success 'fsck --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 && - rm -f $empty && - echo invalid >$empty && + 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:) && @@ -535,12 +571,18 @@ test_expect_success 'fsck --connectivity-only' ' ) ' -remove_loose_object () { - sha1="$(git rev-parse "$1")" && - remainder=${sha1#??} && - firsttwo=${sha1%$remainder} && - rm .git/objects/$firsttwo/$remainder -} +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 && @@ -550,11 +592,170 @@ test_expect_success 'fsck --name-objects' ' test_commit julius caesar.t && test_commit augustus && test_commit caesar && - remove_loose_object $(git rev-parse julius:caesar.t) && + remove_object $(git rev-parse julius:caesar.t) && test_must_fail git fsck --name-objects >out && tree=$(git rev-parse --verify julius:) && - grep "$tree (\(refs/heads/master\|HEAD\)@{[0-9]*}:" out + 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 $ZERO_OID +' + +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 $ZERO_OID >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 038e24c401..5c715fe2cf 100755 --- a/t/t1500-rev-parse.sh +++ b/t/t1500-rev-parse.sh @@ -3,7 +3,7 @@ test_description='test git rev-parse' . ./test-lib.sh -# usage: [options] label is-bare is-inside-git is-inside-work prefix git-dir +# usage: [options] label is-bare is-inside-git is-inside-work prefix git-dir absolute-git-dir test_rev_parse () { d= bare= @@ -29,7 +29,8 @@ test_rev_parse () { --is-inside-git-dir \ --is-inside-work-tree \ --show-prefix \ - --git-dir + --git-dir \ + --absolute-git-dir do test $# -eq 0 && break expect="$1" @@ -62,29 +63,86 @@ test_expect_success 'setup' ' cp -R .git repo.git ' -test_rev_parse toplevel false false true '' .git +test_rev_parse toplevel false false true '' .git "$ROOT/.git" -test_rev_parse -C .git .git/ false true false '' . -test_rev_parse -C .git/objects .git/objects/ false true false '' "$ROOT/.git" +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" -test_rev_parse -C sub/dir subdirectory false false true sub/dir/ "$ROOT/.git" +test_rev_parse -C sub/dir subdirectory false false true sub/dir/ "$ROOT/.git" "$ROOT/.git" 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 '' +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 -C work -g ../.git -b u 'GIT_DIR=../.git, core.bare undefined' false false true '' -test_rev_parse -C work -g ../repo.git -b f 'GIT_DIR=../repo.git, core.bare = false' false false true '' +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" test_rev_parse -C work -g ../repo.git -b t 'GIT_DIR=../repo.git, core.bare = true' true false false '' test_rev_parse -C work -g ../repo.git -b u 'GIT_DIR=../repo.git, core.bare undefined' false false true '' +test_expect_success 'git-common-dir from worktree root' ' + echo .git >expect && + git rev-parse --git-common-dir >actual && + test_cmp expect actual +' + +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 +' + +test_expect_success 'git-path from worktree root' ' + echo .git/objects >expect && + git rev-parse --git-path objects >actual && + test_cmp expect actual +' + +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 +' + +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 +' + +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 +' + +test_expect_success 'showing the superproject correctly' ' + git rev-parse --show-superproject-working-tree >out && + test_must_be_empty out && + + 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-work-tree.sh b/t/t1501-work-tree.sh index cc5b870e58..afcdfafe45 100755 --- a/t/t1501-work-tree.sh +++ b/t/t1501-work-tree.sh @@ -238,10 +238,10 @@ test_expect_success '_gently() groks relative GIT_DIR & GIT_WORK_TREE' ' test_expect_success 'diff-index respects work tree under .git dir' ' cat >diff-index-cached.expected <<-EOF && - :000000 100644 $_z40 $EMPTY_BLOB A sub/dir/tracked + :000000 100644 $ZERO_OID $EMPTY_BLOB A sub/dir/tracked EOF cat >diff-index.expected <<-EOF && - :000000 100644 $_z40 $_z40 A sub/dir/tracked + :000000 100644 $ZERO_OID $ZERO_OID A sub/dir/tracked EOF ( @@ -257,7 +257,7 @@ test_expect_success 'diff-index respects work tree under .git dir' ' test_expect_success 'diff-files respects work tree under .git dir' ' cat >diff-files.expected <<-EOF && - :100644 100644 $EMPTY_BLOB $_z40 M sub/dir/tracked + :100644 100644 $EMPTY_BLOB $ZERO_OID M sub/dir/tracked EOF ( @@ -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 310f93fd30..a859abedf5 100755 --- a/t/t1502-rev-parse-parseopt.sh +++ b/t/t1502-rev-parse-parseopt.sh @@ -28,6 +28,9 @@ test_expect_success 'setup optionspec' ' |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 @@ -35,6 +38,25 @@ test_expect_success 'setup optionspec' ' 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 @@ -62,6 +84,9 @@ test_expect_success 'test --parseopt help output' ' | --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 @@ -73,19 +98,100 @@ END_EXPECT test_i18ncmp expect output ' +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! +| +|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>... +| +| some-command does foo and bar! +| +|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>... +| +| 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 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 +' + test_expect_success 'setup expect.1' " cat > expect <<EOF -set -- --foo --bar 'ham' -b -- 'arg' +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 ' diff --git a/t/t1506-rev-parse-diagnosis.sh b/t/t1506-rev-parse-diagnosis.sh index 79a0251efa..4ee009da66 100755 --- a/t/t1506-rev-parse-diagnosis.sh +++ b/t/t1506-rev-parse-diagnosis.sh @@ -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' ' 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/t1510-repo-setup.sh b/t/t1510-repo-setup.sh index 13ae12dfa7..972bd9c785 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) @@ -234,7 +238,6 @@ test_expect_success '#0: nonbare repo, no explicit configuration' ' ' test_expect_success '#1: GIT_WORK_TREE without explicit GIT_DIR is accepted' ' - mkdir -p wt && try_repo 1 "$here" unset unset "" unset \ "$here/1/.git" "$here" "$here" 1/ \ "$here/1/.git" "$here" "$here" 1/sub/ 2>message && diff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh index 711704ba5a..96fe3754c8 100755 --- a/t/t1512-rev-parse-disambiguation.sh +++ b/t/t1512-rev-parse-disambiguation.sh @@ -22,6 +22,12 @@ one tagged as v1.0.0. They all have one regular file each. . ./test-lib.sh +if ! test_have_prereq SHA1 +then + skip_all='not using SHA-1 for objects' + test_done +fi + test_expect_success 'blob and tree' ' test_tick && ( @@ -361,4 +367,25 @@ test_expect_success 'core.disambiguate does not override context' ' git -c core.disambiguate=committish rev-parse $sha1^{tree} ' +test_expect_success C_LOCALE_OUTPUT 'ambiguous commits are printed by type first, then hash order' ' + test_must_fail git rev-parse 0000 2>stderr && + grep ^hint: stderr >hints && + grep 0000 hints >objects && + cat >expected <<-\EOF && + tag + commit + tree + blob + EOF + awk "{print \$3}" <objects >objects.types && + uniq <objects.types >objects.types.uniq && + test_cmp expected objects.types.uniq && + for type in tag commit tree blob + do + grep $type objects >$type.objects && + sort $type.objects >$type.objects.sorted && + test_cmp $type.objects.sorted $type.objects + done +' + test_done diff --git a/t/t1514-rev-parse-push.sh b/t/t1514-rev-parse-push.sh index 623a32aa6e..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' ' 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..4171f1e141 --- /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 $ZERO_OID\\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 292a0720fc..1e81b33b2e 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 $EMPTY_BLOB 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 $EMPTY_BLOB 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 $EMPTY_BLOB 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 $EMPTY_BLOB 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 $EMPTY_BLOB 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 $EMPTY_BLOB 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 $EMPTY_BLOB 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 $EMPTY_BLOB 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 $EMPTY_BLOB 0 one -100644 $EMPTY_BLOB 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 $EMPTY_BLOB 0 one -100644 $EMPTY_BLOB 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 $ZERO_OID\\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/t2011-checkout-invalid-head.sh b/t/t2011-checkout-invalid-head.sh index c5501b008c..0e8d56aa76 100755 --- a/t/t2011-checkout-invalid-head.sh +++ b/t/t2011-checkout-invalid-head.sh @@ -15,7 +15,7 @@ test_expect_success 'checkout should not start branch from a tree' ' ' test_expect_success 'checkout master from invalid HEAD' ' - echo $_z40 >.git/HEAD && + echo $ZERO_OID >.git/HEAD && git checkout master -- ' 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/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh index fbb4ee9bb4..1fa670625c 100755 --- a/t/t2020-checkout-detach.sh +++ b/t/t2020-checkout-detach.sh @@ -186,4 +186,135 @@ test_expect_success 'no advice given for explicit detached head state' ' 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..fc3eb43b89 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 && + echo "1000000000" >expect && + test-tool chmtime --get file2 >actual && test_cmp expect actual ' diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh index b618d6be21..d2e49f7632 100755 --- a/t/t2025-worktree-add.sh +++ b/t/t2025-worktree-add.sh @@ -63,6 +63,12 @@ test_expect_success '"add" worktree' ' ) ' +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 && @@ -192,13 +198,25 @@ test_expect_success '"add" with <branch> omitted' ' 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" checks out existing branch of dwimd name' ' + git branch dwim HEAD~1 && + git worktree add dwim && + test_cmp_rev HEAD~1 dwim && + ( + cd dwim && + test_cmp_rev HEAD dwim + ) +' + +test_expect_success '"add <path>" dwim fails with checked out branch' ' + git checkout -b test-branch && + test_must_fail git worktree add test-branch && + test_path_is_missing test-branch +' + +test_expect_success '"add --force" with existing dwimd name doesnt die' ' + git checkout test-branch && + git worktree add --force test-branch ' test_expect_success '"add" no auto-vivify with --detach and <branch> omitted' ' @@ -239,6 +257,12 @@ test_expect_success 'local clone from linked checkout' ' ( 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 && @@ -307,5 +331,200 @@ test_expect_success 'checkout a branch under 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 $ZERO_OID $(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 $ZERO_OID $(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 $ZERO_OID $(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 $ZERO_OID $(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-worktree-prune.sh b/t/t2026-worktree-prune.sh index a0f1e3bb80..b7d6d5d45a 100755 --- a/t/t2026-worktree-prune.sh +++ b/t/t2026-worktree-prune.sh @@ -78,10 +78,9 @@ test_expect_success 'not prune locked checkout' ' 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 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 ' diff --git a/t/t2027-worktree-list.sh b/t/t2027-worktree-list.sh index 465eeeacd3..bb6fb9b12c 100755 --- a/t/t2027-worktree-list.sh +++ b/t/t2027-worktree-list.sh @@ -14,25 +14,35 @@ test_expect_success 'rev-parse --git-common-dir on main worktree' ' test_cmp expected actual && mkdir sub && git -C sub rev-parse --git-common-dir >actual2 && - echo sub/.git >expected2 && + 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 && git worktree prune" && + 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 | sed "s/ */ /g" >actual && + 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 && git worktree prune" && + 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 | sed "s/ */ /g" >actual && + git -C here worktree list >out && + sed "s/ */ /g" <out >actual && test_cmp expect actual ' @@ -41,7 +51,7 @@ test_expect_success '"list" all worktrees --porcelain' ' echo "HEAD $(git rev-parse HEAD)" >>expect && echo "branch $(git symbolic-ref HEAD)" >>expect && echo >>expect && - test_when_finished "rm -rf here && git worktree prune" && + 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 && @@ -61,16 +71,17 @@ test_expect_success 'bare repo setup' ' ' test_expect_success '"list" all worktrees from bare main' ' - test_when_finished "rm -rf there && git -C bare1 worktree prune" && + 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 | sed "s/ */ /g" >actual && + 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 && git -C bare1 worktree prune" && + 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 && @@ -84,11 +95,12 @@ test_expect_success '"list" all worktrees --porcelain from bare main' ' ' test_expect_success '"list" all worktrees from linked with a bare main' ' - test_when_finished "rm -rf there && git -C bare1 worktree prune" && + 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 | sed "s/ */ /g" >actual && + git -C there worktree list >out && + sed "s/ */ /g" <out >actual && test_cmp expect actual ' @@ -104,15 +116,17 @@ test_expect_success 'broken main worktree still at the top' ' git worktree add linked && cat >expected <<-EOF && worktree $(pwd) - HEAD $_z40 + HEAD $ZERO_OID EOF cd linked && echo "worktree $(pwd)" >expected && echo "ref: .broken" >../.git/HEAD && - git worktree list --porcelain | head -n 3 >actual && + git worktree list --porcelain >out && + head -n 3 out >actual && test_cmp ../expected actual && - git worktree list | head -n 1 >actual.2 && + git worktree list >out && + head -n 1 out >actual.2 && grep -F "(error)" actual.2 ) ' @@ -126,7 +140,8 @@ test_expect_success 'linked worktrees are sorted' ' test_commit new && git worktree add ../first && git worktree add ../second && - git worktree list --porcelain | grep ^worktree >actual + git worktree list --porcelain >out && + grep ^worktree out >actual ) && cat >expected <<-EOF && worktree $(pwd)/sorted/main diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh index 8298aaf97f..5f7d45b7b7 100755 --- a/t/t2028-worktree-move.sh +++ b/t/t2028-worktree-move.sh @@ -7,7 +7,8 @@ test_description='test git worktree move, remove, lock and unlock' test_expect_success 'setup' ' test_commit init && git worktree add source && - git worktree list --porcelain | grep "^worktree" >actual && + git worktree list --porcelain >out && + grep "^worktree" out >actual && cat <<-EOF >expected && worktree $(pwd) worktree $(pwd)/source @@ -59,4 +60,85 @@ test_expect_success 'unlock worktree twice' ' 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/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 32ac6e09bd..2242cd098e 100755 --- a/t/t2107-update-index-basic.sh +++ b/t/t2107-update-index-basic.sh @@ -37,7 +37,7 @@ test_expect_success '--cacheinfo does not accept blob null sha1' ' echo content >file && git add file && git rev-parse :file >expect && - test_must_fail git update-index --cacheinfo 100644 $_z40 file && + test_must_fail git update-index --cacheinfo 100644 $ZERO_OID file && git rev-parse :file >actual && test_cmp expect actual ' @@ -47,7 +47,7 @@ test_expect_success '--cacheinfo does not accept gitlink null sha1' ' (cd submodule && test_commit foo) && git add submodule && git rev-parse :submodule >expect && - test_must_fail git update-index --cacheinfo 160000 $_z40 submodule && + test_must_fail git update-index --cacheinfo 160000 $ZERO_OID submodule && git rev-parse :submodule >actual && test_cmp expect actual ' @@ -85,9 +85,9 @@ test_expect_success '--chmod=+x and chmod=-x in the same argument list' ' >B && git add A B && git update-index --chmod=+x A --chmod=-x B && - cat >expect <<-\EOF && - 100755 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 A - 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 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 diff --git a/t/t2201-add-update-typechange.sh b/t/t2201-add-update-typechange.sh index 954fc51e5b..a4eec0a346 100755 --- a/t/t2201-add-update-typechange.sh +++ b/t/t2201-add-update-typechange.sh @@ -75,35 +75,35 @@ test_expect_success modify ' git ls-tree -r HEAD | sed -e "s/^/:/" -e " / caskly/{ - s/ caskly/ $_z40 D&/ + s/ caskly/ $ZERO_OID D&/ s/blob/000000/ } / nitfol/{ - s/ nitfol/ $_z40 $T_letter&/ + s/ nitfol/ $ZERO_OID $T_letter&/ s/blob/100644/ } / rezrov.bozbar/{ - s/ rezrov.bozbar/ $_z40 D&/ + s/ rezrov.bozbar/ $ZERO_OID D&/ s/blob/000000/ } / xyzzy/{ - s/ xyzzy/ $_z40 D&/ + s/ xyzzy/ $ZERO_OID D&/ s/blob/000000/ } / yomin/{ - s/ yomin/ $_z40 T&/ + s/ yomin/ $ZERO_OID T&/ s/blob/160000/ } " } >expect && { cat expect - echo ":100644 160000 $_empty $_z40 T yonk" - echo ":100644 000000 $_empty $_z40 D zifmia" + echo ":100644 160000 $_empty $ZERO_OID T yonk" + echo ":100644 000000 $_empty $ZERO_OID D zifmia" } >expect-files && { cat expect - echo ":000000 160000 $_z40 $_z40 A yonk" + echo ":000000 160000 $ZERO_OID $ZERO_OID A yonk" } >expect-index && { echo "100644 $_empty 0 nitfol" diff --git a/t/t2203-add-intent.sh b/t/t2203-add-intent.sh index 84a9028c43..e7a400b4c7 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=$(echo 1 | git hash-object --stdin) && + nam2=$(git hash-object elif) && + cat >expect <<-EOF && + 1 DA N... 100644 000000 100644 $nam1 $ZERO_OID 1.t + 1 A. N... 000000 100644 100644 $ZERO_OID $nam2 elif + 1 .A N... 000000 000000 100644 $ZERO_OID $ZERO_OID 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) && @@ -58,8 +70,7 @@ test_expect_success 'i-t-a entry is simply ignored' ' git commit -m second && test $(git ls-tree HEAD -- nitfol | wc -l) = 0 && 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 $(git diff --name-only -- nitfol | wc -l) = 1 ' test_expect_success 'can commit with an unrelated i-t-a entry in index' ' @@ -87,13 +98,13 @@ test_expect_success 'cache-tree invalidates i-t-a paths' ' : >dir/bar && git add -N dir/bar && - git diff --cached --name-only >actual && + git diff --name-only >actual && echo dir/bar >expect && test_cmp expect actual && git write-tree >/dev/null && - git diff --cached --name-only >actual && + git diff --name-only >actual && echo dir/bar >expect && test_cmp expect actual ' @@ -129,10 +140,10 @@ test_expect_success 'cache-tree does skip dir that becomes empty' ' ) ' -test_expect_success 'commit: ita entries ignored in empty intial commit check' ' - git init empty-intial-commit && +test_expect_success 'commit: ita entries ignored in empty initial commit check' ' + git init empty-initial-commit && ( - cd empty-intial-commit && + cd empty-initial-commit && : >one && git add -N one && test_must_fail git commit -m nothing-new-here @@ -150,5 +161,118 @@ test_expect_success 'commit: ita entries ignored in empty commit check' ' ) ' -test_done +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=$(git hash-object third) && + 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 && + + git diff --stat >actual.4 && + cat >expected.4 <<-EOF && + first => third | 0 + 1 file changed, 0 insertions(+), 0 deletions(-) + EOF + test_cmp expected.4 actual.4 && + + git diff --cached --stat >actual.5 && + : >expected.5 && + test_cmp expected.5 actual.5 + + ) +' + +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=$(git hash-object third) && + 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_expect_success 'diff-files/diff-cached shows ita as new/not-new files' ' + git reset --hard && + echo new >new-ita && + git add -N new-ita && + git diff --summary >actual && + echo " create mode 100644 new-ita" >expected && + test_cmp expected actual && + git diff --cached --summary >actual2 && + : >expected2 && + test_cmp expected2 actual2 +' + + +test_expect_success '"diff HEAD" includes ita as new files' ' + git reset --hard && + echo new >new-ita && + git add -N new-ita && + git diff HEAD >actual && + cat >expected <<-\EOF && + diff --git a/new-ita b/new-ita + new file mode 100644 + index 0000000..3e75765 + --- /dev/null + +++ b/new-ita + @@ -0,0 +1 @@ + +new + EOF + test_cmp expected actual +' + +test_expect_success 'apply --intent-to-add' ' + git reset --hard && + echo new >new-ita && + git add -N new-ita && + git diff >expected && + grep "new file" expected && + git reset --hard && + git apply --intent-to-add expected && + git diff >actual && + test_cmp expected actual +' + +test_done diff --git a/t/t3007-ls-files-recurse-submodules.sh b/t/t3007-ls-files-recurse-submodules.sh index a5426171d3..318b5bce7e 100755 --- a/t/t3007-ls-files-recurse-submodules.sh +++ b/t/t3007-ls-files-recurse-submodules.sh @@ -77,10 +77,22 @@ test_expect_success 'ls-files recurses more than 1 level' ' 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 && @@ -123,6 +135,45 @@ test_expect_success '--recurse-submodules and pathspecs setup' ' 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 @@ -188,6 +239,45 @@ test_expect_success '--recurse-submodules and pathspecs' ' 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 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/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh index 9a893b5fe7..3563e77b37 100755 --- a/t/t3030-merge-recursive.sh +++ b/t/t3030-merge-recursive.sh @@ -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' ' @@ -678,4 +682,54 @@ test_expect_success 'merge-recursive remembers the names of all base trees' ' 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/t3034-merge-recursive-rename-options.sh b/t/t3034-merge-recursive-rename-options.sh index b9c4028496..3d9fae68c4 100755 --- a/t/t3034-merge-recursive-rename-options.sh +++ b/t/t3034-merge-recursive-rename-options.sh @@ -309,4 +309,22 @@ test_expect_success 'last wins in --find-renames=<m> --rename-threshold=<n>' ' check_threshold_0 ' +test_expect_success 'merge.renames disables rename detection' ' + git read-tree --reset -u HEAD && + git -c merge.renames=false merge-recursive $tail && + check_no_renames +' + +test_expect_success 'merge.renames defaults to diff.renames' ' + git read-tree --reset -u HEAD && + git -c diff.renames=false merge-recursive $tail && + check_no_renames +' + +test_expect_success 'merge.renames overrides diff.renames' ' + git read-tree --reset -u HEAD && + test_must_fail git -c diff.renames=false -c merge.renames=true merge-recursive $tail && + $check_50 +' + 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 325114f8fe..18baf49a49 100755 --- a/t/t3100-ls-tree-restrict.sh +++ b/t/t3100-ls-tree-restrict.sh @@ -32,7 +32,7 @@ test_expect_success \ echo $tree' test_output () { - sed -e "s/ $_x40 / X /" <current >check + sed -e "s/ $OID_REGEX / X /" <current >check test_cmp expected check } diff --git a/t/t3101-ls-tree-dirname.sh b/t/t3101-ls-tree-dirname.sh index 327ded4000..12bf31022a 100755 --- a/t/t3101-ls-tree-dirname.sh +++ b/t/t3101-ls-tree-dirname.sh @@ -40,7 +40,7 @@ test_expect_success 'setup' ' ' test_output () { - sed -e "s/ $_x40 / X /" <current >check && + sed -e "s/ $OID_REGEX / X /" <current >check && test_cmp expected check } diff --git a/t/t3103-ls-tree-misc.sh b/t/t3103-ls-tree-misc.sh index 09dcf043fd..14520913af 100755 --- a/t/t3103-ls-tree-misc.sh +++ b/t/t3103-ls-tree-misc.sh @@ -17,7 +17,8 @@ test_expect_success 'setup' ' ' test_expect_success 'ls-tree fails with non-zero exit code on broken tree' ' - rm -f .git/objects/5f/cffbd6e4c5c5b8d81f5e9314b20e338e3ffff5 && + tree=$(git rev-parse HEAD:a) && + rm -f .git/objects/$(echo $tree | sed -e "s,^\(..\),\1/,") && test_must_fail git ls-tree -r HEAD ' diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 8a833f354e..08467982f6 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 && @@ -46,7 +47,7 @@ test_expect_success 'git branch HEAD should fail' ' ' cat >expect <<EOF -$_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master +$ZERO_OID $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master EOF test_expect_success 'git branch -l d/e/f should create a branch and a log' ' GIT_COMMITTER_DATE="2005-05-26 23:30" \ @@ -100,6 +101,33 @@ test_expect_success 'git branch -m n/n n should work' ' 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' ' git branch o/o && git branch o/p && @@ -139,6 +167,35 @@ test_expect_success 'git branch -M baz bam should succeed when baz is checked ou 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 && @@ -177,34 +234,59 @@ test_expect_success 'git branch -M master2 master2 should work when master is ch test_expect_success 'git branch -v -d t should work' ' git branch t && - test_path_is_file .git/refs/heads/t && + git rev-parse --verify refs/heads/t && git branch -v -d t && - test_path_is_missing .git/refs/heads/t + test_must_fail git rev-parse --verify refs/heads/t ' test_expect_success 'git branch -v -m t s should work' ' git branch t && - test_path_is_file .git/refs/heads/t && + git rev-parse --verify refs/heads/t && git branch -v -m t s && - test_path_is_missing .git/refs/heads/t && - test_path_is_file .git/refs/heads/s && + test_must_fail git rev-parse --verify refs/heads/t && + git rev-parse --verify refs/heads/s && git branch -d s ' test_expect_success 'git branch -m -d t s should fail' ' git branch t && - test_path_is_file .git/refs/heads/t && + git rev-parse refs/heads/t && test_must_fail git branch -m -d t s && git branch -d t && - test_path_is_missing .git/refs/heads/t + test_must_fail git rev-parse refs/heads/t ' test_expect_success 'git branch --list -d t should fail' ' git branch t && - test_path_is_file .git/refs/heads/t && + git rev-parse refs/heads/t && test_must_fail git branch --list -d t && git branch -d t && - test_path_is_missing .git/refs/heads/t + test_must_fail git rev-parse 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' ' @@ -307,7 +389,263 @@ test_expect_success 'git branch -m s/s s should work when s/t is deleted' ' 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' ' @@ -501,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" ' @@ -546,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_i18ncmp 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_i18ncmp 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_i18ncmp expected actual +test_expect_success 'disabled option --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' ' @@ -592,7 +901,7 @@ test_expect_success '--set-upstream-to notices an error to set branch as own ups # Keep this test last, as it changes the current branch cat >expect <<EOF -$_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master +$ZERO_OID $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master EOF test_expect_success 'git checkout -b g/h/i -l should create a branch and a log' ' GIT_COMMITTER_DATE="2005-05-26 23:30" \ @@ -902,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" @@ -947,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 7f3ec47241..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 @@ -159,4 +205,15 @@ test_expect_success 'branch --merged with --verbose' ' 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 c6a3ccba1b..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* @@ -189,11 +195,73 @@ test_expect_success 'local-branch symrefs shortened properly' ' git symbolic-ref refs/heads/ref-to-remote refs/remotes/origin/branch-one && cat >expect <<-\EOF && ref-to-branch -> branch-one - ref-to-remote -> refs/remotes/origin/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 9b182a0c32..afa27ffe2d 100755 --- a/t/t3210-pack-refs.sh +++ b/t/t3210-pack-refs.sh @@ -194,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" && @@ -211,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/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 19aed7ec95..ab946a5153 100755 --- a/t/t3308-notes-merge.sh +++ b/t/t3308-notes-merge.sh @@ -79,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 && diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh index baef2d6924..9c1bf6eb3d 100755 --- a/t/t3310-notes-merge-manual-resolve.sh +++ b/t/t3310-notes-merge-manual-resolve.sh @@ -176,7 +176,7 @@ 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 test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output && # Inspect merge conflicts @@ -379,7 +379,7 @@ 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 test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output && # Inspect merge conflicts @@ -413,7 +413,7 @@ 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 test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output && # Inspect merge conflicts @@ -494,7 +494,7 @@ 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 test_i18ngrep "\\.git/NOTES_MERGE_WORKTREE" output && # Inspect merge conflicts diff --git a/t/t3320-notes-merge-worktrees.sh b/t/t3320-notes-merge-worktrees.sh index b9c3bc2487..10bfc8b947 100755 --- a/t/t3320-notes-merge-worktrees.sh +++ b/t/t3320-notes-merge-worktrees.sh @@ -61,7 +61,7 @@ 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 2>&1 >out && + 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 ) && diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index f5fd15e559..72d9564747 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -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/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index c896a4c106..352a52e59d 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -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) && @@ -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 && - test_i18ngrep "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' ' @@ -665,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 --get file3) && test 123456789 = $MTIME ' @@ -844,7 +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 && @@ -881,10 +927,8 @@ test_expect_success 'rebase --exec works without -i ' ' 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 ' @@ -927,7 +971,17 @@ 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 'rebase -i --root reword root commit' ' + test_when_finished "test_might_fail git rebase --abort" && + git checkout -b reword-root-branch master && + set_fake_editor && + FAKE_LINES="reword 1 2" FAKE_COMMIT_MESSAGE="A changed" \ + git rebase -i --root && + git show HEAD^ | grep "A changed" && + test -z "$(git show -s --format=%p HEAD^)" +' + +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 && @@ -1160,10 +1214,6 @@ test_expect_success 'drop' ' 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 && @@ -1171,7 +1221,9 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = ignore' ' 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 + test_i18ngrep \ + "Successfully rebased and updated refs/heads/missing-commit" \ + actual ' cat >expect <<EOF @@ -1183,15 +1235,24 @@ 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. +Rebasing (1/4) +Rebasing (2/4) +Rebasing (3/4) +Rebasing (4/4) Successfully rebased and updated refs/heads/missing-commit. EOF +cr_to_nl () { + tr '\015' '\012' +} + 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 && + git rebase -i --root 2>actual.2 && + cr_to_nl <actual.2 >actual && test_i18ncmp expect actual && test D = $(git cat-file commit HEAD | sed -ne \$p) ' @@ -1226,20 +1287,35 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = error' ' test B = $(git cat-file commit HEAD^ | sed -ne \$p) ' -cat >expect <<EOF -Warning: the command isn't recognized in the following line: - - badcmd $(git rev-list --oneline -1 master~1) - -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 '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_i18ncmp expect 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) && @@ -1261,20 +1337,13 @@ test_expect_success 'tabs and spaces are accepted in the todolist' ' test E = $(git cat-file commit HEAD | sed -ne \$p) ' -cat >expect <<EOF -Warning: the SHA-1 is missing or isn't a commit in the following line: - - edit XXXXXXX False commit - -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 '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_i18ncmp expect 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) @@ -1294,6 +1363,16 @@ test_expect_success 'editor saves as CR/LF' ' 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 && 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/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh index 48346f1cc0..e364c12622 100755 --- a/t/t3415-rebase-autosquash.sh +++ b/t/t3415-rebase-autosquash.sh @@ -234,23 +234,23 @@ 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 'autosquash with custom inst format' ' +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 && @@ -271,6 +271,18 @@ test_expect_success 'autosquash with custom inst format' ' 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")" @@ -278,7 +290,7 @@ set_backup_editor () { test_set_editor "$PWD/backup-editor.sh" } -test_expect_failure 'autosquash with multiple empty patches' ' +test_expect_success 'autosquash with multiple empty patches' ' test_tick && git commit --allow-empty -m "empty" && test_tick && @@ -304,4 +316,18 @@ test_expect_success 'extra spaces after fixup!' ' 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 4428b9086e..03bf1b8a3b 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,29 +36,10 @@ 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 ' -test_expect_success 'non-interactive rebase --continue with rerere enabled' ' - test_config rerere.enabled true && - test_when_finished "test_might_fail git rebase --abort" && - git reset --hard commit-new-file-F2-on-topic-branch && - git checkout master && - rm -fr .git/rebase-* && - - test_must_fail git rebase --onto master master topic && - echo "Resolved" >F2 && - git add F2 && - cp F2 F2.expected && - git rebase --continue && - - git reset --hard commit-new-file-F2-on-topic-branch && - git checkout master && - test_must_fail git rebase --onto master master topic && - test_cmp F2.expected F2 -' - test_expect_success 'rebase --continue can not be used with other options' ' test_must_fail git rebase -v --continue && test_must_fail git rebase --continue -v @@ -93,25 +74,138 @@ 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 '--skip after failed fixup cleans commit message' ' + test_when_finished "test_might_fail git rebase --abort" && + git checkout -b with-conflicting-fixup && + test_commit wants-fixup && + test_commit "fixup! wants-fixup" wants-fixup.t 1 wants-fixup-1 && + test_commit "fixup! wants-fixup" wants-fixup.t 2 wants-fixup-2 && + test_commit "fixup! wants-fixup" wants-fixup.t 3 wants-fixup-3 && + test_must_fail env FAKE_LINES="1 fixup 2 squash 4" \ + git rebase -i HEAD~4 && + + : now there is a conflict, and comments in the commit message && + git show HEAD >out && + grep "fixup! wants-fixup" out && + + : skip and continue && + echo "cp \"\$1\" .git/copy.txt" | write_script copy-editor.sh && + (test_set_editor "$PWD/copy-editor.sh" && git rebase --skip) && + + : the user should not have had to edit the commit message && + test_path_is_missing .git/copy.txt && + + : now the comments in the commit message should have been cleaned up && + git show HEAD >out && + ! grep "fixup! wants-fixup" out && + + : now, let us ensure that "squash" is handled correctly && + git reset --hard wants-fixup-3 && + test_must_fail env FAKE_LINES="1 squash 4 squash 2 squash 4" \ + git rebase -i HEAD~4 && + + : the first squash failed, but there are two more in the chain && + (test_set_editor "$PWD/copy-editor.sh" && + test_must_fail git rebase --skip) && + + : not the final squash, no need to edit the commit message && + test_path_is_missing .git/copy.txt && + + : The first squash was skipped, therefore: && + git show HEAD >out && + test_i18ngrep "# This is a combination of 2 commits" out && + + (test_set_editor "$PWD/copy-editor.sh" && git rebase --skip) && + git show HEAD >out && + test_i18ngrep ! "# This is a combination" out && + + : Final squash failed, but there was still a squash && + test_i18ngrep "# This is a combination of 2 commits" .git/copy.txt +' + +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/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh index ab8a63e8d6..e243700660 100755 --- a/t/t3420-rebase-autostash.sh +++ b/t/t3420-rebase-autostash.sh @@ -33,7 +33,123 @@ 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 @@ -51,14 +167,20 @@ 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 >>file3 && - git rebase$type unrelated-onto-branch && + 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 && @@ -137,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 && @@ -149,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" ' diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh index 68fe2003ef..99b2aac921 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,9 +214,10 @@ 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 +test_run_rebase success --rebase-merges # m # / @@ -327,9 +328,9 @@ test_run_rebase () { test_cmp_rev c HEAD " } -test_run_rebase failure '' -test_run_rebase failure -m -test_run_rebase failure -i +test_run_rebase success '' +test_run_rebase success -m +test_run_rebase success -i test_run_rebase failure -p test_run_rebase () { diff --git a/t/t3426-rebase-submodule.sh b/t/t3426-rebase-submodule.sh index ebf4f5e4b2..a2bba04ba9 100755 --- a/t/t3426-rebase-submodule.sh +++ b/t/t3426-rebase-submodule.sh @@ -40,4 +40,21 @@ git_rebase_interactive () { 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/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/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh new file mode 100755 index 0000000000..78f7c99580 --- /dev/null +++ b/t/t3430-rebase-merges.sh @@ -0,0 +1,332 @@ +#!/bin/sh +# +# Copyright (c) 2018 Johannes E. Schindelin +# + +test_description='git rebase -i --rebase-merges + +This test runs git rebase "interactively", retaining the branch structure by +recreating merge commits. + +Initial setup: + + -- B -- (first) + / \ + A - C - D - E - H (master) + \ / + F - G (second) +' +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh + +test_cmp_graph () { + cat >expect && + git log --graph --boundary --format=%s "$@" >output && + sed "s/ *$//" <output >output.trimmed && + test_cmp expect output.trimmed +} + +test_expect_success 'setup' ' + write_script replace-editor.sh <<-\EOF && + mv "$1" "$(git rev-parse --git-path ORIGINAL-TODO)" + cp script-from-scratch "$1" + EOF + + test_commit A && + git checkout -b first && + test_commit B && + git checkout master && + test_commit C && + test_commit D && + git merge --no-commit B && + test_tick && + git commit -m E && + git tag -m E E && + git checkout -b second C && + test_commit F && + test_commit G && + git checkout master && + git merge --no-commit G && + test_tick && + git commit -m H && + git tag -m H H +' + +test_expect_success 'create completely different structure' ' + cat >script-from-scratch <<-\EOF && + label onto + + # onebranch + pick G + pick D + label onebranch + + # second + reset onto + pick B + label second + + reset onto + merge -C H second + merge onebranch # Merge the topic branch '\''onebranch'\'' + EOF + test_config sequence.editor \""$PWD"/replace-editor.sh\" && + test_tick && + git rebase -i -r A && + test_cmp_graph <<-\EOF + * Merge the topic branch '\''onebranch'\'' + |\ + | * D + | * G + * | H + |\ \ + | |/ + |/| + | * B + |/ + * A + EOF +' + +test_expect_success 'generate correct todo list' ' + cat >expect <<-\EOF && + label onto + + reset onto + pick d9df450 B + label E + + reset onto + pick 5dee784 C + label branch-point + pick ca2c861 F + pick 088b00a G + label H + + reset branch-point # C + pick 12bd07b D + merge -C 2051b56 E # E + merge -C 233d48a H # H + + EOF + + grep -v "^#" <.git/ORIGINAL-TODO >output && + test_cmp expect output +' + +test_expect_success '`reset` refuses to overwrite untracked files' ' + git checkout -b refuse-to-reset && + test_commit dont-overwrite-untracked && + git checkout @{-1} && + : >dont-overwrite-untracked.t && + echo "reset refs/tags/dont-overwrite-untracked" >script-from-scratch && + test_config sequence.editor \""$PWD"/replace-editor.sh\" && + test_must_fail git rebase -r HEAD && + git rebase --abort +' + +test_expect_success 'failed `merge` writes patch (may be rescheduled, too)' ' + test_when_finished "test_might_fail git rebase --abort" && + git checkout -b conflicting-merge A && + + : fail because of conflicting untracked file && + >G.t && + echo "merge -C H G" >script-from-scratch && + test_config sequence.editor \""$PWD"/replace-editor.sh\" && + test_tick && + test_must_fail git rebase -ir HEAD && + grep "^merge -C .* G$" .git/rebase-merge/done && + grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo && + test_path_is_file .git/rebase-merge/patch && + + : fail because of merge conflict && + rm G.t .git/rebase-merge/patch && + git reset --hard && + test_commit conflicting-G G.t not-G conflicting-G && + test_must_fail git rebase --continue && + ! grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo && + test_path_is_file .git/rebase-merge/patch +' + +test_expect_success 'with a branch tip that was cherry-picked already' ' + git checkout -b already-upstream master && + base="$(git rev-parse --verify HEAD)" && + + test_commit A1 && + test_commit A2 && + git reset --hard $base && + test_commit B1 && + test_tick && + git merge -m "Merge branch A" A2 && + + git checkout -b upstream-with-a2 $base && + test_tick && + git cherry-pick A2 && + + git checkout already-upstream && + test_tick && + git rebase -i -r upstream-with-a2 && + test_cmp_graph upstream-with-a2.. <<-\EOF + * Merge branch A + |\ + | * A1 + * | B1 + |/ + o A2 + EOF +' + +test_expect_success 'do not rebase cousins unless asked for' ' + git checkout -b cousins master && + before="$(git rev-parse --verify HEAD)" && + test_tick && + git rebase -r HEAD^ && + test_cmp_rev HEAD $before && + test_tick && + git rebase --rebase-merges=rebase-cousins HEAD^ && + test_cmp_graph HEAD^.. <<-\EOF + * Merge the topic branch '\''onebranch'\'' + |\ + | * D + | * G + |/ + o H + EOF +' + +test_expect_success 'refs/rewritten/* is worktree-local' ' + git worktree add wt && + cat >wt/script-from-scratch <<-\EOF && + label xyz + exec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || : + exec git rev-parse --verify refs/rewritten/xyz >b + EOF + + test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" && + git -C wt rebase -i HEAD && + test_must_be_empty wt/a && + test_cmp_rev HEAD "$(cat wt/b)" +' + +test_expect_success 'post-rewrite hook and fixups work for merges' ' + git checkout -b post-rewrite && + test_commit same1 && + git reset --hard HEAD^ && + test_commit same2 && + git merge -m "to fix up" same1 && + echo same old same old >same2.t && + test_tick && + git commit --fixup HEAD same2.t && + fixup="$(git rev-parse HEAD)" && + + mkdir -p .git/hooks && + test_when_finished "rm .git/hooks/post-rewrite" && + echo "cat >actual" | write_script .git/hooks/post-rewrite && + + test_tick && + git rebase -i --autosquash -r HEAD^^^ && + printf "%s %s\n%s %s\n%s %s\n%s %s\n" >expect $(git rev-parse \ + $fixup^^2 HEAD^2 \ + $fixup^^ HEAD^ \ + $fixup^ HEAD \ + $fixup HEAD) && + test_cmp expect actual +' + +test_expect_success 'refuse to merge ancestors of HEAD' ' + echo "merge HEAD^" >script-from-scratch && + test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" && + before="$(git rev-parse HEAD)" && + git rebase -i HEAD && + test_cmp_rev HEAD $before +' + +test_expect_success 'root commits' ' + git checkout --orphan unrelated && + (GIT_AUTHOR_NAME="Parsnip" GIT_AUTHOR_EMAIL="root@example.com" \ + test_commit second-root) && + test_commit third-root && + cat >script-from-scratch <<-\EOF && + pick third-root + label first-branch + reset [new root] + pick second-root + merge first-branch # Merge the 3rd root + EOF + test_config sequence.editor \""$PWD"/replace-editor.sh\" && + test_tick && + git rebase -i --force --root -r && + test "Parsnip" = "$(git show -s --format=%an HEAD^)" && + test $(git rev-parse second-root^0) != $(git rev-parse HEAD^) && + test $(git rev-parse second-root:second-root.t) = \ + $(git rev-parse HEAD^:second-root.t) && + test_cmp_graph HEAD <<-\EOF && + * Merge the 3rd root + |\ + | * third-root + * second-root + EOF + + : fast forward if possible && + before="$(git rev-parse --verify HEAD)" && + test_might_fail git config --unset sequence.editor && + test_tick && + git rebase -i --root -r && + test_cmp_rev HEAD $before +' + +test_expect_success 'a "merge" into a root commit is a fast-forward' ' + head=$(git rev-parse HEAD) && + cat >script-from-scratch <<-EOF && + reset [new root] + merge $head + EOF + test_config sequence.editor \""$PWD"/replace-editor.sh\" && + test_tick && + git rebase -i -r HEAD^ && + test_cmp_rev HEAD $head +' + +test_expect_success 'A root commit can be a cousin, treat it that way' ' + git checkout --orphan khnum && + test_commit yama && + git checkout -b asherah master && + test_commit shamkat && + git merge --allow-unrelated-histories khnum && + test_tick && + git rebase -f -r HEAD^ && + ! test_cmp_rev HEAD^2 khnum && + test_cmp_graph HEAD^.. <<-\EOF && + * Merge branch '\''khnum'\'' into asherah + |\ + | * yama + o shamkat + EOF + test_tick && + git rebase --rebase-merges=rebase-cousins HEAD^ && + test_cmp_graph HEAD^.. <<-\EOF + * Merge branch '\''khnum'\'' into asherah + |\ + | * yama + |/ + o shamkat + EOF +' + +test_expect_success 'labels that are object IDs are rewritten' ' + git checkout -b third B && + test_commit I && + third=$(git rev-parse HEAD) && + git checkout -b labels master && + git merge --no-commit third && + test_tick && + git commit -m "Merge commit '\''$third'\'' into labels" && + echo noop >script-from-scratch && + test_config sequence.editor \""$PWD"/replace-editor.sh\" && + test_tick && + git rebase -i -r A && + grep "^label $third-" .git/ORIGINAL-TODO && + ! grep "^label $third$" .git/ORIGINAL-TODO +' + +test_done diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh index 4f2a263b63..d1c68af8c5 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 ) ' @@ -150,7 +150,9 @@ test_expect_success 'cherry-pick works with dirty renamed file' ' test_tick && git commit -m renamed && echo modified >renamed && - git cherry-pick refs/heads/unrelated + git cherry-pick refs/heads/unrelated >out && + test $(git rev-parse :0:renamed) = $(git rev-parse HEAD~2: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 372307c21b..b42cd66d3a 100755 --- a/t/t3510-cherry-pick-sequence.sh +++ b/t/t3510-cherry-pick-sequence.sh @@ -122,7 +122,7 @@ test_expect_success '--quit keeps HEAD and conflicted index intact' ' { git rev-list HEAD | git diff-tree --root --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual && test_cmp expect actual ' @@ -220,7 +220,7 @@ test_expect_success 'cherry-pick still writes sequencer state when one commit is { git rev-list HEAD | git diff-tree --root --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual && cat >expect <<-\EOF && OBJID @@ -247,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 --get .git/sequencer >expect && test_expect_code 128 git cherry-pick unrelatedpick && - test-chmtime -v +0 .git/sequencer >actual && + test-tool chmtime --get .git/sequencer >actual && test_cmp expect actual ' @@ -317,7 +317,7 @@ test_expect_success '--continue after resolving conflicts' ' { git rev-list HEAD | git diff-tree --root --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual.log && test_cmp expect foo && test_cmp expect.log actual.log @@ -334,7 +334,7 @@ test_expect_success '--continue after resolving conflicts and committing' ' { git rev-list HEAD | git diff-tree --root --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual && cat >expect <<-\EOF && OBJID @@ -385,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 @@ -426,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 9cce5ae881..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>" @@ -112,6 +111,17 @@ 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) && @@ -198,6 +208,50 @@ 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) && 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 db9378142a..5e39fcdb66 100755 --- a/t/t3513-revert-submodule.sh +++ b/t/t3513-revert-submodule.sh @@ -25,7 +25,6 @@ git_revert () { 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 bcbb680651..b8fbdefcdc 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -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 \ @@ -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 @@ -421,7 +429,7 @@ test_expect_success 'rm of a populated submodule with modifications fails unless test -d submod && test -f submod/.git && git status -s -uno --ignore-submodules=none >actual && - test_cmp expect.modified actual && + test_cmp expect.modified_inside actual && git rm -f submod && test ! -d submod && git status -s -uno --ignore-submodules=none >actual && @@ -436,7 +444,7 @@ test_expect_success 'rm of a populated submodule with untracked files fails unle test -d submod && test -f submod/.git && git status -s -uno --ignore-submodules=none >actual && - test_cmp expect.modified actual && + test_cmp expect.modified_untracked actual && git rm -f submod && test ! -d submod && git status -s -uno --ignore-submodules=none >actual && @@ -569,26 +577,22 @@ test_expect_success 'rm of a conflicted unpopulated submodule succeeds' ' 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 rm submod 2>output.err && + ! test -d submod && + ! test -d submod/.git && git status -s -uno --ignore-submodules=none >actual && - ! test -s actual && - rm -rf submod + test -s actual && + test_i18ngrep Migrating output.err ' cat >expect.deepmodified <<EOF @@ -625,7 +629,7 @@ test_expect_success 'rm of a populated nested submodule with different nested HE test -d submod && test -f submod/.git && git status -s -uno --ignore-submodules=none >actual && - test_cmp expect.modified actual && + test_cmp expect.modified_inside actual && git rm -f submod && test ! -d submod && git status -s -uno --ignore-submodules=none >actual && @@ -640,7 +644,7 @@ test_expect_success 'rm of a populated nested submodule with nested modification test -d submod && test -f submod/.git && git status -s -uno --ignore-submodules=none >actual && - test_cmp expect.modified actual && + test_cmp expect.modified_inside actual && git rm -f submod && test ! -d submod && git status -s -uno --ignore-submodules=none >actual && @@ -655,41 +659,36 @@ test_expect_success 'rm of a populated nested submodule with nested untracked fi test -d submod && test -f submod/.git && git status -s -uno --ignore-submodules=none >actual && - test_cmp expect.modified actual && + test_cmp expect.modified_untracked actual && git rm -f submod && test ! -d submod && 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 rm submod 2>output.err && + ! test -d submod && + ! test -d submod/subsubmod/.git && git status -s -uno --ignore-submodules=none >actual && - ! test -s actual && - rm -rf submod + 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^ && 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 && @@ -859,9 +858,8 @@ test_expect_success 'rm files with two different errors' ' test_i18ncmp expect actual ' -test_expect_success 'rm empty string should invoke warning' ' - git rm -rf "" 2>output && - test_i18ngrep "warning: empty strings" output +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 f3a4b4a913..07af05d7ae 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -187,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 && @@ -331,9 +331,8 @@ 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 invoke warning' ' - git add "" 2>output && - test_i18ngrep "warning: empty strings" output +test_expect_success 'git add empty string should fail' ' + test_must_fail git add "" ' test_expect_success 'git add --chmod=[+-]x stages correctly' ' @@ -356,6 +355,7 @@ test_expect_success POSIXPERM,SYMLINKS 'git add --chmod=+x with symlinks' ' 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 && 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/t3702-add-edit.sh b/t/t3702-add-edit.sh index 3cb74ca296..c6af7f82b5 100755 --- a/t/t3702-add-edit.sh +++ b/t/t3702-add-edit.sh @@ -40,7 +40,6 @@ test_expect_success 'setup' ' cat > expected-patch << EOF diff --git a/file b/file -index b9834b5..9020acb 100644 --- a/file +++ b/file @@ -1,11 +1,6 @@ @@ -80,7 +79,6 @@ EOF cat > expected << EOF diff --git a/file b/file -index b9834b5..ef6e94c 100644 --- a/file +++ b/file @@ -1,10 +1,12 @@ @@ -100,7 +98,7 @@ EOF echo "#!$SHELL_PATH" >fake-editor.sh cat >> fake-editor.sh <<\EOF -mv -f "$1" orig-patch && +egrep -v '^index' "$1" >orig-patch && mv -f patch "$1" EOF @@ -113,7 +111,8 @@ test_expect_success 'add -e' ' git add -e && test_cmp second-part file && test_cmp orig-patch expected-patch && - git diff --cached > out && + git diff --cached >actual && + grep -v index actual >out && test_cmp out expected ' diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh index 3b94283e35..b92ff95977 100755 --- a/t/t3900-i18n-commit.sh +++ b/t/t3900-i18n-commit.sh @@ -40,7 +40,7 @@ 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" && @@ -49,7 +49,7 @@ test_expect_success 'UTF-8 invalid characters refused' ' ' 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" \ @@ -59,7 +59,7 @@ test_expect_success 'UTF-8 overlong sequences rejected' ' ' 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" && @@ -68,7 +68,7 @@ test_expect_success 'UTF-8 non-characters refused' ' ' 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" && diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh index f663d567c8..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 && @@ -254,7 +254,7 @@ test_expect_success 'rebase --merge (L/U)' ' 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 && + . "$TEST_DIRECTORY"/t3901/utf8.txt && git reset --hard master && git am out-u1 out-u2 && @@ -265,7 +265,7 @@ test_expect_success 'am (U/U)' ' 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 && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && git reset --hard master && git am out-l1 out-l2 && @@ -276,7 +276,7 @@ test_expect_success !MINGW 'am (L/L)' ' 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 && + . "$TEST_DIRECTORY"/t3901/utf8.txt && git reset --hard master && # am specifies --utf8 by default. @@ -288,7 +288,7 @@ test_expect_success 'am (U/L)' ' 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 && + . "$TEST_DIRECTORY"/t3901/utf8.txt && git reset --hard master && git am --no-utf8 out-l1 out-l2 2>err && @@ -303,7 +303,7 @@ test_expect_success 'am --no-utf8 (U/L)' ' 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 && + . "$TEST_DIRECTORY"/t3901/8859-1.txt && git reset --hard master && # mailinfo will re-code the commit message to the charset specified by 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/t3903-stash.sh b/t/t3903-stash.sh index 2de3e18ce6..1f871d3cca 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -274,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' ' @@ -446,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" && @@ -650,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 && @@ -665,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 ' @@ -706,7 +726,7 @@ test_expect_success 'store updates stash ref and reflog' ' git reset --hard && ! grep quux bazzy && git stash store -m quuxery $STASH_ID && - test $(cat .git/refs/stash) = $STASH_ID && + test $(git rev-parse stash) = $STASH_ID && git reflog --format=%H stash| grep $STASH_ID && git stash pop && grep quux bazzy @@ -775,4 +795,305 @@ test_expect_success 'stash is not confused by partial renames' ' 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..597b0637d1 100755 --- a/t/t3905-stash-include-untracked.sh +++ b/t/t3905-stash-include-untracked.sh @@ -35,24 +35,26 @@ test_expect_success 'stash save --include-untracked cleaned the untracked files' test_cmp expect actual ' +tracked=$(git rev-parse --short $(echo 1 | git hash-object --stdin)) +untracked=$(git rev-parse --short $(echo untracked | git hash-object --stdin)) cat > expect.diff <<EOF diff --git a/HEAD b/HEAD new file mode 100644 -index 0000000..d00491f +index 0000000..$tracked --- /dev/null +++ b/HEAD @@ -0,0 +1 @@ +1 diff --git a/file2 b/file2 new file mode 100644 -index 0000000..d00491f +index 0000000..$tracked --- /dev/null +++ b/file2 @@ -0,0 +1 @@ +1 diff --git a/untracked/untracked b/untracked/untracked new file mode 100644 -index 0000000..5a72eb2 +index 0000000..$untracked --- /dev/null +++ b/untracked/untracked @@ -0,0 +1 @@ @@ -109,10 +111,11 @@ test_expect_success 'stash save -u dirty index' ' git stash -u ' +blob=$(git rev-parse --short $(echo 4 | git hash-object --stdin)) cat > expect <<EOF diff --git a/file3 b/file3 new file mode 100644 -index 0000000..b8626c4 +index 0000000..$blob --- /dev/null +++ b/file3 @@ -0,0 +1 @@ @@ -185,4 +188,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/t4001-diff-rename.sh b/t/t4001-diff-rename.sh index 0d1fa45d25..bf4030371a 100755 --- a/t/t4001-diff-rename.sh +++ b/t/t4001-diff-rename.sh @@ -134,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 'test diff.renames=true for git status' ' + git -c diff.renames=true status >out && + test_i18ngrep "renamed: .*path1 -> subdir/path1" out +' + +test_expect_success 'test diff.renames=false for git status' ' + git -c diff.renames=false status >out && + test_i18ngrep ! "renamed: .*path1 -> subdir/path1" out && + test_i18ngrep "new file: .*subdir/path1" out && + test_i18ngrep "deleted: .*[^/]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 && @@ -148,7 +164,8 @@ test_expect_success 'two files with same basename and same content' ' git add dir && git commit -m 2 && git mv dir other-dir && - git status | test_i18ngrep "renamed: .*dir/A/file -> other-dir/A/file" + git status >out && + test_i18ngrep "renamed: .*dir/A/file -> other-dir/A/file" out ' test_expect_success 'setup for many rename source candidates' ' @@ -230,4 +247,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/t4002-diff-basic.sh b/t/t4002-diff-basic.sh index a5e8b83083..3a6c21e825 100755 --- a/t/t4002-diff-basic.sh +++ b/t/t4002-diff-basic.sh @@ -131,7 +131,7 @@ cmp_diff_files_output () { # object ID for the changed files because it wants you to look at the # filesystem. sed <"$2" >.test-tmp \ - -e '/^:000000 /d;s/'$_x40'\( [MCRNDU][0-9]*\) /'$_z40'\1 /' && + -e '/^:000000 /d;s/'$OID_REGEX'\( [MCRNDU][0-9]*\) /'$ZERO_OID'\1 /' && test_cmp "$1" .test-tmp } 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/t4006-diff-mode.sh b/t/t4006-diff-mode.sh index 76f643b2c2..a8e01eccd1 100755 --- a/t/t4006-diff-mode.sh +++ b/t/t4006-diff-mode.sh @@ -8,7 +8,7 @@ test_description='Test mode change diffs. ' . ./test-lib.sh -sed_script='s/\(:100644 100755\) \('"$_x40"'\) \2 /\1 X X /' +sed_script='s/\(:100644 100755\) \('"$OID_REGEX"'\) \2 /\1 X X /' test_expect_success 'setup' ' echo frotz >rezrov && diff --git a/t/t4007-rename-3.sh b/t/t4007-rename-3.sh index dae327fabb..b187b7f6c6 100755 --- a/t/t4007-rename-3.sh +++ b/t/t4007-rename-3.sh @@ -17,6 +17,7 @@ test_expect_success 'prepare reference tree' ' echo $tree ' +blob=$(git hash-object "$TEST_DIRECTORY/diff-lib/COPYING") test_expect_success 'prepare work tree' ' cp path0/COPYING path1/COPYING && git update-index --add --remove path0/COPYING path1/COPYING @@ -26,8 +27,8 @@ test_expect_success 'prepare work tree' ' # path1 both have COPYING and the latter is a copy of path0/COPYING. # Comparing the full tree with cache should tell us so. -cat >expected <<\EOF -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 C100 path0/COPYING path1/COPYING +cat >expected <<EOF +:100644 100644 $blob $blob C100 path0/COPYING path1/COPYING EOF test_expect_success 'copy detection' ' @@ -46,8 +47,8 @@ test_expect_success 'copy detection, cached' ' # path1/COPYING suddenly appearing from nowhere, not detected as # a copy from path0/COPYING. -cat >expected <<\EOF -:000000 100644 0000000000000000000000000000000000000000 6ff87c4664981e4397625791c8ea3bbb5f2279a3 A path1/COPYING +cat >expected <<EOF +:000000 100644 $ZERO_OID $blob A path1/COPYING EOF test_expect_success 'copy, limited to a subtree' ' @@ -64,8 +65,8 @@ test_expect_success 'tweak work tree' ' # path0/COPYING. Showing the full tree with cache should tell us about # the rename. -cat >expected <<\EOF -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100 path0/COPYING path1/COPYING +cat >expected <<EOF +:100644 100644 $blob $blob R100 path0/COPYING path1/COPYING EOF test_expect_success 'rename detection' ' @@ -78,8 +79,8 @@ test_expect_success 'rename detection' ' # path0/COPYING. When we say we care only about path1, we should just # see path1/COPYING appearing from nowhere. -cat >expected <<\EOF -:000000 100644 0000000000000000000000000000000000000000 6ff87c4664981e4397625791c8ea3bbb5f2279a3 A path1/COPYING +cat >expected <<EOF +:000000 100644 $ZERO_OID $blob A path1/COPYING EOF test_expect_success 'rename, limited to a subtree' ' diff --git a/t/t4008-diff-break-rewrite.sh b/t/t4008-diff-break-rewrite.sh index 9dd1bc5e16..b1ccd4102e 100755 --- a/t/t4008-diff-break-rewrite.sh +++ b/t/t4008-diff-break-rewrite.sh @@ -27,29 +27,32 @@ Further, with -B and -M together, these should turn into two renames. test_expect_success setup ' cat "$TEST_DIRECTORY"/diff-lib/README >file0 && cat "$TEST_DIRECTORY"/diff-lib/COPYING >file1 && + blob0_id=$(git hash-object file0) && + blob1_id=$(git hash-object file1) && git update-index --add file0 file1 && git tag reference $(git write-tree) ' test_expect_success 'change file1 with copy-edit of file0 and remove file0' ' sed -e "s/git/GIT/" file0 >file1 && + blob2_id=$(git hash-object file1) && rm -f file0 && git update-index --remove file0 file1 ' test_expect_success 'run diff with -B (#1)' ' git diff-index -B --cached reference >current && - cat >expect <<-\EOF && - :100644 000000 548142c327a6790ff8821d67c2ee1eff7a656b52 0000000000000000000000000000000000000000 D file0 - :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 2fbedd0b5d4b8126e4750c3bee305e8ff79f80ec M100 file1 + cat >expect <<-EOF && + :100644 000000 $blob0_id $ZERO_OID D file0 + :100644 100644 $blob1_id $blob2_id M100 file1 EOF compare_diff_raw expect current ' test_expect_success 'run diff with -B and -M (#2)' ' git diff-index -B -M reference >current && - cat >expect <<-\EOF && - :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 2fbedd0b5d4b8126e4750c3bee305e8ff79f80ec R100 file0 file1 + cat >expect <<-EOF && + :100644 100644 $blob0_id $blob2_id R100 file0 file1 EOF compare_diff_raw expect current ' @@ -66,18 +69,18 @@ test_expect_success 'swap file0 and file1' ' test_expect_success 'run diff with -B (#3)' ' git diff-index -B reference >current && - cat >expect <<-\EOF && - :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 6ff87c4664981e4397625791c8ea3bbb5f2279a3 M100 file0 - :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 548142c327a6790ff8821d67c2ee1eff7a656b52 M100 file1 + cat >expect <<-EOF && + :100644 100644 $blob0_id $blob1_id M100 file0 + :100644 100644 $blob1_id $blob0_id M100 file1 EOF compare_diff_raw expect current ' test_expect_success 'run diff with -B and -M (#4)' ' git diff-index -B -M reference >current && - cat >expect <<-\EOF && - :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100 file1 file0 - :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 548142c327a6790ff8821d67c2ee1eff7a656b52 R100 file0 file1 + cat >expect <<-EOF && + :100644 100644 $blob1_id $blob1_id R100 file1 file0 + :100644 100644 $blob0_id $blob0_id R100 file0 file1 EOF compare_diff_raw expect current ' @@ -85,14 +88,15 @@ test_expect_success 'run diff with -B and -M (#4)' ' test_expect_success 'make file0 into something completely different' ' rm -f file0 && test_ln_s_add frotz file0 && + slink_id=$(printf frotz | git hash-object --stdin) && git update-index file1 ' test_expect_success 'run diff with -B (#5)' ' git diff-index -B reference >current && - cat >expect <<-\EOF && - :100644 120000 548142c327a6790ff8821d67c2ee1eff7a656b52 67be421f88824578857624f7b3dc75e99a8a1481 T file0 - :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 548142c327a6790ff8821d67c2ee1eff7a656b52 M100 file1 + cat >expect <<-EOF && + :100644 120000 $blob0_id $slink_id T file0 + :100644 100644 $blob1_id $blob0_id M100 file1 EOF compare_diff_raw expect current ' @@ -103,9 +107,9 @@ test_expect_success 'run diff with -B -M (#6)' ' # file0 changed from regular to symlink. file1 is the same as the preimage # of file0. Because the change does not make file0 disappear, file1 is # denoted as a copy of file0 - cat >expect <<-\EOF && - :100644 120000 548142c327a6790ff8821d67c2ee1eff7a656b52 67be421f88824578857624f7b3dc75e99a8a1481 T file0 - :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 548142c327a6790ff8821d67c2ee1eff7a656b52 C file0 file1 + cat >expect <<-EOF && + :100644 120000 $blob0_id $slink_id T file0 + :100644 100644 $blob0_id $blob0_id C file0 file1 EOF compare_diff_raw expect current ' @@ -115,9 +119,9 @@ test_expect_success 'run diff with -M (#7)' ' # This should not mistake file0 as the copy source of new file1 # due to type differences. - cat >expect <<-\EOF && - :100644 120000 548142c327a6790ff8821d67c2ee1eff7a656b52 67be421f88824578857624f7b3dc75e99a8a1481 T file0 - :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 548142c327a6790ff8821d67c2ee1eff7a656b52 M file1 + cat >expect <<-EOF && + :100644 120000 $blob0_id $slink_id T file0 + :100644 100644 $blob1_id $blob0_id M file1 EOF compare_diff_raw expect current ' @@ -128,25 +132,26 @@ test_expect_success 'file1 edited to look like file0 and file0 rename-edited to git checkout-index -f -u -a && sed -e "s/git/GIT/" file0 >file1 && sed -e "s/git/GET/" file0 >file2 && + blob3_id=$(git hash-object file2) && rm -f file0 && git update-index --add --remove file0 file1 file2 ' test_expect_success 'run diff with -B (#8)' ' git diff-index -B reference >current && - cat >expect <<-\EOF && - :100644 000000 548142c327a6790ff8821d67c2ee1eff7a656b52 0000000000000000000000000000000000000000 D file0 - :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 2fbedd0b5d4b8126e4750c3bee305e8ff79f80ec M100 file1 - :000000 100644 0000000000000000000000000000000000000000 69a939f651686f56322566e2fd76715947a24162 A file2 + cat >expect <<-EOF && + :100644 000000 $blob0_id $ZERO_OID D file0 + :100644 100644 $blob1_id $blob2_id M100 file1 + :000000 100644 $ZERO_OID $blob3_id A file2 EOF compare_diff_raw expect current ' test_expect_success 'run diff with -B -C (#9)' ' git diff-index -B -C reference >current && - cat >expect <<-\EOF && - :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 2fbedd0b5d4b8126e4750c3bee305e8ff79f80ec C095 file0 file1 - :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 69a939f651686f56322566e2fd76715947a24162 R095 file0 file2 + cat >expect <<-EOF && + :100644 100644 $blob0_id $blob2_id C095 file0 file1 + :100644 100644 $blob0_id $blob3_id R095 file0 file2 EOF compare_diff_raw expect current ' diff --git a/t/t4011-diff-symlink.sh b/t/t4011-diff-symlink.sh index 13e7f621ab..108c012a3a 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,11 +139,13 @@ 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 - index e69de29..d95f3ad 100644 - Binary files a/file.bin and b/file.bin differ + new file mode 100644 + index 0000000..d95f3ad + Binary files /dev/null and b/file.bin differ diff --git a/link.bin b/link.bin - index e69de29..dce41ec 120000 - --- a/link.bin + new file mode 120000 + index 0000000..dce41ec + --- /dev/null +++ b/link.bin @@ -0,0 +1 @@ +file.bin diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index d09acfe48e..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,14 @@ 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 @@ -110,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 "\$" @@ -150,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 @@ -192,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 @@ -243,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 @@ -254,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/ @@ -272,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 @@ -299,8 +336,10 @@ 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 @@ -313,11 +352,20 @@ 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' ' 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.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 ba4902df2b..028d5507a6 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -578,7 +578,11 @@ test_expect_success 'excessive subject' ' rm -rf patches/ && git checkout side && + before=$(git hash-object file) && + before=$(git rev-parse --short $before) && for i in 5 6 1 2 3 A 4 B C 7 8 9 10 D E F; do echo "$i"; done >>file && + after=$(git hash-object file) && + after=$(git rev-parse --short $after) && git update-index file && git commit -m "This is an excessively long subject line for a message due to the habit some projects have of not having a short, one-line subject at the start of the commit message, but rather sticking a whole paragraph right at the start as the only thing in the commit message. It had better not become the filename for the patch." && git format-patch -o patches/ master..side && @@ -586,7 +590,6 @@ test_expect_success 'excessive subject' ' ' test_expect_success 'cover-letter inherits diff options' ' - git mv file foo && git commit -m foo && git format-patch --no-renames --cover-letter -1 && @@ -616,7 +619,7 @@ test_expect_success 'shortlog of cover-letter wraps overly-long onelines' ' ' cat > expect << EOF -index 40f36c6..2dc5c23 100644 +index $before..$after 100644 --- a/file +++ b/file @@ -13,4 +13,20 @@ C @@ -640,7 +643,7 @@ test_expect_success 'format-patch respects -U' ' cat > expect << EOF diff --git a/file b/file -index 40f36c6..2dc5c23 100644 +index $before..$after 100644 --- a/file +++ b/file @@ -14,3 +14,19 @@ C @@ -1294,8 +1297,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 ' @@ -1368,7 +1370,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 @@ -1383,8 +1385,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 ' @@ -1496,14 +1526,14 @@ test_expect_success 'cover letter auto user override' ' 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 && + echo "From $ZERO_OID 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 && + grep "^From $OID_REGEX Mon Sep 17 00:00:00 2001$" patch2 >filtered && test_cmp from filtered ' @@ -1634,6 +1664,15 @@ test_expect_success 'format-patch --base with --attach' ' test_write_lines 1 2 >expect && test_cmp expect actual ' +test_expect_success 'format-patch --attach cover-letter only is non-multipart' ' + test_when_finished "rm -fr patches" && + git format-patch -o patches --cover-letter --attach=mimemime --base=HEAD~ -1 && + ! egrep "^--+mimemime" patches/0000*.patch && + egrep "^--+mimemime$" patches/0001*.patch >output && + test_line_count = 2 output && + egrep "^--+mimemime--$" patches/0001*.patch >output && + test_line_count = 1 output +' test_expect_success 'format-patch --pretty=mboxrd' ' sp=" " && diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 289806d0c7..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 && @@ -923,15 +967,15 @@ test_expect_success 'ws-error-highlight test setup' ' test_expect_success 'test --ws-error-highlight option' ' - git -c color.diff=always diff --ws-error-highlight=default,old | + git diff --color --ws-error-highlight=default,old | test_decode_color >current && test_cmp expect.default-old current && - git -c color.diff=always diff --ws-error-highlight=all | + git diff --color --ws-error-highlight=all | test_decode_color >current && test_cmp expect.all current && - git -c color.diff=always diff --ws-error-highlight=none | + git diff --color --ws-error-highlight=none | test_decode_color >current && test_cmp expect.none current @@ -939,15 +983,15 @@ test_expect_success 'test --ws-error-highlight option' ' test_expect_success 'test diff.wsErrorHighlight config' ' - git -c color.diff=always -c diff.wsErrorHighlight=default,old diff | + git -c diff.wsErrorHighlight=default,old diff --color | test_decode_color >current && test_cmp expect.default-old current && - git -c color.diff=always -c diff.wsErrorHighlight=all diff | + git -c diff.wsErrorHighlight=all diff --color | test_decode_color >current && test_cmp expect.all current && - git -c color.diff=always -c diff.wsErrorHighlight=none diff | + git -c diff.wsErrorHighlight=none diff --color | test_decode_color >current && test_cmp expect.none current @@ -955,21 +999,730 @@ test_expect_success 'test diff.wsErrorHighlight config' ' test_expect_success 'option overrides diff.wsErrorHighlight' ' - git -c color.diff=always -c diff.wsErrorHighlight=none \ - diff --ws-error-highlight=default,old | + git -c diff.wsErrorHighlight=none \ + diff --color --ws-error-highlight=default,old | test_decode_color >current && test_cmp expect.default-old current && - git -c color.diff=always -c diff.wsErrorHighlight=default \ - diff --ws-error-highlight=all | + git -c diff.wsErrorHighlight=default \ + diff --color --ws-error-highlight=all | test_decode_color >current && test_cmp expect.all current && - git -c color.diff=always -c diff.wsErrorHighlight=all \ - diff --ws-error-highlight=none | + 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/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh index 1795ffc3aa..22f9f88f0a 100755 --- a/t/t4018-diff-funcname.sh +++ b/t/t4018-diff-funcname.sh @@ -33,6 +33,7 @@ diffpatterns=" css fortran fountain + golang html java matlab 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/t4020-diff-external.sh b/t/t4020-diff-external.sh index 044620186d..e009826fcb 100755 --- a/t/t4020-diff-external.sh +++ b/t/t4020-diff-external.sh @@ -13,6 +13,8 @@ test_expect_success setup ' test_tick && echo second >file && + before=$(git hash-object file) && + before=$(git rev-parse --short $before) && git add file && git commit -m second && @@ -26,7 +28,7 @@ test_expect_success 'GIT_EXTERNAL_DIFF environment' ' read path oldfile oldhex oldmode newfile newhex newmode && test "z$path" = zfile && test "z$oldmode" = z100644 && - test "z$newhex" = "z$_z40" && + test "z$newhex" = "z$ZERO_OID" && test "z$newmode" = z100644 && oh=$(git rev-parse --verify HEAD:file) && test "z$oh" = "z$oldhex" @@ -55,7 +57,7 @@ test_expect_success SYMLINKS 'typechange diff' ' read path oldfile oldhex oldmode newfile newhex newmode && test "z$path" = zfile && test "z$oldmode" = z100644 && - test "z$newhex" = "z$_z40" && + test "z$newhex" = "z$ZERO_OID" && test "z$newmode" = z120000 && oh=$(git rev-parse --verify HEAD:file) && test "z$oh" = "z$oldhex" @@ -73,7 +75,7 @@ test_expect_success 'diff.external' ' read path oldfile oldhex oldmode newfile newhex newmode && test "z$path" = zfile && test "z$oldmode" = z100644 && - test "z$newhex" = "z$_z40" && + test "z$newhex" = "z$ZERO_OID" && test "z$newmode" = z100644 && oh=$(git rev-parse --verify HEAD:file) && test "z$oh" = "z$oldhex" @@ -104,7 +106,7 @@ test_expect_success 'diff attribute' ' read path oldfile oldhex oldmode newfile newhex newmode && test "z$path" = zfile && test "z$oldmode" = z100644 && - test "z$newhex" = "z$_z40" && + test "z$newhex" = "z$ZERO_OID" && test "z$newmode" = z100644 && oh=$(git rev-parse --verify HEAD:file) && test "z$oh" = "z$oldhex" @@ -137,7 +139,7 @@ test_expect_success 'diff attribute' ' read path oldfile oldhex oldmode newfile newhex newmode && test "z$path" = zfile && test "z$oldmode" = z100644 && - test "z$newhex" = "z$_z40" && + test "z$newhex" = "z$ZERO_OID" && test "z$newmode" = z100644 && oh=$(git rev-parse --verify HEAD:file) && test "z$oh" = "z$oldhex" @@ -180,9 +182,13 @@ test_expect_success 'no diff with -diff' ' echo NULZbetweenZwords | perl -pe 'y/Z/\000/' > file test_expect_success 'force diff with "diff"' ' + after=$(git hash-object file) && + after=$(git rev-parse --short $after) && echo >.gitattributes "file diff" && git diff >actual && - test_cmp "$TEST_DIRECTORY"/t4020/diff.NUL actual + sed -e "s/^index .*/index $before..$after 100644/" \ + "$TEST_DIRECTORY"/t4020/diff.NUL >expected-diff && + test_cmp expected-diff actual ' test_expect_success 'GIT_EXTERNAL_DIFF with more than one changed files' ' @@ -237,7 +243,7 @@ test_expect_success 'diff --cached' ' git update-index --assume-unchanged file && echo second >file && git diff --cached >actual && - test_cmp "$TEST_DIRECTORY"/t4020/diff.NUL actual + test_cmp expected-diff actual ' test_expect_success 'clean up crlf leftovers' ' diff --git a/t/t4022-diff-rewrite.sh b/t/t4022-diff-rewrite.sh index cb51d9f9d4..6d1c3d949c 100755 --- a/t/t4022-diff-rewrite.sh +++ b/t/t4022-diff-rewrite.sh @@ -13,6 +13,8 @@ test_expect_success setup ' "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM" \ <"$TEST_DIRECTORY"/../COPYING >test && echo "to be deleted" >test2 && + blob=$(git hash-object test2) && + blob=$(git rev-parse --short $blob) && git add test2 ' @@ -27,7 +29,7 @@ test_expect_success 'detect rewrite' ' cat >expect <<EOF diff --git a/test2 b/test2 deleted file mode 100644 -index 4202011..0000000 +index $blob..0000000 --- a/test2 +++ /dev/null @@ -1 +0,0 @@ @@ -43,7 +45,7 @@ test_expect_success 'show deletion diff without -D' ' cat >expect <<EOF diff --git a/test2 b/test2 deleted file mode 100644 -index 4202011..0000000 +index $blob..0000000 EOF test_expect_success 'suppress deletion diff with -D' ' diff --git a/t/t4026-color.sh b/t/t4026-color.sh index ec78c5e3ac..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" ' diff --git a/t/t4027-diff-submodule.sh b/t/t4027-diff-submodule.sh index 518bf9524e..6304130ad4 100755 --- a/t/t4027-diff-submodule.sh +++ b/t/t4027-diff-submodule.sh @@ -31,7 +31,7 @@ test_expect_success setup ' cd sub && git rev-list HEAD ) && - echo ":160000 160000 $3 $_z40 M sub" >expect && + echo ":160000 160000 $3 $ZERO_OID M sub" >expect && subtip=$3 subprev=$2 ' @@ -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 && @@ -317,7 +250,7 @@ test_expect_success 'conflicted submodule setup' ' # 39 efs c=fffffffffffffffffffffffffffffffffffffff && ( - echo "000000 $_z40 0 sub" && + echo "000000 $ZERO_OID 0 sub" && echo "160000 1$c 1 sub" && echo "160000 2$c 2 sub" && echo "160000 3$c 3 sub" @@ -332,7 +265,7 @@ index 2ffffff,3ffffff..0000000 ++Subproject commit 0000000000000000000000000000000000000000'\'' && hh=$(git rev-parse HEAD) && - sed -e "s/$_z40/$hh/" expect.nosub >expect.withsub + sed -e "s/$ZERO_OID/$hh/" expect.nosub >expect.withsub ' diff --git a/t/t4029-diff-trailing-space.sh b/t/t4029-diff-trailing-space.sh index 3ccc237a8d..32b6e9a4e7 100755 --- a/t/t4029-diff-trailing-space.sh +++ b/t/t4029-diff-trailing-space.sh @@ -6,7 +6,7 @@ test_description='diff honors config option, diff.suppressBlankEmpty' . ./test-lib.sh -cat <<\EOF > exp || +cat <<\EOF >expected || diff --git a/f b/f index 5f6a263..8cb8bae 100644 --- a/f @@ -18,22 +18,26 @@ index 5f6a263..8cb8bae 100644 EOF exit 1 -test_expect_success \ - "$test_description" \ - 'printf "\nx\n" > f && - git add f && - git commit -q -m. f && - printf "\ny\n" > f && - git config --bool diff.suppressBlankEmpty true && - git diff f > actual && - test_cmp exp actual && - perl -i.bak -p -e "s/^\$/ /" exp && - git config --bool diff.suppressBlankEmpty false && - git diff f > actual && - test_cmp exp actual && - git config --bool --unset diff.suppressBlankEmpty && - git diff f > actual && - test_cmp exp actual - ' +test_expect_success "$test_description" ' + printf "\nx\n" > f && + before=$(git hash-object f) && + before=$(git rev-parse --short $before) && + git add f && + git commit -q -m. f && + printf "\ny\n" > f && + after=$(git hash-object f) && + after=$(git rev-parse --short $after) && + sed -e "s/^index .*/index $before..$after 100644/" expected >exp && + git config --bool diff.suppressBlankEmpty true && + git diff f > actual && + test_cmp exp actual && + perl -i.bak -p -e "s/^\$/ /" exp && + git config --bool diff.suppressBlankEmpty false && + git diff f > actual && + test_cmp exp actual && + git config --bool --unset diff.suppressBlankEmpty && + git diff f > actual && + test_cmp exp actual +' test_done diff --git a/t/t4030-diff-textconv.sh b/t/t4030-diff-textconv.sh index aad6c7f78d..4cb9f0e523 100755 --- a/t/t4030-diff-textconv.sh +++ b/t/t4030-diff-textconv.sh @@ -148,7 +148,8 @@ test_expect_success 'diffstat does not run textconv' ' # restore working setup echo file diff=foo >.gitattributes -cat >expect.typechange <<'EOF' +symlink=$(git rev-parse --short $(printf frotz | git hash-object --stdin)) +cat >expect.typechange <<EOF --- a/file +++ /dev/null @@ -1,2 +0,0 @@ @@ -156,7 +157,7 @@ cat >expect.typechange <<'EOF' -1 diff --git a/file b/file new file mode 120000 -index 0000000..67be421 +index 0000000..$symlink --- /dev/null +++ b/file @@ -0,0 +1 @@ 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/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/t4042-diff-textconv-caching.sh b/t/t4042-diff-textconv-caching.sh index 04a44d5c61..bf33aedf4b 100755 --- a/t/t4042-diff-textconv-caching.sh +++ b/t/t4042-diff-textconv-caching.sh @@ -15,9 +15,13 @@ test_expect_success 'setup' ' echo bar content 1 >bar.bin && git add . && git commit -m one && + foo1=$(git rev-parse --short HEAD:foo.bin) && + bar1=$(git rev-parse --short HEAD:bar.bin) && echo foo content 2 >foo.bin && echo bar content 2 >bar.bin && git commit -a -m two && + foo2=$(git rev-parse --short HEAD:foo.bin) && + bar2=$(git rev-parse --short HEAD:bar.bin) && echo "*.bin diff=magic" >.gitattributes && git config diff.magic.textconv ./helper && git config diff.magic.cachetextconv true @@ -25,14 +29,14 @@ test_expect_success 'setup' ' cat >expect <<EOF diff --git a/bar.bin b/bar.bin -index fcf9166..28283d5 100644 +index $bar1..$bar2 100644 --- a/bar.bin +++ b/bar.bin @@ -1 +1 @@ -converted: bar content 1 +converted: bar content 2 diff --git a/foo.bin b/foo.bin -index d5b9fe3..1345db2 100644 +index $foo1..$foo2 100644 --- a/foo.bin +++ b/foo.bin @@ -1 +1 @@ @@ -59,7 +63,7 @@ test_expect_success 'cached textconv does not run helper' ' cat >expect <<EOF diff --git a/bar.bin b/bar.bin -index fcf9166..28283d5 100644 +index $bar1..$bar2 100644 --- a/bar.bin +++ b/bar.bin @@ -1,2 +1,2 @@ @@ -67,7 +71,7 @@ index fcf9166..28283d5 100644 -converted: bar content 1 +converted: bar content 2 diff --git a/foo.bin b/foo.bin -index d5b9fe3..1345db2 100644 +index $foo1..$foo2 100644 --- a/foo.bin +++ b/foo.bin @@ -1,2 +1,2 @@ @@ -84,7 +88,7 @@ test_expect_success 'changing textconv invalidates cache' ' cat >expect <<EOF diff --git a/bar.bin b/bar.bin -index fcf9166..28283d5 100644 +index $bar1..$bar2 100644 --- a/bar.bin +++ b/bar.bin @@ -1,2 +1,2 @@ @@ -92,7 +96,7 @@ index fcf9166..28283d5 100644 -converted: bar content 1 +converted: bar content 2 diff --git a/foo.bin b/foo.bin -index d5b9fe3..1345db2 100644 +index $foo1..$foo2 100644 --- a/foo.bin +++ b/foo.bin @@ -1 +1 @@ diff --git a/t/t4044-diff-index-unique-abbrev.sh b/t/t4044-diff-index-unique-abbrev.sh index d5ce72be63..647905e01f 100755 --- a/t/t4044-diff-index-unique-abbrev.sh +++ b/t/t4044-diff-index-unique-abbrev.sh @@ -3,6 +3,12 @@ test_description='test unique sha1 abbreviation on "index from..to" line' . ./test-lib.sh +if ! test_have_prereq SHA1 +then + skip_all='not using SHA-1 for objects' + test_done +fi + cat >expect_initial <<EOF 100644 blob 51d2738463ea4ca66f8691c91e33ce64b7d41bb1 foo EOF diff --git a/t/t4045-diff-relative.sh b/t/t4045-diff-relative.sh index 3950f5034d..36f8ed8a81 100755 --- a/t/t4045-diff-relative.sh +++ b/t/t4045-diff-relative.sh @@ -8,66 +8,82 @@ test_expect_success 'setup' ' echo content >file1 && mkdir subdir && echo other content >subdir/file2 && + blob=$(git hash-object subdir/file2) && git add . && 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 + short_blob=$(git rev-parse --short $blob) + cat >expected <<-EOF + diff --git a/$expect b/$expect + new file mode 100644 + index 0000000..$short_blob + --- /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 $blob 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/t4046-diff-unmerged.sh b/t/t4046-diff-unmerged.sh index d0f14475ca..ff7cfd884a 100755 --- a/t/t4046-diff-unmerged.sh +++ b/t/t4046-diff-unmerged.sh @@ -37,7 +37,7 @@ test_expect_success 'diff-files -0' ' for path in $paths do >"$path" && - echo ":000000 100644 $_z40 $_z40 U $path" + echo ":000000 100644 $ZERO_OID $ZERO_OID U $path" done >diff-files-0.expect && git diff-files -0 >diff-files-0.actual && test_cmp diff-files-0.expect diff-files-0.actual @@ -47,9 +47,9 @@ test_expect_success 'diff-files -1' ' for path in $paths do >"$path" && - echo ":000000 100644 $_z40 $_z40 U $path" && + echo ":000000 100644 $ZERO_OID $ZERO_OID U $path" && case "$path" in - x??) echo ":100644 100644 $blob1 $_z40 M $path" + x??) echo ":100644 100644 $blob1 $ZERO_OID M $path" esac done >diff-files-1.expect && git diff-files -1 >diff-files-1.actual && @@ -60,9 +60,9 @@ test_expect_success 'diff-files -2' ' for path in $paths do >"$path" && - echo ":000000 100644 $_z40 $_z40 U $path" && + echo ":000000 100644 $ZERO_OID $ZERO_OID U $path" && case "$path" in - ?x?) echo ":100644 100644 $blob2 $_z40 M $path" + ?x?) echo ":100644 100644 $blob2 $ZERO_OID M $path" esac done >diff-files-2.expect && git diff-files -2 >diff-files-2.actual && @@ -75,9 +75,9 @@ test_expect_success 'diff-files -3' ' for path in $paths do >"$path" && - echo ":000000 100644 $_z40 $_z40 U $path" && + echo ":000000 100644 $ZERO_OID $ZERO_OID U $path" && case "$path" in - ??x) echo ":100644 100644 $blob3 $_z40 M $path" + ??x) echo ":100644 100644 $blob3 $ZERO_OID M $path" esac done >diff-files-3.expect && git diff-files -3 >diff-files-3.actual && diff --git a/t/t4051-diff-function-context.sh b/t/t4051-diff-function-context.sh index 6154acb456..2d76a971c4 100755 --- a/t/t4051-diff-function-context.sh +++ b/t/t4051-diff-function-context.sh @@ -72,7 +72,8 @@ test_expect_success 'setup' ' # overlap function context of 1st change and -u context of 2nd change grep -v "delete me from hello" <"$dir/hello.c" >file.c && - sed 2p <"$dir/dummy.c" >>file.c && + sed "2a\\ + extra line" <"$dir/dummy.c" >>file.c && commit_and_tag changed_hello_dummy file.c && git checkout initial && @@ -84,6 +85,10 @@ test_expect_success 'setup' ' 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 ' diff --git a/t/t4051/hello.c b/t/t4051/hello.c index 63b1a1e4ef..73e767e178 100644 --- a/t/t4051/hello.c +++ b/t/t4051/hello.c @@ -1,4 +1,7 @@ +/* + * Hello comment. + */ static void hello(void) // Begin of hello { /* 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/t4054-diff-bogus-tree.sh b/t/t4054-diff-bogus-tree.sh index 18f42c5fff..fcae82fffa 100755 --- a/t/t4054-diff-bogus-tree.sh +++ b/t/t4054-diff-bogus-tree.sh @@ -19,37 +19,37 @@ 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 && + echo ":000000 100644 $ZERO_OID $ZERO_OID A foo" >expect && 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 && + echo ":100644 000000 $ZERO_OID $ZERO_OID D foo" >expect && git diff-tree $bogus_tree $EMPTY_TREE >actual && test_cmp expect actual ' test_expect_success 'raw diff shows null sha1 (modification)' ' - echo ":100644 100644 $blob $_z40 M foo" >expect && + echo ":100644 100644 $blob $ZERO_OID M foo" >expect && git diff-tree $good_tree $bogus_tree >actual && test_cmp expect actual ' test_expect_success 'raw diff shows null sha1 (other direction)' ' - echo ":100644 100644 $_z40 $blob M foo" >expect && + echo ":100644 100644 $ZERO_OID $blob M foo" >expect && git diff-tree $bogus_tree $good_tree >actual && test_cmp expect actual ' test_expect_success 'raw diff shows null sha1 (reverse)' ' - echo ":100644 100644 $_z40 $blob M foo" >expect && + echo ":100644 100644 $ZERO_OID $blob M foo" >expect && git diff-tree -R $good_tree $bogus_tree >actual && test_cmp expect actual ' test_expect_success 'raw diff shows null sha1 (index)' ' - echo ":100644 100644 $_z40 $blob M foo" >expect && + echo ":100644 100644 $ZERO_OID $blob M foo" >expect && git diff-index $bogus_tree >actual && test_cmp expect actual ' diff --git a/t/t4058-diff-duplicates.sh b/t/t4058-diff-duplicates.sh index 0a23242cb6..c24ee175ef 100755 --- a/t/t4058-diff-duplicates.sh +++ b/t/t4058-diff-duplicates.sh @@ -59,12 +59,12 @@ test_expect_success 'create trees with duplicate entries' ' test_expect_success 'diff-tree between trees' ' { - printf ":000000 100644 $_z40 $blob_two A\touter/inner\n" && - printf ":000000 100644 $_z40 $blob_two A\touter/inner\n" && - printf ":000000 100644 $_z40 $blob_two A\touter/inner\n" && - printf ":100644 000000 $blob_two $_z40 D\touter/inner\n" && - printf ":100644 000000 $blob_two $_z40 D\touter/inner\n" && - printf ":100644 000000 $blob_two $_z40 D\touter/inner\n" + printf ":000000 100644 $ZERO_OID $blob_two A\touter/inner\n" && + printf ":000000 100644 $ZERO_OID $blob_two A\touter/inner\n" && + printf ":000000 100644 $ZERO_OID $blob_two A\touter/inner\n" && + printf ":100644 000000 $blob_two $ZERO_OID D\touter/inner\n" && + printf ":100644 000000 $blob_two $ZERO_OID D\touter/inner\n" && + printf ":100644 000000 $blob_two $ZERO_OID D\touter/inner\n" } >expect && git diff-tree -r --no-abbrev one two >actual && test_cmp expect actual diff --git a/t/t4059-diff-submodule-not-initialized.sh b/t/t4059-diff-submodule-not-initialized.sh index cd70fd5192..49bca7b48d 100755 --- a/t/t4059-diff-submodule-not-initialized.sh +++ b/t/t4059-diff-submodule-not-initialized.sh @@ -95,7 +95,7 @@ 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 (not initialized) + Submodule sm1 $smhead1...$smhead2 (commits not present) EOF test_cmp expected actual ' diff --git a/t/t4060-diff-submodule-option-diff-format.sh b/t/t4060-diff-submodule-option-diff-format.sh index 7e23b55ea4..4b168d0ed7 100755 --- a/t/t4060-diff-submodule-option-diff-format.sh +++ b/t/t4060-diff-submodule-option-diff-format.sh @@ -643,9 +643,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=diff HEAD >actual && @@ -746,4 +748,74 @@ test_expect_success 'diff --submodule=diff with .git file' ' 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 index 556450609b..2affd7a100 100755 --- a/t/t4061-diff-indent.sh +++ b/t/t4061-diff-indent.sh @@ -152,26 +152,28 @@ test_expect_success 'prepare' ' EOF ' +# --- diff tests ---------------------------------------------------------- + test_expect_success 'diff: ugly spaces' ' - git diff old new -- spaces.txt >out && + 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 diff --indent-heuristic old new -- spaces.txt >out-compacted && + 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' ' +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: --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: --indent-heuristic with --patience' ' git diff --indent-heuristic --patience old new -- spaces.txt >out-compacted3 && compare_diff spaces-compacted-expect out-compacted3 @@ -183,7 +185,7 @@ test_expect_success 'diff: --indent-heuristic with --histogram' ' ' test_expect_success 'diff: ugly functions' ' - git diff old new -- functions.c >out && + git diff --no-indent-heuristic old new -- functions.c >out && compare_diff functions-expect out ' @@ -192,25 +194,175 @@ test_expect_success 'diff: nice functions with --indent-heuristic' ' compare_diff functions-compacted-expect out-compacted ' -test_expect_success 'blame: ugly spaces' ' - git blame old..new -- spaces.txt >out-blame && - compare_blame spaces-expect out-blame -' +# --- 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' ' +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-blame2 && + 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-blame2 + 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 index f0bf50bda7..1130c8019b 100755 --- a/t/t4062-diff-pickaxe.sh +++ b/t/t4062-diff-pickaxe.sh @@ -14,9 +14,16 @@ test_expect_success setup ' 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{4096}$" HEAD^ >out && + 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/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 27cb0009fb..c7c688fcc4 100755 --- a/t/t4135-apply-weird-filenames.sh +++ b/t/t4135-apply-weird-filenames.sh @@ -89,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 4b0a374b63..6d92872318 100755 --- a/t/t4136-apply-check.sh +++ b/t/t4136-apply-check.sh @@ -29,4 +29,22 @@ test_expect_success 'apply exits non-zero with no-op patch' ' 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 89a5bacac5..1ebc587f8f 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 && @@ -93,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 && @@ -124,8 +126,6 @@ test_expect_success setup ' echo "Date: $GIT_AUTHOR_DATE" && echo && sed -e "1,2d" msg && - echo && - echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" && echo "---" && git diff-tree --no-commit-id --stat -p second } >patch1-stgit.eml && @@ -140,12 +140,10 @@ test_expect_success setup ' 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" && + echo "# Node ID $ZERO_OID" && + echo "# Parent $ZERO_OID" && cat msg && echo && - echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" && - echo && git diff-tree --no-commit-id -p second } >patch1-hg.eml && @@ -470,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' ' @@ -486,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 @@ -619,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 && @@ -983,7 +1031,9 @@ test_expect_success 'am works with multi-line in-body headers' ' rm -fr .git/rebase-apply && git checkout -f first && echo one >> file && - git commit -am "$LONG" --author="$LONG <long@example.com>" && + 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 " @@ -997,7 +1047,19 @@ test_expect_success 'am works with multi-line in-body headers' ' 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" + 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 9473c2779e..9d8d3c72e7 100755 --- a/t/t4151-am-abort.sh +++ b/t/t4151-am-abort.sh @@ -46,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 ' @@ -172,7 +171,7 @@ 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-chmtime =0 skip-should-be-untouched.t && + 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 && @@ -184,7 +183,7 @@ 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-chmtime =0 abort-should-be-untouched.t && + 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 && diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index 1a080e7823..8417e5a4b1 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 --get -60 $rr/postimage) && git checkout -b third master && git show second^:a1 | sed "s/To die: t/To die! T/" >a1 && @@ -179,7 +179,7 @@ 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 --get $rr/postimage) && test $oldmtimepost -lt $newmtimepost ' @@ -220,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' ' @@ -232,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/$ZERO_OID && + 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 && @@ -419,24 +456,6 @@ count_pre_post () { test_line_count = "$2" actual } -test_expect_success 'rerere gc' ' - find .git/rr-cache -type f >original && - xargs test-chmtime -172800 <original && - - git -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc && - find .git/rr-cache -type f >actual && - test_cmp original actual && - - git -c gc.rerereresolved=5 -c gc.rerereunresolved=0 rerere gc && - find .git/rr-cache -type f >actual && - test_cmp original actual && - - git -c gc.rerereresolved=0 -c gc.rerereunresolved=0 rerere gc && - find .git/rr-cache -type f >actual && - >expect && - test_cmp expect actual -' - merge_conflict_resolve () { git reset --hard && test_must_fail git merge six.1 && @@ -446,6 +465,8 @@ merge_conflict_resolve () { } test_expect_success 'multiple identical conflicts' ' + rm -fr .git/rr-cache && + mkdir .git/rr-cache && git reset --hard && test_seq 1 6 >early && @@ -491,7 +512,7 @@ test_expect_success 'multiple identical conflicts' ' count_pre_post 2 0 && # Pretend that the conflicts were made quite some time ago - find .git/rr-cache/ -type f | xargs test-chmtime -172800 && + test-tool chmtime -172800 $(find .git/rr-cache/ -type f) && # Unresolved entries have not expired yet git -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc && @@ -547,7 +568,7 @@ test_expect_success 'multiple identical conflicts' ' git rerere && # Pretend that the resolutions are old again - find .git/rr-cache/ -type f | xargs test-chmtime -172800 && + test-tool chmtime -172800 $(find .git/rr-cache/ -type f) && # Resolved entries have not expired yet git -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc && diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh index ae08b57712..58c2773676 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) && @@ -58,8 +59,8 @@ test_expect_success 'setup' ' fuzz() { file=$1 && sed " - s/$_x40/OBJECT_NAME/g - s/$_x05/OBJID/g + s/$OID_REGEX/OBJECT_NAME/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 ' @@ -126,6 +127,11 @@ test_expect_success !MINGW 'shortlog can read --format=raw output' ' 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): @@ -190,4 +196,23 @@ test_expect_success 'shortlog with --output=<file>' ' 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 -nsc HEAD >actual && + test_cmp expect actual +' + +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 1ccbd5948a..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 ' @@ -230,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' ' @@ -255,13 +289,53 @@ 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 ' @@ -279,6 +353,93 @@ test_expect_success 'log with grep.patternType configuration and command line' ' 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 @@ -359,6 +520,28 @@ test_expect_success 'log --graph --line-prefix="| | | " with merge' ' 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 @@ -376,7 +559,7 @@ cat > expect <<\EOF | | | | Merge branch 'side' | | -| * commit side +| * commit tags/side-2 | | Author: A U Thor <author@example.com> | | | | side-2 @@ -498,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 && @@ -554,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 && @@ -1190,6 +1493,54 @@ test_expect_success 'log --line-prefix="*** " --graph with diff and stats' ' 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 && @@ -1273,6 +1624,12 @@ test_expect_success 'log diagnoses bogus HEAD' ' 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 && @@ -1302,4 +1659,13 @@ test_expect_success 'log --source paints tag names' ' 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/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh index f5435fd250..2052cadb11 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' ' @@ -516,23 +516,86 @@ test_expect_success 'log decoration properly follows tag chain' ' 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) + $head1 (tag: refs/tags/tag2) EOF - sort actual >actual1 && + sort -k3 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 + $head1 tag: refs/tags/tag2 EOF - sort actual >actual1 && + sort -k3 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 b972296f06..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 && @@ -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 001343e2fc..62f335b2d9 100755 --- a/t/t4208-log-magic-pathspec.sh +++ b/t/t4208-log-magic-pathspec.sh @@ -29,6 +29,12 @@ 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 && test_i18ngrep ambiguous error @@ -39,13 +45,40 @@ test_expect_success 'git log -- :' ' ' test_expect_success 'git log HEAD -- :/' ' + initial=$(git rev-parse --short HEAD^) && cat >expected <<-EOF && - 24b24cf initial + $initial initial EOF (cd sub && git log --oneline HEAD -- :/ >../actual) && 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 +94,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 9d87777b59..d0377fae5c 100755 --- a/t/t4211-line-log.sh +++ b/t/t4211-line-log.sh @@ -106,4 +106,14 @@ test_expect_success '-L with --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 index e01a8f6ac9..7f90f58c03 100755 --- a/t/t4213-log-tabexpand.sh +++ b/t/t4213-log-tabexpand.sh @@ -37,7 +37,7 @@ count_expand () # 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 differene together with the + # 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 $*" diff --git a/t/t4254-am-corrupt.sh b/t/t4254-am-corrupt.sh index 168739c721..fd3bdbfe2c 100755 --- a/t/t4254-am-corrupt.sh +++ b/t/t4254-am-corrupt.sh @@ -25,7 +25,7 @@ test_expect_success setup ' # fatal: unable to write file '(null)' mode 100644: Bad address # Also, it had the unwanted side-effect of deleting f. test_expect_success 'try to apply corrupted patch' ' - test_must_fail git am bad-patch.diff 2>actual + test_must_fail git -c advice.amWorkDir=false am bad-patch.diff 2>actual ' test_expect_success 'compare diagnostic; ensure file is still here' ' diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 886b6953e4..2a97b27b0a 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 @@ -192,7 +192,7 @@ 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 --get extract/a/a >b.mtime && echo "1117231200" >expected.mtime && test_cmp expected.mtime b.mtime' @@ -390,7 +390,7 @@ test_expect_success TAR_HUGE,LONG_IS_64BIT 'system tar can read our huge size' ' test_cmp expect actual ' -test_expect_success LONG_IS_64BIT 'set up repository with far-future commit' ' +test_expect_success TIME_IS_64BIT 'set up repository with far-future commit' ' rm -f .git/index && echo content >file && git add file && @@ -398,11 +398,11 @@ test_expect_success LONG_IS_64BIT 'set up repository with far-future commit' ' git commit -m "tempori parendum" ' -test_expect_success LONG_IS_64BIT 'generate tar with future mtime' ' +test_expect_success TIME_IS_64BIT 'generate tar with future mtime' ' git archive HEAD >future.tar ' -test_expect_success TAR_HUGE,LONG_IS_64BIT 'system tar can read our future mtime' ' +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 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/t5004-archive-corner-cases.sh b/t/t5004-archive-corner-cases.sh index cca23383c5..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,14 @@ 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 @@ -155,4 +158,51 @@ test_expect_success ZIPINFO 'zip archive with many entries' ' 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 7171f67539..9690dcad4f 100755 --- a/t/t5100-mailinfo.sh +++ b/t/t5100-mailinfo.sh @@ -171,4 +171,46 @@ test_expect_success 'mailinfo with mailinfo.scissors config' ' ' +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/t5150-request-pull.sh b/t/t5150-request-pull.sh index 82c33b88e7..fca001eb9b 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/ @@ -81,7 +81,7 @@ test_expect_success 'setup: two scripts for reading pull requests' ' cat <<-EOT >fuzz.sed #!/bin/sed -nf s/$downstream_url_for_sed/URL/g - s/$_x40/OBJECT_NAME/g + s/$OID_REGEX/OBJECT_NAME/g s/A U Thor/AUTHOR/g s/[-0-9]\{10\} [:0-9]\{8\} [-+][0-9]\{4\}/DATE/g s/ [^ ].*/ SUBJECT/g @@ -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 43a672c345..2336d09dcc 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -16,8 +16,8 @@ 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) && @@ -311,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" @@ -358,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" @@ -421,6 +421,53 @@ test_expect_success 'index-pack <pack> works in non-repo' ' test_path_is_file foo.idx ' +test_expect_success 'index-pack --strict <pack> works in non-repo' ' + rm -f foo.idx && + nongit git index-pack --strict ../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 +' + +test_expect_success 'pack-objects in too-many-packs mode' ' + GIT_TEST_FULL_IN_PACK_ARRAY=1 git repack -ad && + git fsck +' + # # WARNING! # @@ -430,9 +477,11 @@ test_expect_success 'index-pack <pack> works in non-repo' ' test_expect_success \ 'fake a SHA1 hash collision' \ - 'test -f .git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67 && - cp -f .git/objects/9d/235ed07cd19811a6ceb342de82f190e49c9f68 \ - .git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67' + 'long_a=$(git hash-object a | sed -e "s!^..!&/!") && + long_b=$(git hash-object b | sed -e "s!^..!&/!") && + test -f .git/objects/$long_b && + cp -f .git/objects/$long_a \ + .git/objects/$long_b' test_expect_success \ 'make sure index-pack detects the SHA1 collision' \ diff --git a/t/t5301-sliding-window.sh b/t/t5301-sliding-window.sh index cae8c2e882..76f9798ab9 100755 --- a/t/t5301-sliding-window.sh +++ b/t/t5301-sliding-window.sh @@ -12,7 +12,7 @@ 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 && diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh index c2fc584dac..bb9b8bb309 100755 --- a/t/t5302-pack-index.sh +++ b/t/t5302-pack-index.sh @@ -15,17 +15,17 @@ test_expect_success \ 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 && + 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 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) && { @@ -262,4 +262,9 @@ EOF 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 5940ce2084..3634e258f8 100755 --- a/t/t5303-pack-corruption-resilience.sh +++ b/t/t5303-pack-corruption-resilience.sh @@ -19,14 +19,14 @@ 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() { diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh index 133b5842b1..f20f03c103 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 && @@ -283,4 +283,51 @@ 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_expect_success 'prune: handle expire option correctly' ' + test_must_fail git prune --expire 2>error && + test_i18ngrep "requires a value" error && + + test_must_fail git prune --expire=nyah 2>error && + test_i18ngrep "malformed expiration" error && + + git prune --no-expire +' + test_done diff --git a/t/t5308-pack-detect-duplicates.sh b/t/t5308-pack-detect-duplicates.sh index 9c5a8766ab..6845c1f3c3 100755 --- a/t/t5308-pack-detect-duplicates.sh +++ b/t/t5308-pack-detect-duplicates.sh @@ -4,6 +4,12 @@ test_description='handling of duplicate objects in incoming packfiles' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-pack.sh +if ! test_have_prereq SHA1 +then + skip_all='not using SHA-1 for objects' + test_done +fi + # The sha1s we have in our pack. It's important that these have the same # starting byte, so that they end up in the same fanout section of the index. # That lets us make sure we are exercising the binary search with both sets. @@ -56,20 +62,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/t5309-pack-delta-cycles.sh b/t/t5309-pack-delta-cycles.sh index 3e7861b075..491556dad9 100755 --- a/t/t5309-pack-delta-cycles.sh +++ b/t/t5309-pack-delta-cycles.sh @@ -4,6 +4,12 @@ test_description='test index-pack handling of delta cycles in packfiles' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-pack.sh +if ! test_have_prereq SHA1 +then + skip_all='not using SHA-1 for objects' + test_done +fi + # Two similar-ish objects that we have computed deltas between. A=01d7713666f4de822776c7622c10f1b07de280dc B=e68fe8129b546b101aee9510c5328e7f21ca1d18 diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh index 424bec7d77..2d22a17c4a 100755 --- a/t/t5310-pack-bitmaps.sh +++ b/t/t5310-pack-bitmaps.sh @@ -20,11 +20,13 @@ has_any () { } 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 && @@ -104,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 ' @@ -261,9 +264,9 @@ test_expect_success 'pack with missing parent' ' ' test_expect_success JGIT 'we can read jgit bitmaps' ' - git clone . compat-jgit && + git clone --bare . compat-jgit.git && ( - cd compat-jgit && + cd compat-jgit.git && rm -f .git/objects/pack/*.bitmap && jgit gc && git rev-list --test-bitmap HEAD @@ -271,9 +274,9 @@ test_expect_success JGIT 'we can read jgit bitmaps' ' ' test_expect_success JGIT 'jgit can read our bitmaps' ' - git clone . compat-us && + git clone --bare . compat-us.git && ( - cd compat-us && + cd compat-us.git && git repack -adb && # jgit gc will barf if it does not like our bitmaps jgit gc @@ -281,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 && @@ -289,4 +292,56 @@ 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_expect_success 'truncated bitmap fails gracefully' ' + git repack -ad && + git rev-list --use-bitmap-index --count --all >expect && + bitmap=$(ls .git/objects/pack/*.bitmap) && + test_when_finished "rm -f $bitmap" && + head -c 512 <$bitmap >$bitmap.tmp && + mv -f $bitmap.tmp $bitmap && + git rev-list --use-bitmap-index --count --all >actual 2>stderr && + test_cmp expect actual && + test_i18ngrep corrupt stderr +' + test_done diff --git a/t/t5313-pack-bounds-checks.sh b/t/t5313-pack-bounds-checks.sh index a8a587abc3..4fe4ad9d61 100755 --- a/t/t5313-pack-bounds-checks.sh +++ b/t/t5313-pack-bounds-checks.sh @@ -139,7 +139,13 @@ test_expect_success 'bogus offset into v2 extended table' ' 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). - do_pack "$object $(git rev-parse HEAD)" --index-version=2 && + # + # 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. @@ -157,8 +163,8 @@ test_expect_success 'bogus offset inside v2 extended table' ' test_expect_success 'bogus OFS_DELTA in packfile' ' # Generate a pack with a delta in it. - base=$(test-genrandom foo 3000 | git hash-object --stdin -w) && - delta=$(test-genrandom foo 2000 | git hash-object --stdin -w) && + 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/??/* && diff --git a/t/t5314-pack-cycle-detection.sh b/t/t5314-pack-cycle-detection.sh index f7dbdfb412..f31995d3d2 100755 --- a/t/t5314-pack-cycle-detection.sh +++ b/t/t5314-pack-cycle-detection.sh @@ -73,7 +73,7 @@ make_pack () { } test_expect_success 'setup' ' - test-genrandom base 4096 >base && + test-tool genrandom base 4096 >base && for i in one two do # we want shared content here to encourage deltas... 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/t5318-commit-graph.sh b/t/t5318-commit-graph.sh new file mode 100755 index 0000000000..77d85aefe7 --- /dev/null +++ b/t/t5318-commit-graph.sh @@ -0,0 +1,233 @@ +#!/bin/sh + +test_description='commit graph' +. ./test-lib.sh + +test_expect_success 'setup full repo' ' + mkdir full && + cd "$TRASH_DIRECTORY/full" && + git init && + git config core.commitGraph true && + objdir=".git/objects" +' + +test_expect_success 'write graph with no packs' ' + cd "$TRASH_DIRECTORY/full" && + git commit-graph write --object-dir . && + test_path_is_file info/commit-graph +' + +test_expect_success 'create commits and repack' ' + cd "$TRASH_DIRECTORY/full" && + for i in $(test_seq 3) + do + test_commit $i && + git branch commits/$i + done && + git repack +' + +graph_git_two_modes() { + git -c core.graph=true $1 >output + git -c core.graph=false $1 >expect + test_cmp output expect +} + +graph_git_behavior() { + MSG=$1 + DIR=$2 + BRANCH=$3 + COMPARE=$4 + test_expect_success "check normal git operations: $MSG" ' + cd "$TRASH_DIRECTORY/$DIR" && + graph_git_two_modes "log --oneline $BRANCH" && + graph_git_two_modes "log --topo-order $BRANCH" && + graph_git_two_modes "log --graph $COMPARE..$BRANCH" && + graph_git_two_modes "branch -vv" && + graph_git_two_modes "merge-base -a $BRANCH $COMPARE" + ' +} + +graph_git_behavior 'no graph' full commits/3 commits/1 + +graph_read_expect() { + OPTIONAL="" + NUM_CHUNKS=3 + if test ! -z $2 + then + OPTIONAL=" $2" + NUM_CHUNKS=$((3 + $(echo "$2" | wc -w))) + fi + cat >expect <<- EOF + header: 43475048 1 1 $NUM_CHUNKS 0 + num_commits: $1 + chunks: oid_fanout oid_lookup commit_metadata$OPTIONAL + EOF + git commit-graph read >output && + test_cmp expect output +} + +test_expect_success 'write graph' ' + cd "$TRASH_DIRECTORY/full" && + graph1=$(git commit-graph write) && + test_path_is_file $objdir/info/commit-graph && + graph_read_expect "3" +' + +graph_git_behavior 'graph exists' full commits/3 commits/1 + +test_expect_success 'Add more commits' ' + cd "$TRASH_DIRECTORY/full" && + git reset --hard commits/1 && + for i in $(test_seq 4 5) + do + test_commit $i && + git branch commits/$i + done && + git reset --hard commits/2 && + for i in $(test_seq 6 7) + do + test_commit $i && + git branch commits/$i + done && + git reset --hard commits/2 && + git merge commits/4 && + git branch merge/1 && + git reset --hard commits/4 && + git merge commits/6 && + git branch merge/2 && + git reset --hard commits/3 && + git merge commits/5 commits/7 && + git branch merge/3 && + git repack +' + +# Current graph structure: +# +# __M3___ +# / | \ +# 3 M1 5 M2 7 +# |/ \|/ \| +# 2 4 6 +# |___/____/ +# 1 + +test_expect_success 'write graph with merges' ' + cd "$TRASH_DIRECTORY/full" && + git commit-graph write && + test_path_is_file $objdir/info/commit-graph && + graph_read_expect "10" "large_edges" +' + +graph_git_behavior 'merge 1 vs 2' full merge/1 merge/2 +graph_git_behavior 'merge 1 vs 3' full merge/1 merge/3 +graph_git_behavior 'merge 2 vs 3' full merge/2 merge/3 + +test_expect_success 'Add one more commit' ' + cd "$TRASH_DIRECTORY/full" && + test_commit 8 && + git branch commits/8 && + ls $objdir/pack | grep idx >existing-idx && + git repack && + ls $objdir/pack| grep idx | grep -v --file=existing-idx >new-idx +' + +# Current graph structure: +# +# 8 +# | +# __M3___ +# / | \ +# 3 M1 5 M2 7 +# |/ \|/ \| +# 2 4 6 +# |___/____/ +# 1 + +graph_git_behavior 'mixed mode, commit 8 vs merge 1' full commits/8 merge/1 +graph_git_behavior 'mixed mode, commit 8 vs merge 2' full commits/8 merge/2 + +test_expect_success 'write graph with new commit' ' + cd "$TRASH_DIRECTORY/full" && + git commit-graph write && + test_path_is_file $objdir/info/commit-graph && + graph_read_expect "11" "large_edges" +' + +graph_git_behavior 'full graph, commit 8 vs merge 1' full commits/8 merge/1 +graph_git_behavior 'full graph, commit 8 vs merge 2' full commits/8 merge/2 + +test_expect_success 'write graph with nothing new' ' + cd "$TRASH_DIRECTORY/full" && + git commit-graph write && + test_path_is_file $objdir/info/commit-graph && + graph_read_expect "11" "large_edges" +' + +graph_git_behavior 'cleared graph, commit 8 vs merge 1' full commits/8 merge/1 +graph_git_behavior 'cleared graph, commit 8 vs merge 2' full commits/8 merge/2 + +test_expect_success 'build graph from latest pack with closure' ' + cd "$TRASH_DIRECTORY/full" && + cat new-idx | git commit-graph write --stdin-packs && + test_path_is_file $objdir/info/commit-graph && + graph_read_expect "9" "large_edges" +' + +graph_git_behavior 'graph from pack, commit 8 vs merge 1' full commits/8 merge/1 +graph_git_behavior 'graph from pack, commit 8 vs merge 2' full commits/8 merge/2 + +test_expect_success 'build graph from commits with closure' ' + cd "$TRASH_DIRECTORY/full" && + git tag -a -m "merge" tag/merge merge/2 && + git rev-parse tag/merge >commits-in && + git rev-parse merge/1 >>commits-in && + cat commits-in | git commit-graph write --stdin-commits && + test_path_is_file $objdir/info/commit-graph && + graph_read_expect "6" +' + +graph_git_behavior 'graph from commits, commit 8 vs merge 1' full commits/8 merge/1 +graph_git_behavior 'graph from commits, commit 8 vs merge 2' full commits/8 merge/2 + +test_expect_success 'build graph from commits with append' ' + cd "$TRASH_DIRECTORY/full" && + git rev-parse merge/3 | git commit-graph write --stdin-commits --append && + test_path_is_file $objdir/info/commit-graph && + graph_read_expect "10" "large_edges" +' + +graph_git_behavior 'append graph, commit 8 vs merge 1' full commits/8 merge/1 +graph_git_behavior 'append graph, commit 8 vs merge 2' full commits/8 merge/2 + +test_expect_success 'setup bare repo' ' + cd "$TRASH_DIRECTORY" && + git clone --bare --no-local full bare && + cd bare && + git config core.commitGraph true && + baredir="./objects" +' + +graph_git_behavior 'bare repo, commit 8 vs merge 1' bare commits/8 merge/1 +graph_git_behavior 'bare repo, commit 8 vs merge 2' bare commits/8 merge/2 + +test_expect_success 'write graph in bare repo' ' + cd "$TRASH_DIRECTORY/bare" && + git commit-graph write && + test_path_is_file $baredir/info/commit-graph && + graph_read_expect "11" "large_edges" +' + +graph_git_behavior 'bare repo with graph, commit 8 vs merge 1' bare commits/8 merge/1 +graph_git_behavior 'bare repo with graph, commit 8 vs merge 2' bare commits/8 merge/2 + +test_expect_success 'perform fast-forward merge in full repo' ' + cd "$TRASH_DIRECTORY/full" && + git checkout -b merge-5-to-8 commits/5 && + git merge commits/8 && + git show-ref -s merge-5-to-8 >output && + git show-ref -s commits/8 >expect && + test_cmp expect output +' + +test_done diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index 305ca7a930..911eae1bf7 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -180,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 && @@ -255,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/t5404-tracking-branches.sh b/t/t5404-tracking-branches.sh index 2b8c0bac7d..2762f420bc 100755 --- a/t/t5404-tracking-branches.sh +++ b/t/t5404-tracking-branches.sh @@ -56,7 +56,7 @@ test_expect_success 'deleted branches have their tracking branches removed' ' test_expect_success 'already deleted tracking branches ignored' ' git branch -d -r origin/b3 && git push origin :b3 >output 2>&1 && - ! grep error output + ! grep "^error: " output ' test_done diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index 5879ee44d7..ea6570e819 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -30,7 +30,7 @@ add () { test_tick && commit=$(echo "$text" | git commit-tree $tree $parents) && eval "$name=$commit; export $name" && - echo $commit > .git/refs/heads/$branch && + git update-ref "refs/heads/$branch" "$commit" && eval ${branch}TIP=$commit } @@ -45,10 +45,10 @@ pull_to_client () { case "$heads" in *A*) - echo $ATIP > .git/refs/heads/A;; + git update-ref refs/heads/A "$ATIP";; esac && case "$heads" in *B*) - echo $BTIP > .git/refs/heads/B;; + git update-ref refs/heads/B "$BTIP";; esac && git symbolic-ref HEAD refs/heads/$(echo $heads \ | sed -e "s/^\(.\).*$/\1/") && @@ -92,8 +92,8 @@ test_expect_success 'setup' ' cur=$(($cur+1)) done && add B1 $A1 && - echo $ATIP > .git/refs/heads/A && - echo $BTIP > .git/refs/heads/B && + git update-ref refs/heads/A "$ATIP" && + git update-ref refs/heads/B "$BTIP" && git symbolic-ref HEAD refs/heads/B ' @@ -482,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' ' @@ -588,6 +588,41 @@ test_expect_success 'fetch-pack can fetch a raw sha1' ' 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 @@ -717,6 +752,17 @@ test_expect_success 'fetch shallow since ...' ' test_cmp expected actual ' +test_expect_success 'clone shallow since selects no commits' ' + test_create_repo shallow-since-the-future && + ( + cd shallow-since-the-future && + 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 && + test_must_fail git clone --shallow-since "900000000 +0700" "file://$(pwd)/." ../shallow111 + ) +' + test_expect_success 'shallow clone exclude tag two' ' test_create_repo shallow-exclude && ( @@ -761,4 +807,67 @@ test_expect_success 'fetching deepen' ' ) ' +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 9b19cff729..49d3621a92 100755 --- a/t/t5504-fetch-receive-strict.sh +++ b/t/t5504-fetch-receive-strict.sh @@ -152,7 +152,7 @@ test_expect_success 'push with receive.fsck.missingEmail=warn' ' git --git-dir=dst/.git config --add \ receive.fsck.badDate warn && git push --porcelain dst bogus >act 2>&1 && - test_must_fail grep "missingEmail" act + ! grep "missingEmail" act ' test_expect_success \ diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 8198d8eb05..a6c0178f3a 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -153,6 +153,25 @@ test_expect_success 'remove errors out early when deleting non-existent branch' ) ' +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 && @@ -725,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/*" && @@ -764,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 diff --git a/t/t5509-fetch-push-namespaces.sh b/t/t5509-fetch-push-namespaces.sh index bc44ac36d5..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 && ( diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 668c54be41..e402aee6a2 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -63,7 +63,7 @@ test_expect_success "fetch test" ' git commit -a -m "updated by origin" && cd two && git fetch && - test -f .git/refs/heads/one && + git rev-parse --verify refs/heads/one && mine=$(git rev-parse refs/heads/one) && his=$(cd ../one && git rev-parse refs/heads/master) && test "z$mine" = "z$his" @@ -73,8 +73,8 @@ test_expect_success "fetch test for-merge" ' cd "$D" && cd three && git fetch && - test -f .git/refs/heads/two && - test -f .git/refs/heads/one && + git rev-parse --verify refs/heads/two && + git rev-parse --verify refs/heads/one && master_in_two=$(cd ../two && git rev-parse master) && one_in_two=$(cd ../two && git rev-parse one) && { @@ -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 ' @@ -543,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 && @@ -693,8 +840,8 @@ test_expect_success C_LOCALE_OUTPUT 'fetch aligned output' ' test_commit looooooooooooong-tag && ( cd full-output && - git -c fetch.output=full fetch origin 2>&1 | \ - grep -e "->" | cut -c 22- >../actual + git -c fetch.output=full fetch origin >actual 2>&1 && + grep -e "->" actual | cut -c 22- >../actual ) && cat >expect <<-\EOF && master -> origin/master @@ -708,8 +855,8 @@ test_expect_success C_LOCALE_OUTPUT 'fetch compact output' ' test_commit extraaa && ( cd compact && - git -c fetch.output=compact fetch origin 2>&1 | \ - grep -e "->" | cut -c 22- >../actual + git -c fetch.output=compact fetch origin >actual 2>&1 && + grep -e "->" actual | cut -c 22- >../actual ) && cat >expect <<-\EOF && master -> origin/* diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh index 55fc83fc06..6a949484d0 100755 --- a/t/t5512-ls-remote.sh +++ b/t/t5512-ls-remote.sh @@ -10,6 +10,9 @@ test_expect_success setup ' test_tick && git commit -m initial && git tag mark && + git tag mark1.1 && + git tag mark1.2 && + git tag mark1.10 && git show-ref --tags -d | sed -e "s/ / /" >expected.tag && ( echo "$(git rev-parse HEAD) HEAD" @@ -39,6 +42,39 @@ test_expect_success 'ls-remote self' ' test_cmp expected.all actual ' +test_expect_success 'ls-remote --sort="version:refname" --tags self' ' + cat >expect <<-EOF && + $(git rev-parse mark) refs/tags/mark + $(git rev-parse mark1.1) refs/tags/mark1.1 + $(git rev-parse mark1.2) refs/tags/mark1.2 + $(git rev-parse mark1.10) refs/tags/mark1.10 + EOF + git ls-remote --sort="version:refname" --tags self >actual && + test_cmp expect actual +' + +test_expect_success 'ls-remote --sort="-version:refname" --tags self' ' + cat >expect <<-EOF && + $(git rev-parse mark1.10) refs/tags/mark1.10 + $(git rev-parse mark1.2) refs/tags/mark1.2 + $(git rev-parse mark1.1) refs/tags/mark1.1 + $(git rev-parse mark) refs/tags/mark + EOF + git ls-remote --sort="-version:refname" --tags self >actual && + test_cmp expect actual +' + +test_expect_success 'ls-remote --sort="-refname" --tags self' ' + cat >expect <<-EOF && + $(git rev-parse mark1.2) refs/tags/mark1.2 + $(git rev-parse mark1.10) refs/tags/mark1.10 + $(git rev-parse mark1.1) refs/tags/mark1.1 + $(git rev-parse mark) refs/tags/mark + EOF + git ls-remote --sort="-refname" --tags self >actual && + test_cmp expect actual +' + test_expect_success 'dies when no remote specified and no default remotes found' ' test_must_fail git ls-remote ' @@ -85,8 +121,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,7 +141,7 @@ 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_must_fail git ls-remote "$does_not_exist" >actual 2>&1 && test_i18ncmp exp actual ' @@ -124,7 +167,7 @@ test_expect_success 'Report no-match with --exit-code' ' test_expect_success 'Report match with --exit-code' ' git ls-remote --exit-code other.git "refs/tags/*" >actual && - git ls-remote . tags/mark >expect && + git ls-remote . tags/mark* >expect && test_cmp expect actual ' @@ -164,13 +207,17 @@ test_expect_success 'overrides work between mixed transfer/upload-pack hideRefs' ' test_expect_success 'ls-remote --symref' ' - cat >expect <<-\EOF && + git fetch origin && + 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 + $(git rev-parse HEAD) HEAD + $(git rev-parse refs/heads/master) refs/heads/master + $(git rev-parse HEAD) refs/remotes/origin/HEAD + $(git rev-parse refs/remotes/origin/master) refs/remotes/origin/master + $(git rev-parse refs/tags/mark) refs/tags/mark + $(git rev-parse refs/tags/mark1.1) refs/tags/mark1.1 + $(git rev-parse refs/tags/mark1.10) refs/tags/mark1.10 + $(git rev-parse refs/tags/mark1.2) refs/tags/mark1.2 EOF git ls-remote --symref >actual && test_cmp expect actual @@ -248,4 +295,13 @@ test_expect_success PIPE,JGIT,GIT_DAEMON 'indicate no refs in standards-complian 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/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 26b2cafc47..a5077d8b7c 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -94,6 +94,9 @@ mk_child() { } check_push_result () { + test $# -ge 3 || + error "bug in the test script: check_push_result requires at least 3 parameters" + repo_name="$1" shift @@ -553,10 +556,7 @@ test_expect_success 'branch.*.pushremote config order is irrelevant' ' test_expect_success 'push with dry-run' ' mk_test testrepo heads/master && - ( - cd testrepo && - old_commit=$(git show-ref -s --verify refs/heads/master) - ) && + old_commit=$(git -C testrepo show-ref -s --verify refs/heads/master) && git push --dry-run testrepo : && check_push_result testrepo $old_commit heads/master ' @@ -612,7 +612,7 @@ test_expect_success 'push does not update local refs on failure' ' chmod +x testrepo/.git/hooks/pre-receive && ( cd child && - git pull .. master + git pull .. master && test_must_fail git push && test $(git rev-parse master) != \ $(git rev-parse remotes/origin/master) @@ -634,7 +634,7 @@ test_expect_success 'pushing valid refs triggers post-receive and post-update ho orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) && newmaster=$(git show-ref -s --verify refs/heads/master) && orgnext=$(cd testrepo && git show-ref -s --verify refs/heads/next) && - newnext=$_z40 && + newnext=$ZERO_OID && git push testrepo refs/heads/master:refs/heads/master :refs/heads/next && ( cd testrepo/.git && @@ -672,15 +672,15 @@ test_expect_success 'deleting dangling ref triggers hooks with correct args' ' ( cd testrepo/.git && cat >pre-receive.expect <<-EOF && - $_z40 $_z40 refs/heads/master + $ZERO_OID $ZERO_OID refs/heads/master EOF cat >update.expect <<-EOF && - refs/heads/master $_z40 $_z40 + refs/heads/master $ZERO_OID $ZERO_OID EOF cat >post-receive.expect <<-EOF && - $_z40 $_z40 refs/heads/master + $ZERO_OID $ZERO_OID refs/heads/master EOF cat >post-update.expect <<-EOF && @@ -703,12 +703,12 @@ test_expect_success 'deletion of a non-existent ref is not fed to post-receive a cd testrepo/.git && cat >pre-receive.expect <<-EOF && $orgmaster $newmaster refs/heads/master - $_z40 $_z40 refs/heads/nonexistent + $ZERO_OID $ZERO_OID refs/heads/nonexistent EOF cat >update.expect <<-EOF && refs/heads/master $orgmaster $newmaster - refs/heads/nonexistent $_z40 $_z40 + refs/heads/nonexistent $ZERO_OID $ZERO_OID EOF cat >post-receive.expect <<-EOF && @@ -732,11 +732,11 @@ test_expect_success 'deletion of a non-existent ref alone does trigger post-rece ( cd testrepo/.git && cat >pre-receive.expect <<-EOF && - $_z40 $_z40 refs/heads/nonexistent + $ZERO_OID $ZERO_OID refs/heads/nonexistent EOF cat >update.expect <<-EOF && - refs/heads/nonexistent $_z40 $_z40 + refs/heads/nonexistent $ZERO_OID $ZERO_OID EOF test_cmp pre-receive.expect pre-receive.actual && @@ -751,7 +751,7 @@ test_expect_success 'mixed ref updates, deletes, invalid deletes trigger hooks w orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) && newmaster=$(git show-ref -s --verify refs/heads/master) && orgnext=$(cd testrepo && git show-ref -s --verify refs/heads/next) && - newnext=$_z40 && + newnext=$ZERO_OID && orgpu=$(cd testrepo && git show-ref -s --verify refs/heads/pu) && newpu=$(git show-ref -s --verify refs/heads/master) && git push testrepo refs/heads/master:refs/heads/master \ @@ -763,14 +763,14 @@ test_expect_success 'mixed ref updates, deletes, invalid deletes trigger hooks w $orgmaster $newmaster refs/heads/master $orgnext $newnext refs/heads/next $orgpu $newpu refs/heads/pu - $_z40 $_z40 refs/heads/nonexistent + $ZERO_OID $ZERO_OID refs/heads/nonexistent EOF cat >update.expect <<-EOF && refs/heads/master $orgmaster $newmaster refs/heads/next $orgnext $newnext refs/heads/pu $orgpu $newpu - refs/heads/nonexistent $_z40 $_z40 + refs/heads/nonexistent $ZERO_OID $ZERO_OID EOF cat >post-receive.expect <<-EOF && @@ -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 @@ -1120,6 +1121,25 @@ test_expect_success 'fetch exact SHA1' ' ) ' +test_expect_success 'fetch exact SHA1 in protocol v2' ' + mk_test testrepo heads/master hidden/one && + git push testrepo master:refs/hidden/one && + git -C testrepo config transfer.hiderefs refs/hidden && + check_push_result testrepo $the_commit hidden/one && + + mk_child testrepo child && + git -C child config protocol.version 2 && + + # make sure $the_commit does not exist here + git -C child repack -a -d && + git -C child prune && + test_must_fail git -C child cat-file -t $the_commit && + + # fetching the hidden object succeeds by default + # NEEDSWORK: should this match the v0 behavior instead? + git -C child fetch -v ../testrepo $the_commit:refs/heads/copy +' + for configallowtipsha1inwant in true false do test_expect_success "shallow fetch reachable SHA1 (but not a ref), allowtipsha1inwant=$configallowtipsha1inwant" ' @@ -1417,7 +1437,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/t5520-pull.sh b/t/t5520-pull.sh index 17f4d0fe4e..59c4b778d3 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -272,6 +272,24 @@ test_expect_success '--rebase fast forward' ' 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 && @@ -287,7 +305,7 @@ test_expect_success '--rebase with conflicts shows advice' ' test_tick && git commit -m "Create conflict" seq.txt && test_must_fail git pull --rebase . seq 2>err >out && - test_i18ngrep "When you have resolved this problem" out + test_i18ngrep "Resolve all conflicts manually" out ' test_expect_success 'failed --rebase shows advice' ' @@ -301,7 +319,7 @@ test_expect_success 'failed --rebase shows advice' ' 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 "When you have resolved this problem" out + test_i18ngrep "Resolve all conflicts manually" out ' test_expect_success '--rebase fails with multiple branches' ' diff --git a/t/t5521-pull-options.sh b/t/t5521-pull-options.sh index ded8f98dbe..c19d8dbc9d 100755 --- a/t/t5521-pull-options.sh +++ b/t/t5521-pull-options.sh @@ -165,4 +165,49 @@ test_expect_success 'git pull --allow-unrelated-histories' ' ) ' +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/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh index f3b0a8d30a..9cc4b569c0 100755 --- a/t/t5526-fetch-submodules.sh +++ b/t/t5526-fetch-submodules.sh @@ -71,11 +71,21 @@ test_expect_success "fetch --recurse-submodules recurses into submodules" ' 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=$(pwd)/../trace.out git fetch --recurse-submodules -j2 2>../actual.err + 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 && @@ -183,7 +193,7 @@ 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_must_be_empty actual.out && @@ -208,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 && @@ -222,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 && @@ -302,7 +312,7 @@ 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 "Fetching submodule submodule" > ../expect.err.sub && echo "From $pwd/submodule" >> ../expect.err.sub && @@ -468,7 +478,47 @@ 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' ' @@ -520,4 +570,39 @@ test_expect_success 'fetching submodule into a broken repository' ' 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/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh index 207899a99f..3b0cb98422 100755 --- a/t/t5527-fetch-odd-refs.sh +++ b/t/t5527-fetch-odd-refs.sh @@ -27,7 +27,7 @@ test_expect_success 'suffix ref is ignored during fetch' ' ' test_expect_success 'try to create repo with absurdly long refname' ' - ref240=$_z40/$_z40/$_z40/$_z40/$_z40/$_z40 && + ref240=$ZERO_OID/$ZERO_OID/$ZERO_OID/$ZERO_OID/$ZERO_OID/$ZERO_OID && ref1440=$ref240/$ref240/$ref240/$ref240/$ref240/$ref240 && git init long && ( diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh index 1524ff5ba6..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 @@ -126,6 +126,27 @@ test_expect_success 'push succeeds if submodule commit not on remote but using o ) ' +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 && @@ -277,6 +298,16 @@ test_expect_success 'push succeeds if submodule commit disabling recursion from ) ' +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 && @@ -454,4 +485,100 @@ test_expect_success 'push --dry-run does not recursively update submodules' ' 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/t5533-push-cas.sh b/t/t5533-push-cas.sh index a2c9e7439f..d38ecee217 100755 --- a/t/t5533-push-cas.sh +++ b/t/t5533-push-cas.sh @@ -229,4 +229,33 @@ test_expect_success 'new branch already exists' ' ) ' +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 2e42cf3316..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": - test_i18ngrep -E '^(fatal|warning):' <error | grep -v 'hung up' >actual | sort && - test_i18ncmp 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/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh index d38bf32470..a2af693068 100755 --- a/t/t5541-http-push-smart.sh +++ b/t/t5541-http-push-smart.sh @@ -234,7 +234,7 @@ test_expect_success TTY 'push --no-progress silences progress but not status' ' test_commit no-progress && test_terminal git push --no-progress >output 2>&1 && test_i18ngrep "^To http" output && - test_i18ngrep ! "^Writing objects" + test_i18ngrep ! "^Writing objects" output ' test_expect_success 'push --progress shows progress to non-tty' ' @@ -377,5 +377,17 @@ test_expect_success 'push status output scrubs password' ' grep "^To $HTTPD_URL/smart/test_repo.git" status ' +test_expect_success 'colorize errors/hints' ' + cd "$ROOT_PATH"/test_repo_clone && + test_must_fail git -c color.transport=always -c color.advice=always \ + -c color.push=always \ + push origin origin/master^:master 2>act && + test_decode_color <act >decoded && + test_i18ngrep "<RED>.*rejected.*<RESET>" decoded && + test_i18ngrep "<RED>error: failed to push some refs" decoded && + test_i18ngrep "<YELLOW>hint: " decoded && + test_i18ngrep ! "^hint: " decoded +' + stop_httpd test_done diff --git a/t/t5545-push-options.sh b/t/t5545-push-options.sh index ea813b9383..b47a95871c 100755 --- a/t/t5545-push-options.sh +++ b/t/t5545-push-options.sh @@ -100,4 +100,184 @@ test_expect_success 'two push options work' ' 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 index 10cb0be2b7..0b0e987fdb 100755 --- a/t/t5546-receive-limits.sh +++ b/t/t5546-receive-limits.sh @@ -44,7 +44,7 @@ test_pack_input_limit () { } test_expect_success "create known-size (1024 bytes) commit" ' - test-genrandom foo 1024 >one-k && + test-tool genrandom foo 1024 >one-k && git add one-k && test_commit one-k ' diff --git a/t/t5547-push-quarantine.sh b/t/t5547-push-quarantine.sh index af9fcd833a..faaa51ccc5 100755 --- a/t/t5547-push-quarantine.sh +++ b/t/t5547-push-quarantine.sh @@ -39,7 +39,7 @@ test_expect_success 'push to repo path with path separator (colon)' ' # so make it likely for us to generate a delta by having # a non-trivial file with multiple versions. - test-genrandom foo 4096 >file.bin && + test-tool genrandom foo 4096 >file.bin && git add file.bin && git commit -m bin && @@ -58,4 +58,15 @@ test_expect_success 'push to repo path with path separator (colon)' ' 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 264a1ab8b0..6d7d88ccc9 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" \ @@ -159,6 +169,17 @@ test_expect_success 'fetch changes via manual http-fetch' ' test_cmp file clone2/file ' +test_expect_success 'manual http-fetch without -a works just as well' ' + cp -R clone-tmpl clone3 && + + HEAD=$(git rev-parse --verify HEAD) && + (cd clone3 && + git http-fetch -w heads/master-new $HEAD $(git config remote.origin.url) && + git checkout master-new && + test $HEAD = $(git rev-parse --verify HEAD)) && + test_cmp file clone3/file +' + test_expect_success 'http remote detects correct HEAD' ' git push public master:other && (cd clone && @@ -368,5 +389,24 @@ test_expect_success 'http-alternates cannot point at funny protocols' ' 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 test_done diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index a51b7e20d3..913089b144 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -26,14 +26,14 @@ setup_askpass_helper cat >exp <<EOF > GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 > Accept: */* -> Accept-Encoding: gzip +> Accept-Encoding: ENCODINGS > Pragma: no-cache < HTTP/1.1 200 OK < Pragma: no-cache < Cache-Control: no-cache, max-age=0, must-revalidate < Content-Type: application/x-git-upload-pack-advertisement > POST /smart/repo.git/git-upload-pack HTTP/1.1 -> Accept-Encoding: gzip +> Accept-Encoding: ENCODINGS > Content-Type: application/x-git-upload-pack-request > Accept: application/x-git-upload-pack-result > Content-Length: xxx @@ -79,8 +79,13 @@ test_expect_success 'clone http repository' ' /^< Date: /d /^< Content-Length: /d /^< Transfer-Encoding: /d - " >act && - test_cmp exp act + " >actual && + sed -e "s/^> Accept-Encoding: .*/> Accept-Encoding: ENCODINGS/" \ + actual >actual.smudged && + test_cmp exp actual.smudged && + + grep "Accept-Encoding:.*gzip" actual >actual.gzip && + test_line_count = 2 actual.gzip ' test_expect_success 'fetch changes via http' ' @@ -364,5 +369,38 @@ test_expect_success 'custom http headers' ' 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/t5561-http-backend.sh b/t/t5561-http-backend.sh index 90e0d6f0fe..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$// diff --git a/t/t5570-git-daemon.sh b/t/t5570-git-daemon.sh index 225a022e8a..0d4c52016b 100755 --- a/t/t5570-git-daemon.sh +++ b/t/t5570-git-daemon.sh @@ -50,7 +50,7 @@ test_expect_success 'no-op fetch -v stderr is as expected' ' ' test_expect_success 'no-op fetch without "-v" is quiet' ' - (cd clone && git fetch) 2>stderr && + (cd clone && git fetch 2>../stderr) && ! test -s stderr ' @@ -167,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 ba975bb355..ac53d63869 100755 --- a/t/t5571-pre-push-hook.sh +++ b/t/t5571-pre-push-hook.sh @@ -78,8 +78,8 @@ test_expect_success 'push to default' ' cat >expected <<EOF parent1 repo1 -refs/tags/one $COMMIT1 refs/tags/tag1 $_z40 -HEAD~ $COMMIT2 refs/heads/prev $_z40 +refs/tags/one $COMMIT1 refs/tags/tag1 $ZERO_OID +HEAD~ $COMMIT2 refs/heads/prev $ZERO_OID EOF test_expect_success 'push non-branches' ' @@ -90,7 +90,7 @@ test_expect_success 'push non-branches' ' cat >expected <<EOF parent1 repo1 -(delete) $_z40 refs/heads/prev $COMMIT2 +(delete) $ZERO_OID refs/heads/prev $COMMIT2 EOF test_expect_success 'push delete' ' @@ -101,7 +101,7 @@ test_expect_success 'push delete' ' cat >expected <<EOF repo1 repo1 -HEAD $COMMIT3 refs/heads/other $_z40 +HEAD $COMMIT3 refs/heads/other $ZERO_OID EOF test_expect_success 'push to URL' ' diff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh index accfa5cc0c..f916729a12 100755 --- a/t/t5572-pull-submodule.sh +++ b/t/t5572-pull-submodule.sh @@ -42,4 +42,115 @@ 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_expect_success 'branch has no merge base with remote-tracking counterpart' ' + rm -rf parent child && + + test_create_repo a-submodule && + test_commit -C a-submodule foo && + + test_create_repo parent && + git -C parent submodule add "$(pwd)/a-submodule" && + git -C parent commit -m foo && + + git clone parent child && + + # Reset master so that it has no merge base with + # refs/remotes/origin/master. + OTHER=$(git -C child commit-tree -m bar \ + $(git -C child rev-parse HEAD^{tree})) && + git -C child reset --hard "$OTHER" && + + git -C child pull --recurse-submodules --rebase +' + test_done diff --git a/t/t5573-pull-verify-signatures.sh b/t/t5573-pull-verify-signatures.sh new file mode 100755 index 0000000000..747775c147 --- /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 a433394200..0b62037744 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -151,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 ' @@ -306,21 +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' ' - cp "$GIT_BUILD_DIR/t/helper/test-fake-ssh$X" \ - "$TRASH_DIRECTORY/ssh-wrapper$X" && - GIT_SSH="$TRASH_DIRECTORY/ssh-wrapper$X" && - 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$X" "${1%$X}$X" && + 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" && - export GIT_SSH + test_when_finished "GIT_SSH=\"\$TRASH_DIRECTORY/ssh\$X\"" } expect_ssh () { @@ -344,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 @@ -362,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 ' @@ -386,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 @@ -524,4 +611,122 @@ test_expect_success 'GIT_TRACE_PACKFILE produces a usable pack' ' 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 index d5af758129..13b5e5eb9b 100755 --- a/t/t5603-clone-dirname.sh +++ b/t/t5603-clone-dirname.sh @@ -11,7 +11,9 @@ test_expect_success 'setup ssh wrapper' ' 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 ' diff --git a/t/t5608-clone-2gb.sh b/t/t5608-clone-2gb.sh index 191d6d3a78..df822d9a3e 100755 --- a/t/t5608-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/t5611-clone-config.sh b/t/t5611-clone-config.sh index e4850b778c..39329eb7a8 100755 --- a/t/t5611-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 && diff --git a/t/t5612-clone-refspec.sh b/t/t5612-clone-refspec.sh index 7ace2535c8..fac5a73851 100755 --- a/t/t5612-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/t5614-clone-submodules.sh b/t/t5614-clone-submodules-shallow.sh index a87d329656..e4e6ea4d52 100755 --- a/t/t5614-clone-submodules.sh +++ b/t/t5614-clone-submodules-shallow.sh @@ -71,7 +71,7 @@ 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 "recommed shallow for sub" && + git commit -m "recommend shallow for sub" && git clone --recurse-submodules --no-local "file://$pwd/." super_clone && ( cd super_clone && @@ -105,7 +105,7 @@ 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 "recommed non shallow for sub" && + git commit -m "recommend non shallow for sub" && git clone --recurse-submodules --no-local "file://$pwd/." super_clone && ( cd super_clone && diff --git a/t/t5615-alternate-env.sh b/t/t5615-alternate-env.sh index 26ebb0375d..b4905b822c 100755 --- a/t/t5615-alternate-env.sh +++ b/t/t5615-alternate-env.sh @@ -7,9 +7,9 @@ check_obj () { alt=$1; shift while read obj expect do - echo "$obj" >&3 && - echo "$obj $expect" >&4 - done 3>input 4>expect && + 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 && @@ -77,6 +77,7 @@ 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' ' 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-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/t5701-git-serve.sh b/t/t5701-git-serve.sh new file mode 100755 index 0000000000..75ec79e6cb --- /dev/null +++ b/t/t5701-git-serve.sh @@ -0,0 +1,211 @@ +#!/bin/sh + +test_description='test git-serve and server commands' + +. ./test-lib.sh + +test_expect_success 'test capability advertisement' ' + cat >expect <<-EOF && + version 2 + agent=git/$(git version | cut -d" " -f3) + ls-refs + fetch=shallow + server-option + 0000 + EOF + + git serve --advertise-capabilities >out && + test-pkt-line unpack <out >actual && + test_cmp actual expect +' + +test_expect_success 'stateless-rpc flag does not list capabilities' ' + # Empty request + test-pkt-line pack >in <<-EOF && + 0000 + EOF + git serve --stateless-rpc >out <in && + test_must_be_empty out && + + # EOF + git serve --stateless-rpc >out && + test_must_be_empty out +' + +test_expect_success 'request invalid capability' ' + test-pkt-line pack >in <<-EOF && + foobar + 0000 + EOF + test_must_fail git serve --stateless-rpc 2>err <in && + test_i18ngrep "unknown capability" err +' + +test_expect_success 'request with no command' ' + test-pkt-line pack >in <<-EOF && + agent=git/test + 0000 + EOF + test_must_fail git serve --stateless-rpc 2>err <in && + test_i18ngrep "no command requested" err +' + +test_expect_success 'request invalid command' ' + test-pkt-line pack >in <<-EOF && + command=foo + agent=git/test + 0000 + EOF + test_must_fail git serve --stateless-rpc 2>err <in && + test_i18ngrep "invalid command" err +' + +# Test the basics of ls-refs +# +test_expect_success 'setup some refs and tags' ' + test_commit one && + git branch dev master && + test_commit two && + git symbolic-ref refs/heads/release refs/heads/master && + git tag -a -m "annotated tag" annotated-tag +' + +test_expect_success 'basics of ls-refs' ' + test-pkt-line pack >in <<-EOF && + command=ls-refs + 0000 + EOF + + cat >expect <<-EOF && + $(git rev-parse HEAD) HEAD + $(git rev-parse refs/heads/dev) refs/heads/dev + $(git rev-parse refs/heads/master) refs/heads/master + $(git rev-parse refs/heads/release) refs/heads/release + $(git rev-parse refs/tags/annotated-tag) refs/tags/annotated-tag + $(git rev-parse refs/tags/one) refs/tags/one + $(git rev-parse refs/tags/two) refs/tags/two + 0000 + EOF + + git serve --stateless-rpc <in >out && + test-pkt-line unpack <out >actual && + test_cmp actual expect +' + +test_expect_success 'basic ref-prefixes' ' + test-pkt-line pack >in <<-EOF && + command=ls-refs + 0001 + ref-prefix refs/heads/master + ref-prefix refs/tags/one + 0000 + EOF + + cat >expect <<-EOF && + $(git rev-parse refs/heads/master) refs/heads/master + $(git rev-parse refs/tags/one) refs/tags/one + 0000 + EOF + + git serve --stateless-rpc <in >out && + test-pkt-line unpack <out >actual && + test_cmp actual expect +' + +test_expect_success 'refs/heads prefix' ' + test-pkt-line pack >in <<-EOF && + command=ls-refs + 0001 + ref-prefix refs/heads/ + 0000 + EOF + + cat >expect <<-EOF && + $(git rev-parse refs/heads/dev) refs/heads/dev + $(git rev-parse refs/heads/master) refs/heads/master + $(git rev-parse refs/heads/release) refs/heads/release + 0000 + EOF + + git serve --stateless-rpc <in >out && + test-pkt-line unpack <out >actual && + test_cmp actual expect +' + +test_expect_success 'peel parameter' ' + test-pkt-line pack >in <<-EOF && + command=ls-refs + 0001 + peel + ref-prefix refs/tags/ + 0000 + EOF + + cat >expect <<-EOF && + $(git rev-parse refs/tags/annotated-tag) refs/tags/annotated-tag peeled:$(git rev-parse refs/tags/annotated-tag^{}) + $(git rev-parse refs/tags/one) refs/tags/one + $(git rev-parse refs/tags/two) refs/tags/two + 0000 + EOF + + git serve --stateless-rpc <in >out && + test-pkt-line unpack <out >actual && + test_cmp actual expect +' + +test_expect_success 'symrefs parameter' ' + test-pkt-line pack >in <<-EOF && + command=ls-refs + 0001 + symrefs + ref-prefix refs/heads/ + 0000 + EOF + + cat >expect <<-EOF && + $(git rev-parse refs/heads/dev) refs/heads/dev + $(git rev-parse refs/heads/master) refs/heads/master + $(git rev-parse refs/heads/release) refs/heads/release symref-target:refs/heads/master + 0000 + EOF + + git serve --stateless-rpc <in >out && + test-pkt-line unpack <out >actual && + test_cmp actual expect +' + +test_expect_success 'sending server-options' ' + test-pkt-line pack >in <<-EOF && + command=ls-refs + server-option=hello + server-option=world + 0001 + ref-prefix HEAD + 0000 + EOF + + cat >expect <<-EOF && + $(git rev-parse HEAD) HEAD + 0000 + EOF + + git serve --stateless-rpc <in >out && + test-pkt-line unpack <out >actual && + test_cmp actual expect +' + +test_expect_success 'unexpected lines are not allowed in fetch request' ' + git init server && + + test-pkt-line pack >in <<-EOF && + command=fetch + 0001 + this-is-not-a-command + 0000 + EOF + + test_must_fail git -C server serve --stateless-rpc <in >/dev/null 2>err && + grep "unexpected line: .this-is-not-a-command." err +' + +test_done diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh new file mode 100755 index 0000000000..a4fe6508bd --- /dev/null +++ b/t/t5702-protocol-v2.sh @@ -0,0 +1,431 @@ +#!/bin/sh + +test_description='test git wire-protocol version 2' + +TEST_NO_CREATE_REPO=1 + +. ./test-lib.sh + +# Test protocol v2 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 'list refs with git:// using protocol v2' ' + test_when_finished "rm -f log" && + + GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \ + ls-remote --symref "$GIT_DAEMON_URL/parent" >actual && + + # Client requested to use protocol v2 + grep "git> .*\\\0\\\0version=2\\\0$" log && + # Server responded using protocol v2 + grep "git< version 2" log && + + git ls-remote --symref "$GIT_DAEMON_URL/parent" >expect && + test_cmp actual expect +' + +test_expect_success 'ref advertisment is filtered with ls-remote using protocol v2' ' + test_when_finished "rm -f log" && + + GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \ + ls-remote "$GIT_DAEMON_URL/parent" master >actual && + + cat >expect <<-EOF && + $(git -C "$daemon_parent" rev-parse refs/heads/master)$(printf "\t")refs/heads/master + EOF + + test_cmp actual expect +' + +test_expect_success 'clone with git:// using protocol v2' ' + test_when_finished "rm -f log" && + + GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \ + clone "$GIT_DAEMON_URL/parent" daemon_child && + + 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 v2 + grep "clone> .*\\\0\\\0version=2\\\0$" log && + # Server responded using protocol v2 + grep "clone< version 2" log +' + +test_expect_success 'fetch with git:// using protocol v2' ' + test_when_finished "rm -f log" && + + test_commit -C "$daemon_parent" two && + + GIT_TRACE_PACKET="$(pwd)/log" git -C daemon_child -c protocol.version=2 \ + fetch && + + 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 v2 + grep "fetch> .*\\\0\\\0version=2\\\0$" log && + # Server responded using protocol v2 + grep "fetch< version 2" log +' + +test_expect_success 'pull with git:// using protocol v2' ' + test_when_finished "rm -f log" && + + GIT_TRACE_PACKET="$(pwd)/log" git -C daemon_child -c protocol.version=2 \ + pull && + + 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 v2 + grep "fetch> .*\\\0\\\0version=2\\\0$" log && + # Server responded using protocol v2 + grep "fetch< version 2" log +' + +test_expect_success 'push with git:// and a config of v2 does not request v2' ' + test_when_finished "rm -f log" && + + # Till v2 for push is designed, make sure that if a client has + # protocol.version configured to use v2, that the client instead falls + # back and uses v0. + + 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="$(pwd)/log" git -C daemon_child -c protocol.version=2 \ + push origin HEAD:client_branch && + + 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 v2 + ! grep "push> .*\\\0\\\0version=2\\\0$" log && + # Server responded using protocol v2 + ! grep "push< version 2" log +' + +stop_git_daemon + +# Test protocol v2 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 'list refs with file:// using protocol v2' ' + test_when_finished "rm -f log" && + + GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \ + ls-remote --symref "file://$(pwd)/file_parent" >actual && + + # Server responded using protocol v2 + grep "git< version 2" log && + + git ls-remote --symref "file://$(pwd)/file_parent" >expect && + test_cmp actual expect +' + +test_expect_success 'ref advertisment is filtered with ls-remote using protocol v2' ' + test_when_finished "rm -f log" && + + GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \ + ls-remote "file://$(pwd)/file_parent" master >actual && + + cat >expect <<-EOF && + $(git -C file_parent rev-parse refs/heads/master)$(printf "\t")refs/heads/master + EOF + + test_cmp actual expect +' + +test_expect_success 'server-options are sent when using ls-remote' ' + test_when_finished "rm -f log" && + + GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \ + ls-remote -o hello -o world "file://$(pwd)/file_parent" master >actual && + + cat >expect <<-EOF && + $(git -C file_parent rev-parse refs/heads/master)$(printf "\t")refs/heads/master + EOF + + test_cmp actual expect && + grep "server-option=hello" log && + grep "server-option=world" log +' + + +test_expect_success 'clone with file:// using protocol v2' ' + test_when_finished "rm -f log" && + + GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \ + clone "file://$(pwd)/file_parent" file_child && + + 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 v2 + grep "clone< version 2" log +' + +test_expect_success 'fetch with file:// using protocol v2' ' + test_when_finished "rm -f log" && + + test_commit -C file_parent two && + + GIT_TRACE_PACKET="$(pwd)/log" git -C file_child -c protocol.version=2 \ + fetch origin && + + 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 v2 + grep "fetch< version 2" log +' + +test_expect_success 'ref advertisment is filtered during fetch using protocol v2' ' + test_when_finished "rm -f log" && + + test_commit -C file_parent three && + + GIT_TRACE_PACKET="$(pwd)/log" git -C file_child -c protocol.version=2 \ + fetch origin master && + + git -C file_child log -1 --format=%s origin/master >actual && + git -C file_parent log -1 --format=%s >expect && + test_cmp expect actual && + + ! grep "refs/tags/one" log && + ! grep "refs/tags/two" log && + ! grep "refs/tags/three" log +' + +test_expect_success 'server-options are sent when fetching' ' + test_when_finished "rm -f log" && + + test_commit -C file_parent four && + + GIT_TRACE_PACKET="$(pwd)/log" git -C file_child -c protocol.version=2 \ + fetch -o hello -o world origin master && + + git -C file_child log -1 --format=%s origin/master >actual && + git -C file_parent log -1 --format=%s >expect && + test_cmp expect actual && + + grep "server-option=hello" log && + grep "server-option=world" log +' + +test_expect_success 'upload-pack respects config using protocol v2' ' + git init server && + write_script server/.git/hook <<-\EOF && + touch hookout + "$@" + EOF + test_commit -C server one && + + test_config_global uploadpack.packobjectshook ./hook && + test_path_is_missing server/.git/hookout && + git -c protocol.version=2 clone "file://$(pwd)/server" client && + test_path_is_file server/.git/hookout +' + +test_expect_success 'setup filter tests' ' + rm -rf server client && + git init server && + + # 1 commit to create a file, and 1 commit to modify it + test_commit -C server message1 a.txt && + test_commit -C server message2 a.txt && + git -C server config protocol.version 2 && + git -C server config uploadpack.allowfilter 1 && + git -C server config uploadpack.allowanysha1inwant 1 && + git -C server config protocol.version 2 +' + +test_expect_success 'partial clone' ' + GIT_TRACE_PACKET="$(pwd)/trace" git -c protocol.version=2 \ + clone --filter=blob:none "file://$(pwd)/server" client && + grep "version 2" trace && + + # Ensure that the old version of the file is missing + git -C client rev-list master --quiet --objects --missing=print \ + >observed.oids && + grep "$(git -C server rev-parse message1:a.txt)" observed.oids && + + # Ensure that client passes fsck + git -C client fsck +' + +test_expect_success 'dynamically fetch missing object' ' + rm "$(pwd)/trace" && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client -c protocol.version=2 \ + cat-file -p $(git -C server rev-parse message1:a.txt) && + grep "version 2" trace +' + +test_expect_success 'partial fetch' ' + rm -rf client "$(pwd)/trace" && + git init client && + SERVER="file://$(pwd)/server" && + test_config -C client extensions.partialClone "$SERVER" && + + GIT_TRACE_PACKET="$(pwd)/trace" git -C client -c protocol.version=2 \ + fetch --filter=blob:none "$SERVER" master:refs/heads/other && + grep "version 2" trace && + + # Ensure that the old version of the file is missing + git -C client rev-list other --quiet --objects --missing=print \ + >observed.oids && + grep "$(git -C server rev-parse message1:a.txt)" observed.oids && + + # Ensure that client passes fsck + git -C client fsck +' + +test_expect_success 'do not advertise filter if not configured to do so' ' + SERVER="file://$(pwd)/server" && + + rm "$(pwd)/trace" && + git -C server config uploadpack.allowfilter 1 && + GIT_TRACE_PACKET="$(pwd)/trace" git -c protocol.version=2 \ + ls-remote "$SERVER" && + grep "fetch=.*filter" trace && + + rm "$(pwd)/trace" && + git -C server config uploadpack.allowfilter 0 && + GIT_TRACE_PACKET="$(pwd)/trace" git -c protocol.version=2 \ + ls-remote "$SERVER" && + grep "fetch=" trace >fetch_capabilities && + ! grep filter fetch_capabilities +' + +test_expect_success 'partial clone warns if filter is not advertised' ' + rm -rf client && + git -C server config uploadpack.allowfilter 0 && + git -c protocol.version=2 \ + clone --filter=blob:none "file://$(pwd)/server" client 2>err && + test_i18ngrep "filtering not recognized by server, ignoring" err +' + +test_expect_success 'even with handcrafted request, filter does not work if not advertised' ' + git -C server config uploadpack.allowfilter 0 && + + # Custom request that tries to filter even though it is not advertised. + test-pkt-line pack >in <<-EOF && + command=fetch + 0001 + want $(git -C server rev-parse master) + filter blob:none + 0000 + EOF + + test_must_fail git -C server serve --stateless-rpc <in >/dev/null 2>err && + grep "unexpected line: .filter blob:none." err && + + # Exercise to ensure that if advertised, filter works + git -C server config uploadpack.allowfilter 1 && + git -C server serve --stateless-rpc <in >/dev/null +' + +test_expect_success 'default refspec is used to filter ref when fetchcing' ' + test_when_finished "rm -f log" && + + GIT_TRACE_PACKET="$(pwd)/log" git -C file_child -c protocol.version=2 \ + fetch origin && + + git -C file_child log -1 --format=%s three >actual && + git -C file_parent log -1 --format=%s three >expect && + test_cmp expect actual && + + grep "ref-prefix refs/heads/" log && + grep "ref-prefix refs/tags/" log +' + +# Test protocol v2 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 v2' ' + test_when_finished "rm -f log" && + + GIT_TRACE_PACKET="$(pwd)/log" GIT_TRACE_CURL="$(pwd)/log" git -c protocol.version=2 \ + clone "$HTTPD_URL/smart/http_parent" http_child && + + 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 v2 + grep "Git-Protocol: version=2" log && + # Server responded using protocol v2 + grep "git< version 2" log +' + +test_expect_success 'fetch with http:// using protocol v2' ' + test_when_finished "rm -f log" && + + test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" two && + + GIT_TRACE_PACKET="$(pwd)/log" git -C http_child -c protocol.version=2 \ + fetch && + + 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 v2 + grep "git< version 2" log +' + +test_expect_success 'push with http:// and a config of v2 does not request v2' ' + test_when_finished "rm -f log" && + # Till v2 for push is designed, make sure that if a client has + # protocol.version configured to use v2, that the client instead falls + # back and uses v0. + + 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="$(pwd)/log" git -C http_child -c protocol.version=2 \ + push origin HEAD:client_branch && + + 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 && + + # Client didnt request to use protocol v2 + ! grep "Git-Protocol: version=2" log && + # Server didnt respond using protocol v2 + ! grep "git< version 2" log +' + + +stop_httpd + +test_done diff --git a/t/t5802-connect-helper.sh b/t/t5802-connect-helper.sh index b7a7f9d588..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 && diff --git a/t/t5812-proto-disable-http.sh b/t/t5812-proto-disable-http.sh index 044cc152f8..872788ac8c 100755 --- a/t/t5812-proto-disable-http.sh +++ b/t/t5812-proto-disable-http.sh @@ -20,15 +20,19 @@ 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 "ftp.*disabled" stderr || - test_i18ngrep "your curl version is too old" - } + 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/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh index 05ddc69cf2..7504ba4751 100755 --- a/t/t6001-rev-list-graft.sh +++ b/t/t6001-rev-list-graft.sh @@ -110,4 +110,13 @@ do " done + +test_expect_success 'show advice that grafts are deprecated' ' + git show HEAD 2>err && + test_i18ngrep "git replace" err && + test_config advice.graftFileDeprecated false && + git show HEAD 2>err && + test_i18ngrep ! "git replace" err +' + test_done diff --git a/t/t6002-rev-list-bisect.sh b/t/t6002-rev-list-bisect.sh index 3bf2759eae..a661408038 100755 --- a/t/t6002-rev-list-bisect.sh +++ b/t/t6002-rev-list-bisect.sh @@ -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 a1dcdb81d7..ec42c2f779 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,62 +174,83 @@ $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 && - has_color 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 '%C(auto,...) respects --color' ' - git log --format=$AUTO_COLOR -1 --color >actual && - has_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 '%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 "$desc enables colors for color.ui" ' + git -c color.ui=always log --format=$color -1 >actual && + has_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 -' + test_expect_success "$desc respects --color" ' + git log --format=$color -1 --color >actual && + has_color 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 && + 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 --color --format="%C(auto)%H" -1 >actual && - printf "\\033[33m%s\\033[m\\n" $(git rev-parse HEAD) >expect && + 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 ' @@ -235,6 +260,17 @@ test_expect_success '%C(auto) respects --no-color' ' test_cmp expect 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 Test printing of complex bodies @@ -411,8 +447,8 @@ test_expect_success '--abbrev' ' git log -1 --format="%h %h %h" HEAD >actual1 && git log -1 --abbrev=5 --format="%h %h %h" HEAD >actual2 && git log -1 --abbrev=5 --format="%H %H %H" HEAD >actual3 && - sed -e "s/$_x40/LONG/g" -e "s/$_x05/SHORT/g" <actual2 >fuzzy2 && - sed -e "s/$_x40/LONG/g" -e "s/$_x05/SHORT/g" <actual3 >fuzzy3 && + sed -e "s/$OID_REGEX/LONG/g" -e "s/$_x05/SHORT/g" <actual2 >fuzzy2 && + sed -e "s/$OID_REGEX/LONG/g" -e "s/$_x05/SHORT/g" <actual3 >fuzzy3 && test_cmp expect2 fuzzy2 && test_cmp expect3 fuzzy3 && ! test_cmp actual1 actual2 diff --git a/t/t6007-rev-list-cherry-pick-file.sh b/t/t6007-rev-list-cherry-pick-file.sh index 1408b608eb..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' ' diff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh index 31db7b5f91..aa2d360ce3 100755 --- a/t/t6010-merge-base.sh +++ b/t/t6010-merge-base.sh @@ -34,7 +34,7 @@ doit () { commit=$(echo $NAME | git commit-tree $T $PARENTS) && - echo $commit >.git/refs/tags/$NAME && + git update-ref "refs/tags/$NAME" "$commit" && echo $commit } diff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh index 2a0fbb87b1..b5a1190ffe 100755 --- a/t/t6012-rev-list-simplify.sh +++ b/t/t6012-rev-list-simplify.sh @@ -9,7 +9,7 @@ note () { } unnote () { - git name-rev --tags --stdin | sed -e "s|$_x40 (tags/\([^)]*\)) |\1 |g" + git name-rev --tags --stdin | sed -e "s|$OID_REGEX (tags/\([^)]*\)) |\1 |g" } test_expect_success setup ' 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 3c73c93ba6..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 381f35ed16..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 programmatically -# 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..b760c223c6 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,9 @@ 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 --get =1000000000 rename >expect && git merge merge-branch-1 && - test-chmtime -v +0 rename >actual && + test-tool chmtime --get rename >actual && test_cmp expect actual # "rename" should have stayed intact ' @@ -666,10 +667,9 @@ 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 --get =1000000000 df >expect && git merge merge-branch-2 && - test-chmtime -v +0 df >actual && + test-tool chmtime --get df >actual && test_cmp expect actual # "df" should have stayed intact ' @@ -698,10 +698,9 @@ 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 --get =1000000000 df >expect && git merge side && - test-chmtime -v +0 df >actual && + test-tool chmtime --get df >actual && test_cmp expect actual # "df" should have stayed intact ' @@ -728,10 +727,9 @@ 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 --get =1000000000 file >expect && test_must_fail git merge side && - test-chmtime -v +0 file >actual && + test-tool chmtime --get file >actual && test_cmp expect actual # "file" should have stayed intact ' @@ -757,10 +755,9 @@ 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 --get =1000000000 newfile >expect && git merge side && - test-chmtime -v +0 newfile >actual && + test-tool chmtime --get newfile >actual && test_cmp expect actual # "file" should have stayed intact ' diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 5e5370feb4..f84ff941c3 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -407,7 +407,7 @@ test_expect_success 'good merge base when good and bad are siblings' ' 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 ' @@ -894,4 +894,21 @@ test_expect_success 'bisect start takes options and revs in any order' ' 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/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh index 18aa88b5c0..b5621303d6 100755 --- a/t/t6036-recursive-corner-cases.sh +++ b/t/t6036-recursive-corner-cases.sh @@ -4,12 +4,6 @@ test_description='recursive merge corner cases involving criss-cross merges' . ./test-lib.sh -get_clean_checkout () { - git reset --hard && - git clean -fdqx && - git checkout "$1" -} - # # L1 L2 # o---o @@ -21,51 +15,66 @@ get_clean_checkout () { # test_expect_success 'setup basic criss-cross + rename with no modifications' ' - ten="0 1 2 3 4 5 6 7 8 9" && - for i in $ten - do - echo line $i in a sample file - done >one && - for i in $ten - do - echo line $i in another sample file - done >two && - git add one two && - test_tick && git commit -m initial && - - git branch L1 && - git checkout -b R1 && - git mv one three && - test_tick && git commit -m R1 && - - git checkout L1 && - git mv two three && - test_tick && git commit -m L1 && - - git checkout L1^0 && - test_tick && git merge -s ours R1 && - git tag L2 && - - git checkout R1^0 && - test_tick && git merge -s ours L1 && - git tag R2 + test_create_repo basic-rename && + ( + cd basic-rename && + + ten="0 1 2 3 4 5 6 7 8 9" && + for i in $ten + do + echo line $i in a sample file + done >one && + for i in $ten + do + echo line $i in another sample file + done >two && + git add one two && + test_tick && git commit -m initial && + + git branch L1 && + git checkout -b R1 && + git mv one three && + test_tick && git commit -m R1 && + + git checkout L1 && + git mv two three && + test_tick && git commit -m L1 && + + git checkout L1^0 && + test_tick && git merge -s ours R1 && + git tag L2 && + + git checkout R1^0 && + test_tick && git merge -s ours L1 && + git tag R2 + ) ' test_expect_success 'merge simple rename+criss-cross with no modifications' ' - git reset --hard && - git checkout L2^0 && - - test_must_fail git merge -s recursive R2^0 && - - test 2 = $(git ls-files -s | wc -l) && - test 2 = $(git ls-files -u | wc -l) && - test 2 = $(git ls-files -o | wc -l) && - - test $(git rev-parse :2:three) = $(git rev-parse L2:three) && - test $(git rev-parse :3:three) = $(git rev-parse R2:three) && - - test $(git rev-parse L2:three) = $(git hash-object three~HEAD) && - test $(git rev-parse R2:three) = $(git hash-object three~R2^0) + ( + cd basic-rename && + + git reset --hard && + git checkout L2^0 && + + test_must_fail git merge -s recursive R2^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 3 out && + + git rev-parse >expect \ + L2:three R2:three \ + L2:three R2:three && + git rev-parse >actual \ + :2:three :3:three && + git hash-object >>actual \ + three~HEAD three~R2^0 + test_cmp expect actual + ) ' # @@ -81,58 +90,67 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' ' # test_expect_success 'setup criss-cross + rename merges with basic modification' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - ten="0 1 2 3 4 5 6 7 8 9" && - for i in $ten - do - echo line $i in a sample file - done >one && - for i in $ten - do - echo line $i in another sample file - done >two && - git add one two && - test_tick && git commit -m initial && - - git branch L1 && - git checkout -b R1 && - git mv one three && - echo more >>two && - git add two && - test_tick && git commit -m R1 && - - git checkout L1 && - git mv two three && - test_tick && git commit -m L1 && - - git checkout L1^0 && - test_tick && git merge -s ours R1 && - git tag L2 && - - git checkout R1^0 && - test_tick && git merge -s ours L1 && - git tag R2 + test_create_repo rename-modify && + ( + cd rename-modify && + + ten="0 1 2 3 4 5 6 7 8 9" && + for i in $ten + do + echo line $i in a sample file + done >one && + for i in $ten + do + echo line $i in another sample file + done >two && + git add one two && + test_tick && git commit -m initial && + + git branch L1 && + git checkout -b R1 && + git mv one three && + echo more >>two && + git add two && + test_tick && git commit -m R1 && + + git checkout L1 && + git mv two three && + test_tick && git commit -m L1 && + + git checkout L1^0 && + test_tick && git merge -s ours R1 && + git tag L2 && + + git checkout R1^0 && + test_tick && git merge -s ours L1 && + git tag R2 + ) ' test_expect_success 'merge criss-cross + rename merges with basic modification' ' - git reset --hard && - git checkout L2^0 && - - test_must_fail git merge -s recursive R2^0 && - - test 2 = $(git ls-files -s | wc -l) && - test 2 = $(git ls-files -u | wc -l) && - test 2 = $(git ls-files -o | wc -l) && - - test $(git rev-parse :2:three) = $(git rev-parse L2:three) && - test $(git rev-parse :3:three) = $(git rev-parse R2:three) && - - test $(git rev-parse L2:three) = $(git hash-object three~HEAD) && - test $(git rev-parse R2:three) = $(git hash-object three~R2^0) + ( + cd rename-modify && + + git checkout L2^0 && + + test_must_fail git merge -s recursive R2^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 3 out && + + git rev-parse >expect \ + L2:three R2:three \ + L2:three R2:three && + git rev-parse >actual \ + :2:three :3:three && + git hash-object >>actual \ + three~HEAD three~R2^0 + test_cmp expect actual + ) ' # @@ -156,64 +174,74 @@ test_expect_success 'merge criss-cross + rename merges with basic modification' # test_expect_success 'setup differently handled merges of rename/add conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n" >a && - git add a && - test_tick && git commit -m A && - - git branch B && - git checkout -b C && - echo 10 >>a && - echo "other content" >>new_a && - git add a new_a && - test_tick && git commit -m C && - - git checkout B && - git mv a new_a && - test_tick && git commit -m B && - - git checkout B^0 && - test_must_fail git merge C && - git clean -f && - test_tick && git commit -m D && - git tag D && - - git checkout C^0 && - test_must_fail git merge B && - rm new_a~HEAD new_a && - printf "Incorrectly merged content" >>new_a && - git add -u && - test_tick && git commit -m E && - git tag E + test_create_repo rename-add && + ( + cd rename-add && + + printf "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n" >a && + git add a && + test_tick && git commit -m A && + + git branch B && + git checkout -b C && + echo 10 >>a && + echo "other content" >>new_a && + git add a new_a && + test_tick && git commit -m C && + + git checkout B && + git mv a new_a && + test_tick && git commit -m B && + + git checkout B^0 && + test_must_fail git merge C && + git clean -f && + test_tick && git commit -m D && + git tag D && + + git checkout C^0 && + test_must_fail git merge B && + rm new_a~HEAD new_a && + printf "Incorrectly merged content" >>new_a && + git add -u && + test_tick && git commit -m E && + git tag E + ) ' test_expect_success 'git detects differently handled merges conflict' ' - git reset --hard && - git checkout D^0 && - - test_must_fail git merge -s recursive E^0 && - - test 3 = $(git ls-files -s | wc -l) && - test 3 = $(git ls-files -u | wc -l) && - test 0 = $(git ls-files -o | wc -l) && - - test $(git rev-parse :2:new_a) = $(git rev-parse D:new_a) && - test $(git rev-parse :3:new_a) = $(git rev-parse E:new_a) && - - git cat-file -p B:new_a >>merged && - git cat-file -p C:new_a >>merge-me && - >empty && - test_must_fail git merge-file \ - -L "Temporary merge branch 2" \ - -L "" \ - -L "Temporary merge branch 1" \ - merged empty merge-me && - sed -e "s/^\([<=>]\)/\1\1\1/" merged >merged-internal && - test $(git rev-parse :1:new_a) = $(git hash-object merged-internal) + ( + cd rename-add && + + git checkout D^0 && + + test_must_fail git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + D:new_a E:new_a && + git rev-parse >actual \ + :2:new_a :3:new_a && + test_cmp expect actual + + git cat-file -p B:new_a >ours && + git cat-file -p C:new_a >theirs && + >empty && + test_must_fail git merge-file \ + -L "Temporary merge branch 2" \ + -L "" \ + -L "Temporary merge branch 1" \ + ours empty theirs && + sed -e "s/^\([<=>]\)/\1\1\1/" ours >expect && + git cat-file -p :1:new_a >actual && + test_cmp expect actual + ) ' # @@ -236,67 +264,85 @@ test_expect_success 'git detects differently handled merges conflict' ' # Merging commits D & E should result in modify/delete conflict. test_expect_success 'setup criss-cross + modify/delete resolved differently' ' - git rm -rf . && - 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 && - git rm 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 B >file && - git add file && - test_tick && - git commit -m D && - git tag D && - - git checkout C^0 && - test_must_fail git merge B && - git rm file && - test_tick && - git commit -m E && - git tag E + test_create_repo modify-delete && + ( + cd modify-delete && + + echo A >file && + git add file && + test_tick && + git commit -m A && + + git branch B && + git checkout -b C && + git rm 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 B >file && + git add file && + test_tick && + git commit -m D && + git tag D && + + git checkout C^0 && + test_must_fail git merge B && + git rm file && + test_tick && + git commit -m E && + git tag E + ) ' test_expect_success 'git detects conflict merging criss-cross+modify/delete' ' - git checkout D^0 && + ( + cd modify-delete && + + git checkout D^0 && - test_must_fail git merge -s recursive E^0 && + test_must_fail git merge -s recursive E^0 && - test 2 -eq $(git ls-files -s | wc -l) && - test 2 -eq $(git ls-files -u | wc -l) && + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && - test $(git rev-parse :1:file) = $(git rev-parse master:file) && - test $(git rev-parse :2:file) = $(git rev-parse B:file) + git rev-parse >expect \ + master:file B:file && + git rev-parse >actual \ + :1:file :2:file && + test_cmp expect actual + ) ' test_expect_success 'git detects conflict merging criss-cross+modify/delete, reverse direction' ' - git reset --hard && - git checkout E^0 && + ( + cd modify-delete && - test_must_fail git merge -s recursive D^0 && + git reset --hard && + git checkout E^0 && - test 2 -eq $(git ls-files -s | wc -l) && - test 2 -eq $(git ls-files -u | wc -l) && + test_must_fail git merge -s recursive D^0 && - test $(git rev-parse :1:file) = $(git rev-parse master:file) && - test $(git rev-parse :3:file) = $(git rev-parse B:file) + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + + git rev-parse >expect \ + master:file B:file && + git rev-parse >actual \ + :1:file :3:file && + test_cmp expect actual + ) ' # @@ -336,120 +382,164 @@ test_expect_success 'git detects conflict merging criss-cross+modify/delete, rev # test_expect_success 'setup differently handled merges of directory/file conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - >ignore-me && - git add ignore-me && - test_tick && - git commit -m A && - git tag A && - - git branch B && - git checkout -b C && - mkdir a && - echo 10 >a/file && - git add a/file && - test_tick && - git commit -m C && - - git checkout B && - echo 5 >a && - git add a && - test_tick && - git commit -m B && - - git checkout B^0 && - test_must_fail git merge C && - git clean -f && - rm -rf a/ && - echo 5 >a && - git add a && - test_tick && - git commit -m D && - git tag D && - - git checkout C^0 && - test_must_fail git merge B && - git clean -f && - git rm --cached a && - echo 10 >a/file && - git add a/file && - test_tick && - git commit -m E1 && - git tag E1 && - - git checkout C^0 && - test_must_fail git merge B && - git clean -f && - git rm --cached a && - printf "10\n11\n" >a/file && - git add a/file && - test_tick && - git commit -m E2 && - git tag E2 + test_create_repo directory-file && + ( + cd directory-file && + + >ignore-me && + git add ignore-me && + test_tick && + git commit -m A && + git tag A && + + git branch B && + git checkout -b C && + mkdir a && + echo 10 >a/file && + git add a/file && + test_tick && + git commit -m C && + + git checkout B && + echo 5 >a && + git add a && + test_tick && + git commit -m B && + + git checkout B^0 && + test_must_fail git merge C && + git clean -f && + rm -rf a/ && + echo 5 >a && + git add a && + test_tick && + git commit -m D && + git tag D && + + git checkout C^0 && + test_must_fail git merge B && + git clean -f && + git rm --cached a && + echo 10 >a/file && + git add a/file && + test_tick && + git commit -m E1 && + git tag E1 && + + git checkout C^0 && + test_must_fail git merge B && + git clean -f && + git rm --cached a && + printf "10\n11\n" >a/file && + git add a/file && + test_tick && + git commit -m E2 && + git tag E2 + ) ' test_expect_success 'merge of D & E1 fails but has appropriate contents' ' - get_clean_checkout D^0 && - - test_must_fail git merge -s recursive E1^0 && - - test 2 -eq $(git ls-files -s | wc -l) && - test 1 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) && - test $(git rev-parse :2:a) = $(git rev-parse B:a) + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && + + git checkout D^0 && + + test_must_fail git merge -s recursive E1^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + A:ignore-me B:a && + git rev-parse >actual \ + :0:ignore-me :2:a && + test_cmp expect actual + ) ' test_expect_success 'merge of E1 & D fails but has appropriate contents' ' - get_clean_checkout E1^0 && - - test_must_fail git merge -s recursive D^0 && - - test 2 -eq $(git ls-files -s | wc -l) && - test 1 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) && - test $(git rev-parse :3:a) = $(git rev-parse B:a) + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && + + git checkout E1^0 && + + test_must_fail git merge -s recursive D^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + A:ignore-me B:a && + git rev-parse >actual \ + :0:ignore-me :3:a && + test_cmp expect actual + ) ' test_expect_success 'merge of D & E2 fails but has appropriate contents' ' - get_clean_checkout D^0 && - - test_must_fail git merge -s recursive E2^0 && - - test 4 -eq $(git ls-files -s | wc -l) && - test 3 -eq $(git ls-files -u | wc -l) && - test 1 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse :2:a) = $(git rev-parse B:a) && - test $(git rev-parse :3:a/file) = $(git rev-parse E2:a/file) && - test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file) && - test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) && - - test -f a~HEAD + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && + + git checkout D^0 && + + test_must_fail git merge -s recursive E2^0 && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 2 out && + + git rev-parse >expect \ + B:a E2:a/file c:a/file A:ignore-me && + git rev-parse >actual \ + :2:a :3:a/file :1:a/file :0:ignore-me && + test_cmp expect actual + + test_path_is_file a~HEAD + ) ' test_expect_success 'merge of E2 & D fails but has appropriate contents' ' - get_clean_checkout E2^0 && - - test_must_fail git merge -s recursive D^0 && - - test 4 -eq $(git ls-files -s | wc -l) && - test 3 -eq $(git ls-files -u | wc -l) && - test 1 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse :3:a) = $(git rev-parse B:a) && - test $(git rev-parse :2:a/file) = $(git rev-parse E2:a/file) && - test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file) && - test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) && - - test -f a~D^0 + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && + + git checkout E2^0 && + + test_must_fail git merge -s recursive D^0 && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 2 out && + + git rev-parse >expect \ + B:a E2:a/file c:a/file A:ignore-me && + git rev-parse >actual \ + :3:a :2:a/file :1:a/file :0:ignore-me && + test_cmp expect actual + + test_path_is_file a~D^0 + ) ' # @@ -492,52 +582,58 @@ test_expect_success 'merge of E2 & D fails but has appropriate contents' ' # but that may cancel out at the final merge stage". test_expect_success 'setup rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' ' - git reset --hard && - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n6\n" >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - echo 7 >>b && - git add -u && - git commit -m B && - - git checkout -b C A && - git mv a c && - git commit -m C && - - git checkout -q B^0 && - git merge --no-commit -s ours C^0 && - git mv b newname && - git commit -m "Merge commit C^0 into HEAD" && - git tag D && - - git checkout -q C^0 && - git merge --no-commit -s ours B^0 && - git mv c newname && - printf "7\n8\n" >>newname && - git add -u && - git commit -m "Merge commit B^0 into HEAD" && - git tag E + test_create_repo rename-squared-squared && + ( + cd rename-squared-squared && + + printf "1\n2\n3\n4\n5\n6\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + echo 7 >>b && + git add -u && + git commit -m B && + + git checkout -b C A && + git mv a c && + git commit -m C && + + git checkout -q B^0 && + git merge --no-commit -s ours C^0 && + git mv b newname && + git commit -m "Merge commit C^0 into HEAD" && + git tag D && + + git checkout -q C^0 && + git merge --no-commit -s ours B^0 && + git mv c newname && + printf "7\n8\n" >>newname && + git add -u && + git commit -m "Merge commit B^0 into HEAD" && + git tag E + ) ' test_expect_success 'handle rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' ' - git checkout D^0 && + ( + cd rename-squared-squared && + + git checkout D^0 && - git merge -s recursive E^0 && + git merge -s recursive E^0 && - test 1 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && + git ls-files -s >out && + test_line_count = 1 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && - test $(git rev-parse HEAD:newname) = $(git rev-parse E:newname) + test $(git rev-parse HEAD:newname) = $(git rev-parse E:newname) + ) ' # @@ -562,59 +658,72 @@ test_expect_success 'handle rename/rename(1to2)/modify followed by what looks li # renaming carefully (both in the virtual merge base and later), and getting # content merge handled. -test_expect_success 'setup criss-cross + rename/rename/add + modify/modify' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "lots\nof\nwords\nand\ncontent\n" >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - git commit -m B && - - git checkout -b C A && - git mv a c && - printf "2\n3\n4\n5\n6\n7\n" >a && - git add a && - git commit -m C && - - git checkout B^0 && - git merge --no-commit -s ours C^0 && - git checkout C -- a c && - mv a old_a && - echo 1 >a && - cat old_a >>a && - rm old_a && - git add -u && - git commit -m "Merge commit C^0 into HEAD" && - git tag D && - - git checkout C^0 && - git merge --no-commit -s ours B^0 && - git checkout B -- b && - echo 8 >>a && - git add -u && - git commit -m "Merge commit B^0 into HEAD" && - git tag E +test_expect_success 'setup criss-cross + rename/rename/add-source + modify/modify' ' + test_create_repo rename-rename-add-source && + ( + cd rename-rename-add-source && + + printf "lots\nof\nwords\nand\ncontent\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + git commit -m B && + + git checkout -b C A && + git mv a c && + printf "2\n3\n4\n5\n6\n7\n" >a && + git add a && + git commit -m C && + + git checkout B^0 && + git merge --no-commit -s ours C^0 && + git checkout C -- a c && + mv a old_a && + echo 1 >a && + cat old_a >>a && + rm old_a && + git add -u && + git commit -m "Merge commit C^0 into HEAD" && + git tag D && + + git checkout C^0 && + git merge --no-commit -s ours B^0 && + git checkout B -- b && + echo 8 >>a && + git add -u && + git commit -m "Merge commit B^0 into HEAD" && + git tag E + ) ' test_expect_failure 'detect rename/rename/add-source for virtual merge-base' ' - git checkout D^0 && - - git merge -s recursive E^0 && - - test 3 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse HEAD:b) = $(git rev-parse A:a) && - test $(git rev-parse HEAD:c) = $(git rev-parse A:a) && - test "$(cat a)" = "$(printf "1\n2\n3\n4\n5\n6\n7\n8\n")" + ( + cd rename-rename-add-source && + + git checkout D^0 && + + git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + printf "1\n2\n3\n4\n5\n6\n7\n8\n" >correct && + git rev-parse >expect \ + A:a A:a \ + correct && + git rev-parse >actual \ + :0:b :0:c && + git hash-object >>actual \ + a && + test_cmp expect actual + ) ' # @@ -638,52 +747,62 @@ test_expect_failure 'detect rename/rename/add-source for virtual merge-base' ' # base of B & C needs to not delete B:c for that to work, though... test_expect_success 'setup criss-cross+rename/rename/add-dest + simple modify' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - printf "1\n2\n3\n4\n5\n6\n7\n" >c && - git add c && - git commit -m B && - - git checkout -b C A && - git mv a c && - git commit -m C && - - git checkout B^0 && - git merge --no-commit -s ours C^0 && - git mv b a && - git commit -m "D is like B but renames b back to a" && - git tag D && - - git checkout B^0 && - git merge --no-commit -s ours C^0 && - git mv b a && - echo 8 >>c && - git add c && - git commit -m "E like D but has mod in c" && - git tag E + test_create_repo rename-rename-add-dest && + ( + cd rename-rename-add-dest && + + >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + printf "1\n2\n3\n4\n5\n6\n7\n" >c && + git add c && + git commit -m B && + + git checkout -b C A && + git mv a c && + git commit -m C && + + git checkout B^0 && + git merge --no-commit -s ours C^0 && + git mv b a && + git commit -m "D is like B but renames b back to a" && + git tag D && + + git checkout B^0 && + git merge --no-commit -s ours C^0 && + git mv b a && + echo 8 >>c && + git add c && + git commit -m "E like D but has mod in c" && + git tag E + ) ' test_expect_success 'virtual merge base handles rename/rename(1to2)/add-dest' ' - git checkout D^0 && - - git merge -s recursive E^0 && - - test 2 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse HEAD:a) = $(git rev-parse A:a) && - test $(git rev-parse HEAD:c) = $(git rev-parse E:c) + ( + cd rename-rename-add-dest && + + git checkout D^0 && + + git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + A:a E:c && + git rev-parse >actual \ + :0:a :0:c && + test_cmp expect actual + ) ' test_done 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/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/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh index 411550d2b6..1cbd946fc2 100755 --- a/t/t6042-merge-rename-corner-cases.sh +++ b/t/t6042-merge-rename-corner-cases.sh @@ -6,31 +6,40 @@ test_description="recursive merge corner cases w/ renames but not criss-crosses" . ./test-lib.sh test_expect_success 'setup rename/delete + untracked file' ' - echo "A pretty inscription" >ring && - git add ring && - test_tick && - git commit -m beginning && - - git branch people && - git checkout -b rename-the-ring && - git mv ring one-ring-to-rule-them-all && - test_tick && - git commit -m fullname && - - git checkout people && - git rm ring && - echo gollum >owner && - git add owner && - test_tick && - git commit -m track-people-instead-of-objects && - echo "Myyy PRECIOUSSS" >ring + test_create_repo rename-delete-untracked && + ( + cd rename-delete-untracked && + + echo "A pretty inscription" >ring && + git add ring && + test_tick && + git commit -m beginning && + + git branch people && + git checkout -b rename-the-ring && + git mv ring one-ring-to-rule-them-all && + test_tick && + git commit -m fullname && + + git checkout people && + git rm ring && + echo gollum >owner && + git add owner && + test_tick && + git commit -m track-people-instead-of-objects && + echo "Myyy PRECIOUSSS" >ring + ) ' test_expect_success "Does git preserve Gollum's precious artifact?" ' - test_must_fail git merge -s recursive rename-the-ring && + ( + cd rename-delete-untracked && - # Make sure git did not delete an untracked file - test -f ring + test_must_fail git merge -s recursive rename-the-ring && + + # Make sure git did not delete an untracked file + test_path_is_file ring + ) ' # Testcase setup for rename/modify/add-source: @@ -41,96 +50,125 @@ test_expect_success "Does git preserve Gollum's precious artifact?" ' # We should be able to merge B & C cleanly test_expect_success 'setup rename/modify/add-source conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n6\n7\n" >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - echo 8 >>a && - git add a && - git commit -m B && - - git checkout -b C A && - git mv a b && - echo something completely different >a && - git add a && - git commit -m C + test_create_repo rename-modify-add-source && + ( + cd rename-modify-add-source && + + printf "1\n2\n3\n4\n5\n6\n7\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + echo 8 >>a && + git add a && + git commit -m B && + + git checkout -b C A && + git mv a b && + echo something completely different >a && + git add a && + git commit -m C + ) ' test_expect_failure 'rename/modify/add-source conflict resolvable' ' - git checkout B^0 && + ( + cd rename-modify-add-source && + + git checkout B^0 && - git merge -s recursive C^0 && + git merge -s recursive C^0 && - test $(git rev-parse B:a) = $(git rev-parse b) && - test $(git rev-parse C:a) = $(git rev-parse a) + git rev-parse >expect \ + B:a C:a && + git rev-parse >actual \ + b c && + test_cmp expect actual + ) ' test_expect_success 'setup resolvable conflict missed if rename missed' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n" >a && - echo foo >b && - git add a b && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a c && - echo "Completely different content" >a && - git add a && - git commit -m B && - - git checkout -b C A && - echo 6 >>a && - git add a && - git commit -m C + test_create_repo break-detection-1 && + ( + cd break-detection-1 && + + printf "1\n2\n3\n4\n5\n" >a && + echo foo >b && + git add a b && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a c && + echo "Completely different content" >a && + git add a && + git commit -m B && + + git checkout -b C A && + echo 6 >>a && + git add a && + git commit -m C + ) ' test_expect_failure 'conflict caused if rename not detected' ' - git checkout -q C^0 && - git merge -s recursive B^0 && - - test 3 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && - - test_line_count = 6 c && - test $(git rev-parse HEAD:a) = $(git rev-parse B:a) && - test $(git rev-parse HEAD:b) = $(git rev-parse A:b) + ( + cd break-detection-1 && + + git checkout -q C^0 && + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + test_line_count = 6 c && + git rev-parse >expect \ + B:a A:b && + git rev-parse >actual \ + :0:a :0:b && + test_cmp expect actual + ) ' test_expect_success 'setup conflict resolved wrong if rename missed' ' - git reset --hard && - git clean -f && - - git checkout -b D A && - echo 7 >>a && - git add a && - git mv a c && - echo "Completely different content" >a && - git add a && - git commit -m D && - - git checkout -b E A && - git rm a && - echo "Completely different content" >>a && - git add a && - git commit -m E + test_create_repo break-detection-2 && + ( + cd break-detection-2 && + + printf "1\n2\n3\n4\n5\n" >a && + echo foo >b && + git add a b && + git commit -m A && + git tag A && + + git checkout -b D A && + echo 7 >>a && + git add a && + git mv a c && + echo "Completely different content" >a && + git add a && + git commit -m D && + + git checkout -b E A && + git rm a && + echo "Completely different content" >>a && + git add a && + git commit -m E + ) ' test_expect_failure 'missed conflict if rename not detected' ' - git checkout -q E^0 && - test_must_fail git merge -s recursive D^0 + ( + cd break-detection-2 && + + git checkout -q E^0 && + test_must_fail git merge -s recursive D^0 + ) ' # Tests for undetected rename/add-source causing a file to erroneously be @@ -145,198 +183,233 @@ test_expect_failure 'missed conflict if rename not detected' ' # Commit C: rename a->b, add unrelated a test_expect_success 'setup undetected rename/add-source causes data loss' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n" >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - git commit -m B && - - git checkout -b C A && - git mv a b && - echo foobar >a && - git add a && - git commit -m C + test_create_repo break-detection-3 && + ( + cd break-detection-3 && + + printf "1\n2\n3\n4\n5\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + git commit -m B && + + git checkout -b C A && + git mv a b && + echo foobar >a && + git add a && + git commit -m C + ) ' test_expect_failure 'detect rename/add-source and preserve all data' ' - git checkout B^0 && + ( + cd break-detection-3 && - git merge -s recursive C^0 && + git checkout B^0 && - test 2 -eq $(git ls-files -s | wc -l) && - test 2 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && + git merge -s recursive C^0 && - test -f a && - test -f b && + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && - test $(git rev-parse HEAD:b) = $(git rev-parse A:a) && - test $(git rev-parse HEAD:a) = $(git rev-parse C:a) + test_path_is_file a && + test_path_is_file b && + + git rev-parse >expect \ + A:a C:a && + git rev-parse >actual \ + :0:b :0:a && + test_cmp expect actual + ) ' test_expect_failure 'detect rename/add-source and preserve all data, merge other way' ' - git checkout C^0 && + ( + cd break-detection-3 && + + git checkout C^0 && - git merge -s recursive B^0 && + git merge -s recursive B^0 && - test 2 -eq $(git ls-files -s | wc -l) && - test 2 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && - test -f a && - test -f b && + test_path_is_file a && + test_path_is_file b && - test $(git rev-parse HEAD:b) = $(git rev-parse A:a) && - test $(git rev-parse HEAD:a) = $(git rev-parse C:a) + git rev-parse >expect \ + A:a C:a && + git rev-parse >actual \ + :0:b :0:a && + test_cmp expect actual + ) ' test_expect_success 'setup content merge + rename/directory conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n6\n" >file && - git add file && - test_tick && - git commit -m base && - git tag base && - - git checkout -b right && - echo 7 >>file && - mkdir newfile && - echo junk >newfile/realfile && - git add file newfile/realfile && - test_tick && - git commit -m right && - - git checkout -b left-conflict base && - echo 8 >>file && - git add file && - git mv file newfile && - test_tick && - git commit -m left && - - git checkout -b left-clean base && - echo 0 >newfile && - cat file >>newfile && - git add newfile && - git rm file && - test_tick && - git commit -m left + test_create_repo rename-directory-1 && + ( + cd rename-directory-1 && + + printf "1\n2\n3\n4\n5\n6\n" >file && + git add file && + test_tick && + git commit -m base && + git tag base && + + git checkout -b right && + echo 7 >>file && + mkdir newfile && + echo junk >newfile/realfile && + git add file newfile/realfile && + test_tick && + git commit -m right && + + git checkout -b left-conflict base && + echo 8 >>file && + git add file && + git mv file newfile && + test_tick && + git commit -m left && + + git checkout -b left-clean base && + echo 0 >newfile && + cat file >>newfile && + git add newfile && + git rm file && + test_tick && + git commit -m left + ) ' test_expect_success 'rename/directory conflict + clean content merge' ' - git reset --hard && - git reset --hard && - git clean -fdqx && + ( + cd rename-directory-1 && - git checkout left-clean^0 && + git checkout left-clean^0 && - test_must_fail git merge -s recursive right^0 && + test_must_fail git merge -s recursive right^0 && - test 2 -eq $(git ls-files -s | wc -l) && - test 1 -eq $(git ls-files -u | wc -l) && - test 1 -eq $(git ls-files -o | wc -l) && + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 2 out && - echo 0 >expect && - git cat-file -p base:file >>expect && - echo 7 >>expect && - test_cmp expect newfile~HEAD && + echo 0 >expect && + git cat-file -p base:file >>expect && + echo 7 >>expect && + test_cmp expect newfile~HEAD && - test $(git rev-parse :2:newfile) = $(git hash-object expect) && + test $(git rev-parse :2:newfile) = $(git hash-object expect) && - test -f newfile/realfile && - test -f newfile~HEAD + test_path_is_file newfile/realfile && + test_path_is_file newfile~HEAD + ) ' test_expect_success 'rename/directory conflict + content merge conflict' ' - git reset --hard && - git reset --hard && - git clean -fdqx && - - git checkout left-conflict^0 && - - test_must_fail git merge -s recursive right^0 && - - test 4 -eq $(git ls-files -s | wc -l) && - test 3 -eq $(git ls-files -u | wc -l) && - test 1 -eq $(git ls-files -o | wc -l) && - - git cat-file -p left-conflict:newfile >left && - git cat-file -p base:file >base && - git cat-file -p right:file >right && - test_must_fail git merge-file \ - -L "HEAD:newfile" \ - -L "" \ - -L "right^0:file" \ - left base right && - test_cmp left newfile~HEAD && - - test $(git rev-parse :1:newfile) = $(git rev-parse base:file) && - test $(git rev-parse :2:newfile) = $(git rev-parse left-conflict:newfile) && - test $(git rev-parse :3:newfile) = $(git rev-parse right:file) && - - test -f newfile/realfile && - test -f newfile~HEAD + ( + cd rename-directory-1 && + + git reset --hard && + git reset --hard && + git clean -fdqx && + + git checkout left-conflict^0 && + + test_must_fail git merge -s recursive right^0 && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 2 out && + + git cat-file -p left-conflict:newfile >left && + git cat-file -p base:file >base && + git cat-file -p right:file >right && + test_must_fail git merge-file \ + -L "HEAD:newfile" \ + -L "" \ + -L "right^0:file" \ + left base right && + test_cmp left newfile~HEAD && + + git rev-parse >expect \ + base:file left-conflict:newfile right:file && + git rev-parse >actual \ + :1:newfile :2:newfile :3:newfile && + test_cmp expect actual + + test_path_is_file newfile/realfile && + test_path_is_file newfile~HEAD + ) ' test_expect_success 'setup content merge + rename/directory conflict w/ disappearing dir' ' - git reset --hard && - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - mkdir sub && - printf "1\n2\n3\n4\n5\n6\n" >sub/file && - git add sub/file && - test_tick && - git commit -m base && - git tag base && - - git checkout -b right && - echo 7 >>sub/file && - git add sub/file && - test_tick && - git commit -m right && - - git checkout -b left base && - echo 0 >newfile && - cat sub/file >>newfile && - git rm sub/file && - mv newfile sub && - git add sub && - test_tick && - git commit -m left + test_create_repo rename-directory-2 && + ( + cd rename-directory-2 && + + mkdir sub && + printf "1\n2\n3\n4\n5\n6\n" >sub/file && + git add sub/file && + test_tick && + git commit -m base && + git tag base && + + git checkout -b right && + echo 7 >>sub/file && + git add sub/file && + test_tick && + git commit -m right && + + git checkout -b left base && + echo 0 >newfile && + cat sub/file >>newfile && + git rm sub/file && + mv newfile sub && + git add sub && + test_tick && + git commit -m left + ) ' test_expect_success 'disappearing dir in rename/directory conflict handled' ' - git reset --hard && - git clean -fdqx && + ( + cd rename-directory-2 && - git checkout left^0 && + git checkout left^0 && - git merge -s recursive right^0 && + git merge -s recursive right^0 && - test 1 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && + git ls-files -s >out && + test_line_count = 1 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && - echo 0 >expect && - git cat-file -p base:sub/file >>expect && - echo 7 >>expect && - test_cmp expect sub && + echo 0 >expect && + git cat-file -p base:sub/file >>expect && + echo 7 >>expect && + test_cmp expect sub && - test -f sub + test_path_is_file sub + ) ' # Test for all kinds of things that can go wrong with rename/rename (2to1): @@ -352,48 +425,59 @@ test_expect_success 'disappearing dir in rename/directory conflict handled' ' # * Nothing else should be present. Is anything? test_expect_success 'setup rename/rename (2to1) + modify/modify' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n" >a && - printf "5\n4\n3\n2\n1\n" >b && - git add a b && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a c && - echo 0 >>b && - git add b && - git commit -m B && - - git checkout -b C A && - git mv b c && - echo 6 >>a && - git add a && - git commit -m C + test_create_repo rename-rename-2to1 && + ( + cd rename-rename-2to1 && + + printf "1\n2\n3\n4\n5\n" >a && + printf "5\n4\n3\n2\n1\n" >b && + git add a b && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a c && + echo 0 >>b && + git add b && + git commit -m B && + + git checkout -b C A && + git mv b c && + echo 6 >>a && + git add a && + git commit -m C + ) ' test_expect_success 'handle rename/rename (2to1) conflict correctly' ' - git checkout B^0 && - - test_must_fail git merge -s recursive C^0 >out && - test_i18ngrep "CONFLICT (rename/rename)" out && - - test 2 -eq $(git ls-files -s | wc -l) && - test 2 -eq $(git ls-files -u | wc -l) && - test 2 -eq $(git ls-files -u c | wc -l) && - test 3 -eq $(git ls-files -o | wc -l) && - - test ! -f a && - test ! -f b && - test -f c~HEAD && - test -f c~C^0 && - - test $(git hash-object c~HEAD) = $(git rev-parse C:a) && - test $(git hash-object c~C^0) = $(git rev-parse B:b) + ( + cd rename-rename-2to1 && + + git checkout B^0 && + + test_must_fail git merge -s recursive C^0 >out && + test_i18ngrep "CONFLICT (rename/rename)" out && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -u c >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 3 out && + + test_path_is_missing a && + test_path_is_missing b && + test_path_is_file c~HEAD && + test_path_is_file c~C^0 && + + git rev-parse >expect \ + C:a B:b && + git hash-object >actual \ + c~HEAD c~C^0 && + test_cmp expect actual + ) ' # Testcase setup for simple rename/rename (1to2) conflict: @@ -401,44 +485,53 @@ test_expect_success 'handle rename/rename (2to1) conflict correctly' ' # Commit B: rename a->b # Commit C: rename a->c test_expect_success 'setup simple rename/rename (1to2) conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - echo stuff >a && - git add a && - test_tick && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - test_tick && - git commit -m B && - - git checkout -b C A && - git mv a c && - test_tick && - git commit -m C + test_create_repo rename-rename-1to2 && + ( + cd rename-rename-1to2 && + + echo stuff >a && + git add a && + test_tick && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + test_tick && + git commit -m B && + + git checkout -b C A && + git mv a c && + test_tick && + git commit -m C + ) ' test_expect_success 'merge has correct working tree contents' ' - git checkout C^0 && - - test_must_fail git merge -s recursive B^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 :1:a) = $(git rev-parse A:a) && - test $(git rev-parse :3:b) = $(git rev-parse A:a) && - test $(git rev-parse :2:c) = $(git rev-parse A:a) && - - test ! -f a && - test $(git hash-object b) = $(git rev-parse A:a) && - test $(git hash-object c) = $(git rev-parse A:a) + ( + cd rename-rename-1to2 && + + git checkout C^0 && + + test_must_fail git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 1 out && + + test_path_is_missing a && + git rev-parse >expect \ + A:a A:a A:a \ + A:a A:a && + git rev-parse >actual \ + :1:a :3:b :2:c && + git hash-object >>actual \ + b c && + test_cmp expect actual + ) ' # Testcase setup for rename/rename(1to2)/add-source conflict: @@ -449,130 +542,155 @@ test_expect_success 'merge has correct working tree contents' ' # Merging of B & C should NOT be clean; there's a rename/rename conflict test_expect_success 'setup rename/rename(1to2)/add-source conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n6\n7\n" >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - git commit -m B && - - git checkout -b C A && - git mv a c && - echo something completely different >a && - git add a && - git commit -m C + test_create_repo rename-rename-1to2-add-source-1 && + ( + cd rename-rename-1to2-add-source-1 && + + printf "1\n2\n3\n4\n5\n6\n7\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + git commit -m B && + + git checkout -b C A && + git mv a c && + echo something completely different >a && + git add a && + git commit -m C + ) ' test_expect_failure 'detect conflict with rename/rename(1to2)/add-source merge' ' - git checkout B^0 && + ( + cd rename-rename-1to2-add-source-1 && + + git checkout B^0 && - test_must_fail git merge -s recursive C^0 && + test_must_fail git merge -s recursive C^0 && - test 4 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -o >out && + test_line_count = 1 out && - test $(git rev-parse 3:a) = $(git rev-parse C:a) && - test $(git rev-parse 1:a) = $(git rev-parse A:a) && - test $(git rev-parse 2:b) = $(git rev-parse B:b) && - test $(git rev-parse 3:c) = $(git rev-parse C:c) && + git rev-parse >expect \ + C:a A:a B:b C:C && + git rev-parse >actual \ + :3:a :1:a :2:b :3:c && + test_cmp expect actual - test -f a && - test -f b && - test -f c + test_path_is_file a && + test_path_is_file b && + test_path_is_file c + ) ' test_expect_success 'setup rename/rename(1to2)/add-source resolvable conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - >a && - git add a && - test_tick && - git commit -m base && - git tag A && - - git checkout -b B A && - git mv a b && - test_tick && - git commit -m one && - - git checkout -b C A && - git mv a b && - echo important-info >a && - git add a && - test_tick && - git commit -m two + test_create_repo rename-rename-1to2-add-source-2 && + ( + cd rename-rename-1to2-add-source-2 && + + >a && + git add a && + test_tick && + git commit -m base && + git tag A && + + git checkout -b B A && + git mv a b && + test_tick && + git commit -m one && + + git checkout -b C A && + git mv a b && + echo important-info >a && + git add a && + test_tick && + git commit -m two + ) ' test_expect_failure 'rename/rename/add-source still tracks new a file' ' - git checkout C^0 && - git merge -s recursive B^0 && - - test 2 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse HEAD:a) = $(git rev-parse C:a) && - test $(git rev-parse HEAD:b) = $(git rev-parse A:a) + ( + cd rename-rename-1to2-add-source-2 && + + git checkout C^0 && + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + C:a A:a && + git rev-parse >actual \ + :0:a :0:b && + test_cmp expect actual + ) ' test_expect_success 'setup rename/rename(1to2)/add-dest conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - echo stuff >a && - git add a && - test_tick && - git commit -m base && - git tag A && - - git checkout -b B A && - git mv a b && - echo precious-data >c && - git add c && - test_tick && - git commit -m one && - - git checkout -b C A && - git mv a c && - echo important-info >b && - git add b && - test_tick && - git commit -m two + test_create_repo rename-rename-1to2-add-dest && + ( + cd rename-rename-1to2-add-dest && + + echo stuff >a && + git add a && + test_tick && + git commit -m base && + git tag A && + + git checkout -b B A && + git mv a b && + echo precious-data >c && + git add c && + test_tick && + git commit -m one && + + git checkout -b C A && + git mv a c && + echo important-info >b && + git add b && + test_tick && + git commit -m two + ) ' test_expect_success 'rename/rename/add-dest merge still knows about conflicting file versions' ' - git checkout C^0 && - test_must_fail git merge -s recursive B^0 && - - test 5 -eq $(git ls-files -s | wc -l) && - test 2 -eq $(git ls-files -u b | wc -l) && - test 2 -eq $(git ls-files -u c | wc -l) && - test 4 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse :1:a) = $(git rev-parse A:a) && - test $(git rev-parse :2:b) = $(git rev-parse C:b) && - test $(git rev-parse :3:b) = $(git rev-parse B:b) && - test $(git rev-parse :2:c) = $(git rev-parse C:c) && - test $(git rev-parse :3:c) = $(git rev-parse B:c) && - - test $(git hash-object c~HEAD) = $(git rev-parse C:c) && - test $(git hash-object c~B\^0) = $(git rev-parse B:c) && - test $(git hash-object b~HEAD) = $(git rev-parse C:b) && - test $(git hash-object b~B\^0) = $(git rev-parse B:b) && - - test ! -f b && - test ! -f c + ( + cd rename-rename-1to2-add-dest && + + git checkout C^0 && + test_must_fail git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 5 out && + git ls-files -u b >out && + test_line_count = 2 out && + git ls-files -u c >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 5 out && + + git rev-parse >expect \ + A:a C:b B:b C:c B:c && + git rev-parse >actual \ + :1:a :2:b :3:b :2:c :3:c && + test_cmp expect actual + + git rev-parse >expect \ + C:c B:c C:b B:b && + git hash-object >actual \ + c~HEAD c~B\^0 b~HEAD b~B\^0 && + test_cmp expect actual + + test_path_is_missing b && + test_path_is_missing c + ) ' test_done diff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh new file mode 100755 index 0000000000..2e28f2908d --- /dev/null +++ b/t/t6043-merge-rename-directories.sh @@ -0,0 +1,3998 @@ +#!/bin/sh + +test_description="recursive merge with directory renames" +# includes checking of many corner cases, with a similar methodology to: +# t6042: corner cases with renames but not criss-cross merges +# t6036: corner cases with both renames and criss-cross merges +# +# The setup for all of them, pictorially, is: +# +# A +# o +# / \ +# O o ? +# \ / +# o +# B +# +# To help make it easier to follow the flow of tests, they have been +# divided into sections and each test will start with a quick explanation +# of what commits O, A, and B contain. +# +# Notation: +# z/{b,c} means files z/b and z/c both exist +# x/d_1 means file x/d exists with content d1. (Purpose of the +# underscore notation is to differentiate different +# files that might be renamed into each other's paths.) + +. ./test-lib.sh + + +########################################################################### +# SECTION 1: Basic cases we should be able to handle +########################################################################### + +# Testcase 1a, Basic directory rename. +# Commit O: z/{b,c} +# Commit A: y/{b,c} +# Commit B: z/{b,c,d,e/f} +# Expected: y/{b,c,d,e/f} + +test_expect_success '1a-setup: Simple directory rename detection' ' + test_create_repo 1a && + ( + cd 1a && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + test_tick && + git commit -m "A" && + + git checkout B && + echo d >z/d && + mkdir z/e && + echo f >z/e/f && + git add z/d z/e/f && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '1a-check: Simple directory rename detection' ' + ( + cd 1a && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 4 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:y/d HEAD:y/e/f && + git rev-parse >expect \ + O:z/b O:z/c B:z/d B:z/e/f && + test_cmp expect actual && + + git hash-object y/d >actual && + git rev-parse B:z/d >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:z/d && + test_must_fail git rev-parse HEAD:z/e/f && + test_path_is_missing z/d && + test_path_is_missing z/e/f + ) +' + +# Testcase 1b, Merge a directory with another +# Commit O: z/{b,c}, y/d +# Commit A: z/{b,c,e}, y/d +# Commit B: y/{b,c,d} +# Expected: y/{b,c,d,e} + +test_expect_success '1b-setup: Merge a directory with another' ' + test_create_repo 1b && + ( + cd 1b && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir y && + echo d >y/d && + git add z y && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + echo e >z/e && + git add z/e && + test_tick && + git commit -m "A" && + + git checkout B && + git mv z/b y && + git mv z/c y && + rmdir z && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '1b-check: Merge a directory with another' ' + ( + cd 1b && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 4 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:y/d HEAD:y/e && + git rev-parse >expect \ + O:z/b O:z/c O:y/d A:z/e && + test_cmp expect actual && + test_must_fail git rev-parse HEAD:z/e + ) +' + +# Testcase 1c, Transitive renaming +# (Related to testcases 3a and 6d -- when should a transitive rename apply?) +# (Related to testcases 9c and 9d -- can transitivity repeat?) +# (Related to testcase 12b -- joint-transitivity?) +# Commit O: z/{b,c}, x/d +# Commit A: y/{b,c}, x/d +# Commit B: z/{b,c,d} +# Expected: y/{b,c,d} (because x/d -> z/d -> y/d) + +test_expect_success '1c-setup: Transitive renaming' ' + test_create_repo 1c && + ( + cd 1c && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir x && + echo d >x/d && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/d z/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '1c-check: Transitive renaming' ' + ( + cd 1c && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:y/d && + git rev-parse >expect \ + O:z/b O:z/c O:x/d && + test_cmp expect actual && + test_must_fail git rev-parse HEAD:x/d && + test_must_fail git rev-parse HEAD:z/d && + test_path_is_missing z/d + ) +' + +# Testcase 1d, Directory renames (merging two directories into one new one) +# cause a rename/rename(2to1) conflict +# (Related to testcases 1c and 7b) +# Commit O. z/{b,c}, y/{d,e} +# Commit A. x/{b,c}, y/{d,e,m,wham_1} +# Commit B. z/{b,c,n,wham_2}, x/{d,e} +# Expected: x/{b,c,d,e,m,n}, CONFLICT:(y/wham_1 & z/wham_2 -> x/wham) +# Note: y/m & z/n should definitely move into x. By the same token, both +# y/wham_1 & z/wham_2 should too...giving us a conflict. + +test_expect_success '1d-setup: Directory renames cause a rename/rename(2to1) conflict' ' + test_create_repo 1d && + ( + cd 1d && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir y && + echo d >y/d && + echo e >y/e && + git add z y && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z x && + echo m >y/m && + echo wham1 >y/wham && + git add y && + test_tick && + git commit -m "A" && + + git checkout B && + git mv y x && + echo n >z/n && + echo wham2 >z/wham && + git add z && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) conflict' ' + ( + cd 1d && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (rename/rename)" out && + + git ls-files -s >out && + test_line_count = 8 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 3 out && + + git rev-parse >actual \ + :0:x/b :0:x/c :0:x/d :0:x/e :0:x/m :0:x/n && + git rev-parse >expect \ + O:z/b O:z/c O:y/d O:y/e A:y/m B:z/n && + test_cmp expect actual && + + test_must_fail git rev-parse :0:x/wham && + git rev-parse >actual \ + :2:x/wham :3:x/wham && + git rev-parse >expect \ + A:y/wham B:z/wham && + test_cmp expect actual && + + test_path_is_missing x/wham && + test_path_is_file x/wham~HEAD && + test_path_is_file x/wham~B^0 && + + git hash-object >actual \ + x/wham~HEAD x/wham~B^0 && + git rev-parse >expect \ + A:y/wham B:z/wham && + test_cmp expect actual + ) +' + +# Testcase 1e, Renamed directory, with all filenames being renamed too +# (Related to testcases 9f & 9g) +# Commit O: z/{oldb,oldc} +# Commit A: y/{newb,newc} +# Commit B: z/{oldb,oldc,d} +# Expected: y/{newb,newc,d} + +test_expect_success '1e-setup: Renamed directory, with all files being renamed too' ' + test_create_repo 1e && + ( + cd 1e && + + mkdir z && + echo b >z/oldb && + echo c >z/oldc && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + mkdir y && + git mv z/oldb y/newb && + git mv z/oldc y/newc && + test_tick && + git commit -m "A" && + + git checkout B && + echo d >z/d && + git add z/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '1e-check: Renamed directory, with all files being renamed too' ' + ( + cd 1e && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + + git rev-parse >actual \ + HEAD:y/newb HEAD:y/newc HEAD:y/d && + git rev-parse >expect \ + O:z/oldb O:z/oldc B:z/d && + test_cmp expect actual && + test_must_fail git rev-parse HEAD:z/d + ) +' + +# Testcase 1f, Split a directory into two other directories +# (Related to testcases 3a, all of section 2, and all of section 4) +# Commit O: z/{b,c,d,e,f} +# Commit A: z/{b,c,d,e,f,g} +# Commit B: y/{b,c}, x/{d,e,f} +# Expected: y/{b,c}, x/{d,e,f,g} + +test_expect_success '1f-setup: Split a directory into two other directories' ' + test_create_repo 1f && + ( + cd 1f && + + mkdir z && + echo b >z/b && + echo c >z/c && + echo d >z/d && + echo e >z/e && + echo f >z/f && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + echo g >z/g && + git add z/g && + test_tick && + git commit -m "A" && + + git checkout B && + mkdir y && + mkdir x && + git mv z/b y/ && + git mv z/c y/ && + git mv z/d x/ && + git mv z/e x/ && + git mv z/f x/ && + rmdir z && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '1f-check: Split a directory into two other directories' ' + ( + cd 1f && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 6 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:x/d HEAD:x/e HEAD:x/f HEAD:x/g && + git rev-parse >expect \ + O:z/b O:z/c O:z/d O:z/e O:z/f A:z/g && + test_cmp expect actual && + test_path_is_missing z/g && + test_must_fail git rev-parse HEAD:z/g + ) +' + +########################################################################### +# Rules suggested by testcases in section 1: +# +# We should still detect the directory rename even if it wasn't just +# the directory renamed, but the files within it. (see 1b) +# +# If renames split a directory into two or more others, the directory +# with the most renames, "wins" (see 1c). However, see the testcases +# in section 2, plus testcases 3a and 4a. +########################################################################### + + +########################################################################### +# SECTION 2: Split into multiple directories, with equal number of paths +# +# Explore the splitting-a-directory rules a bit; what happens in the +# edge cases? +# +# Note that there is a closely related case of a directory not being +# split on either side of history, but being renamed differently on +# each side. See testcase 8e for that. +########################################################################### + +# Testcase 2a, Directory split into two on one side, with equal numbers of paths +# Commit O: z/{b,c} +# Commit A: y/b, w/c +# Commit B: z/{b,c,d} +# Expected: y/b, w/c, z/d, with warning about z/ -> (y/ vs. w/) conflict +test_expect_success '2a-setup: Directory split into two on one side, with equal numbers of paths' ' + test_create_repo 2a && + ( + cd 2a && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + mkdir y && + mkdir w && + git mv z/b y/ && + git mv z/c w/ && + test_tick && + git commit -m "A" && + + git checkout B && + echo d >z/d && + git add z/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '2a-check: Directory split into two on one side, with equal numbers of paths' ' + ( + cd 2a && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT.*directory rename split" out && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:y/b :0:w/c :0:z/d && + git rev-parse >expect \ + O:z/b O:z/c B:z/d && + test_cmp expect actual + ) +' + +# Testcase 2b, Directory split into two on one side, with equal numbers of paths +# Commit O: z/{b,c} +# Commit A: y/b, w/c +# Commit B: z/{b,c}, x/d +# Expected: y/b, w/c, x/d; No warning about z/ -> (y/ vs. w/) conflict +test_expect_success '2b-setup: Directory split into two on one side, with equal numbers of paths' ' + test_create_repo 2b && + ( + cd 2b && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + mkdir y && + mkdir w && + git mv z/b y/ && + git mv z/c w/ && + test_tick && + git commit -m "A" && + + git checkout B && + mkdir x && + echo d >x/d && + git add x/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '2b-check: Directory split into two on one side, with equal numbers of paths' ' + ( + cd 2b && + + git checkout A^0 && + + git merge -s recursive B^0 >out && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:y/b :0:w/c :0:x/d && + git rev-parse >expect \ + O:z/b O:z/c B:x/d && + test_cmp expect actual && + test_i18ngrep ! "CONFLICT.*directory rename split" out + ) +' + +########################################################################### +# Rules suggested by section 2: +# +# None; the rule was already covered in section 1. These testcases are +# here just to make sure the conflict resolution and necessary warning +# messages are handled correctly. +########################################################################### + + +########################################################################### +# SECTION 3: Path in question is the source path for some rename already +# +# Combining cases from Section 1 and trying to handle them could lead to +# directory renaming detection being over-applied. So, this section +# provides some good testcases to check that the implementation doesn't go +# too far. +########################################################################### + +# Testcase 3a, Avoid implicit rename if involved as source on other side +# (Related to testcases 1c, 1f, and 9h) +# Commit O: z/{b,c,d} +# Commit A: z/{b,c,d} (no change) +# Commit B: y/{b,c}, x/d +# Expected: y/{b,c}, x/d +test_expect_success '3a-setup: Avoid implicit rename if involved as source on other side' ' + test_create_repo 3a && + ( + cd 3a && + + mkdir z && + echo b >z/b && + echo c >z/c && + echo d >z/d && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_tick && + git commit --allow-empty -m "A" && + + git checkout B && + mkdir y && + mkdir x && + git mv z/b y/ && + git mv z/c y/ && + git mv z/d x/ && + rmdir z && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '3a-check: Avoid implicit rename if involved as source on other side' ' + ( + cd 3a && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:x/d && + git rev-parse >expect \ + O:z/b O:z/c O:z/d && + test_cmp expect actual + ) +' + +# Testcase 3b, Avoid implicit rename if involved as source on other side +# (Related to testcases 5c and 7c, also kind of 1e and 1f) +# Commit O: z/{b,c,d} +# Commit A: y/{b,c}, x/d +# Commit B: z/{b,c}, w/d +# Expected: y/{b,c}, CONFLICT:(z/d -> x/d vs. w/d) +# NOTE: We're particularly checking that since z/d is already involved as +# a source in a file rename on the same side of history, that we don't +# get it involved in directory rename detection. If it were, we might +# end up with CONFLICT:(z/d -> y/d vs. x/d vs. w/d), i.e. a +# rename/rename/rename(1to3) conflict, which is just weird. +test_expect_success '3b-setup: Avoid implicit rename if involved as source on current side' ' + test_create_repo 3b && + ( + cd 3b && + + mkdir z && + echo b >z/b && + echo c >z/c && + echo d >z/d && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + mkdir y && + mkdir x && + git mv z/b y/ && + git mv z/c y/ && + git mv z/d x/ && + rmdir z && + test_tick && + git commit -m "A" && + + git checkout B && + mkdir w && + git mv z/d w/ && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '3b-check: Avoid implicit rename if involved as source on current side' ' + ( + cd 3b && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep CONFLICT.*rename/rename.*z/d.*x/d.*w/d out && + test_i18ngrep ! CONFLICT.*rename/rename.*y/d out && + + git ls-files -s >out && + test_line_count = 5 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:y/b :0:y/c :1:z/d :2:x/d :3:w/d && + git rev-parse >expect \ + O:z/b O:z/c O:z/d O:z/d O:z/d && + test_cmp expect actual && + + test_path_is_missing z/d && + git hash-object >actual \ + x/d w/d && + git rev-parse >expect \ + O:z/d O:z/d && + test_cmp expect actual + ) +' + +########################################################################### +# Rules suggested by section 3: +# +# Avoid directory-rename-detection for a path, if that path is the source +# of a rename on either side of a merge. +########################################################################### + + +########################################################################### +# SECTION 4: Partially renamed directory; still exists on both sides of merge +# +# What if we were to attempt to do directory rename detection when someone +# "mostly" moved a directory but still left some files around, or, +# equivalently, fully renamed a directory in one commmit and then recreated +# that directory in a later commit adding some new files and then tried to +# merge? +# +# It's hard to divine user intent in these cases, because you can make an +# argument that, depending on the intermediate history of the side being +# merged, that some users will want files in that directory to +# automatically be detected and renamed, while users with a different +# intermediate history wouldn't want that rename to happen. +# +# I think that it is best to simply not have directory rename detection +# apply to such cases. My reasoning for this is four-fold: (1) it's +# easiest for users in general to figure out what happened if we don't +# apply directory rename detection in any such case, (2) it's an easy rule +# to explain ["We don't do directory rename detection if the directory +# still exists on both sides of the merge"], (3) we can get some hairy +# edge/corner cases that would be really confusing and possibly not even +# representable in the index if we were to even try, and [related to 3] (4) +# attempting to resolve this issue of divining user intent by examining +# intermediate history goes against the spirit of three-way merges and is a +# path towards crazy corner cases that are far more complex than what we're +# already dealing with. +# +# Note that the wording of the rule ("We don't do directory rename +# detection if the directory still exists on both sides of the merge.") +# also excludes "renaming" of a directory into a subdirectory of itself +# (e.g. /some/dir/* -> /some/dir/subdir/*). It may be possible to carve +# out an exception for "renaming"-beneath-itself cases without opening +# weird edge/corner cases for other partial directory renames, but for now +# we are keeping the rule simple. +# +# This section contains a test for a partially-renamed-directory case. +########################################################################### + +# Testcase 4a, Directory split, with original directory still present +# (Related to testcase 1f) +# Commit O: z/{b,c,d,e} +# Commit A: y/{b,c,d}, z/e +# Commit B: z/{b,c,d,e,f} +# Expected: y/{b,c,d}, z/{e,f} +# NOTE: Even though most files from z moved to y, we don't want f to follow. + +test_expect_success '4a-setup: Directory split, with original directory still present' ' + test_create_repo 4a && + ( + cd 4a && + + mkdir z && + echo b >z/b && + echo c >z/c && + echo d >z/d && + echo e >z/e && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + mkdir y && + git mv z/b y/ && + git mv z/c y/ && + git mv z/d y/ && + test_tick && + git commit -m "A" && + + git checkout B && + echo f >z/f && + git add z/f && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '4a-check: Directory split, with original directory still present' ' + ( + cd 4a && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 5 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:y/d HEAD:z/e HEAD:z/f && + git rev-parse >expect \ + O:z/b O:z/c O:z/d O:z/e B:z/f && + test_cmp expect actual + ) +' + +########################################################################### +# Rules suggested by section 4: +# +# Directory-rename-detection should be turned off for any directories (as +# a source for renames) that exist on both sides of the merge. (The "as +# a source for renames" clarification is due to cases like 1c where +# the target directory exists on both sides and we do want the rename +# detection.) But, sadly, see testcase 8b. +########################################################################### + + +########################################################################### +# SECTION 5: Files/directories in the way of subset of to-be-renamed paths +# +# Implicitly renaming files due to a detected directory rename could run +# into problems if there are files or directories in the way of the paths +# we want to rename. Explore such cases in this section. +########################################################################### + +# Testcase 5a, Merge directories, other side adds files to original and target +# Commit O: z/{b,c}, y/d +# Commit A: z/{b,c,e_1,f}, y/{d,e_2} +# Commit B: y/{b,c,d} +# Expected: z/e_1, y/{b,c,d,e_2,f} + CONFLICT warning +# NOTE: While directory rename detection is active here causing z/f to +# become y/f, we did not apply this for z/e_1 because that would +# give us an add/add conflict for y/e_1 vs y/e_2. This problem with +# this add/add, is that both versions of y/e are from the same side +# of history, giving us no way to represent this conflict in the +# index. + +test_expect_success '5a-setup: Merge directories, other side adds files to original and target' ' + test_create_repo 5a && + ( + cd 5a && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir y && + echo d >y/d && + git add z y && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + echo e1 >z/e && + echo f >z/f && + echo e2 >y/e && + git add z/e z/f y/e && + test_tick && + git commit -m "A" && + + git checkout B && + git mv z/b y/ && + git mv z/c y/ && + rmdir z && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '5a-check: Merge directories, other side adds files to original and target' ' + ( + cd 5a && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT.*implicit dir rename" out && + + git ls-files -s >out && + test_line_count = 6 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:y/b :0:y/c :0:y/d :0:y/e :0:z/e :0:y/f && + git rev-parse >expect \ + O:z/b O:z/c O:y/d A:y/e A:z/e A:z/f && + test_cmp expect actual + ) +' + +# Testcase 5b, Rename/delete in order to get add/add/add conflict +# (Related to testcase 8d; these may appear slightly inconsistent to users; +# Also related to testcases 7d and 7e) +# Commit O: z/{b,c,d_1} +# Commit A: y/{b,c,d_2} +# Commit B: z/{b,c,d_1,e}, y/d_3 +# Expected: y/{b,c,e}, CONFLICT(add/add: y/d_2 vs. y/d_3) +# NOTE: If z/d_1 in commit B were to be involved in dir rename detection, as +# we normaly would since z/ is being renamed to y/, then this would be +# a rename/delete (z/d_1 -> y/d_1 vs. deleted) AND an add/add/add +# conflict of y/d_1 vs. y/d_2 vs. y/d_3. Add/add/add is not +# representable in the index, so the existence of y/d_3 needs to +# cause us to bail on directory rename detection for that path, falling +# back to git behavior without the directory rename detection. + +test_expect_success '5b-setup: Rename/delete in order to get add/add/add conflict' ' + test_create_repo 5b && + ( + cd 5b && + + mkdir z && + echo b >z/b && + echo c >z/c && + echo d1 >z/d && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git rm z/d && + git mv z y && + echo d2 >y/d && + git add y/d && + test_tick && + git commit -m "A" && + + git checkout B && + mkdir y && + echo d3 >y/d && + echo e >z/e && + git add y/d z/e && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '5b-check: Rename/delete in order to get add/add/add conflict' ' + ( + cd 5b && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (add/add).* y/d" out && + + git ls-files -s >out && + test_line_count = 5 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:y/b :0:y/c :0:y/e :2:y/d :3:y/d && + git rev-parse >expect \ + O:z/b O:z/c B:z/e A:y/d B:y/d && + test_cmp expect actual && + + test_must_fail git rev-parse :1:y/d && + test_path_is_file y/d + ) +' + +# Testcase 5c, Transitive rename would cause rename/rename/rename/add/add/add +# (Directory rename detection would result in transitive rename vs. +# rename/rename(1to2) and turn it into a rename/rename(1to3). Further, +# rename paths conflict with separate adds on the other side) +# (Related to testcases 3b and 7c) +# Commit O: z/{b,c}, x/d_1 +# Commit A: y/{b,c,d_2}, w/d_1 +# Commit B: z/{b,c,d_1,e}, w/d_3, y/d_4 +# Expected: A mess, but only a rename/rename(1to2)/add/add mess. Use the +# presence of y/d_4 in B to avoid doing transitive rename of +# x/d_1 -> z/d_1 -> y/d_1, so that the only paths we have at +# y/d are y/d_2 and y/d_4. We still do the move from z/e to y/e, +# though, because it doesn't have anything in the way. + +test_expect_success '5c-setup: Transitive rename would cause rename/rename/rename/add/add/add' ' + test_create_repo 5c && + ( + cd 5c && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir x && + echo d1 >x/d && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + echo d2 >y/d && + git add y/d && + git mv x w && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/d z/ && + mkdir w && + mkdir y && + echo d3 >w/d && + echo d4 >y/d && + echo e >z/e && + git add w/ y/ z/e && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '5c-check: Transitive rename would cause rename/rename/rename/add/add/add' ' + ( + cd 5c && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (rename/rename).*x/d.*w/d.*z/d" out && + test_i18ngrep "CONFLICT (add/add).* y/d" out && + + git ls-files -s >out && + test_line_count = 9 out && + git ls-files -u >out && + test_line_count = 6 out && + git ls-files -o >out && + test_line_count = 3 out && + + git rev-parse >actual \ + :0:y/b :0:y/c :0:y/e && + git rev-parse >expect \ + O:z/b O:z/c B:z/e && + test_cmp expect actual && + + test_must_fail git rev-parse :1:y/d && + git rev-parse >actual \ + :2:w/d :3:w/d :1:x/d :2:y/d :3:y/d :3:z/d && + git rev-parse >expect \ + O:x/d B:w/d O:x/d A:y/d B:y/d O:x/d && + test_cmp expect actual && + + git hash-object >actual \ + w/d~HEAD w/d~B^0 z/d && + git rev-parse >expect \ + O:x/d B:w/d O:x/d && + test_cmp expect actual && + test_path_is_missing x/d && + test_path_is_file y/d && + grep -q "<<<<" y/d # conflict markers should be present + ) +' + +# Testcase 5d, Directory/file/file conflict due to directory rename +# Commit O: z/{b,c} +# Commit A: y/{b,c,d_1} +# Commit B: z/{b,c,d_2,f}, y/d/e +# Expected: y/{b,c,d/e,f}, z/d_2, CONFLICT(file/directory), y/d_1~HEAD +# Note: The fact that y/d/ exists in B makes us bail on directory rename +# detection for z/d_2, but that doesn't prevent us from applying the +# directory rename detection for z/f -> y/f. + +test_expect_success '5d-setup: Directory/file/file conflict due to directory rename' ' + test_create_repo 5d && + ( + cd 5d && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + echo d1 >y/d && + git add y/d && + test_tick && + git commit -m "A" && + + git checkout B && + mkdir -p y/d && + echo e >y/d/e && + echo d2 >z/d && + echo f >z/f && + git add y/d/e z/d z/f && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '5d-check: Directory/file/file conflict due to directory rename' ' + ( + cd 5d && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (file/directory).*y/d" out && + + git ls-files -s >out && + test_line_count = 6 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 2 out && + + git rev-parse >actual \ + :0:y/b :0:y/c :0:z/d :0:y/f :2:y/d :0:y/d/e && + git rev-parse >expect \ + O:z/b O:z/c B:z/d B:z/f A:y/d B:y/d/e && + test_cmp expect actual && + + git hash-object y/d~HEAD >actual && + git rev-parse A:y/d >expect && + test_cmp expect actual + ) +' + +########################################################################### +# Rules suggested by section 5: +# +# If a subset of to-be-renamed files have a file or directory in the way, +# "turn off" the directory rename for those specific sub-paths, falling +# back to old handling. But, sadly, see testcases 8a and 8b. +########################################################################### + + +########################################################################### +# SECTION 6: Same side of the merge was the one that did the rename +# +# It may sound obvious that you only want to apply implicit directory +# renames to directories if the _other_ side of history did the renaming. +# If you did make an implementation that didn't explicitly enforce this +# rule, the majority of cases that would fall under this section would +# also be solved by following the rules from the above sections. But +# there are still a few that stick out, so this section covers them just +# to make sure we also get them right. +########################################################################### + +# Testcase 6a, Tricky rename/delete +# Commit O: z/{b,c,d} +# Commit A: z/b +# Commit B: y/{b,c}, z/d +# Expected: y/b, CONFLICT(rename/delete, z/c -> y/c vs. NULL) +# Note: We're just checking here that the rename of z/b and z/c to put +# them under y/ doesn't accidentally catch z/d and make it look like +# it is also involved in a rename/delete conflict. + +test_expect_success '6a-setup: Tricky rename/delete' ' + test_create_repo 6a && + ( + cd 6a && + + mkdir z && + echo b >z/b && + echo c >z/c && + echo d >z/d && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git rm z/c && + git rm z/d && + test_tick && + git commit -m "A" && + + git checkout B && + mkdir y && + git mv z/b y/ && + git mv z/c y/ && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '6a-check: Tricky rename/delete' ' + ( + cd 6a && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (rename/delete).*z/c.*y/c" out && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:y/b :3:y/c && + git rev-parse >expect \ + O:z/b O:z/c && + test_cmp expect actual + ) +' + +# Testcase 6b, Same rename done on both sides +# (Related to testcases 6c and 8e) +# Commit O: z/{b,c} +# Commit A: y/{b,c} +# Commit B: y/{b,c}, z/d +# Expected: y/{b,c}, z/d +# Note: If we did directory rename detection here, we'd move z/d into y/, +# but B did that rename and still decided to put the file into z/, +# so we probably shouldn't apply directory rename detection for it. + +test_expect_success '6b-setup: Same rename done on both sides' ' + test_create_repo 6b && + ( + cd 6b && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + test_tick && + git commit -m "A" && + + git checkout B && + git mv z y && + mkdir z && + echo d >z/d && + git add z/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '6b-check: Same rename done on both sides' ' + ( + cd 6b && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:z/d && + git rev-parse >expect \ + O:z/b O:z/c B:z/d && + test_cmp expect actual + ) +' + +# Testcase 6c, Rename only done on same side +# (Related to testcases 6b and 8e) +# Commit O: z/{b,c} +# Commit A: z/{b,c} (no change) +# Commit B: y/{b,c}, z/d +# Expected: y/{b,c}, z/d +# NOTE: Seems obvious, but just checking that the implementation doesn't +# "accidentally detect a rename" and give us y/{b,c,d}. + +test_expect_success '6c-setup: Rename only done on same side' ' + test_create_repo 6c && + ( + cd 6c && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_tick && + git commit --allow-empty -m "A" && + + git checkout B && + git mv z y && + mkdir z && + echo d >z/d && + git add z/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '6c-check: Rename only done on same side' ' + ( + cd 6c && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:z/d && + git rev-parse >expect \ + O:z/b O:z/c B:z/d && + test_cmp expect actual + ) +' + +# Testcase 6d, We don't always want transitive renaming +# (Related to testcase 1c) +# Commit O: z/{b,c}, x/d +# Commit A: z/{b,c}, x/d (no change) +# Commit B: y/{b,c}, z/d +# Expected: y/{b,c}, z/d +# NOTE: Again, this seems obvious but just checking that the implementation +# doesn't "accidentally detect a rename" and give us y/{b,c,d}. + +test_expect_success '6d-setup: We do not always want transitive renaming' ' + test_create_repo 6d && + ( + cd 6d && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir x && + echo d >x/d && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_tick && + git commit --allow-empty -m "A" && + + git checkout B && + git mv z y && + git mv x z && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '6d-check: We do not always want transitive renaming' ' + ( + cd 6d && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:z/d && + git rev-parse >expect \ + O:z/b O:z/c O:x/d && + test_cmp expect actual + ) +' + +# Testcase 6e, Add/add from one-side +# Commit O: z/{b,c} +# Commit A: z/{b,c} (no change) +# Commit B: y/{b,c,d_1}, z/d_2 +# Expected: y/{b,c,d_1}, z/d_2 +# NOTE: Again, this seems obvious but just checking that the implementation +# doesn't "accidentally detect a rename" and give us y/{b,c} + +# add/add conflict on y/d_1 vs y/d_2. + +test_expect_success '6e-setup: Add/add from one side' ' + test_create_repo 6e && + ( + cd 6e && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_tick && + git commit --allow-empty -m "A" && + + git checkout B && + git mv z y && + echo d1 > y/d && + mkdir z && + echo d2 > z/d && + git add y/d z/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '6e-check: Add/add from one side' ' + ( + cd 6e && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:y/d HEAD:z/d && + git rev-parse >expect \ + O:z/b O:z/c B:y/d B:z/d && + test_cmp expect actual + ) +' + +########################################################################### +# Rules suggested by section 6: +# +# Only apply implicit directory renames to directories if the other +# side of history is the one doing the renaming. +########################################################################### + + +########################################################################### +# SECTION 7: More involved Edge/Corner cases +# +# The ruleset we have generated in the above sections seems to provide +# well-defined merges. But can we find edge/corner cases that either (a) +# are harder for users to understand, or (b) have a resolution that is +# non-intuitive or suboptimal? +# +# The testcases in this section dive into cases that I've tried to craft in +# a way to find some that might be surprising to users or difficult for +# them to understand (the next section will look at non-intuitive or +# suboptimal merge results). Some of the testcases are similar to ones +# from past sections, but have been simplified to try to highlight error +# messages using a "modified" path (due to the directory rename). Are +# users okay with these? +# +# In my opinion, testcases that are difficult to understand from this +# section is due to difficulty in the testcase rather than the directory +# renaming (similar to how t6042 and t6036 have difficult resolutions due +# to the problem setup itself being complex). And I don't think the +# error messages are a problem. +# +# On the other hand, the testcases in section 8 worry me slightly more... +########################################################################### + +# Testcase 7a, rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file +# Commit O: z/{b,c} +# Commit A: y/{b,c} +# Commit B: w/b, x/c, z/d +# Expected: y/d, CONFLICT(rename/rename for both z/b and z/c) +# NOTE: There's a rename of z/ here, y/ has more renames, so z/d -> y/d. + +test_expect_success '7a-setup: rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file' ' + test_create_repo 7a && + ( + cd 7a && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + test_tick && + git commit -m "A" && + + git checkout B && + mkdir w && + mkdir x && + git mv z/b w/ && + git mv z/c x/ && + echo d > z/d && + git add z/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '7a-check: rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file' ' + ( + cd 7a && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (rename/rename).*z/b.*y/b.*w/b" out && + test_i18ngrep "CONFLICT (rename/rename).*z/c.*y/c.*x/c" out && + + git ls-files -s >out && + test_line_count = 7 out && + git ls-files -u >out && + test_line_count = 6 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :1:z/b :2:y/b :3:w/b :1:z/c :2:y/c :3:x/c :0:y/d && + git rev-parse >expect \ + O:z/b O:z/b O:z/b O:z/c O:z/c O:z/c B:z/d && + test_cmp expect actual && + + git hash-object >actual \ + y/b w/b y/c x/c && + git rev-parse >expect \ + O:z/b O:z/b O:z/c O:z/c && + test_cmp expect actual + ) +' + +# Testcase 7b, rename/rename(2to1), but only due to transitive rename +# (Related to testcase 1d) +# Commit O: z/{b,c}, x/d_1, w/d_2 +# Commit A: y/{b,c,d_2}, x/d_1 +# Commit B: z/{b,c,d_1}, w/d_2 +# Expected: y/{b,c}, CONFLICT(rename/rename(2to1): x/d_1, w/d_2 -> y_d) + +test_expect_success '7b-setup: rename/rename(2to1), but only due to transitive rename' ' + test_create_repo 7b && + ( + cd 7b && + + mkdir z && + mkdir x && + mkdir w && + echo b >z/b && + echo c >z/c && + echo d1 > x/d && + echo d2 > w/d && + git add z x w && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + git mv w/d y/ && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/d z/ && + rmdir x && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '7b-check: rename/rename(2to1), but only due to transitive rename' ' + ( + cd 7b && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (rename/rename)" out && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 3 out && + + git rev-parse >actual \ + :0:y/b :0:y/c :2:y/d :3:y/d && + git rev-parse >expect \ + O:z/b O:z/c O:w/d O:x/d && + test_cmp expect actual && + + test_path_is_missing y/d && + test_path_is_file y/d~HEAD && + test_path_is_file y/d~B^0 && + + git hash-object >actual \ + y/d~HEAD y/d~B^0 && + git rev-parse >expect \ + O:w/d O:x/d && + test_cmp expect actual + ) +' + +# Testcase 7c, rename/rename(1to...2or3); transitive rename may add complexity +# (Related to testcases 3b and 5c) +# Commit O: z/{b,c}, x/d +# Commit A: y/{b,c}, w/d +# Commit B: z/{b,c,d} +# Expected: y/{b,c}, CONFLICT(x/d -> w/d vs. y/d) +# NOTE: z/ was renamed to y/ so we do want to report +# neither CONFLICT(x/d -> w/d vs. z/d) +# nor CONFLiCT x/d -> w/d vs. y/d vs. z/d) + +test_expect_success '7c-setup: rename/rename(1to...2or3); transitive rename may add complexity' ' + test_create_repo 7c && + ( + cd 7c && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir x && + echo d >x/d && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + git mv x w && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/d z/ && + rmdir x && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '7c-check: rename/rename(1to...2or3); transitive rename may add complexity' ' + ( + cd 7c && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (rename/rename).*x/d.*w/d.*y/d" out && + + git ls-files -s >out && + test_line_count = 5 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:y/b :0:y/c :1:x/d :2:w/d :3:y/d && + git rev-parse >expect \ + O:z/b O:z/c O:x/d O:x/d O:x/d && + test_cmp expect actual + ) +' + +# Testcase 7d, transitive rename involved in rename/delete; how is it reported? +# (Related somewhat to testcases 5b and 8d) +# Commit O: z/{b,c}, x/d +# Commit A: y/{b,c} +# Commit B: z/{b,c,d} +# Expected: y/{b,c}, CONFLICT(delete x/d vs rename to y/d) +# NOTE: z->y so NOT CONFLICT(delete x/d vs rename to z/d) + +test_expect_success '7d-setup: transitive rename involved in rename/delete; how is it reported?' ' + test_create_repo 7d && + ( + cd 7d && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir x && + echo d >x/d && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + git rm -rf x && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/d z/ && + rmdir x && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '7d-check: transitive rename involved in rename/delete; how is it reported?' ' + ( + cd 7d && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (rename/delete).*x/d.*y/d" out && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:y/b :0:y/c :3:y/d && + git rev-parse >expect \ + O:z/b O:z/c O:x/d && + test_cmp expect actual + ) +' + +# Testcase 7e, transitive rename in rename/delete AND dirs in the way +# (Very similar to 'both rename source and destination involved in D/F conflict' from t6022-merge-rename.sh) +# (Also related to testcases 9c and 9d) +# Commit O: z/{b,c}, x/d_1 +# Commit A: y/{b,c,d/g}, x/d/f +# Commit B: z/{b,c,d_1} +# Expected: rename/delete(x/d_1->y/d_1 vs. None) + D/F conflict on y/d +# y/{b,c,d/g}, y/d_1~B^0, x/d/f + +# NOTE: The main path of interest here is d_1 and where it ends up, but +# this is actually a case that has two potential directory renames +# involved and D/F conflict(s), so it makes sense to walk through +# each step. +# +# Commit A renames z/ -> y/. Thus everything that B adds to z/ +# should be instead moved to y/. This gives us the D/F conflict on +# y/d because x/d_1 -> z/d_1 -> y/d_1 conflicts with y/d/g. +# +# Further, commit B renames x/ -> z/, thus everything A adds to x/ +# should instead be moved to z/...BUT we removed z/ and renamed it +# to y/, so maybe everything should move not from x/ to z/, but +# from x/ to z/ to y/. Doing so might make sense from the logic so +# far, but note that commit A had both an x/ and a y/; it did the +# renaming of z/ to y/ and created x/d/f and it clearly made these +# things separate, so it doesn't make much sense to push these +# together. Doing so is what I'd call a doubly transitive rename; +# see testcases 9c and 9d for further discussion of this issue and +# how it's resolved. + +test_expect_success '7e-setup: transitive rename in rename/delete AND dirs in the way' ' + test_create_repo 7e && + ( + cd 7e && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir x && + echo d1 >x/d && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + git rm x/d && + mkdir -p x/d && + mkdir -p y/d && + echo f >x/d/f && + echo g >y/d/g && + git add x/d/f y/d/g && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/d z/ && + rmdir x && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '7e-check: transitive rename in rename/delete AND dirs in the way' ' + ( + cd 7e && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (rename/delete).*x/d.*y/d" out && + + git ls-files -s >out && + test_line_count = 5 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 2 out && + + git rev-parse >actual \ + :0:x/d/f :0:y/d/g :0:y/b :0:y/c :3:y/d && + git rev-parse >expect \ + A:x/d/f A:y/d/g O:z/b O:z/c O:x/d && + test_cmp expect actual && + + git hash-object y/d~B^0 >actual && + git rev-parse O:x/d >expect && + test_cmp expect actual + ) +' + +########################################################################### +# SECTION 8: Suboptimal merges +# +# As alluded to in the last section, the ruleset we have built up for +# detecting directory renames unfortunately has some special cases where it +# results in slightly suboptimal or non-intuitive behavior. This section +# explores these cases. +# +# To be fair, we already had non-intuitive or suboptimal behavior for most +# of these cases in git before introducing implicit directory rename +# detection, but it'd be nice if there was a modified ruleset out there +# that handled these cases a bit better. +########################################################################### + +# Testcase 8a, Dual-directory rename, one into the others' way +# Commit O. x/{a,b}, y/{c,d} +# Commit A. x/{a,b,e}, y/{c,d,f} +# Commit B. y/{a,b}, z/{c,d} +# +# Possible Resolutions: +# w/o dir-rename detection: y/{a,b,f}, z/{c,d}, x/e +# Currently expected: y/{a,b,e,f}, z/{c,d} +# Optimal: y/{a,b,e}, z/{c,d,f} +# +# Note: Both x and y got renamed and it'd be nice to detect both, and we do +# better with directory rename detection than git did without, but the +# simple rule from section 5 prevents me from handling this as optimally as +# we potentially could. + +test_expect_success '8a-setup: Dual-directory rename, one into the others way' ' + test_create_repo 8a && + ( + cd 8a && + + mkdir x && + mkdir y && + echo a >x/a && + echo b >x/b && + echo c >y/c && + echo d >y/d && + git add x y && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + echo e >x/e && + echo f >y/f && + git add x/e y/f && + test_tick && + git commit -m "A" && + + git checkout B && + git mv y z && + git mv x y && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '8a-check: Dual-directory rename, one into the others way' ' + ( + cd 8a && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 6 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + HEAD:y/a HEAD:y/b HEAD:y/e HEAD:y/f HEAD:z/c HEAD:z/d && + git rev-parse >expect \ + O:x/a O:x/b A:x/e A:y/f O:y/c O:y/d && + test_cmp expect actual + ) +' + +# Testcase 8b, Dual-directory rename, one into the others' way, with conflicting filenames +# Commit O. x/{a_1,b_1}, y/{a_2,b_2} +# Commit A. x/{a_1,b_1,e_1}, y/{a_2,b_2,e_2} +# Commit B. y/{a_1,b_1}, z/{a_2,b_2} +# +# w/o dir-rename detection: y/{a_1,b_1,e_2}, z/{a_2,b_2}, x/e_1 +# Currently expected: <same> +# Scary: y/{a_1,b_1}, z/{a_2,b_2}, CONFLICT(add/add, e_1 vs. e_2) +# Optimal: y/{a_1,b_1,e_1}, z/{a_2,b_2,e_2} +# +# Note: Very similar to 8a, except instead of 'e' and 'f' in directories x and +# y, both are named 'e'. Without directory rename detection, neither file +# moves directories. Implement directory rename detection suboptimally, and +# you get an add/add conflict, but both files were added in commit A, so this +# is an add/add conflict where one side of history added both files -- +# something we can't represent in the index. Obviously, we'd prefer the last +# resolution, but our previous rules are too coarse to allow it. Using both +# the rules from section 4 and section 5 save us from the Scary resolution, +# making us fall back to pre-directory-rename-detection behavior for both +# e_1 and e_2. + +test_expect_success '8b-setup: Dual-directory rename, one into the others way, with conflicting filenames' ' + test_create_repo 8b && + ( + cd 8b && + + mkdir x && + mkdir y && + echo a1 >x/a && + echo b1 >x/b && + echo a2 >y/a && + echo b2 >y/b && + git add x y && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + echo e1 >x/e && + echo e2 >y/e && + git add x/e y/e && + test_tick && + git commit -m "A" && + + git checkout B && + git mv y z && + git mv x y && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '8b-check: Dual-directory rename, one into the others way, with conflicting filenames' ' + ( + cd 8b && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 6 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + HEAD:y/a HEAD:y/b HEAD:z/a HEAD:z/b HEAD:x/e HEAD:y/e && + git rev-parse >expect \ + O:x/a O:x/b O:y/a O:y/b A:x/e A:y/e && + test_cmp expect actual + ) +' + +# Testcase 8c, modify/delete or rename+modify/delete? +# (Related to testcases 5b, 8d, and 9h) +# Commit O: z/{b,c,d} +# Commit A: y/{b,c} +# Commit B: z/{b,c,d_modified,e} +# Expected: y/{b,c,e}, CONFLICT(modify/delete: on z/d) +# +# Note: It could easily be argued that the correct resolution here is +# y/{b,c,e}, CONFLICT(rename/delete: z/d -> y/d vs deleted) +# and that the modifed version of d should be present in y/ after +# the merge, just marked as conflicted. Indeed, I previously did +# argue that. But applying directory renames to the side of +# history where a file is merely modified results in spurious +# rename/rename(1to2) conflicts -- see testcase 9h. See also +# notes in 8d. + +test_expect_success '8c-setup: modify/delete or rename+modify/delete?' ' + test_create_repo 8c && + ( + cd 8c && + + mkdir z && + echo b >z/b && + echo c >z/c && + test_seq 1 10 >z/d && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git rm z/d && + git mv z y && + test_tick && + git commit -m "A" && + + git checkout B && + echo 11 >z/d && + test_chmod +x z/d && + echo e >z/e && + git add z/d z/e && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '8c-check: modify/delete or rename+modify/delete' ' + ( + cd 8c && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (modify/delete).* z/d" out && + + git ls-files -s >out && + test_line_count = 5 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:y/b :0:y/c :0:y/e :1:z/d :3:z/d && + git rev-parse >expect \ + O:z/b O:z/c B:z/e O:z/d B:z/d && + test_cmp expect actual && + + test_must_fail git rev-parse :2:z/d && + git ls-files -s z/d | grep ^100755 && + test_path_is_file z/d && + test_path_is_missing y/d + ) +' + +# Testcase 8d, rename/delete...or not? +# (Related to testcase 5b; these may appear slightly inconsistent to users; +# Also related to testcases 7d and 7e) +# Commit O: z/{b,c,d} +# Commit A: y/{b,c} +# Commit B: z/{b,c,d,e} +# Expected: y/{b,c,e} +# +# Note: It would also be somewhat reasonable to resolve this as +# y/{b,c,e}, CONFLICT(rename/delete: x/d -> y/d or deleted) +# +# In this case, I'm leaning towards: commit A was the one that deleted z/d +# and it did the rename of z to y, so the two "conflicts" (rename vs. +# delete) are both coming from commit A, which is illogical. Conflicts +# during merging are supposed to be about opposite sides doing things +# differently. + +test_expect_success '8d-setup: rename/delete...or not?' ' + test_create_repo 8d && + ( + cd 8d && + + mkdir z && + echo b >z/b && + echo c >z/c && + test_seq 1 10 >z/d && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git rm z/d && + git mv z y && + test_tick && + git commit -m "A" && + + git checkout B && + echo e >z/e && + git add z/e && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '8d-check: rename/delete...or not?' ' + ( + cd 8d && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:y/e && + git rev-parse >expect \ + O:z/b O:z/c B:z/e && + test_cmp expect actual + ) +' + +# Testcase 8e, Both sides rename, one side adds to original directory +# Commit O: z/{b,c} +# Commit A: y/{b,c} +# Commit B: w/{b,c}, z/d +# +# Possible Resolutions: +# w/o dir-rename detection: z/d, CONFLICT(z/b -> y/b vs. w/b), +# CONFLICT(z/c -> y/c vs. w/c) +# Currently expected: y/d, CONFLICT(z/b -> y/b vs. w/b), +# CONFLICT(z/c -> y/c vs. w/c) +# Optimal: ?? +# +# Notes: In commit A, directory z got renamed to y. In commit B, directory z +# did NOT get renamed; the directory is still present; instead it is +# considered to have just renamed a subset of paths in directory z +# elsewhere. Therefore, the directory rename done in commit A to z/ +# applies to z/d and maps it to y/d. +# +# It's possible that users would get confused about this, but what +# should we do instead? Silently leaving at z/d seems just as bad or +# maybe even worse. Perhaps we could print a big warning about z/d +# and how we're moving to y/d in this case, but when I started thinking +# about the ramifications of doing that, I didn't know how to rule out +# that opening other weird edge and corner cases so I just punted. + +test_expect_success '8e-setup: Both sides rename, one side adds to original directory' ' + test_create_repo 8e && + ( + cd 8e && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + test_tick && + git commit -m "A" && + + git checkout B && + git mv z w && + mkdir z && + echo d >z/d && + git add z/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '8e-check: Both sides rename, one side adds to original directory' ' + ( + cd 8e && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out 2>err && + test_i18ngrep CONFLICT.*rename/rename.*z/c.*y/c.*w/c out && + test_i18ngrep CONFLICT.*rename/rename.*z/b.*y/b.*w/b out && + + git ls-files -s >out && + test_line_count = 7 out && + git ls-files -u >out && + test_line_count = 6 out && + git ls-files -o >out && + test_line_count = 2 out && + + git rev-parse >actual \ + :1:z/b :2:y/b :3:w/b :1:z/c :2:y/c :3:w/c :0:y/d && + git rev-parse >expect \ + O:z/b O:z/b O:z/b O:z/c O:z/c O:z/c B:z/d && + test_cmp expect actual && + + git hash-object >actual \ + y/b w/b y/c w/c && + git rev-parse >expect \ + O:z/b O:z/b O:z/c O:z/c && + test_cmp expect actual && + + test_path_is_missing z/b && + test_path_is_missing z/c + ) +' + +########################################################################### +# SECTION 9: Other testcases +# +# This section consists of miscellaneous testcases I thought of during +# the implementation which round out the testing. +########################################################################### + +# Testcase 9a, Inner renamed directory within outer renamed directory +# (Related to testcase 1f) +# Commit O: z/{b,c,d/{e,f,g}} +# Commit A: y/{b,c}, x/w/{e,f,g} +# Commit B: z/{b,c,d/{e,f,g,h},i} +# Expected: y/{b,c,i}, x/w/{e,f,g,h} +# NOTE: The only reason this one is interesting is because when a directory +# is split into multiple other directories, we determine by the weight +# of which one had the most paths going to it. A naive implementation +# of that could take the new file in commit B at z/i to x/w/i or x/i. + +test_expect_success '9a-setup: Inner renamed directory within outer renamed directory' ' + test_create_repo 9a && + ( + cd 9a && + + mkdir -p z/d && + echo b >z/b && + echo c >z/c && + echo e >z/d/e && + echo f >z/d/f && + echo g >z/d/g && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + mkdir x && + git mv z/d x/w && + git mv z y && + test_tick && + git commit -m "A" && + + git checkout B && + echo h >z/d/h && + echo i >z/i && + git add z && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '9a-check: Inner renamed directory within outer renamed directory' ' + ( + cd 9a && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 7 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:y/i && + git rev-parse >expect \ + O:z/b O:z/c B:z/i && + test_cmp expect actual && + + git rev-parse >actual \ + HEAD:x/w/e HEAD:x/w/f HEAD:x/w/g HEAD:x/w/h && + git rev-parse >expect \ + O:z/d/e O:z/d/f O:z/d/g B:z/d/h && + test_cmp expect actual + ) +' + +# Testcase 9b, Transitive rename with content merge +# (Related to testcase 1c) +# Commit O: z/{b,c}, x/d_1 +# Commit A: y/{b,c}, x/d_2 +# Commit B: z/{b,c,d_3} +# Expected: y/{b,c,d_merged} + +test_expect_success '9b-setup: Transitive rename with content merge' ' + test_create_repo 9b && + ( + cd 9b && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir x && + test_seq 1 10 >x/d && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + test_seq 1 11 >x/d && + git add x/d && + test_tick && + git commit -m "A" && + + git checkout B && + test_seq 0 10 >x/d && + git mv x/d z/d && + git add z/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '9b-check: Transitive rename with content merge' ' + ( + cd 9b && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + + test_seq 0 11 >expected && + test_cmp expected y/d && + git add expected && + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:y/d && + git rev-parse >expect \ + O:z/b O:z/c :0:expected && + test_cmp expect actual && + test_must_fail git rev-parse HEAD:x/d && + test_must_fail git rev-parse HEAD:z/d && + test_path_is_missing z/d && + + test $(git rev-parse HEAD:y/d) != $(git rev-parse O:x/d) && + test $(git rev-parse HEAD:y/d) != $(git rev-parse A:x/d) && + test $(git rev-parse HEAD:y/d) != $(git rev-parse B:z/d) + ) +' + +# Testcase 9c, Doubly transitive rename? +# (Related to testcase 1c, 7e, and 9d) +# Commit O: z/{b,c}, x/{d,e}, w/f +# Commit A: y/{b,c}, x/{d,e,f,g} +# Commit B: z/{b,c,d,e}, w/f +# Expected: y/{b,c,d,e}, x/{f,g} +# +# NOTE: x/f and x/g may be slightly confusing here. The rename from w/f to +# x/f is clear. Let's look beyond that. Here's the logic: +# Commit B renamed x/ -> z/ +# Commit A renamed z/ -> y/ +# So, we could possibly further rename x/f to z/f to y/f, a doubly +# transient rename. However, where does it end? We can chain these +# indefinitely (see testcase 9d). What if there is a D/F conflict +# at z/f/ or y/f/? Or just another file conflict at one of those +# paths? In the case of an N-long chain of transient renamings, +# where do we "abort" the rename at? Can the user make sense of +# the resulting conflict and resolve it? +# +# To avoid this confusion I use the simple rule that if the other side +# of history did a directory rename to a path that your side renamed +# away, then ignore that particular rename from the other side of +# history for any implicit directory renames. + +test_expect_success '9c-setup: Doubly transitive rename?' ' + test_create_repo 9c && + ( + cd 9c && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir x && + echo d >x/d && + echo e >x/e && + mkdir w && + echo f >w/f && + git add z x w && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + git mv w/f x/ && + echo g >x/g && + git add x/g && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/d z/d && + git mv x/e z/e && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '9c-check: Doubly transitive rename?' ' + ( + cd 9c && + + git checkout A^0 && + + git merge -s recursive B^0 >out && + test_i18ngrep "WARNING: Avoiding applying x -> z rename to x/f" out && + + git ls-files -s >out && + test_line_count = 6 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:y/d HEAD:y/e HEAD:x/f HEAD:x/g && + git rev-parse >expect \ + O:z/b O:z/c O:x/d O:x/e O:w/f A:x/g && + test_cmp expect actual + ) +' + +# Testcase 9d, N-fold transitive rename? +# (Related to testcase 9c...and 1c and 7e) +# Commit O: z/a, y/b, x/c, w/d, v/e, u/f +# Commit A: y/{a,b}, w/{c,d}, u/{e,f} +# Commit B: z/{a,t}, x/{b,c}, v/{d,e}, u/f +# Expected: <see NOTE first> +# +# NOTE: z/ -> y/ (in commit A) +# y/ -> x/ (in commit B) +# x/ -> w/ (in commit A) +# w/ -> v/ (in commit B) +# v/ -> u/ (in commit A) +# So, if we add a file to z, say z/t, where should it end up? In u? +# What if there's another file or directory named 't' in one of the +# intervening directories and/or in u itself? Also, shouldn't the +# same logic that places 't' in u/ also move ALL other files to u/? +# What if there are file or directory conflicts in any of them? If +# we attempted to do N-way (N-fold? N-ary? N-uple?) transitive renames +# like this, would the user have any hope of understanding any +# conflicts or how their working tree ended up? I think not, so I'm +# ruling out N-ary transitive renames for N>1. +# +# Therefore our expected result is: +# z/t, y/a, x/b, w/c, u/d, u/e, u/f +# The reason that v/d DOES get transitively renamed to u/d is that u/ isn't +# renamed somewhere. A slightly sub-optimal result, but it uses fairly +# simple rules that are consistent with what we need for all the other +# testcases and simplifies things for the user. + +test_expect_success '9d-setup: N-way transitive rename?' ' + test_create_repo 9d && + ( + cd 9d && + + mkdir z y x w v u && + echo a >z/a && + echo b >y/b && + echo c >x/c && + echo d >w/d && + echo e >v/e && + echo f >u/f && + git add z y x w v u && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z/a y/ && + git mv x/c w/ && + git mv v/e u/ && + test_tick && + git commit -m "A" && + + git checkout B && + echo t >z/t && + git mv y/b x/ && + git mv w/d v/ && + git add z/t && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '9d-check: N-way transitive rename?' ' + ( + cd 9d && + + git checkout A^0 && + + git merge -s recursive B^0 >out && + test_i18ngrep "WARNING: Avoiding applying z -> y rename to z/t" out && + test_i18ngrep "WARNING: Avoiding applying y -> x rename to y/a" out && + test_i18ngrep "WARNING: Avoiding applying x -> w rename to x/b" out && + test_i18ngrep "WARNING: Avoiding applying w -> v rename to w/c" out && + + git ls-files -s >out && + test_line_count = 7 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + HEAD:z/t \ + HEAD:y/a HEAD:x/b HEAD:w/c \ + HEAD:u/d HEAD:u/e HEAD:u/f && + git rev-parse >expect \ + B:z/t \ + O:z/a O:y/b O:x/c \ + O:w/d O:v/e A:u/f && + test_cmp expect actual + ) +' + +# Testcase 9e, N-to-1 whammo +# (Related to testcase 9c...and 1c and 7e) +# Commit O: dir1/{a,b}, dir2/{d,e}, dir3/{g,h}, dirN/{j,k} +# Commit A: dir1/{a,b,c,yo}, dir2/{d,e,f,yo}, dir3/{g,h,i,yo}, dirN/{j,k,l,yo} +# Commit B: combined/{a,b,d,e,g,h,j,k} +# Expected: combined/{a,b,c,d,e,f,g,h,i,j,k,l}, CONFLICT(Nto1) warnings, +# dir1/yo, dir2/yo, dir3/yo, dirN/yo + +test_expect_success '9e-setup: N-to-1 whammo' ' + test_create_repo 9e && + ( + cd 9e && + + mkdir dir1 dir2 dir3 dirN && + echo a >dir1/a && + echo b >dir1/b && + echo d >dir2/d && + echo e >dir2/e && + echo g >dir3/g && + echo h >dir3/h && + echo j >dirN/j && + echo k >dirN/k && + git add dir* && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + echo c >dir1/c && + echo yo >dir1/yo && + echo f >dir2/f && + echo yo >dir2/yo && + echo i >dir3/i && + echo yo >dir3/yo && + echo l >dirN/l && + echo yo >dirN/yo && + git add dir* && + test_tick && + git commit -m "A" && + + git checkout B && + git mv dir1 combined && + git mv dir2/* combined/ && + git mv dir3/* combined/ && + git mv dirN/* combined/ && + test_tick && + git commit -m "B" + ) +' + +test_expect_success C_LOCALE_OUTPUT '9e-check: N-to-1 whammo' ' + ( + cd 9e && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + grep "CONFLICT (implicit dir rename): Cannot map more than one path to combined/yo" out >error_line && + grep -q dir1/yo error_line && + grep -q dir2/yo error_line && + grep -q dir3/yo error_line && + grep -q dirN/yo error_line && + + git ls-files -s >out && + test_line_count = 16 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 2 out && + + git rev-parse >actual \ + :0:combined/a :0:combined/b :0:combined/c \ + :0:combined/d :0:combined/e :0:combined/f \ + :0:combined/g :0:combined/h :0:combined/i \ + :0:combined/j :0:combined/k :0:combined/l && + git rev-parse >expect \ + O:dir1/a O:dir1/b A:dir1/c \ + O:dir2/d O:dir2/e A:dir2/f \ + O:dir3/g O:dir3/h A:dir3/i \ + O:dirN/j O:dirN/k A:dirN/l && + test_cmp expect actual && + + git rev-parse >actual \ + :0:dir1/yo :0:dir2/yo :0:dir3/yo :0:dirN/yo && + git rev-parse >expect \ + A:dir1/yo A:dir2/yo A:dir3/yo A:dirN/yo && + test_cmp expect actual + ) +' + +# Testcase 9f, Renamed directory that only contained immediate subdirs +# (Related to testcases 1e & 9g) +# Commit O: goal/{a,b}/$more_files +# Commit A: priority/{a,b}/$more_files +# Commit B: goal/{a,b}/$more_files, goal/c +# Expected: priority/{a,b}/$more_files, priority/c + +test_expect_success '9f-setup: Renamed directory that only contained immediate subdirs' ' + test_create_repo 9f && + ( + cd 9f && + + mkdir -p goal/a && + mkdir -p goal/b && + echo foo >goal/a/foo && + echo bar >goal/b/bar && + echo baz >goal/b/baz && + git add goal && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv goal/ priority && + test_tick && + git commit -m "A" && + + git checkout B && + echo c >goal/c && + git add goal/c && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '9f-check: Renamed directory that only contained immediate subdirs' ' + ( + cd 9f && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 4 out && + + git rev-parse >actual \ + HEAD:priority/a/foo \ + HEAD:priority/b/bar \ + HEAD:priority/b/baz \ + HEAD:priority/c && + git rev-parse >expect \ + O:goal/a/foo \ + O:goal/b/bar \ + O:goal/b/baz \ + B:goal/c && + test_cmp expect actual && + test_must_fail git rev-parse HEAD:goal/c + ) +' + +# Testcase 9g, Renamed directory that only contained immediate subdirs, immediate subdirs renamed +# (Related to testcases 1e & 9f) +# Commit O: goal/{a,b}/$more_files +# Commit A: priority/{alpha,bravo}/$more_files +# Commit B: goal/{a,b}/$more_files, goal/c +# Expected: priority/{alpha,bravo}/$more_files, priority/c + +test_expect_success '9g-setup: Renamed directory that only contained immediate subdirs, immediate subdirs renamed' ' + test_create_repo 9g && + ( + cd 9g && + + mkdir -p goal/a && + mkdir -p goal/b && + echo foo >goal/a/foo && + echo bar >goal/b/bar && + echo baz >goal/b/baz && + git add goal && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + mkdir priority && + git mv goal/a/ priority/alpha && + git mv goal/b/ priority/beta && + rmdir goal/ && + test_tick && + git commit -m "A" && + + git checkout B && + echo c >goal/c && + git add goal/c && + test_tick && + git commit -m "B" + ) +' + +test_expect_failure '9g-check: Renamed directory that only contained immediate subdirs, immediate subdirs renamed' ' + ( + cd 9g && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 4 out && + + git rev-parse >actual \ + HEAD:priority/alpha/foo \ + HEAD:priority/beta/bar \ + HEAD:priority/beta/baz \ + HEAD:priority/c && + git rev-parse >expect \ + O:goal/a/foo \ + O:goal/b/bar \ + O:goal/b/baz \ + B:goal/c && + test_cmp expect actual && + test_must_fail git rev-parse HEAD:goal/c + ) +' + +# Testcase 9h, Avoid implicit rename if involved as source on other side +# (Extremely closely related to testcase 3a) +# Commit O: z/{b,c,d_1} +# Commit A: z/{b,c,d_2} +# Commit B: y/{b,c}, x/d_1 +# Expected: y/{b,c}, x/d_2 +# NOTE: If we applied the z/ -> y/ rename to z/d, then we'd end up with +# a rename/rename(1to2) conflict (z/d -> y/d vs. x/d) +test_expect_success '9h-setup: Avoid dir rename on merely modified path' ' + test_create_repo 9h && + ( + cd 9h && + + mkdir z && + echo b >z/b && + echo c >z/c && + printf "1\n2\n3\n4\n5\n6\n7\n8\nd\n" >z/d && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_tick && + echo more >>z/d && + git add z/d && + git commit -m "A" && + + git checkout B && + mkdir y && + mkdir x && + git mv z/b y/ && + git mv z/c y/ && + git mv z/d x/ && + rmdir z && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '9h-check: Avoid dir rename on merely modified path' ' + ( + cd 9h && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:x/d && + git rev-parse >expect \ + O:z/b O:z/c A:z/d && + test_cmp expect actual + ) +' + +########################################################################### +# Rules suggested by section 9: +# +# If the other side of history did a directory rename to a path that your +# side renamed away, then ignore that particular rename from the other +# side of history for any implicit directory renames. +########################################################################### + +########################################################################### +# SECTION 10: Handling untracked files +# +# unpack_trees(), upon which the recursive merge algorithm is based, aborts +# the operation if untracked or dirty files would be deleted or overwritten +# by the merge. Unfortunately, unpack_trees() does not understand renames, +# and if it doesn't abort, then it muddies up the working directory before +# we even get to the point of detecting renames, so we need some special +# handling, at least in the case of directory renames. +########################################################################### + +# Testcase 10a, Overwrite untracked: normal rename/delete +# Commit O: z/{b,c_1} +# Commit A: z/b + untracked z/c + untracked z/d +# Commit B: z/{b,d_1} +# Expected: Aborted Merge + +# ERROR_MSG(untracked working tree files would be overwritten by merge) + +test_expect_success '10a-setup: Overwrite untracked with normal rename/delete' ' + test_create_repo 10a && + ( + cd 10a && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git rm z/c && + test_tick && + git commit -m "A" && + + git checkout B && + git mv z/c z/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '10a-check: Overwrite untracked with normal rename/delete' ' + ( + cd 10a && + + git checkout A^0 && + echo very >z/c && + echo important >z/d && + + test_must_fail git merge -s recursive B^0 >out 2>err && + test_i18ngrep "The following untracked working tree files would be overwritten by merge" err && + + git ls-files -s >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 4 out && + + echo very >expect && + test_cmp expect z/c && + + echo important >expect && + test_cmp expect z/d && + + git rev-parse HEAD:z/b >actual && + git rev-parse O:z/b >expect && + test_cmp expect actual + ) +' + +# Testcase 10b, Overwrite untracked: dir rename + delete +# Commit O: z/{b,c_1} +# Commit A: y/b + untracked y/{c,d,e} +# Commit B: z/{b,d_1,e} +# Expected: Failed Merge; y/b + untracked y/c + untracked y/d on disk + +# z/c_1 -> z/d_1 rename recorded at stage 3 for y/d + +# ERROR_MSG(refusing to lose untracked file at 'y/d') + +test_expect_success '10b-setup: Overwrite untracked with dir rename + delete' ' + test_create_repo 10b && + ( + cd 10b && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git rm z/c && + git mv z/ y/ && + test_tick && + git commit -m "A" && + + git checkout B && + git mv z/c z/d && + echo e >z/e && + git add z/e && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '10b-check: Overwrite untracked with dir rename + delete' ' + ( + cd 10b && + + git checkout A^0 && + echo very >y/c && + echo important >y/d && + echo contents >y/e && + + test_must_fail git merge -s recursive B^0 >out 2>err && + test_i18ngrep "CONFLICT (rename/delete).*Version B\^0 of y/d left in tree at y/d~B\^0" out && + test_i18ngrep "Error: Refusing to lose untracked file at y/e; writing to y/e~B\^0 instead" out && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 5 out && + + git rev-parse >actual \ + :0:y/b :3:y/d :3:y/e && + git rev-parse >expect \ + O:z/b O:z/c B:z/e && + test_cmp expect actual && + + echo very >expect && + test_cmp expect y/c && + + echo important >expect && + test_cmp expect y/d && + + echo contents >expect && + test_cmp expect y/e + ) +' + +# Testcase 10c, Overwrite untracked: dir rename/rename(1to2) +# Commit O: z/{a,b}, x/{c,d} +# Commit A: y/{a,b}, w/c, x/d + different untracked y/c +# Commit B: z/{a,b,c}, x/d +# Expected: Failed Merge; y/{a,b} + x/d + untracked y/c + +# CONFLICT(rename/rename) x/c -> w/c vs y/c + +# y/c~B^0 + +# ERROR_MSG(Refusing to lose untracked file at y/c) + +test_expect_success '10c-setup: Overwrite untracked with dir rename/rename(1to2)' ' + test_create_repo 10c && + ( + cd 10c && + + mkdir z x && + echo a >z/a && + echo b >z/b && + echo c >x/c && + echo d >x/d && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + mkdir w && + git mv x/c w/c && + git mv z/ y/ && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/c z/ && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2)' ' + ( + cd 10c && + + git checkout A^0 && + echo important >y/c && + + test_must_fail git merge -s recursive B^0 >out 2>err && + test_i18ngrep "CONFLICT (rename/rename)" out && + test_i18ngrep "Refusing to lose untracked file at y/c; adding as y/c~B\^0 instead" out && + + git ls-files -s >out && + test_line_count = 6 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 3 out && + + git rev-parse >actual \ + :0:y/a :0:y/b :0:x/d :1:x/c :2:w/c :3:y/c && + git rev-parse >expect \ + O:z/a O:z/b O:x/d O:x/c O:x/c O:x/c && + test_cmp expect actual && + + git hash-object y/c~B^0 >actual && + git rev-parse O:x/c >expect && + test_cmp expect actual && + + echo important >expect && + test_cmp expect y/c + ) +' + +# Testcase 10d, Delete untracked w/ dir rename/rename(2to1) +# Commit O: z/{a,b,c_1}, x/{d,e,f_2} +# Commit A: y/{a,b}, x/{d,e,f_2,wham_1} + untracked y/wham +# Commit B: z/{a,b,c_1,wham_2}, y/{d,e} +# Expected: Failed Merge; y/{a,b,d,e} + untracked y/{wham,wham~B^0,wham~HEAD}+ +# CONFLICT(rename/rename) z/c_1 vs x/f_2 -> y/wham +# ERROR_MSG(Refusing to lose untracked file at y/wham) + +test_expect_success '10d-setup: Delete untracked with dir rename/rename(2to1)' ' + test_create_repo 10d && + ( + cd 10d && + + mkdir z x && + echo a >z/a && + echo b >z/b && + echo c >z/c && + echo d >x/d && + echo e >x/e && + echo f >x/f && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z/c x/wham && + git mv z/ y/ && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/f z/wham && + git mv x/ y/ && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' ' + ( + cd 10d && + + git checkout A^0 && + echo important >y/wham && + + test_must_fail git merge -s recursive B^0 >out 2>err && + test_i18ngrep "CONFLICT (rename/rename)" out && + test_i18ngrep "Refusing to lose untracked file at y/wham" out && + + git ls-files -s >out && + test_line_count = 6 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 4 out && + + git rev-parse >actual \ + :0:y/a :0:y/b :0:y/d :0:y/e :2:y/wham :3:y/wham && + git rev-parse >expect \ + O:z/a O:z/b O:x/d O:x/e O:z/c O:x/f && + test_cmp expect actual && + + test_must_fail git rev-parse :1:y/wham && + + echo important >expect && + test_cmp expect y/wham && + + git hash-object >actual \ + y/wham~B^0 y/wham~HEAD && + git rev-parse >expect \ + O:x/f O:z/c && + test_cmp expect actual + ) +' + +# Testcase 10e, Does git complain about untracked file that's not in the way? +# Commit O: z/{a,b} +# Commit A: y/{a,b} + untracked z/c +# Commit B: z/{a,b,c} +# Expected: y/{a,b,c} + untracked z/c + +test_expect_success '10e-setup: Does git complain about untracked file that is not really in the way?' ' + test_create_repo 10e && + ( + cd 10e && + + mkdir z && + echo a >z/a && + echo b >z/b && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z/ y/ && + test_tick && + git commit -m "A" && + + git checkout B && + echo c >z/c && + git add z/c && + test_tick && + git commit -m "B" + ) +' + +test_expect_failure '10e-check: Does git complain about untracked file that is not really in the way?' ' + ( + cd 10e && + + git checkout A^0 && + mkdir z && + echo random >z/c && + + git merge -s recursive B^0 >out 2>err && + test_i18ngrep ! "following untracked working tree files would be overwritten by merge" err && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 3 out && + + git rev-parse >actual \ + :0:y/a :0:y/b :0:y/c && + git rev-parse >expect \ + O:z/a O:z/b B:z/c && + test_cmp expect actual && + + echo random >expect && + test_cmp expect z/c + ) +' + +########################################################################### +# SECTION 11: Handling dirty (not up-to-date) files +# +# unpack_trees(), upon which the recursive merge algorithm is based, aborts +# the operation if untracked or dirty files would be deleted or overwritten +# by the merge. Unfortunately, unpack_trees() does not understand renames, +# and if it doesn't abort, then it muddies up the working directory before +# we even get to the point of detecting renames, so we need some special +# handling. This was true even of normal renames, but there are additional +# codepaths that need special handling with directory renames. Add +# testcases for both renamed-by-directory-rename-detection and standard +# rename cases. +########################################################################### + +# Testcase 11a, Avoid losing dirty contents with simple rename +# Commit O: z/{a,b_v1}, +# Commit A: z/{a,c_v1}, and z/c_v1 has uncommitted mods +# Commit B: z/{a,b_v2} +# Expected: ERROR_MSG(Refusing to lose dirty file at z/c) + +# z/a, staged version of z/c has sha1sum matching B:z/b_v2, +# z/c~HEAD with contents of B:z/b_v2, +# z/c with uncommitted mods on top of A:z/c_v1 + +test_expect_success '11a-setup: Avoid losing dirty contents with simple rename' ' + test_create_repo 11a && + ( + cd 11a && + + mkdir z && + echo a >z/a && + test_seq 1 10 >z/b && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z/b z/c && + test_tick && + git commit -m "A" && + + git checkout B && + echo 11 >>z/b && + git add z/b && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '11a-check: Avoid losing dirty contents with simple rename' ' + ( + cd 11a && + + git checkout A^0 && + echo stuff >>z/c && + + test_must_fail git merge -s recursive B^0 >out 2>err && + test_i18ngrep "Refusing to lose dirty file at z/c" out && + + test_seq 1 10 >expected && + echo stuff >>expected && + test_cmp expected z/c && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 4 out && + + git rev-parse >actual \ + :0:z/a :2:z/c && + git rev-parse >expect \ + O:z/a B:z/b && + test_cmp expect actual && + + git hash-object z/c~HEAD >actual && + git rev-parse B:z/b >expect && + test_cmp expect actual + ) +' + +# Testcase 11b, Avoid losing dirty file involved in directory rename +# Commit O: z/a, x/{b,c_v1} +# Commit A: z/{a,c_v1}, x/b, and z/c_v1 has uncommitted mods +# Commit B: y/a, x/{b,c_v2} +# Expected: y/{a,c_v2}, x/b, z/c_v1 with uncommitted mods untracked, +# ERROR_MSG(Refusing to lose dirty file at z/c) + + +test_expect_success '11b-setup: Avoid losing dirty file involved in directory rename' ' + test_create_repo 11b && + ( + cd 11b && + + mkdir z x && + echo a >z/a && + echo b >x/b && + test_seq 1 10 >x/c && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv x/c z/c && + test_tick && + git commit -m "A" && + + git checkout B && + git mv z y && + echo 11 >>x/c && + git add x/c && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '11b-check: Avoid losing dirty file involved in directory rename' ' + ( + cd 11b && + + git checkout A^0 && + echo stuff >>z/c && + + git merge -s recursive B^0 >out 2>err && + test_i18ngrep "Refusing to lose dirty file at z/c" out && + + grep -q stuff z/c && + test_seq 1 10 >expected && + echo stuff >>expected && + test_cmp expected z/c && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -m >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 4 out && + + git rev-parse >actual \ + :0:x/b :0:y/a :0:y/c && + git rev-parse >expect \ + O:x/b O:z/a B:x/c && + test_cmp expect actual && + + git hash-object y/c >actual && + git rev-parse B:x/c >expect && + test_cmp expect actual + ) +' + +# Testcase 11c, Avoid losing not-up-to-date with rename + D/F conflict +# Commit O: y/a, x/{b,c_v1} +# Commit A: y/{a,c_v1}, x/b, and y/c_v1 has uncommitted mods +# Commit B: y/{a,c/d}, x/{b,c_v2} +# Expected: Abort_msg("following files would be overwritten by merge") + +# y/c left untouched (still has uncommitted mods) + +test_expect_success '11c-setup: Avoid losing not-uptodate with rename + D/F conflict' ' + test_create_repo 11c && + ( + cd 11c && + + mkdir y x && + echo a >y/a && + echo b >x/b && + test_seq 1 10 >x/c && + git add y x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv x/c y/c && + test_tick && + git commit -m "A" && + + git checkout B && + mkdir y/c && + echo d >y/c/d && + echo 11 >>x/c && + git add x/c y/c/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '11c-check: Avoid losing not-uptodate with rename + D/F conflict' ' + ( + cd 11c && + + git checkout A^0 && + echo stuff >>y/c && + + test_must_fail git merge -s recursive B^0 >out 2>err && + test_i18ngrep "following files would be overwritten by merge" err && + + grep -q stuff y/c && + test_seq 1 10 >expected && + echo stuff >>expected && + test_cmp expected y/c && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -m >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 3 out + ) +' + +# Testcase 11d, Avoid losing not-up-to-date with rename + D/F conflict +# Commit O: z/a, x/{b,c_v1} +# Commit A: z/{a,c_v1}, x/b, and z/c_v1 has uncommitted mods +# Commit B: y/{a,c/d}, x/{b,c_v2} +# Expected: D/F: y/c_v2 vs y/c/d) + +# Warning_Msg("Refusing to lose dirty file at z/c) + +# y/{a,c~HEAD,c/d}, x/b, now-untracked z/c_v1 with uncommitted mods + +test_expect_success '11d-setup: Avoid losing not-uptodate with rename + D/F conflict' ' + test_create_repo 11d && + ( + cd 11d && + + mkdir z x && + echo a >z/a && + echo b >x/b && + test_seq 1 10 >x/c && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv x/c z/c && + test_tick && + git commit -m "A" && + + git checkout B && + git mv z y && + mkdir y/c && + echo d >y/c/d && + echo 11 >>x/c && + git add x/c y/c/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '11d-check: Avoid losing not-uptodate with rename + D/F conflict' ' + ( + cd 11d && + + git checkout A^0 && + echo stuff >>z/c && + + test_must_fail git merge -s recursive B^0 >out 2>err && + test_i18ngrep "Refusing to lose dirty file at z/c" out && + + grep -q stuff z/c && + test_seq 1 10 >expected && + echo stuff >>expected && + test_cmp expected z/c + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 5 out && + + git rev-parse >actual \ + :0:x/b :0:y/a :0:y/c/d :3:y/c && + git rev-parse >expect \ + O:x/b O:z/a B:y/c/d B:x/c && + test_cmp expect actual && + + git hash-object y/c~HEAD >actual && + git rev-parse B:x/c >expect && + test_cmp expect actual + ) +' + +# Testcase 11e, Avoid deleting not-up-to-date with dir rename/rename(1to2)/add +# Commit O: z/{a,b}, x/{c_1,d} +# Commit A: y/{a,b,c_2}, x/d, w/c_1, and y/c_2 has uncommitted mods +# Commit B: z/{a,b,c_1}, x/d +# Expected: Failed Merge; y/{a,b} + x/d + +# CONFLICT(rename/rename) x/c_1 -> w/c_1 vs y/c_1 + +# ERROR_MSG(Refusing to lose dirty file at y/c) +# y/c~B^0 has O:x/c_1 contents +# y/c~HEAD has A:y/c_2 contents +# y/c has dirty file from before merge + +test_expect_success '11e-setup: Avoid deleting not-uptodate with dir rename/rename(1to2)/add' ' + test_create_repo 11e && + ( + cd 11e && + + mkdir z x && + echo a >z/a && + echo b >z/b && + echo c >x/c && + echo d >x/d && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z/ y/ && + echo different >y/c && + mkdir w && + git mv x/c w/ && + git add y/c && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/c z/ && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rename(1to2)/add' ' + ( + cd 11e && + + git checkout A^0 && + echo mods >>y/c && + + test_must_fail git merge -s recursive B^0 >out 2>err && + test_i18ngrep "CONFLICT (rename/rename)" out && + test_i18ngrep "Refusing to lose dirty file at y/c" out && + + git ls-files -s >out && + test_line_count = 7 out && + git ls-files -u >out && + test_line_count = 4 out && + git ls-files -o >out && + test_line_count = 4 out && + + echo different >expected && + echo mods >>expected && + test_cmp expected y/c && + + git rev-parse >actual \ + :0:y/a :0:y/b :0:x/d :1:x/c :2:w/c :2:y/c :3:y/c && + git rev-parse >expect \ + O:z/a O:z/b O:x/d O:x/c O:x/c A:y/c O:x/c && + test_cmp expect actual && + + git hash-object >actual \ + y/c~B^0 y/c~HEAD && + git rev-parse >expect \ + O:x/c A:y/c && + test_cmp expect actual + ) +' + +# Testcase 11f, Avoid deleting not-up-to-date w/ dir rename/rename(2to1) +# Commit O: z/{a,b}, x/{c_1,d_2} +# Commit A: y/{a,b,wham_1}, x/d_2, except y/wham has uncommitted mods +# Commit B: z/{a,b,wham_2}, x/c_1 +# Expected: Failed Merge; y/{a,b} + untracked y/{wham~B^0,wham~B^HEAD} + +# y/wham with dirty changes from before merge + +# CONFLICT(rename/rename) x/c vs x/d -> y/wham +# ERROR_MSG(Refusing to lose dirty file at y/wham) + +test_expect_success '11f-setup: Avoid deleting not-uptodate with dir rename/rename(2to1)' ' + test_create_repo 11f && + ( + cd 11f && + + mkdir z x && + echo a >z/a && + echo b >z/b && + test_seq 1 10 >x/c && + echo d >x/d && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z/ y/ && + git mv x/c y/wham && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/d z/wham && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '11f-check: Avoid deleting not-uptodate with dir rename/rename(2to1)' ' + ( + cd 11f && + + git checkout A^0 && + echo important >>y/wham && + + test_must_fail git merge -s recursive B^0 >out 2>err && + test_i18ngrep "CONFLICT (rename/rename)" out && + test_i18ngrep "Refusing to lose dirty file at y/wham" out && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 4 out && + + test_seq 1 10 >expected && + echo important >>expected && + test_cmp expected y/wham && + + test_must_fail git rev-parse :1:y/wham && + git hash-object >actual \ + y/wham~B^0 y/wham~HEAD && + git rev-parse >expect \ + O:x/d O:x/c && + test_cmp expect actual && + + git rev-parse >actual \ + :0:y/a :0:y/b :2:y/wham :3:y/wham && + git rev-parse >expect \ + O:z/a O:z/b O:x/c O:x/d && + test_cmp expect actual + ) +' + +########################################################################### +# SECTION 12: Everything else +# +# Tests suggested by others. Tests added after implementation completed +# and submitted. Grab bag. +########################################################################### + +# Testcase 12a, Moving one directory hierarchy into another +# (Related to testcase 9a) +# Commit O: node1/{leaf1,leaf2}, node2/{leaf3,leaf4} +# Commit A: node1/{leaf1,leaf2,node2/{leaf3,leaf4}} +# Commit B: node1/{leaf1,leaf2,leaf5}, node2/{leaf3,leaf4,leaf6} +# Expected: node1/{leaf1,leaf2,leaf5,node2/{leaf3,leaf4,leaf6}} + +test_expect_success '12a-setup: Moving one directory hierarchy into another' ' + test_create_repo 12a && + ( + cd 12a && + + mkdir -p node1 node2 && + echo leaf1 >node1/leaf1 && + echo leaf2 >node1/leaf2 && + echo leaf3 >node2/leaf3 && + echo leaf4 >node2/leaf4 && + git add node1 node2 && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv node2/ node1/ && + test_tick && + git commit -m "A" && + + git checkout B && + echo leaf5 >node1/leaf5 && + echo leaf6 >node2/leaf6 && + git add node1 node2 && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '12a-check: Moving one directory hierarchy into another' ' + ( + cd 12a && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 6 out && + + git rev-parse >actual \ + HEAD:node1/leaf1 HEAD:node1/leaf2 HEAD:node1/leaf5 \ + HEAD:node1/node2/leaf3 \ + HEAD:node1/node2/leaf4 \ + HEAD:node1/node2/leaf6 && + git rev-parse >expect \ + O:node1/leaf1 O:node1/leaf2 B:node1/leaf5 \ + O:node2/leaf3 \ + O:node2/leaf4 \ + B:node2/leaf6 && + test_cmp expect actual + ) +' + +# Testcase 12b, Moving two directory hierarchies into each other +# (Related to testcases 1c and 12c) +# Commit O: node1/{leaf1, leaf2}, node2/{leaf3, leaf4} +# Commit A: node1/{leaf1, leaf2, node2/{leaf3, leaf4}} +# Commit B: node2/{leaf3, leaf4, node1/{leaf1, leaf2}} +# Expected: node1/node2/node1/{leaf1, leaf2}, +# node2/node1/node2/{leaf3, leaf4} +# NOTE: Without directory renames, we would expect +# node2/node1/{leaf1, leaf2}, +# node1/node2/{leaf3, leaf4} +# with directory rename detection, we note that +# commit A renames node2/ -> node1/node2/ +# commit B renames node1/ -> node2/node1/ +# therefore, applying those directory renames to the initial result +# (making all four paths experience a transitive renaming), yields +# the expected result. +# +# You may ask, is it weird to have two directories rename each other? +# To which, I can do no more than shrug my shoulders and say that +# even simple rules give weird results when given weird inputs. + +test_expect_success '12b-setup: Moving one directory hierarchy into another' ' + test_create_repo 12b && + ( + cd 12b && + + mkdir -p node1 node2 && + echo leaf1 >node1/leaf1 && + echo leaf2 >node1/leaf2 && + echo leaf3 >node2/leaf3 && + echo leaf4 >node2/leaf4 && + git add node1 node2 && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv node2/ node1/ && + test_tick && + git commit -m "A" && + + git checkout B && + git mv node1/ node2/ && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '12b-check: Moving one directory hierarchy into another' ' + ( + cd 12b && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 4 out && + + git rev-parse >actual \ + HEAD:node1/node2/node1/leaf1 \ + HEAD:node1/node2/node1/leaf2 \ + HEAD:node2/node1/node2/leaf3 \ + HEAD:node2/node1/node2/leaf4 && + git rev-parse >expect \ + O:node1/leaf1 \ + O:node1/leaf2 \ + O:node2/leaf3 \ + O:node2/leaf4 && + test_cmp expect actual + ) +' + +# Testcase 12c, Moving two directory hierarchies into each other w/ content merge +# (Related to testcase 12b) +# Commit O: node1/{ leaf1_1, leaf2_1}, node2/{leaf3_1, leaf4_1} +# Commit A: node1/{ leaf1_2, leaf2_2, node2/{leaf3_2, leaf4_2}} +# Commit B: node2/{node1/{leaf1_3, leaf2_3}, leaf3_3, leaf4_3} +# Expected: Content merge conflicts for each of: +# node1/node2/node1/{leaf1, leaf2}, +# node2/node1/node2/{leaf3, leaf4} +# NOTE: This is *exactly* like 12c, except that every path is modified on +# each side of the merge. + +test_expect_success '12c-setup: Moving one directory hierarchy into another w/ content merge' ' + test_create_repo 12c && + ( + cd 12c && + + mkdir -p node1 node2 && + printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf1\n" >node1/leaf1 && + printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf2\n" >node1/leaf2 && + printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf3\n" >node2/leaf3 && + printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf4\n" >node2/leaf4 && + git add node1 node2 && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv node2/ node1/ && + for i in `git ls-files`; do echo side A >>$i; done && + git add -u && + test_tick && + git commit -m "A" && + + git checkout B && + git mv node1/ node2/ && + for i in `git ls-files`; do echo side B >>$i; done && + git add -u && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '12c-check: Moving one directory hierarchy into another w/ content merge' ' + ( + cd 12c && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 && + + git ls-files -u >out && + test_line_count = 12 out && + + git rev-parse >actual \ + :1:node1/node2/node1/leaf1 \ + :1:node1/node2/node1/leaf2 \ + :1:node2/node1/node2/leaf3 \ + :1:node2/node1/node2/leaf4 \ + :2:node1/node2/node1/leaf1 \ + :2:node1/node2/node1/leaf2 \ + :2:node2/node1/node2/leaf3 \ + :2:node2/node1/node2/leaf4 \ + :3:node1/node2/node1/leaf1 \ + :3:node1/node2/node1/leaf2 \ + :3:node2/node1/node2/leaf3 \ + :3:node2/node1/node2/leaf4 && + git rev-parse >expect \ + O:node1/leaf1 \ + O:node1/leaf2 \ + O:node2/leaf3 \ + O:node2/leaf4 \ + A:node1/leaf1 \ + A:node1/leaf2 \ + A:node1/node2/leaf3 \ + A:node1/node2/leaf4 \ + B:node2/node1/leaf1 \ + B:node2/node1/leaf2 \ + B:node2/leaf3 \ + B:node2/leaf4 && + test_cmp expect actual + ) +' + +test_done diff --git a/t/t6044-merge-unrelated-index-changes.sh b/t/t6044-merge-unrelated-index-changes.sh index 01023486c5..23b86fb977 100755 --- a/t/t6044-merge-unrelated-index-changes.sh +++ b/t/t6044-merge-unrelated-index-changes.sh @@ -6,18 +6,21 @@ test_description="merges with unrelated index changes" # Testcase for some simple merges # A -# o-----o B +# o-------o B # \ -# \---o C +# \-----o C # \ -# \-o D +# \---o D # \ -# o E +# \-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 && @@ -29,6 +32,7 @@ test_expect_success 'setup trivial merges' ' git branch C && git branch D && git branch E && + git branch F && git checkout B && echo b >b && @@ -52,7 +56,10 @@ test_expect_success 'setup trivial merges' ' git mv a subdir/a && echo e >subdir/e && git add subdir && - test_tick && git commit -m E + test_tick && git commit -m E && + + git checkout F && + test_tick && git commit --allow-empty -m F ' test_expect_success 'ff update' ' @@ -105,6 +112,15 @@ test_expect_success 'recursive' ' 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 && 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/t6046-merge-skip-unneeded-updates.sh b/t/t6046-merge-skip-unneeded-updates.sh new file mode 100755 index 0000000000..fcefffcaec --- /dev/null +++ b/t/t6046-merge-skip-unneeded-updates.sh @@ -0,0 +1,761 @@ +#!/bin/sh + +test_description="merge cases" + +# The setup for all of them, pictorially, is: +# +# A +# o +# / \ +# O o ? +# \ / +# o +# B +# +# To help make it easier to follow the flow of tests, they have been +# divided into sections and each test will start with a quick explanation +# of what commits O, A, and B contain. +# +# Notation: +# z/{b,c} means files z/b and z/c both exist +# x/d_1 means file x/d exists with content d1. (Purpose of the +# underscore notation is to differentiate different +# files that might be renamed into each other's paths.) + +. ./test-lib.sh + + +########################################################################### +# SECTION 1: Cases involving no renames (one side has subset of changes of +# the other side) +########################################################################### + +# Testcase 1a, Changes on A, subset of changes on B +# Commit O: b_1 +# Commit A: b_2 +# Commit B: b_3 +# Expected: b_2 + +test_expect_success '1a-setup: Modify(A)/Modify(B), change on B subset of A' ' + test_create_repo 1a && + ( + cd 1a && + + test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b && + git add b && + test_tick && + git commit -m "A" && + + git checkout B && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '1a-check-L: Modify(A)/Modify(B), change on B subset of A' ' + test_when_finished "git -C 1a reset --hard" && + test_when_finished "git -C 1a clean -fd" && + ( + cd 1a && + + git checkout A^0 && + + test-tool chmtime =31337 b && + test-tool chmtime -v +0 b >expected-mtime && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && + + test_i18ngrep "Skipped b" out && + test_must_be_empty err && + + test-tool chmtime -v +0 b >actual-mtime && + test_cmp expected-mtime actual-mtime && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual HEAD:b && + git rev-parse >expect A:b && + test_cmp expect actual && + + git hash-object b >actual && + git rev-parse A:b >expect && + test_cmp expect actual + ) +' + +test_expect_success '1a-check-R: Modify(A)/Modify(B), change on B subset of A' ' + test_when_finished "git -C 1a reset --hard" && + test_when_finished "git -C 1a clean -fd" && + ( + cd 1a && + + git checkout B^0 && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err && + + test_i18ngrep "Auto-merging b" out && + test_must_be_empty err && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual HEAD:b && + git rev-parse >expect A:b && + test_cmp expect actual && + + git hash-object b >actual && + git rev-parse A:b >expect && + test_cmp expect actual + ) +' + + +########################################################################### +# SECTION 2: Cases involving basic renames +########################################################################### + +# Testcase 2a, Changes on A, rename on B +# Commit O: b_1 +# Commit A: b_2 +# Commit B: c_1 +# Expected: c_2 + +test_expect_success '2a-setup: Modify(A)/rename(B)' ' + test_create_repo 2a && + ( + cd 2a && + + test_seq 1 10 >b && + git add b && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_seq 1 11 >b && + git add b && + test_tick && + git commit -m "A" && + + git checkout B && + git mv b c && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '2a-check-L: Modify/rename, merge into modify side' ' + test_when_finished "git -C 2a reset --hard" && + test_when_finished "git -C 2a clean -fd" && + ( + cd 2a && + + git checkout A^0 && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && + + test_i18ngrep ! "Skipped c" out && + test_must_be_empty err && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual HEAD:c && + git rev-parse >expect A:b && + test_cmp expect actual && + + git hash-object c >actual && + git rev-parse A:b >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:b && + test_path_is_missing b + ) +' + +test_expect_success '2a-check-R: Modify/rename, merge into rename side' ' + test_when_finished "git -C 2a reset --hard" && + test_when_finished "git -C 2a clean -fd" && + ( + cd 2a && + + git checkout B^0 && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err && + + test_i18ngrep ! "Skipped c" out && + test_must_be_empty err && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual HEAD:c && + git rev-parse >expect A:b && + test_cmp expect actual && + + git hash-object c >actual && + git rev-parse A:b >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:b && + test_path_is_missing b + ) +' + +# Testcase 2b, Changed and renamed on A, subset of changes on B +# Commit O: b_1 +# Commit A: c_2 +# Commit B: b_3 +# Expected: c_2 + +test_expect_success '2b-setup: Rename+Mod(A)/Mod(B), B mods subset of A' ' + test_create_repo 2b && + ( + cd 2b && + + test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b && + git add b && + git mv b c && + test_tick && + git commit -m "A" && + + git checkout B && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '2b-check-L: Rename+Mod(A)/Mod(B), B mods subset of A' ' + test_when_finished "git -C 2b reset --hard" && + test_when_finished "git -C 2b clean -fd" && + ( + cd 2b && + + git checkout A^0 && + + test-tool chmtime =31337 c && + test-tool chmtime -v +0 c >expected-mtime && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && + + test_i18ngrep "Skipped c" out && + test_must_be_empty err && + + test-tool chmtime -v +0 c >actual-mtime && + test_cmp expected-mtime actual-mtime && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual HEAD:c && + git rev-parse >expect A:c && + test_cmp expect actual && + + git hash-object c >actual && + git rev-parse A:c >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:b && + test_path_is_missing b + ) +' + +test_expect_success '2b-check-R: Rename+Mod(A)/Mod(B), B mods subset of A' ' + test_when_finished "git -C 2b reset --hard" && + test_when_finished "git -C 2b clean -fd" && + ( + cd 2b && + + git checkout B^0 && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err && + + test_i18ngrep "Auto-merging c" out && + test_must_be_empty err && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual HEAD:c && + git rev-parse >expect A:c && + test_cmp expect actual && + + git hash-object c >actual && + git rev-parse A:c >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:b && + test_path_is_missing b + ) +' + +# Testcase 2c, Changes on A, rename on B +# Commit O: b_1 +# Commit A: b_2, c_3 +# Commit B: c_1 +# Expected: rename/add conflict c_2 vs c_3 +# +# NOTE: Since A modified b_1->b_2, and B renamed b_1->c_1, the threeway +# merge of those files should result in c_2. We then should have a +# rename/add conflict between c_2 and c_3. However, if we note in +# merge_content() that A had the right contents (b_2 has same +# contents as c_2, just at a different name), and that A had the +# right path present (c_3 existed) and thus decides that it can +# skip the update, then we're in trouble. This test verifies we do +# not make that particular mistake. + +test_expect_success '2c-setup: Modify b & add c VS rename b->c' ' + test_create_repo 2c && + ( + cd 2c && + + test_seq 1 10 >b && + git add b && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_seq 1 11 >b && + echo whatever >c && + git add b c && + test_tick && + git commit -m "A" && + + git checkout B && + git mv b c && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '2c-check: Modify b & add c VS rename b->c' ' + ( + cd 2c && + + git checkout A^0 && + + GIT_MERGE_VERBOSITY=3 test_must_fail git merge -s recursive B^0 >out 2>err && + + test_i18ngrep "CONFLICT (rename/add): Rename b->c" out && + test_i18ngrep ! "Skipped c" out && + test_must_be_empty err + + # FIXME: rename/add conflicts are horribly broken right now; + # when I get back to my patch series fixing it and + # rename/rename(2to1) conflicts to bring them in line with + # how add/add conflicts behave, then checks like the below + # could be added. But that patch series is waiting until + # the rename-directory-detection series lands, which this + # is part of. And in the mean time, I do not want to further + # enforce broken behavior. So for now, the main test is the + # one above that err is an empty file. + + #git ls-files -s >index_files && + #test_line_count = 2 index_files && + + #git rev-parse >actual :2:c :3:c && + #git rev-parse >expect A:b A:c && + #test_cmp expect actual && + + #git cat-file -p A:b >>merged && + #git cat-file -p A:c >>merge-me && + #>empty && + #test_must_fail git merge-file \ + # -L "Temporary merge branch 1" \ + # -L "" \ + # -L "Temporary merge branch 2" \ + # merged empty merge-me && + #sed -e "s/^\([<=>]\)/\1\1\1/" merged >merged-internal && + + #git hash-object c >actual && + #git hash-object merged-internal >expect && + #test_cmp expect actual && + + #test_path_is_missing b + ) +' + + +########################################################################### +# SECTION 3: Cases involving directory renames +# +# NOTE: +# Directory renames only apply when one side renames a directory, and the +# other side adds or renames a path into that directory. Applying the +# directory rename to that new path creates a new pathname that didn't +# exist on either side of history. Thus, it is impossible for the +# merge contents to already be at the right path, so all of these checks +# exist just to make sure that updates are not skipped. +########################################################################### + +# Testcase 3a, Change + rename into dir foo on A, dir rename foo->bar on B +# Commit O: bq_1, foo/whatever +# Commit A: foo/{bq_2, whatever} +# Commit B: bq_1, bar/whatever +# Expected: bar/{bq_2, whatever} + +test_expect_success '3a-setup: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' + test_create_repo 3a && + ( + cd 3a && + + mkdir foo && + test_seq 1 10 >bq && + test_write_lines a b c d e f g h i j k >foo/whatever && + git add bq foo/whatever && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_seq 1 11 >bq && + git add bq && + git mv bq foo/ && + test_tick && + git commit -m "A" && + + git checkout B && + git mv foo/ bar/ && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '3a-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' + test_when_finished "git -C 3a reset --hard" && + test_when_finished "git -C 3a clean -fd" && + ( + cd 3a && + + git checkout A^0 && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && + + test_i18ngrep ! "Skipped bar/bq" out && + test_must_be_empty err && + + git ls-files -s >index_files && + test_line_count = 2 index_files && + + git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever && + git rev-parse >expect A:foo/bq A:foo/whatever && + test_cmp expect actual && + + git hash-object bar/bq bar/whatever >actual && + git rev-parse A:foo/bq A:foo/whatever >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:bq HEAD:foo/bq && + test_path_is_missing bq foo/bq foo/whatever + ) +' + +test_expect_success '3a-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' + test_when_finished "git -C 3a reset --hard" && + test_when_finished "git -C 3a clean -fd" && + ( + cd 3a && + + git checkout B^0 && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err && + + test_i18ngrep ! "Skipped bar/bq" out && + test_must_be_empty err && + + git ls-files -s >index_files && + test_line_count = 2 index_files && + + git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever && + git rev-parse >expect A:foo/bq A:foo/whatever && + test_cmp expect actual && + + git hash-object bar/bq bar/whatever >actual && + git rev-parse A:foo/bq A:foo/whatever >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:bq HEAD:foo/bq && + test_path_is_missing bq foo/bq foo/whatever + ) +' + +# Testcase 3b, rename into dir foo on A, dir rename foo->bar + change on B +# Commit O: bq_1, foo/whatever +# Commit A: foo/{bq_1, whatever} +# Commit B: bq_2, bar/whatever +# Expected: bar/{bq_2, whatever} + +test_expect_success '3b-setup: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' + test_create_repo 3b && + ( + cd 3b && + + mkdir foo && + test_seq 1 10 >bq && + test_write_lines a b c d e f g h i j k >foo/whatever && + git add bq foo/whatever && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv bq foo/ && + test_tick && + git commit -m "A" && + + git checkout B && + test_seq 1 11 >bq && + git add bq && + git mv foo/ bar/ && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '3b-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' + test_when_finished "git -C 3b reset --hard" && + test_when_finished "git -C 3b clean -fd" && + ( + cd 3b && + + git checkout A^0 && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && + + test_i18ngrep ! "Skipped bar/bq" out && + test_must_be_empty err && + + git ls-files -s >index_files && + test_line_count = 2 index_files && + + git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever && + git rev-parse >expect B:bq A:foo/whatever && + test_cmp expect actual && + + git hash-object bar/bq bar/whatever >actual && + git rev-parse B:bq A:foo/whatever >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:bq HEAD:foo/bq && + test_path_is_missing bq foo/bq foo/whatever + ) +' + +test_expect_success '3b-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' + test_when_finished "git -C 3b reset --hard" && + test_when_finished "git -C 3b clean -fd" && + ( + cd 3b && + + git checkout B^0 && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err && + + test_i18ngrep ! "Skipped bar/bq" out && + test_must_be_empty err && + + git ls-files -s >index_files && + test_line_count = 2 index_files && + + git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever && + git rev-parse >expect B:bq A:foo/whatever && + test_cmp expect actual && + + git hash-object bar/bq bar/whatever >actual && + git rev-parse B:bq A:foo/whatever >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:bq HEAD:foo/bq && + test_path_is_missing bq foo/bq foo/whatever + ) +' + +########################################################################### +# SECTION 4: Cases involving dirty changes +########################################################################### + +# Testcase 4a, Changed on A, subset of changes on B, locally modified +# Commit O: b_1 +# Commit A: b_2 +# Commit B: b_3 +# Working copy: b_4 +# Expected: b_2 for merge, b_4 in working copy + +test_expect_success '4a-setup: Change on A, change on B subset of A, dirty mods present' ' + test_create_repo 4a && + ( + cd 4a && + + test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b && + git add b && + test_tick && + git commit -m "A" && + + git checkout B && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "B" + ) +' + +# NOTE: For as long as we continue using unpack_trees() without index_only +# set to true, it will error out on a case like this claiming the the locally +# modified file would be overwritten by the merge. Getting this testcase +# correct requires doing the merge in-memory first, then realizing that no +# updates to the file are necessary, and thus that we can just leave the path +# alone. +test_expect_failure '4a-check: Change on A, change on B subset of A, dirty mods present' ' + test_when_finished "git -C 4a reset --hard" && + test_when_finished "git -C 4a clean -fd" && + ( + cd 4a && + + git checkout A^0 && + echo "File rewritten" >b && + + test-tool chmtime =31337 b && + test-tool chmtime -v +0 b >expected-mtime && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && + + test_i18ngrep "Skipped b" out && + test_must_be_empty err && + + test-tool chmtime -v +0 b >actual-mtime && + test_cmp expected-mtime actual-mtime && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual :0:b && + git rev-parse >expect A:b && + test_cmp expect actual && + + git hash-object b >actual && + echo "File rewritten" | git hash-object --stdin >expect && + test_cmp expect actual + ) +' + +# Testcase 4b, Changed+renamed on A, subset of changes on B, locally modified +# Commit O: b_1 +# Commit A: c_2 +# Commit B: b_3 +# Working copy: c_4 +# Expected: c_2 + +test_expect_success '4b-setup: Rename+Mod(A)/Mod(B), change on B subset of A, dirty mods present' ' + test_create_repo 4b && + ( + cd 4b && + + test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b && + git add b && + git mv b c && + test_tick && + git commit -m "A" && + + git checkout B && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '4b-check: Rename+Mod(A)/Mod(B), change on B subset of A, dirty mods present' ' + test_when_finished "git -C 4b reset --hard" && + test_when_finished "git -C 4b clean -fd" && + ( + cd 4b && + + git checkout A^0 && + echo "File rewritten" >c && + + test-tool chmtime =31337 c && + test-tool chmtime -v +0 c >expected-mtime && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && + + test_i18ngrep "Skipped c" out && + test_must_be_empty err && + + test-tool chmtime -v +0 c >actual-mtime && + test_cmp expected-mtime actual-mtime && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual :0:c && + git rev-parse >expect A:c && + test_cmp expect actual && + + git hash-object c >actual && + echo "File rewritten" | git hash-object --stdin >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:b && + test_path_is_missing b + ) +' + +test_done diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh index c630aba657..aa3e249639 100755 --- a/t/t6050-replace.sh +++ b/t/t6050-replace.sh @@ -4,8 +4,6 @@ # test_description='Tests replace refs functionality' -exec </dev/null - . ./test-lib.sh . "$TEST_DIRECTORY/lib-gpg.sh" @@ -444,4 +442,32 @@ test_expect_success GPG '--graft on a commit with a mergetag' ' git replace -d $HASH10 ' +test_expect_success '--convert-graft-file' ' + git checkout -b with-graft-file && + test_commit root2 && + git reset --hard root2^ && + test_commit root1 && + test_commit after-root1 && + test_tick && + git merge -m merge-root2 root2 && + + : add and convert graft file && + printf "%s\n%s %s\n\n# comment\n%s\n" \ + $(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \ + >.git/info/grafts && + git replace --convert-graft-file && + test_path_is_missing .git/info/grafts && + + : verify that the history is now "grafted" && + git rev-list HEAD >out && + test_line_count = 4 out && + + : create invalid graft file and verify that it is not deleted && + test_when_finished "rm -f .git/info/grafts" && + echo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts && + test_must_fail git replace --convert-graft-file 2>err && + test_i18ngrep "$EMPTY_BLOB $EMPTY_TREE" err && + test_i18ngrep "$EMPTY_BLOB $EMPTY_TREE" .git/info/grafts +' + test_done 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 8c617981a3..7683e4a114 100755 --- a/t/t6101-rev-parse-parents.sh +++ b/t/t6101-rev-parse-parents.sh @@ -214,4 +214,12 @@ test_expect_success 'rev-list merge^-1x (garbage after ^-1)' ' test_must_fail git rev-list merge^-1x ' +test_expect_success 'rev-parse $garbage^@ does not segfault' ' + test_must_fail git rev-parse $EMPTY_TREE^@ +' + +test_expect_success 'rev-parse $garbage...$garbage does not segfault' ' + test_must_fail git rev-parse $EMPTY_TREE...$EMPTY_BLOB +' + test_done diff --git a/t/t6111-rev-list-treesame.sh b/t/t6111-rev-list-treesame.sh index 32474c23d3..4244638285 100755 --- a/t/t6111-rev-list-treesame.sh +++ b/t/t6111-rev-list-treesame.sh @@ -20,7 +20,7 @@ note () { } unnote () { - git name-rev --tags --stdin | sed -e "s|$_x40 (tags/\([^)]*\))\([ ]\)|\1\2|g" + git name-rev --tags --stdin | sed -e "s|$OID_REGEX (tags/\([^)]*\))\([ ]\)|\1\2|g" } test_expect_success setup ' 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 85f269411c..84dd1cb690 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -122,7 +122,7 @@ test_expect_success 'describe --contains defaults to HEAD without commit-ish' ' ' : >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 ' @@ -182,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) && @@ -194,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) && @@ -206,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 $ZERO_OID +' + test_done diff --git a/t/t6132-pathspec-exclude.sh b/t/t6132-pathspec-exclude.sh index d51595cf6b..eb829fce97 100755 --- a/t/t6132-pathspec-exclude.sh +++ b/t/t6132-pathspec-exclude.sh @@ -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/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 039509a9cb..48379aa0ee 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -7,6 +7,7 @@ test_description='for-each-ref test' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-gpg.sh +. "$TEST_DIRECTORY"/lib-terminal.sh # Mon Jul 3 23:18:43 2006 +0000 datestamp=1151968723 @@ -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,17 +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 @@ -99,6 +126,8 @@ 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 '' @@ -134,16 +163,6 @@ test_expect_success 'Check invalid atoms names are errors' ' test_must_fail git for-each-ref --format="%(INVALID)" refs/heads ' -test_expect_success 'arguments to :strip must be positive integers' ' - test_must_fail git for-each-ref --format="%(refname:strip=0)" && - test_must_fail git for-each-ref --format="%(refname:strip=-1)" && - test_must_fail git for-each-ref --format="%(refname:strip=foo)" -' - -test_expect_success 'stripping refnames too far gives an error' ' - test_must_fail git for-each-ref --format="%(refname:strip=3)" -' - test_expect_success 'Check format specifiers are ignored in naming date atoms' ' git for-each-ref --format="%(authordate)" refs/heads && git for-each-ref --format="%(authordate:default) %(authordate)" refs/heads && @@ -164,6 +183,12 @@ test_expect_success 'Check invalid format specifiers are errors' ' test_must_fail git for-each-ref --format="%(authordate:INVALID)" refs/heads ' +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_date () { f=$1 && committer_date=$2 && @@ -285,7 +310,7 @@ 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" && + long="long format -- $ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID" && echo $long >expected && git for-each-ref --format="%(authordate:format:$long)" refs/heads >actual && test_cmp expected actual @@ -348,11 +373,8 @@ 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 @@ -362,6 +384,8 @@ test_expect_success 'setup for upstream:track[short]' ' 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 '>' @@ -372,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" && @@ -387,21 +411,37 @@ test_expect_success 'Check for invalid refname format' ' test_must_fail git for-each-ref --format="%(refname:INVALID)" ' -get_color () -{ - git config --get-color no.such.slot "$1" -} +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)" +' -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 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 +' -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) does not show color without tty' ' + TERM=vt100 git for-each-ref --format="$color_format" >actual && + test_cmp expected.bare actual +' + +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 '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 @@ -554,13 +594,205 @@ test_expect_success 'Verify sort with multiple keys' ' 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 HEAD && + 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 index c734ce2388..49cc65bb58 100755 --- a/t/t6301-for-each-ref-errors.sh +++ b/t/t6301-for-each-ref-errors.sh @@ -4,7 +4,7 @@ test_description='for-each-ref errors for broken refs' . ./test-lib.sh -ZEROS=$_z40 +ZEROS=$ZERO_OID MISSING=abababababababababababababababababababab test_expect_success setup ' diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh index d0ab09f4bd..fc067ed672 100755 --- a/t/t6302-for-each-ref-filter.sh +++ b/t/t6302-for-each-ref-filter.sh @@ -93,6 +93,22 @@ test_expect_success 'filtering with --contains' ' 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)" ' @@ -327,4 +343,102 @@ test_expect_success 'reverse version sort' ' 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 1762dfa6a3..818435f04e 100755 --- a/t/t6500-gc.sh +++ b/t/t6500-gc.sh @@ -5,6 +5,13 @@ test_description='basic git gc tests . ./test-lib.sh +test_expect_success 'setup' ' + # do not let the amount of physical memory affects gc + # behavior, make sure we always pack everything to one pack by + # default + git config gc.bigPackThreshold 2g +' + test_expect_success 'gc empty repository' ' git gc ' @@ -43,6 +50,31 @@ test_expect_success 'gc is not aborted due to a stale symref' ' ) ' +test_expect_success 'gc --keep-largest-pack' ' + test_create_repo keep-pack && + ( + cd keep-pack && + test_commit one && + test_commit two && + test_commit three && + git gc && + ( cd .git/objects/pack && ls *.pack ) >pack-list && + test_line_count = 1 pack-list && + BASE_PACK=.git/objects/pack/pack-*.pack && + test_commit four && + git repack -d && + test_commit five && + git repack -d && + ( cd .git/objects/pack && ls *.pack ) >pack-list && + test_line_count = 3 pack-list && + git gc --keep-largest-pack && + ( cd .git/objects/pack && ls *.pack ) >pack-list && + test_line_count = 2 pack-list && + test_path_is_file $BASE_PACK && + git fsck + ) +' + 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 && @@ -67,5 +99,57 @@ test_expect_success 'auto gc with too many loose objects does not attempt to cre 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 cf076dcd94..033871ee5f 100755 --- a/t/t6501-freshen-objects.sh +++ b/t/t6501-freshen-objects.sh @@ -72,8 +72,7 @@ for repack in '' true; do ' test_expect_success "simulate time passing ($title)" ' - find .git/objects -type f | - xargs test-chmtime -v -86400 + test-tool chmtime --get -86400 $(find .git/objects -type f) ' test_expect_success "start writing new commit with old blob ($title)" ' @@ -103,8 +102,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 + test-tool chmtime --get -86400 $(find .git/objects -type f) ' test_expect_success "start writing new commit with same tree ($title)" ' @@ -129,7 +127,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 +142,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 e365d1ff77..cc3fd2baf2 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' \ @@ -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' \ @@ -452,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 && @@ -488,7 +492,32 @@ test_expect_success 'moving a submodule in nested directories' ' git config -f ../.gitmodules submodule.deep/directory/hierarchy/sub.path >../actual && echo "directory/hierarchy/sub" >../expect ) && - test_cmp actual expect + test_cmp expect actual +' + +test_expect_success '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 cb8fbd8e5e..ec4b160ddb 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -187,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" ' @@ -205,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' ' @@ -258,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' ' @@ -313,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 && @@ -341,6 +365,22 @@ test_expect_success 'prune empty works even without index/tree filters' ' 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 && @@ -433,4 +473,18 @@ test_expect_success 'tree-filter deals with object name vs pathname ambiguity' ' 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 8b0f71a2ac..d7b319e919 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,7 +17,6 @@ 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 @@ -27,6 +27,30 @@ test_expect_success 'listing all tags in an empty tree should output nothing' ' 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' \ '! (tag_exists mytag)' @@ -47,6 +71,7 @@ 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 && @@ -56,9 +81,25 @@ test_expect_success 'creating a tag using default HEAD should succeed' ' ' 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 + 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' ' @@ -66,11 +107,30 @@ test_expect_success '--create-reflog does not create reflog on failure' ' 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' ' git tag -l && 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 @@ -81,13 +141,15 @@ 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' -# 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 \ @@ -122,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: @@ -287,8 +349,21 @@ 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 && + COLUMNS=41 git tag -l --column=row >actual && cat >expected <<\EOF && a1 aa1 cba t210 t211 v0.2.1 v1.0 v1.0.1 v1.1.3 @@ -297,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 @@ -314,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 @@ -380,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. @@ -393,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 @@ -572,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 && @@ -585,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 && @@ -847,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 @@ -924,7 +1056,18 @@ test_expect_success GPG \ git tag -s -F sigblanknonlfile blanknonlfile-signed-tag && get_tag_msg blanknonlfile-signed-tag >actual && test_cmp expect actual && - git tag -v signed-tag + git tag -v blanknonlfile-signed-tag +' + +test_expect_success GPG 'signed tag with embedded PGP message' ' + cat >msg <<-\EOF && + -----BEGIN PGP MESSAGE----- + + this is not a real PGP message + -----END PGP MESSAGE----- + EOF + git tag -s -F msg confusing-pgp-message && + git tag -v confusing-pgp-message ' # messages with commented lines for signed tags: @@ -1200,11 +1343,10 @@ 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 (bad key)' \ - 'test_must_fail git tag -s -m tail tag-gpg-failure' -git config --unset user.signingkey + '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 \ @@ -1322,6 +1464,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 @@ -1331,6 +1490,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 @@ -1340,6 +1510,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' ' @@ -1361,6 +1544,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 @@ -1382,6 +1578,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 @@ -1390,21 +1600,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' ' @@ -1484,7 +1749,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 @@ -1495,6 +1760,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 @@ -1509,13 +1775,12 @@ test_expect_success 'invalid sort parameter on command line' ' ' test_expect_success 'invalid sort parameter in configuratoin' ' - git config tag.sort "v:notvalid" && + 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 && @@ -1530,6 +1795,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 @@ -1541,14 +1807,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 +' -# we require ulimit, this excludes Windows -test_expect_success ULIMIT_STACK_SIZE '--contains works in a deep repo' ' +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 +' + +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 @@ -1564,29 +1920,68 @@ EOF" git checkout master && git tag far-far-away HEAD^ && run_with_limited_stack git tag --contains HEAD >actual && - test_cmp expect 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/foo1.10 - refname : refs/tags/foo1.3 - refname : refs/tags/foo1.6 - refname : refs/tags/foo1.6-rc1 - refname : refs/tags/foo1.6-rc2 + refname : refs/tags/v1.0 + refname : refs/tags/v1.0.1 + refname : refs/tags/v1.1.3 EOF - git tag -l --format="refname : %(refname)" "foo*" >actual && + 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 cannot be used in non-list mode' ' - test_must_fail git tag --merged=mergetest-2 foo +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' ' @@ -1606,6 +2001,11 @@ test_expect_success '--no-merged show unmerged tags' ' 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 && @@ -1614,4 +2014,47 @@ test_expect_success 'ambiguous branch/tags not marked' ' 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..b2ca77b338 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,18 +107,12 @@ 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 -if echo 'echo space > "$1"' > "e space.sh" -then - # FS supports spaces in filenames - test_set_prereq SPACES_IN_FILENAMES -fi - -test_expect_success SPACES_IN_FILENAMES 'editor with a space' ' - +test_expect_success 'editor with a space' ' + echo "echo space >\$1" >"e space.sh" && chmod a+x "e space.sh" && GIT_EDITOR="./e\ space.sh" git commit --amend && test space = "$(git show -s --pretty=format:%s)" @@ -126,7 +120,7 @@ test_expect_success SPACES_IN_FILENAMES 'editor with a space' ' ' unset GIT_EDITOR -test_expect_success SPACES_IN_FILENAMES 'core.editor with a space' ' +test_expect_success 'core.editor with a space' ' git config core.editor \"./e\ space.sh\" && git commit --amend && diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh index c8dc665f2f..7541ba5edb 100755 --- a/t/t7006-pager.sh +++ b/t/t7006-pager.sh @@ -110,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 && @@ -134,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() { @@ -159,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 ' @@ -167,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 ' @@ -186,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 ' @@ -360,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' @@ -469,4 +643,18 @@ test_expect_success 'command with underscores does not complain' ' 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 9c9c378119..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 && @@ -162,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 ' @@ -172,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..9ba9f24ad2 100755 --- a/t/t7009-filter-branch-null-sha1.sh +++ b/t/t7009-filter-branch-null-sha1.sh @@ -12,7 +12,7 @@ test_expect_success 'setup: base commits' ' test_expect_success 'setup: a commit with a bogus null sha1 in the tree' ' { git ls-tree HEAD && - printf "160000 commit $_z40\\tbroken\\n" + printf "160000 commit $ZERO_OID\\tbroken\\n" } >broken-tree && echo "add broken entry" >msg && @@ -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 84f41451ec..37525cae3a 100755 --- a/t/t7011-skip-worktree-reading.sh +++ b/t/t7011-skip-worktree-reading.sh @@ -118,7 +118,7 @@ test_expect_success 'grep with skip-worktree file' ' test "$(git grep --no-ext-grep test)" = "1:test" ' -echo ":000000 100644 $_z40 $EMPTY_BLOB A 1" > expected +echo ":000000 100644 $ZERO_OID $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/t7030-verify-tag.sh b/t/t7030-verify-tag.sh index 07079a41c4..291a1e2b07 100755 --- a/t/t7030-verify-tag.sh +++ b/t/t7030-verify-tag.sh @@ -74,7 +74,7 @@ test_expect_success GPG 'verify and show signatures' ' test_expect_success GPG 'detect fudged signature' ' git cat-file tag seventh-signed >raw && - sed -e "s/seventh/7th forged/" raw >forged1 && + sed -e "/^tag / 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 && @@ -125,4 +125,18 @@ test_expect_success GPG 'verify multiple tags' ' 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/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 0667bd9dd3..c61e304e97 100755 --- a/t/t7063-status-untracked-cache.sh +++ b/t/t7063-status-untracked-cache.sh @@ -14,6 +14,9 @@ test_description='test untracked cache' # 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 } @@ -22,6 +25,12 @@ avoid_racy() { sleep 1 } +status_is_clean() { + >../status.expect && + git status --porcelain >../status.actual && + test_cmp ../status.expect ../status.actual +} + test_lazy_prereq UNTRACKED_CACHE ' { git update-index --test-untracked-cache; ret=$?; } && test $ret -ne 1 @@ -661,4 +670,107 @@ test_expect_success 'test ident field is working' ' 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 index e319fa2e84..b9a86d3347 100755 --- a/t/t7064-wtstatus-pv2.sh +++ b/t/t7064-wtstatus-pv2.sh @@ -46,11 +46,11 @@ test_expect_success 'before initial commit, things added' ' 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 + 1 A. N... 000000 100644 100644 $ZERO_OID $OID_A dir1/file_a + 1 A. N... 000000 100644 100644 $ZERO_OID $OID_B dir1/file_b + 1 A. N... 000000 100644 100644 $ZERO_OID $OID_X file_x + 1 A. N... 000000 100644 100644 $ZERO_OID $OID_Y file_y + 1 A. N... 000000 100644 100644 $ZERO_OID $OID_Z file_z ? actual ? expect EOF @@ -63,11 +63,11 @@ 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 + 1 A. N... 000000 100644 100644 $ZERO_OID $OID_A dir1/file_a + 1 A. N... 000000 100644 100644 $ZERO_OID $OID_B dir1/file_b + 1 A. N... 000000 100644 100644 $ZERO_OID $OID_X file_x + 1 A. N... 000000 100644 100644 $ZERO_OID $OID_Y file_y + 1 A. N... 000000 100644 100644 $ZERO_OID $OID_Z file_z ? actual ? expect EOF @@ -128,7 +128,7 @@ test_expect_success 'after first commit, stage existing changes' ' # 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 + 1 D. N... 100644 000000 000000 $OID_Z $ZERO_OID file_z ? actual ? expect EOF @@ -145,7 +145,7 @@ test_expect_success 'rename causes 2 path lines' ' # 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 + 1 D. N... 100644 000000 000000 $OID_Z $ZERO_OID file_z 2 R. N... 100644 100644 100644 $OID_Y $OID_Y R100 renamed_yQfile_y ? actual ? expect @@ -163,7 +163,7 @@ test_expect_success 'rename causes 2 path lines (-z)' ' # 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 + 1 D. N... 100644 000000 000000 $OID_Z $ZERO_OID file_z 2 R. N... 100644 100644 100644 $OID_Y $OID_Y R100 renamed_yQfile_y ? actual ? expect @@ -246,8 +246,8 @@ test_expect_success 'verify --intent-to-add output' ' 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 + 1 .A N... 000000 000000 100644 $ZERO_OID $ZERO_OID intent1.add + 1 .A N... 000000 000000 100644 $ZERO_OID $ZERO_OID intent2.add EOF git status --porcelain=v2 >actual && @@ -280,7 +280,7 @@ test_expect_success 'verify AA (add-add) conflict' ' 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 + u AA N... 000000 100644 100644 100644 $ZERO_OID $OID_AA_B $OID_AA_A conflict.txt EOF git status --porcelain=v2 --branch --untracked-files=all >actual && @@ -373,7 +373,7 @@ test_expect_success 'verify upstream fields in branch header' ' ## Test upstream-gone case. Fake this by pointing origin/master at ## a non-existing commit. OLD=$(git rev-parse origin/master) && - NEW=$_z40 && + NEW=$ZERO_OID && mv .git/packed-refs .git/old-packed-refs && sed "s/$OLD/$NEW/g" <.git/old-packed-refs >.git/packed-refs && @@ -390,6 +390,68 @@ test_expect_success 'verify upstream fields in branch header' ' ) ' +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 && @@ -407,8 +469,8 @@ test_expect_success 'create and add submodule, submodule appears clean (A. S...) # 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 + 1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules + 1 A. S... 000000 160000 160000 $ZERO_OID $HSUB sub1 EOF git status --porcelain=v2 --branch --untracked-files=all >actual && @@ -432,8 +494,8 @@ test_expect_success 'untracked changes in added submodule (AM S..U)' ' # 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 + 1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules + 1 AM S..U 000000 160000 160000 $ZERO_OID $HSUB sub1 EOF git status --porcelain=v2 --branch --untracked-files=all >actual && @@ -457,8 +519,8 @@ test_expect_success 'staged changes in added submodule (AM S.M.)' ' # 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 + 1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules + 1 AM S.M. 000000 160000 160000 $ZERO_OID $HSUB sub1 EOF git status --porcelain=v2 --branch --untracked-files=all >actual && @@ -484,8 +546,8 @@ test_expect_success 'staged and unstaged changes in added (AM S.M.)' ' # 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 + 1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules + 1 AM S.M. 000000 160000 160000 $ZERO_OID $HSUB sub1 EOF git status --porcelain=v2 --branch --untracked-files=all >actual && @@ -511,8 +573,8 @@ test_expect_success 'staged and untracked changes in added submodule (AM S.MU)' # 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 + 1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules + 1 AM S.MU 000000 160000 160000 $ZERO_OID $HSUB sub1 EOF git status --porcelain=v2 --branch --untracked-files=all >actual && @@ -538,8 +600,8 @@ test_expect_success 'commit within the submodule appears as new commit in super # 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 + 1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules + 1 AM SC.. 000000 160000 160000 $ZERO_OID $HSUB sub1 EOF git status --porcelain=v2 --branch --untracked-files=all >actual && diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh index 86f23be34a..95653a08ca 100755 --- a/t/t7102-reset.sh +++ b/t/t7102-reset.sh @@ -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/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 d4b217b0ee..ab9da61da3 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -65,7 +65,7 @@ test_expect_success setup ' test_expect_success "checkout from non-existing branch" ' git checkout -b delete-me master && - rm .git/refs/heads/delete-me && + git update-ref -d --no-deref refs/heads/delete-me && test refs/heads/delete-me = "$(git symbolic-ref HEAD)" && git checkout master && test refs/heads/master = "$(git symbolic-ref HEAD)" @@ -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' ' diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh index b89fd2a6ad..7b36954d63 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -653,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 b77cce8e40..812db137b8 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -38,14 +38,12 @@ test_expect_success 'submodule update aborts on missing .gitmodules file' ' test_i18ngrep "Submodule path .sub. not initialized" actual ' -test_expect_success 'configuration parsing' ' +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' ' @@ -128,6 +126,24 @@ test_expect_success 'submodule add' ' test_cmp empty untracked ' +test_expect_success 'setup parent and one repository' ' + test_create_repo parent && + test_commit -C parent one +' + +test_expect_success 'redirected submodule add does not show progress' ' + git -C addtest submodule add "file://$submodurl/parent" submod-redirected \ + 2>err && + ! grep % err && + test_i18ngrep ! "Checking connectivity" err +' + +test_expect_success 'redirected submodule add --progress does show progress' ' + git -C addtest submodule add --progress "file://$submodurl/parent" \ + submod-redirected-progress 2>err && \ + grep % err +' + test_expect_success 'submodule add to .gitignored path fails' ' ( cd addtest-ignore && @@ -152,6 +168,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 && @@ -259,6 +289,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 && @@ -795,6 +839,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 && @@ -1116,5 +1175,147 @@ test_expect_success 'submodule helper list is not confused by common prefixes' ' 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/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 64f322c4cc..9e0d31700e 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -65,7 +65,7 @@ test_expect_success 'setup a submodule tree' ' git commit -m "none" ) && git clone . recursivesuper && - ( cd recursivesuper + ( cd recursivesuper && git submodule add ../super super ) ' @@ -140,6 +140,23 @@ test_expect_success 'submodule update --init --recursive from subdirectory' ' 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 && @@ -228,13 +245,13 @@ test_expect_success 'submodule update --remote should fetch upstream changes wit ( cd super && git submodule update --remote --force submodule && - git -C submodule log -1 --oneline >actual - git -C ../submodule log -1 --oneline master >expect + 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 + 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 && @@ -389,6 +406,14 @@ 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 @@ -424,6 +449,16 @@ test_expect_success 'submodule update - command in .git/config catches failure - 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' @@ -476,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 && @@ -488,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 && @@ -854,7 +891,7 @@ test_expect_success 'submodule update properly revives a moved submodule' ' rm -rf submodule2 && mkdir -p "moved/sub module" && git update-index --add --cacheinfo 160000 $H "moved/sub module" && - git config -f .gitmodules submodule.submodule2.path "moved/sub module" + git config -f .gitmodules submodule.submodule2.path "moved/sub module" && git commit -am "post move" && git submodule update && git status | sed "s/$H2/XXX/" >actual && diff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh index 6ba5daf42e..77729ac4aa 100755 --- a/t/t7407-submodule-foreach.sh +++ b/t/t7407-submodule-foreach.sh @@ -82,16 +82,16 @@ test_expect_success 'test basic "submodule foreach" usage' ' cat >expect <<EOF Entering '../sub1' -$pwd/clone-foo1-../sub1-$sub1sha1 +$pwd/clone-foo1-sub1-../sub1-$sub1sha1 Entering '../sub3' -$pwd/clone-foo3-../sub3-$sub3sha1 +$pwd/clone-foo3-sub3-../sub3-$sub3sha1 EOF test_expect_success 'test "submodule foreach" from subdirectory' ' mkdir clone/sub && ( cd clone/sub && - git submodule foreach "echo \$toplevel-\$name-\$sm_path-\$sha1" >../../actual + git submodule foreach "echo \$toplevel-\$name-\$sm_path-\$displaypath-\$sha1" >../../actual ) && test_i18ncmp expect actual ' @@ -196,6 +196,38 @@ test_expect_success 'test messages from "foreach --recursive" from subdirectory' ) && test_i18ncmp expect actual ' +sub1sha1=$(cd clone2/sub1 && git rev-parse HEAD) +sub2sha1=$(cd clone2/sub2 && git rev-parse HEAD) +sub3sha1=$(cd clone2/sub3 && git rev-parse HEAD) +nested1sha1=$(cd clone2/nested1 && git rev-parse HEAD) +nested2sha1=$(cd clone2/nested1/nested2 && git rev-parse HEAD) +nested3sha1=$(cd clone2/nested1/nested2/nested3 && git rev-parse HEAD) +submodulesha1=$(cd clone2/nested1/nested2/nested3/submodule && git rev-parse HEAD) + +cat >expect <<EOF +Entering '../nested1' +toplevel: $pwd/clone2 name: nested1 path: nested1 displaypath: ../nested1 hash: $nested1sha1 +Entering '../nested1/nested2' +toplevel: $pwd/clone2/nested1 name: nested2 path: nested2 displaypath: ../nested1/nested2 hash: $nested2sha1 +Entering '../nested1/nested2/nested3' +toplevel: $pwd/clone2/nested1/nested2 name: nested3 path: nested3 displaypath: ../nested1/nested2/nested3 hash: $nested3sha1 +Entering '../nested1/nested2/nested3/submodule' +toplevel: $pwd/clone2/nested1/nested2/nested3 name: submodule path: submodule displaypath: ../nested1/nested2/nested3/submodule hash: $submodulesha1 +Entering '../sub1' +toplevel: $pwd/clone2 name: foo1 path: sub1 displaypath: ../sub1 hash: $sub1sha1 +Entering '../sub2' +toplevel: $pwd/clone2 name: foo2 path: sub2 displaypath: ../sub2 hash: $sub2sha1 +Entering '../sub3' +toplevel: $pwd/clone2 name: foo3 path: sub3 displaypath: ../sub3 hash: $sub3sha1 +EOF + +test_expect_success 'test "submodule foreach --recursive" from subdirectory' ' + ( + cd clone2/untracked && + git submodule foreach --recursive "echo toplevel: \$toplevel name: \$name path: \$sm_path displaypath: \$displaypath hash: \$sha1" >../../actual + ) && + test_i18ncmp expect actual +' cat > expect <<EOF nested1-nested1 diff --git a/t/t7408-submodule-reference.sh b/t/t7408-submodule-reference.sh index 1c1e289ffd..08d9add05e 100755 --- a/t/t7408-submodule-reference.sh +++ b/t/t7408-submodule-reference.sh @@ -59,6 +59,16 @@ test_expect_success 'submodule add --reference uses alternates' ' test_alternate_is_used super/.git/modules/sub/objects/info/alternates super/sub ' +test_expect_success 'submodule add --reference with --dissociate does not use alternates' ' + ( + cd super && + git submodule add --reference ../B --dissociate "file://$base_dir/A" sub-dissociate && + git commit -m B-super-added && + git repack -ad + ) && + test_path_is_missing super/.git/modules/sub-dissociate/objects/info/alternates +' + test_expect_success 'that reference gets used with add' ' ( cd super/sub && @@ -82,6 +92,13 @@ test_expect_success 'updating superproject keeps alternates' ' test_alternate_is_used super-clone/.git/modules/sub/objects/info/alternates super-clone/sub ' +test_expect_success 'updating superproject with --dissociate does not keep alternates' ' + test_when_finished "rm -rf super-clone" && + git clone super super-clone && + git -C super-clone submodule update --init --reference ../B --dissociate && + test_path_is_missing super-clone/.git/modules/sub/objects/info/alternates +' + 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 && @@ -125,4 +142,70 @@ test_expect_success 'ignoring missing submodule alternates passes clone and subm ) ' +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-work-tree.sh b/t/t7409-submodule-detached-work-tree.sh index c20717181e..fc018e3638 100755 --- a/t/t7409-submodule-detached-work-tree.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/t7411-submodule-config.sh b/t/t7411-submodule-config.sh index 47562ce465..0bde5850ac 100755 --- a/t/t7411-submodule-config.sh +++ b/t/t7411-submodule-config.sh @@ -31,6 +31,21 @@ test_expect_success 'submodule config cache setup' ' ) ' +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' @@ -40,7 +55,7 @@ EOF test_expect_success 'test parsing and lookup of submodule config by path' ' (cd super && - test-submodule-config \ + test-tool submodule-config \ HEAD^ a \ HEAD b \ HEAD^ submodule \ @@ -52,7 +67,7 @@ test_expect_success 'test parsing and lookup of submodule config by path' ' test_expect_success 'test parsing and lookup of submodule config by name' ' (cd super && - test-submodule-config --name \ + test-tool submodule-config --name \ HEAD^ a \ HEAD a \ HEAD^ submodule \ @@ -74,7 +89,7 @@ test_expect_success 'error in one submodule config lets continue' ' git add .gitmodules && mv .gitmodules.bak .gitmodules && git commit -m "add error" && - test-submodule-config \ + test-tool submodule-config \ HEAD b \ HEAD submodule \ >actual && @@ -85,7 +100,7 @@ test_expect_success 'error in one submodule config lets continue' ' test_expect_success 'error message contains blob reference' ' (cd super && sha1=$(git rev-parse HEAD) && - test-submodule-config \ + test-tool submodule-config \ HEAD b \ HEAD submodule \ 2>actual_err && @@ -93,54 +108,17 @@ test_expect_success 'error message contains blob reference' ' ) ' -cat >super/expect_url <<EOF -Submodule url: 'git@somewhere.else.net:a.git' for path 'b' -Submodule url: 'git@somewhere.else.net:submodule.git' for path 'submodule' -EOF - -cat >super/expect_local_path <<EOF -Submodule name: 'a' for path 'c' -Submodule name: 'submodule' for path 'submodule' -EOF - -test_expect_success 'reading of local configuration' ' - (cd super && - old_a=$(git config submodule.a.url) && - old_submodule=$(git config submodule.submodule.url) && - git config submodule.a.url git@somewhere.else.net:a.git && - git config submodule.submodule.url git@somewhere.else.net:submodule.git && - test-submodule-config --url \ - "" b \ - "" submodule \ - >actual && - test_cmp expect_url actual && - git config submodule.a.path c && - test-submodule-config \ - "" c \ - "" submodule \ - >actual && - test_cmp expect_local_path actual && - git config submodule.a.url $old_a && - git config submodule.submodule.url $old_submodule && - git config --unset submodule.a.path c - ) -' - -cat >super/expect_fetchrecurse_die.err <<EOF -fatal: bad submodule.submodule.fetchrecursesubmodules argument: blabla -EOF - -test_expect_success 'local error in fetchrecursesubmodule dies early' ' - (cd super && - git config submodule.submodule.fetchrecursesubmodules blabla && - test_must_fail test-submodule-config \ - "" b \ - "" submodule \ - >actual.out 2>actual.err && - touch expect_fetchrecurse_die.out && - test_cmp expect_fetchrecurse_die.out actual.out && - test_cmp expect_fetchrecurse_die.err actual.err && - git config --unset submodule.submodule.fetchrecursesubmodules +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 ) ' @@ -152,7 +130,7 @@ test_expect_success 'error in history in fetchrecursesubmodule lets continue' ' git config --unset -f .gitmodules \ submodule.submodule.fetchrecursesubmodules && git commit -m "add error in fetchrecursesubmodules" && - test-submodule-config \ + test-tool submodule-config \ HEAD b \ HEAD submodule \ >actual && 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/t7415-submodule-names.sh b/t/t7415-submodule-names.sh new file mode 100755 index 0000000000..b68c5f5e85 --- /dev/null +++ b/t/t7415-submodule-names.sh @@ -0,0 +1,179 @@ +#!/bin/sh + +test_description='check handling of .. in submodule names + +Exercise the name-checking function on a variety of names, and then give a +real-world setup that confirms we catch this in practice. +' +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-pack.sh + +test_expect_success 'check names' ' + cat >expect <<-\EOF && + valid + valid/with/paths + EOF + + git submodule--helper check-name >actual <<-\EOF && + valid + valid/with/paths + + ../foo + /../foo + ..\foo + \..\foo + foo/.. + foo/../ + foo\.. + foo\..\ + foo/../bar + EOF + + test_cmp expect actual +' + +test_expect_success 'create innocent subrepo' ' + git init innocent && + git -C innocent commit --allow-empty -m foo +' + +test_expect_success 'submodule add refuses invalid names' ' + test_must_fail \ + git submodule add --name ../../modules/evil "$PWD/innocent" evil +' + +test_expect_success 'add evil submodule' ' + git submodule add "$PWD/innocent" evil && + + mkdir modules && + cp -r .git/modules/evil modules && + write_script modules/evil/hooks/post-checkout <<-\EOF && + echo >&2 "RUNNING POST CHECKOUT" + EOF + + git config -f .gitmodules submodule.evil.update checkout && + git config -f .gitmodules --rename-section \ + submodule.evil submodule.../../modules/evil && + git add modules && + git commit -am evil +' + +# This step seems like it shouldn't be necessary, since the payload is +# contained entirely in the evil submodule. But due to the vagaries of the +# submodule code, checking out the evil module will fail unless ".git/modules" +# exists. Adding another submodule (with a name that sorts before "evil") is an +# easy way to make sure this is the case in the victim clone. +test_expect_success 'add other submodule' ' + git submodule add "$PWD/innocent" another-module && + git add another-module && + git commit -am another +' + +test_expect_success 'clone evil superproject' ' + git clone --recurse-submodules . victim >output 2>&1 && + ! grep "RUNNING POST CHECKOUT" output +' + +test_expect_success 'fsck detects evil superproject' ' + test_must_fail git fsck +' + +test_expect_success 'transfer.fsckObjects detects evil superproject (unpack)' ' + rm -rf dst.git && + git init --bare dst.git && + git -C dst.git config transfer.fsckObjects true && + test_must_fail git push dst.git HEAD +' + +test_expect_success 'transfer.fsckObjects detects evil superproject (index)' ' + rm -rf dst.git && + git init --bare dst.git && + git -C dst.git config transfer.fsckObjects true && + git -C dst.git config transfer.unpackLimit 1 && + test_must_fail git push dst.git HEAD +' + +# Normally our packs contain commits followed by trees followed by blobs. This +# reverses the order, which requires backtracking to find the context of a +# blob. We'll start with a fresh gitmodules-only tree to make it simpler. +test_expect_success 'create oddly ordered pack' ' + git checkout --orphan odd && + git rm -rf --cached . && + git add .gitmodules && + git commit -m odd && + { + pack_header 3 && + pack_obj $(git rev-parse HEAD:.gitmodules) && + pack_obj $(git rev-parse HEAD^{tree}) && + pack_obj $(git rev-parse HEAD) + } >odd.pack && + pack_trailer odd.pack +' + +test_expect_success 'transfer.fsckObjects handles odd pack (unpack)' ' + rm -rf dst.git && + git init --bare dst.git && + test_must_fail git -C dst.git unpack-objects --strict <odd.pack +' + +test_expect_success 'transfer.fsckObjects handles odd pack (index)' ' + rm -rf dst.git && + git init --bare dst.git && + test_must_fail git -C dst.git index-pack --strict --stdin <odd.pack +' + +test_expect_success 'index-pack --strict works for non-repo pack' ' + rm -rf dst.git && + git init --bare dst.git && + cp odd.pack dst.git && + test_must_fail git -C dst.git index-pack --strict odd.pack 2>output && + # Make sure we fail due to bad gitmodules content, not because we + # could not read the blob in the first place. + grep gitmodulesName output +' + +test_expect_success 'fsck detects symlinked .gitmodules file' ' + git init symlink && + ( + cd symlink && + + # Make the tree directly to avoid index restrictions. + # + # Because symlinks store the target as a blob, choose + # a pathname that could be parsed as a .gitmodules file + # to trick naive non-symlink-aware checking. + tricky="[foo]bar=true" && + content=$(git hash-object -w ../.gitmodules) && + target=$(printf "$tricky" | git hash-object -w --stdin) && + { + printf "100644 blob $content\t$tricky\n" && + printf "120000 blob $target\t.gitmodules\n" + } | git mktree && + + # Check not only that we fail, but that it is due to the + # symlink detector; this grep string comes from the config + # variable name and will not be translated. + test_must_fail git fsck 2>output && + grep gitmodulesSymlink output + ) +' + +test_expect_success 'fsck detects non-blob .gitmodules' ' + git init non-blob && + ( + cd non-blob && + + # As above, make the funny tree directly to avoid index + # restrictions. + mkdir subdir && + cp ../.gitmodules subdir/file && + git add subdir/file && + git commit -m ok && + git ls-tree HEAD | sed s/subdir/.gitmodules/ | git mktree && + + test_must_fail git fsck 2>output && + grep gitmodulesBlob output + ) +' + +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 0d8d893090..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 ' @@ -469,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 && diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh index 725687d5d5..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=' diff --git a/t/t7504-commit-msg-hook.sh b/t/t7504-commit-msg-hook.sh index 8728db61d3..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 b13f72975e..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 - source=${2-default} + rebasing=0 fi -if test "$GIT_EDITOR" = :; then - sed -e "1s/.*/$source (no editor)/" "$1" > msg.tmp + +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 - sed -e "1s/.*/$source/" "$1" > msg.tmp + source=${2-default} +fi +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 + mv msg.tmp "$1" fi -mv msg.tmp "$1" exit 0 EOF chmod +x "$HOOK" @@ -156,6 +212,63 @@ test_expect_success 'with hook and editor (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' #!/bin/sh exit 1 @@ -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..b4b74dbe29 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/$OID_REGEX/HASH/" -e "s/$OID_REGEX/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/t7508-status.sh b/t/t7508-status.sh index fb00e6d9b0..18a40257fb 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) @@ -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) @@ -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 --get .git/index >out && + grep ^1234567890 out && + git status && + test-tool chmtime --get .git/index >out && + ! grep ^1234567890 out +' + test_done diff --git a/t/t7509-commit.sh b/t/t7509-commit.sh index db9774e345..ddef7ea6b0 100755 --- a/t/t7509-commit.sh +++ b/t/t7509-commit.sh @@ -101,7 +101,7 @@ 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' ' @@ -114,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 762135adea..6e2015ed9a 100755 --- a/t/t7510-signed-commit.sh +++ b/t/t7510-signed-commit.sh @@ -142,10 +142,9 @@ test_expect_success GPG 'show signed commit with signature' ' test_expect_success GPG 'detect fudged signature' ' git cat-file commit seventh-signed >raw && - - sed -e "s/seventh/7th forged/" raw >forged1 && + sed -e "s/^seventh/7th forged/" raw >forged1 && git hash-object -w -t commit forged1 >forged1.commit && - ! git verify-commit $(cat forged1.commit) && + test_must_fail git verify-commit $(cat forged1.commit) && git show --pretty=short --show-signature $(cat forged1.commit) >actual1 && grep "BAD signature from" actual1 && ! grep "Good signature from" actual1 @@ -156,7 +155,7 @@ test_expect_success GPG 'detect fudged signature with NUL' ' cat raw >forged2 && echo Qwik | tr "Q" "\000" >>forged2 && git hash-object -w -t commit forged2 >forged2.commit && - ! git verify-commit $(cat forged2.commit) && + test_must_fail git verify-commit $(cat forged2.commit) && git show --pretty=short --show-signature $(cat forged2.commit) >actual2 && grep "BAD signature from" actual2 && ! grep "Good signature from" actual2 diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh index 5c3db656df..458608cc1e 100755 --- a/t/t7512-status-help.sh +++ b/t/t7512-status-help.sh @@ -944,4 +944,23 @@ EOF 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 4dd1d7c520..164719d1c9 100755 --- a/t/t7513-interpret-trailers.sh +++ b/t/t7513-interpret-trailers.sh @@ -681,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 && @@ -947,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" && @@ -1026,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 && @@ -1258,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/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/t7525-status-rename.sh b/t/t7525-status-rename.sh new file mode 100755 index 0000000000..ef8b1b3078 --- /dev/null +++ b/t/t7525-status-rename.sh @@ -0,0 +1,113 @@ +#!/bin/sh + +test_description='git status rename detection options' + +. ./test-lib.sh + +test_expect_success 'setup' ' + echo 1 >original && + git add . && + git commit -m"Adding original file." && + mv original renamed && + echo 2 >> renamed && + git add . && + cat >.gitignore <<-\EOF + .gitignore + expect* + actual* + EOF +' + +test_expect_success 'status no-options' ' + git status >actual && + test_i18ngrep "renamed:" actual +' + +test_expect_success 'status --no-renames' ' + git status --no-renames >actual && + test_i18ngrep "deleted:" actual && + test_i18ngrep "new file:" actual +' + +test_expect_success 'status.renames inherits from diff.renames false' ' + git -c diff.renames=false status >actual && + test_i18ngrep "deleted:" actual && + test_i18ngrep "new file:" actual +' + +test_expect_success 'status.renames inherits from diff.renames true' ' + git -c diff.renames=true status >actual && + test_i18ngrep "renamed:" actual +' + +test_expect_success 'status.renames overrides diff.renames false' ' + git -c diff.renames=true -c status.renames=false status >actual && + test_i18ngrep "deleted:" actual && + test_i18ngrep "new file:" actual +' + +test_expect_success 'status.renames overrides from diff.renames true' ' + git -c diff.renames=false -c status.renames=true status >actual && + test_i18ngrep "renamed:" actual +' + +test_expect_success 'status status.renames=false' ' + git -c status.renames=false status >actual && + test_i18ngrep "deleted:" actual && + test_i18ngrep "new file:" actual +' + +test_expect_success 'status status.renames=true' ' + git -c status.renames=true status >actual && + test_i18ngrep "renamed:" actual +' + +test_expect_success 'commit honors status.renames=false' ' + git -c status.renames=false commit --dry-run >actual && + test_i18ngrep "deleted:" actual && + test_i18ngrep "new file:" actual +' + +test_expect_success 'commit honors status.renames=true' ' + git -c status.renames=true commit --dry-run >actual && + test_i18ngrep "renamed:" actual +' + +test_expect_success 'status config overridden' ' + git -c status.renames=true status --no-renames >actual && + test_i18ngrep "deleted:" actual && + test_i18ngrep "new file:" actual +' + +test_expect_success 'status score=100%' ' + git status -M=100% >actual && + test_i18ngrep "deleted:" actual && + test_i18ngrep "new file:" actual && + + git status --find-rename=100% >actual && + test_i18ngrep "deleted:" actual && + test_i18ngrep "new file:" actual +' + +test_expect_success 'status score=01%' ' + git status -M=01% >actual && + test_i18ngrep "renamed:" actual && + + git status --find-rename=01% >actual && + test_i18ngrep "renamed:" actual +' + +test_expect_success 'copies not overridden by find-rename' ' + cp renamed copy && + git add copy && + + git -c status.renames=copies status -M=01% >actual && + test_i18ngrep "copied:" actual && + test_i18ngrep "renamed:" actual && + + git -c status.renames=copies status --find-rename=01% >actual && + test_i18ngrep "copied:" actual && + test_i18ngrep "renamed:" actual +' + +test_done diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 85248a14b6..6736d8d131 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -154,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 ' @@ -693,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' ' @@ -705,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' ' @@ -714,10 +754,10 @@ 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' ' @@ -763,4 +803,26 @@ test_expect_success 'merge nothing into void' ' ) ' +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/t7607-merge-overwrite.sh b/t/t7607-merge-overwrite.sh index 9444d6a9b9..dd8ab7ede1 100755 --- a/t/t7607-merge-overwrite.sh +++ b/t/t7607-merge-overwrite.sh @@ -92,12 +92,15 @@ test_expect_success 'will not overwrite removed file with staged changes' ' test_cmp important c1.c ' -test_expect_failure 'will not overwrite unstaged changes in renamed file' ' +test_expect_success 'will not overwrite unstaged changes in renamed file' ' git reset --hard c1 && 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 ' diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index 63d36fb28e..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,11 +213,12 @@ 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' ' +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 && @@ -189,21 +234,41 @@ 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_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 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,67 +296,57 @@ 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' ' - 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" && + 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 HEAD && + 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 HEAD && + 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 && - git reset --hard HEAD + ! 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 && - git reset --hard HEAD + ! 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 && - git reset --hard HEAD + ! 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 && @@ -302,18 +357,17 @@ test_expect_success 'mergetool keeps tempfiles when aborting delete/delete' ' file_REMOTE_.txt EOF ls -1 a/a | sed -e "s/[0-9]*//g" >actual && - test_cmp expect actual && - git clean -fdx && - git reset --hard HEAD + 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 ) && @@ -329,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)" && @@ -343,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 ) && @@ -359,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 ) && @@ -372,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 ) && @@ -400,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 ) && @@ -413,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 ) && @@ -430,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 ) && @@ -448,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 && @@ -460,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 && @@ -523,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 && @@ -535,58 +591,59 @@ 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 ' @@ -596,53 +653,48 @@ test_lazy_prereq MKTEMP ' ' test_expect_success MKTEMP 'temporary filenames are used with mergetool.writeToTemp' ' - git checkout -b test16 branch1 && + 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 && - 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 && 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 && - git reset --hard >/dev/null + 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: @@ -652,7 +704,7 @@ test_expect_success 'mergetool -Oorder-file is honored' ' 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 >/dev/null 2>&1 && + git reset --hard && git config --unset diff.orderFile && test_must_fail git merge order-file-side1 && @@ -663,8 +715,7 @@ test_expect_success 'mergetool -Oorder-file is honored' ' EOF git mergetool -Oorder-file --no-prompt --tool myecho >output && git grep --no-index -h -A2 Merging: output >actual && - test_cmp expect actual && - git reset --hard >/dev/null 2>&1 + test_cmp expect actual ' test_done diff --git a/t/t7612-merge-verify-signatures.sh b/t/t7612-merge-verify-signatures.sh index 8ae69a61c3..e2b1df817a 100755 --- a/t/t7612-merge-verify-signatures.sh +++ b/t/t7612-merge-verify-signatures.sh @@ -23,7 +23,7 @@ test_expect_success GPG 'create signed commits' ' echo 3 >bar && git add bar && test_tick && git commit -S -m "bad on side" && git cat-file commit side-bad >raw && - sed -e "s/bad/forged bad/" raw >forged && + sed -e "s/^bad/forged bad/" raw >forged && git hash-object -w -t commit forged >forged.commit && git checkout initial && @@ -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 6061a04147..6162e2a8e6 100755 --- a/t/t7700-repack.sh +++ b/t/t7700-repack.sh @@ -4,6 +4,12 @@ test_description='git repack works correctly' . ./test-lib.sh +commit_and_pack() { + test_commit "$@" >/dev/null && + SHA1=$(git pack-objects --all --unpacked --incremental .git/objects/pack/pack </dev/null) && + echo pack-${SHA1}.pack +} + test_expect_success 'objects in packs marked .keep are not repacked' ' echo content1 > file1 && echo content2 > file2 && @@ -194,7 +200,26 @@ test_expect_success 'objects made unreachable by grafts only are kept' ' git reflog expire --expire=$test_tick --expire-unreachable=$test_tick --all && git repack -a -d && git cat-file -t $H1 - ' +' + +test_expect_success 'repack --keep-pack' ' + test_create_repo keep-pack && + ( + cd keep-pack && + P1=$(commit_and_pack 1) && + P2=$(commit_and_pack 2) && + P3=$(commit_and_pack 3) && + P4=$(commit_and_pack 4) && + ls .git/objects/pack/*.pack >old-counts && + test_line_count = 4 old-counts && + git repack -a -d --keep-pack $P1 --keep-pack $P4 && + ls .git/objects/pack/*.pack >new-counts && + grep -q $P1 new-counts && + grep -q $P4 new-counts && + test_line_count = 3 new-counts && + git fsck + ) +' test_done diff --git a/t/t7701-repack-unpack-unreachable.sh b/t/t7701-repack-unpack-unreachable.sh index 987573c41f..48261ba080 100755 --- a/t/t7701-repack-unpack-unreachable.sh +++ b/t/t7701-repack-unpack-unreachable.sh @@ -55,8 +55,8 @@ test_expect_success '-A with -d option leaves unreachable objects unpacked' ' compare_mtimes () { - read tref rest && - while read t rest; do + read tref && + while read t; do test "$tref" = "$t" || return 1 done } @@ -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 --get "$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,7 +117,7 @@ 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 ' diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 99d4123461..668bbee73c 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -23,8 +23,20 @@ prompt_given () 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,13 +136,13 @@ test_expect_success PERL 'difftool stops on error with --trust-exit-code' ' test_cmp expect actual ' -test_expect_success PERL 'difftool honors exit status if command not found' ' +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 PERL 'difftool honors --gui' ' +test_expect_success 'difftool honors --gui' ' difftool_test_setup && test_config merge.tool bogus-tool && test_config diff.tool bogus-tool && @@ -141,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 && @@ -155,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 && @@ -163,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 && @@ -173,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 && @@ -191,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 && @@ -200,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 && @@ -210,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 && @@ -219,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 && @@ -229,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 && @@ -238,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 && @@ -248,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 && @@ -261,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 && @@ -275,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 && @@ -325,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" && @@ -337,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 && @@ -346,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 && @@ -355,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 && @@ -363,12 +375,12 @@ 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 && @@ -381,12 +393,31 @@ test_expect_success PERL 'setup change in subdirectory' ' 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 " @@ -416,9 +447,12 @@ run_dir_diff_test 'difftool --dir-diff branch from subdirectory' ' 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" - test "1" = $(grep sub output | wc -l) && - test "2" = $(grep file"$" output | wc -l) && - test "2" = $(grep file2 output | wc -l) + 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 ) ' @@ -428,9 +462,11 @@ run_dir_diff_test 'difftool --dir-diff v1 from subdirectory' ' git difftool --dir-diff $symlinks --extcmd ls v1 >output && # "sub" and "file" exist in both v1 and HEAD. # "file2" is unchanged. - test "2" = $(grep sub output | wc -l) && - test "2" = $(grep file output | wc -l) && - test "0" = $(grep file2 output | wc -l) + 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 ) ' @@ -440,8 +476,9 @@ run_dir_diff_test 'difftool --dir-diff branch from subdirectory w/ pathspec' ' git difftool --dir-diff $symlinks --extcmd ls branch -- .>output && # "sub" only exists in "right" # "file" and "file2" must not be listed - test "1" = $(grep sub output | wc -l) && - test "0" = $(grep file output | wc -l) + grep sub output >sub-output && + test_line_count = 1 sub-output && + ! grep file output ) ' @@ -451,8 +488,9 @@ run_dir_diff_test 'difftool --dir-diff v1 from subdirectory w/ pathspec' ' git difftool --dir-diff $symlinks --extcmd ls v1 -- .>output && # "sub" exists in v1 and HEAD # "file" is filtered out by the pathspec - test "2" = $(grep sub output | wc -l) && - test "0" = $(grep file output | wc -l) + grep sub output >sub-output && + test_line_count = 2 sub-output && + ! grep file output ) ' @@ -508,7 +546,7 @@ do 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 @@ -545,7 +583,7 @@ 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 && echo "new content" >expect && @@ -558,7 +596,7 @@ 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 && @@ -571,7 +609,8 @@ 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 '\'' @@ -581,11 +620,13 @@ test_expect_success PERL 'difftool properly honors gitlink and core.worktree' ' cd submod/ule && echo good >expect && git difftool --tool=checktrees --dir-diff HEAD~ >actual && - test_cmp expect actual + test_cmp expect actual && + rm -f expect actual ) ' -test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' ' +test_expect_success SYMLINKS 'difftool --dir-diff symlinked directories' ' + test_when_finished git reset --hard && git init dirlinks && ( cd dirlinks && @@ -604,4 +645,64 @@ test_expect_success PERL,SYMLINKS 'difftool --dir-diff symlinked directories' ' ) ' +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 19f0108f8a..1797f632a3 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -60,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 @@ -275,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 \ @@ -370,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 @@ -757,18 +778,61 @@ test_expect_success 'grep -W shows no trailing empty lines' ' test_cmp expected actual ' -cat >expected <<EOF -hello.c= printf("Hello world.\n"); -hello.c: return 0; -hello.c- /* char ?? */ -EOF - 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)' ' @@ -982,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 \ @@ -1004,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" @@ -1019,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 @@ -1027,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" @@ -1036,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[" ' @@ -1052,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[" ' @@ -1125,13 +1276,13 @@ test_expect_success 'grep pattern with grep.patternType=fixed, =basic, =perl, =e test_cmp expected actual ' -test_expect_success LIBPCRE 'grep -G -F -E -P pattern' ' +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' ' +test_expect_success PCRE 'grep pattern with grep.patternType=fixed, =basic, =extended, =perl' ' echo "d0:0" >expected && git \ -c grep.patterntype=fixed \ @@ -1142,7 +1293,7 @@ test_expect_success LIBPCRE 'grep pattern with grep.patternType=fixed, =basic, = 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 \ @@ -1277,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 ' diff --git a/t/t7812-grep-icase-non-ascii.sh b/t/t7812-grep-icase-non-ascii.sh index 169fd8d706..0c685d3598 100755 --- a/t/t7812-grep-icase-non-ascii.sh +++ b/t/t7812-grep-icase-non-ascii.sh @@ -12,7 +12,7 @@ test_expect_success GETTEXT_LOCALE 'setup' ' ' test_have_prereq GETTEXT_LOCALE && -test-regex "HALLÓ" "Halló" ICASE && +test-tool regex "HALLÓ" "Halló" ICASE && test_set_prereq REGEX_LOCALE test_expect_success REGEX_LOCALE 'grep literal string, no -F' ' @@ -20,13 +20,13 @@ test_expect_success REGEX_LOCALE 'grep literal string, no -F' ' git grep -i "TILRAUN: HALLÓ HEIMUR!" ' -test_expect_success GETTEXT_LOCALE,LIBPCRE 'grep pcre utf-8 icase' ' +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,LIBPCRE 'grep pcre utf-8 string with "+"' ' +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 && @@ -36,29 +36,14 @@ test_expect_success GETTEXT_LOCALE,LIBPCRE 'grep pcre utf-8 string with "+"' ' ' test_expect_success REGEX_LOCALE 'grep literal string, with -F' ' - git grep --debug -i -F "TILRAUN: Halló Heimur!" 2>&1 >/dev/null | - grep fixed >debug1 && - test_write_lines "fixed TILRAUN: Halló Heimur!" >expect1 && - test_cmp expect1 debug1 && - - git grep --debug -i -F "TILRAUN: HALLÓ HEIMUR!" 2>&1 >/dev/null | - grep fixed >debug2 && - test_write_lines "fixed TILRAUN: HALLÓ HEIMUR!" >expect2 && - test_cmp expect2 debug2 + 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 "^*TILR^AUN:.* \\Halló \$He[]imur!\$" >file && - - git grep --debug -i -F "^*TILR^AUN:.* \\Halló \$He[]imur!\$" 2>&1 >/dev/null | - grep fixed >debug1 && - test_write_lines "fixed \\^*TILR^AUN:\\.\\* \\\\Halló \$He\\[]imur!\\\$" >expect1 && - test_cmp expect1 debug1 && - - git grep --debug -i -F "^*TILR^AUN:.* \\HALLÓ \$HE[]IMUR!\$" 2>&1 >/dev/null | - grep fixed >debug2 && - test_write_lines "fixed \\^*TILR^AUN:\\.\\* \\\\HALLÓ \$HE\\[]IMUR!\\\$" >expect2 && - test_cmp expect2 debug2 + 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' ' diff --git a/t/t7813-grep-icase-iso.sh b/t/t7813-grep-icase-iso.sh index efef7fb81f..701e08a8e5 100755 --- a/t/t7813-grep-icase-iso.sh +++ b/t/t7813-grep-icase-iso.sh @@ -11,7 +11,7 @@ test_expect_success GETTEXT_ISO_LOCALE 'setup' ' export LC_ALL ' -test_expect_success GETTEXT_ISO_LOCALE,LIBPCRE 'grep pcre string' ' +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!" ' 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/t8008-blame-formats.sh b/t/t8008-blame-formats.sh index 92c8e792d1..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 diff --git a/t/t8010-cat-file-filters.sh b/t/t8010-cat-file-filters.sh index d8242e467e..0f86c19174 100755 --- a/t/t8010-cat-file-filters.sh +++ b/t/t8010-cat-file-filters.sh @@ -51,6 +51,11 @@ test_expect_success '--path=<path> complains without --textconv/--filters' ' 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 <" && diff --git a/t/t8012-blame-colors.sh b/t/t8012-blame-colors.sh new file mode 100755 index 0000000000..ed38f74de9 --- /dev/null +++ b/t/t8012-blame-colors.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +test_description='colored git blame' +. ./test-lib.sh + +PROG='git blame -c' +. "$TEST_DIRECTORY"/annotate-tests.sh + +test_expect_success 'colored blame colors contiguous lines' ' + git -c color.blame.repeatedLines=yellow blame --color-lines --abbrev=12 hello.c >actual.raw && + git -c color.blame.repeatedLines=yellow -c blame.coloring=repeatedLines blame --abbrev=12 hello.c >actual.raw.2 && + test_cmp actual.raw actual.raw.2 && + test_decode_color <actual.raw >actual && + grep "<YELLOW>" <actual >darkened && + grep "(F" darkened > F.expect && + grep "(H" darkened > H.expect && + test_line_count = 2 F.expect && + test_line_count = 3 H.expect +' + +test_expect_success 'color by age consistently colors old code' ' + git blame --color-by-age hello.c >actual.raw && + git -c blame.coloring=highlightRecent blame hello.c >actual.raw.2 && + test_cmp actual.raw actual.raw.2 && + test_decode_color <actual.raw >actual && + grep "<BLUE>" <actual >colored && + test_line_count = 10 colored +' + +test_expect_success 'blame color by age: new code is different' ' + cat >>hello.c <<-EOF && + void qfunc(); + EOF + git add hello.c && + GIT_AUTHOR_DATE="" git commit -m "new commit" && + + git -c color.blame.highlightRecent="yellow,1 month ago, cyan" blame --color-by-age hello.c >actual.raw && + test_decode_color <actual.raw >actual && + + grep "<YELLOW>" <actual >colored && + test_line_count = 10 colored && + + grep "<CYAN>" <actual >colored && + test_line_count = 1 colored && + grep qfunc colored +' + +test_done diff --git a/t/t9000-addresses.sh b/t/t9000-addresses.sh deleted file mode 100755 index a1ebef6de2..0000000000 --- a/t/t9000-addresses.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh - -test_description='compare address parsing with and without Mail::Address' -. ./test-lib.sh - -if ! test_have_prereq PERL; then - skip_all='skipping perl interface tests, perl not available' - test_done -fi - -perl -MTest::More -e 0 2>/dev/null || { - skip_all="Perl Test::More unavailable, skipping test" - test_done -} - -perl -MMail::Address -e 0 2>/dev/null || { - skip_all="Perl Mail::Address unavailable, skipping test" - test_done -} - -test_external_has_tap=1 - -test_external_without_stderr \ - 'Perl address parsing function' \ - perl "$TEST_DIRECTORY"/t9000/test.pl - -test_done diff --git a/t/t9000/test.pl b/t/t9000/test.pl deleted file mode 100755 index dfeaa9c655..0000000000 --- a/t/t9000/test.pl +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/perl -use lib (split(/:/, $ENV{GITPERLLIB})); - -use 5.008; -use warnings; -use strict; - -use Test::More qw(no_plan); -use Mail::Address; - -BEGIN { use_ok('Git') } - -my @success_list = (q[Jane], - q[jdoe@example.com], - q[<jdoe@example.com>], - q[Jane <jdoe@example.com>], - q[Jane Doe <jdoe@example.com>], - q["Jane" <jdoe@example.com>], - q["Doe, Jane" <jdoe@example.com>], - q["Jane@:;\>.,()<Doe" <jdoe@example.com>], - q[Jane!#$%&'*+-/=?^_{|}~Doe' <jdoe@example.com>], - q["<jdoe@example.com>"], - q["Jane jdoe@example.com"], - q[Jane Doe <jdoe @ example.com >], - q[Jane Doe < jdoe@example.com >], - q[Jane @ Doe @ Jane @ Doe], - q["Jane, 'Doe'" <jdoe@example.com>], - q['Doe, "Jane' <jdoe@example.com>], - q["Jane" "Do"e <jdoe@example.com>], - q["Jane' Doe" <jdoe@example.com>], - q["Jane Doe <jdoe@example.com>" <jdoe@example.com>], - q["Jane\" Doe" <jdoe@example.com>], - q[Doe, jane <jdoe@example.com>], - q["Jane Doe <jdoe@example.com>], - q['Jane 'Doe' <jdoe@example.com>], - q[Jane@:;\.,()<>Doe <jdoe@example.com>], - q[Jane <jdoe@example.com> Doe], - q[<jdoe@example.com> Jane Doe]); - -my @known_failure_list = (q[Jane\ Doe <jdoe@example.com>], - q["Doe, Ja"ne <jdoe@example.com>], - q["Doe, Katarina" Jane <jdoe@example.com>], - q[Jane jdoe@example.com], - q["Jane "Kat"a" ri"na" ",Doe" <jdoe@example.com>], - q[Jane Doe], - q[Jane "Doe <jdoe@example.com>"], - q[\"Jane Doe <jdoe@example.com>], - q[Jane\"\" Doe <jdoe@example.com>], - q['Jane "Katarina\" \' Doe' <jdoe@example.com>]); - -foreach my $str (@success_list) { - my @expected = map { $_->format } Mail::Address->parse("$str"); - my @actual = Git::parse_mailboxes("$str"); - is_deeply(\@expected, \@actual, qq[same output : $str]); -} - -TODO: { - local $TODO = "known breakage"; - foreach my $str (@known_failure_list) { - my @expected = map { $_->format } Mail::Address->parse("$str"); - my @actual = Git::parse_mailboxes("$str"); - is_deeply(\@expected, \@actual, qq[same output : $str]); - } -} - -my $is_passing = eval { Test::More->is_passing }; -exit($is_passing ? 0 : 1) unless $@ =~ /Can't locate object method/; diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index 3dc4a3454d..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 && @@ -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 } @@ -149,6 +155,7 @@ cat >expected-cc <<\EOF !three@example.com! !four@example.com! !five@example.com! +!six@example.com! EOF " @@ -159,9 +166,11 @@ test_expect_success $PREREQ 'cc trailer with various syntax' ' Test Cc: trailers. Cc: one@example.com - Cc: <two@example.com> # this is part of the name - Cc: <three@example.com>, <four@example.com> # not.five@example.com - Cc: "Some # Body" <five@example.com> [part.of.name.too] + 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 \ @@ -169,6 +178,25 @@ test_expect_success $PREREQ 'cc trailer with various syntax' ' 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 @@ -196,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 @@ -288,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 ' @@ -551,12 +578,6 @@ Result: OK EOF " -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_suppression () { git send-email \ --dry-run \ @@ -1263,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' ' @@ -1274,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' ' @@ -1286,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' ' @@ -1914,4 +1935,52 @@ test_expect_success $PREREQ 'leading and trailing whitespaces are removed' ' 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/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..0b20b07e68 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 @@ -472,7 +473,7 @@ test_expect_failure PIPE 'change file mode but keep old content' ' { git rev-list HEAD | git diff-tree --root --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual && git show HEAD:greeting >actual.blob && git show HEAD^:greeting >actual.target && @@ -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 && @@ -572,7 +573,7 @@ test_expect_success PIPE 'NUL in log message, file content, and property name' ' { git rev-list HEAD | git diff-tree --root --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual && { git cat-file commit HEAD | nul_to_q && @@ -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 @@ -658,7 +659,7 @@ test_expect_success PIPE 'change file mode and reiterate content' ' { git rev-list HEAD | git diff-tree --root --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual && git show HEAD:greeting >actual.blob && git show HEAD^:greeting >actual.target && @@ -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 @@ -791,12 +792,12 @@ test_expect_success PIPE 'property deltas supported' ' { git rev-list HEAD | git diff-tree --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual && test_cmp expect actual ' -test_expect_success PIPE 'properties on /' ' +test_expect_success 'properties on /' ' reinit_git && cat <<-\EOF >expect && OBJID @@ -845,12 +846,12 @@ test_expect_success PIPE 'properties on /' ' { git rev-list HEAD | git diff-tree --root --always --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual && test_cmp expect actual ' -test_expect_success PIPE 'deltas for typechange' ' +test_expect_success 'deltas for typechange' ' reinit_git && cat >expect <<-\EOF && OBJID @@ -930,12 +931,12 @@ test_expect_success PIPE 'deltas for typechange' ' { git rev-list HEAD | git diff-tree --root --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual && 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 @@ -1029,7 +1030,7 @@ test_expect_success PIPE 'deltas need not consume the whole preimage' ' { git rev-list HEAD | git diff-tree --root --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual && test_cmp expect actual && git show HEAD:postimage >actual.3 && @@ -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 8a8ba65a2a..c937330a5f 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -288,12 +288,12 @@ test_expect_success 'able to dcommit to a subdirectory' ' 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 ' diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index cd480edf16..5e0ad19177 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -33,8 +33,8 @@ test_expect_success 'init and fetch a moved directory' ' 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 && + 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$")" ' @@ -48,8 +48,8 @@ test_expect_success 'init and fetch from one svn-remote' ' 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 + git cat-file blob refs/remotes/svn/thunk:readme >actual && + test "$(sed -n -e "3p" actual)" = goodbye ' test_expect_success 'follow deleted parent' ' @@ -107,7 +107,8 @@ test_expect_success 'follow deleted directory' ' 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 + git ls-tree refs/remotes/glob >actual && + test_line_count = 1 actual ' # ref: r9270 of the Subversion repository: (http://svn.collab.net/repos/svn) @@ -204,8 +205,9 @@ test_expect_success "follow-parent is atomic" ' 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" " @@ -213,7 +215,10 @@ test_expect_success "multi-fetch continues to work" " " test_expect_success "multi-fetch works off a 'clean' repository" ' - rm -r "$GIT_DIR/svn" "$GIT_DIR/refs/remotes" "$GIT_DIR/logs" && + rm -rf "$GIT_DIR/svn" && + git for-each-ref --format="option no-deref%0adelete %(refname)" refs/remotes | + git update-ref --stdin && + git reflog expire --all --expire=all && mkdir "$GIT_DIR/svn" && git svn multi-fetch ' diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index 9f3ef8f2ef..ceaa5bad10 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -28,7 +28,7 @@ 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 && diff --git a/t/t9108-git-svn-glob.sh b/t/t9108-git-svn-glob.sh index a94286c8ec..6990f64364 100755 --- a/t/t9108-git-svn-glob.sh +++ b/t/t9108-git-svn-glob.sh @@ -47,8 +47,8 @@ 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)" && @@ -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/start | wc -l) -eq 3 && + 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 | \ - sed -e "s/^.\{41\}//" > output.two && + git log --pretty=oneline refs/remotes/two/tags/end >actual && + sed -e "s/^.\{41\}//" actual >output.two && test_cmp expect.two output.two ' diff --git a/t/t9109-git-svn-multi-glob.sh b/t/t9109-git-svn-multi-glob.sh index 8d99e848d4..c1e7542a37 100755 --- a/t/t9109-git-svn-multi-glob.sh +++ b/t/t9109-git-svn-multi-glob.sh @@ -47,8 +47,8 @@ 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)" && @@ -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 && + 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 | \ - sed -e "s/^.\{41\}//" > output.two && + 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,14 +126,16 @@ 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 && + 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 | \ - sed -e "s/^.\{41\}//" > output.four && + git log --pretty=oneline refs/remotes/four/tags/next >actual && + sed -e "s/^.\{41\}//" actual >output.four && test_cmp expect.four output.four ' diff --git a/t/t9110-git-svn-use-svm-props.sh b/t/t9110-git-svn-use-svm-props.sh index dde0a3c222..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 22b6e5ee7d..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 a3d388228a..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 } @@ -70,7 +68,8 @@ 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$' + git cat-file commit refs/heads/svn^ >actual && + grep '^friend$' actual " test_expect_success 'git svn dcommit merges' " @@ -84,12 +83,13 @@ test_expect_success 'verify post-merge ancestry' " 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$' + 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/t9117-git-svn-init-clone.sh b/t/t9117-git-svn-init-clone.sh index 69a675052e..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,7 +113,7 @@ 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 diff --git a/t/t9130-git-svn-authors-file.sh b/t/t9130-git-svn-authors-file.sh index 41264818cc..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 ) ' @@ -102,12 +104,28 @@ test_expect_success !MINGW '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/t9138-git-svn-authors-prog.sh b/t/t9138-git-svn-authors-prog.sh index 7d7e9d46bc..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/t9153-git-svn-rewrite-uuid.sh b/t/t9153-git-svn-rewrite-uuid.sh index 372ef15685..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/t9168-git-svn-partially-globbed-names.sh b/t/t9168-git-svn-partially-globbed-names.sh index 8b22f2272c..bdf6e84999 100755 --- a/t/t9168-git-svn-partially-globbed-names.sh +++ b/t/t9168-git-svn-partially-globbed-names.sh @@ -48,8 +48,8 @@ test_expect_success 'test refspec prefixed globbing' ' 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 | \ - sed -e "s/^.\{41\}//" >output.end && + 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)" && @@ -78,14 +78,16 @@ test_expect_success 'test left-hand-side only prefixed globbing' ' svn_cmd commit -m "try to try" ) && git svn fetch two && - test $(git rev-list refs/remotes/two/tags/t_end | wc -l) -eq 6 && - test $(git rev-list refs/remotes/two/branches/b_start | wc -l) -eq 3 && + 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 | \ - sed -e "s/^.\{41\}//" >output.two && + git log --pretty=oneline refs/remotes/two/tags/t_end >actual && + sed -e "s/^.\{41\}//" actual >output.two && test_cmp expect.two output.two ' @@ -118,14 +120,16 @@ test_expect_success 'test prefixed globs match just prefix' ' svn_cmd up ) && git svn fetch three && - test $(git rev-list refs/remotes/three/branches/b_ | wc -l) -eq 2 && - test $(git rev-list refs/remotes/three/tags/t_ | wc -l) -eq 3 && + 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_ | \ - sed -e "s/^.\{41\}//" >output.three && + git log --pretty=oneline refs/remotes/three/tags/t_ >actual && + sed -e "s/^.\{41\}//" actual >output.three && test_cmp expect.three output.three ' @@ -186,14 +190,16 @@ test_expect_success 'test globbing in the middle of the word' ' svn_cmd up ) && git svn fetch five && - test $(git rev-list refs/remotes/five/branches/abcde | wc -l) -eq 2 && - test $(git rev-list refs/remotes/five/tags/fghij | wc -l) -eq 3 && + 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 | \ - sed -e "s/^.\{41\}//" >output.five && + git log --pretty=oneline refs/remotes/five/tags/fghij >actual && + sed -e "s/^.\{41\}//" actual >output.five && test_cmp expect.five output.five ' 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 bb879a527d..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 diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 2e0ba3ebd8..9e7f96223d 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -876,7 +876,7 @@ test_expect_success 'L: verify internal tree sorting' ' EXPECT_END git fast-import <input && - git diff-tree --abbrev --raw L^ L >output && + GIT_PRINT_SHA1_ELLIPSIS="yes" git diff-tree --abbrev --raw L^ L >output && test_cmp expect output ' @@ -1185,7 +1185,7 @@ test_expect_success PIPE 'N: empty directory reads as missing' ' test_cmp expect.response response && git rev-list read-empty | git diff-tree -r --root --stdin | - sed "s/$_x40/OBJNAME/g" >actual && + sed "s/$OID_REGEX/OBJNAME/g" >actual && test_cmp expect actual ' @@ -1271,7 +1271,7 @@ test_expect_success 'N: delete directory by copying' ' git fast-import <input && git rev-list N-delete | git diff-tree -r --stdin --root --always | - sed -e "s/$_x40/OBJID/g" >actual && + sed -e "s/$OID_REGEX/OBJID/g" >actual && test_cmp expect actual ' @@ -2602,7 +2602,7 @@ test_expect_success 'R: terminating "done" within commit' ' EOF git rev-list done-ends | git diff-tree -r --stdin --root --always | - sed -e "s/$_x40/OBJID/g" >actual && + sed -e "s/$OID_REGEX/OBJID/g" >actual && test_cmp expect actual ' @@ -2654,7 +2654,7 @@ test_expect_success 'R: corrupt lines do not mess marks file' ' ## test_expect_success 'R: blob bigger than threshold' ' blobsize=$((2*1024*1024 + 53)) && - test-genrandom bar $blobsize >expect && + 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 @@ -2822,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 @@ -3120,4 +3120,146 @@ 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/t9350-fast-export.sh b/t/t9350-fast-export.sh index b5149fde6e..6a392e87bc 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 ' @@ -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,12 +362,14 @@ 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' ' @@ -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,41 @@ 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_expect_success 'merge commit gets exported with --import-marks' ' + test_create_repo merging && + ( + cd merging && + test_commit initial && + git checkout -b topic && + test_commit on-topic && + git checkout master && + test_commit on-master && + test_tick && + git merge --no-ff -m Yeah topic && + + echo ":1 $(git rev-parse HEAD^^)" >marks && + git fast-export --import-marks=marks master >out && + grep Yeah out + ) +' + test_done diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index c30660d606..06742748e9 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -447,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" diff --git a/t/t9402-git-cvsserver-refs.sh b/t/t9402-git-cvsserver-refs.sh index 6d2d3c8739..cf31ace667 100755 --- a/t/t9402-git-cvsserver-refs.sh +++ b/t/t9402-git-cvsserver-refs.sh @@ -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 6d06ed96cb..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 && 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 1b75c91965..34cd01366f 100755 --- a/t/t9700/test.pl +++ b/t/t9700/test.pl @@ -133,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 4d935222e9..4849edc4ef 100755 --- a/t/t9800-git-p4-basic.sh +++ b/t/t9800-git-p4-basic.sh @@ -277,6 +277,26 @@ test_expect_success 'submit from 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/t9802-git-p4-filetype.sh b/t/t9802-git-p4-filetype.sh index eb9a8ed197..1fc9b33aeb 100755 --- a/t/t9802-git-p4-filetype.sh +++ b/t/t9802-git-p4-filetype.sh @@ -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 593152817d..2325599ee6 100755 --- a/t/t9807-git-p4-submit.sh +++ b/t/t9807-git-p4-submit.sh @@ -139,6 +139,62 @@ 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 + ) +' + +# make two commits, but tell it to apply only one + +test_expect_success 'submit --commit one' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + test_commit "file9" && + test_commit "file10" && + git config git-p4.skipSubmitEdit true && + git p4 submit --commit HEAD + ) && + ( + cd "$cli" && + test_path_is_missing "file9.t" && + test_path_is_file "file10.t" + ) +' + +# make three commits, but tell it to apply only range + +test_expect_success 'submit --commit range' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + test_commit "file11" && + test_commit "file12" && + test_commit "file13" && + git config git-p4.skipSubmitEdit true && + git p4 submit --commit HEAD~2..HEAD + ) && + ( + cd "$cli" && + test_path_is_missing "file11.t" && + test_path_is_file "file12.t" && + test_path_is_file "file13.t" + ) +' + # # Basic submit tests, the five handled cases # @@ -413,6 +469,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/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/t9818-git-p4-block.sh b/t/t9818-git-p4-block.sh index 8840a183ac..ce7cb22ad3 100755 --- a/t/t9818-git-p4-block.sh +++ b/t/t9818-git-p4-block.sh @@ -129,6 +129,7 @@ test_expect_success 'Create a repo with multiple depot paths' ' ' test_expect_success 'Clone repo with multiple depot paths' ' + test_when_finished cleanup_git && ( cd "$git" && git p4 clone --changes-block-size=4 //depot/pathA@all //depot/pathB@all \ @@ -138,6 +139,13 @@ test_expect_success 'Clone repo with multiple depot paths' ' ) ' +test_expect_success 'Clone repo with self-sizing block size' ' + test_when_finished cleanup_git && + git p4 clone --changes-block-size=1000000 //depot@all --destination="$git" && + git -C "$git" log --oneline >log && + test_line_count \> 10 log +' + test_expect_success 'kill p4d' ' kill_p4d ' 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/t9822-git-p4-path-encoding.sh b/t/t9822-git-p4-path-encoding.sh index 7b83e696a9..c78477c19b 100755 --- a/t/t9822-git-p4-path-encoding.sh +++ b/t/t9822-git-p4-path-encoding.sh @@ -51,6 +51,22 @@ test_expect_success 'Clone repo containing iso8859-1 encoded paths with git-p4.p ) ' +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 ' diff --git a/t/t9824-git-p4-git-lfs.sh b/t/t9824-git-p4-git-lfs.sh index 734b8db4cb..ed80ca858c 100755 --- a/t/t9824-git-p4-git-lfs.sh +++ b/t/t9824-git-p4-git-lfs.sh @@ -83,9 +83,9 @@ test_expect_success 'Store files in LFS based on size (>24 bytes)' ' # # Git LFS (see https://git-lfs.github.com/) # - /file2.dat filter=lfs -text - /file4.bin filter=lfs -text - /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs -text + /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 @@ -111,7 +111,7 @@ test_expect_success 'Store files in LFS based on size (>25 bytes)' ' # # Git LFS (see https://git-lfs.github.com/) # - /file4.bin filter=lfs -text + /file4.bin filter=lfs diff=lfs merge=lfs -text EOF test_path_is_file .gitattributes && test_cmp expect .gitattributes @@ -137,7 +137,7 @@ test_expect_success 'Store files in LFS based on extension (dat)' ' # # Git LFS (see https://git-lfs.github.com/) # - *.dat filter=lfs -text + *.dat filter=lfs diff=lfs merge=lfs -text EOF test_path_is_file .gitattributes && test_cmp expect .gitattributes @@ -165,8 +165,8 @@ test_expect_success 'Store files in LFS based on size (>25 bytes) and extension # # Git LFS (see https://git-lfs.github.com/) # - *.dat filter=lfs -text - /file4.bin filter=lfs -text + *.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 @@ -201,8 +201,8 @@ test_expect_success 'Remove file from repo and store files in LFS based on size # # Git LFS (see https://git-lfs.github.com/) # - /file2.dat filter=lfs -text - /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs -text + /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 @@ -239,8 +239,8 @@ test_expect_success 'Add .gitattributes and store files in LFS based on size (>2 # # Git LFS (see https://git-lfs.github.com/) # - /file2.dat filter=lfs -text - /path[[:space:]]with[[:space:]]spaces/file3.bin filter=lfs -text + /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 @@ -280,7 +280,7 @@ test_expect_success 'Add big files to repo and store files in LFS based on compr # # Git LFS (see https://git-lfs.github.com/) # - /file6.bin filter=lfs -text + /file6.bin filter=lfs diff=lfs merge=lfs -text EOF test_path_is_file .gitattributes && test_cmp expect .gitattributes 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/t9832-unshelve.sh b/t/t9832-unshelve.sh new file mode 100755 index 0000000000..48ec7679b8 --- /dev/null +++ b/t/t9832-unshelve.sh @@ -0,0 +1,138 @@ +#!/bin/sh + +last_shelved_change () { + p4 changes -s shelved -m1 | cut -d " " -f 2 +} + +test_description='git p4 unshelve' + +. ./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" && + : >file_to_delete && + p4 add file_to_delete && + p4 submit -d "file to delete" + ) +' + +test_expect_success 'initial clone' ' + git p4 clone --dest="$git" //depot/@all +' + +test_expect_success 'create shelved changelist' ' + ( + cd "$cli" && + p4 edit file1 && + echo "a change" >>file1 && + echo "new file" >file2 && + p4 add file2 && + p4 delete file_to_delete && + p4 opened && + p4 shelve -i <<EOF +Change: new +Description: + Test commit + + Further description +Files: + //depot/file1 + //depot/file2 + //depot/file_to_delete +EOF + + ) && + ( + cd "$git" && + change=$(last_shelved_change) && + git p4 unshelve $change && + git show refs/remotes/p4/unshelved/$change | grep -q "Further description" && + git cherry-pick refs/remotes/p4/unshelved/$change && + test_path_is_file file2 && + test_cmp file1 "$cli"/file1 && + test_cmp file2 "$cli"/file2 && + test_path_is_missing file_to_delete + ) +' + +test_expect_success 'update shelved changelist and re-unshelve' ' + test_when_finished cleanup_git && + ( + cd "$cli" && + change=$(last_shelved_change) && + echo "file3" >file3 && + p4 add -c $change file3 && + p4 shelve -i -r <<EOF && +Change: $change +Description: + Test commit + + Further description +Files: + //depot/file1 + //depot/file2 + //depot/file3 + //depot/file_to_delete +EOF + p4 describe $change + ) && + ( + cd "$git" && + change=$(last_shelved_change) && + git p4 unshelve $change && + git diff refs/remotes/p4/unshelved/$change.0 refs/remotes/p4/unshelved/$change | grep -q file3 + ) +' + +# This is the tricky case where the shelved changelist base revision doesn't +# match git-p4's idea of the base revision +# +# We will attempt to unshelve a change that is based on a change one commit +# ahead of p4/master + +test_expect_success 'create shelved changelist based on p4 change ahead of p4/master' ' + git p4 clone --dest="$git" //depot/@all && + ( + cd "$cli" && + p4 revert ... && + p4 edit file1 && + echo "foo" >>file1 && + p4 submit -d "change:foo" && + p4 edit file1 && + echo "bar" >>file1 && + p4 shelve -i <<EOF && +Change: new +Description: + Change to be unshelved +Files: + //depot/file1 +EOF + change=$(last_shelved_change) && + p4 describe -S $change | grep -q "Change to be unshelved" + ) +' + +# Now try to unshelve it. git-p4 should refuse to do so. +test_expect_success 'try to unshelve the change' ' + test_when_finished cleanup_git && + ( + change=$(last_shelved_change) && + cd "$git" && + test_must_fail git p4 unshelve $change 2>out.txt && + grep -q "cannot unshelve" out.txt + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9833-errors.sh b/t/t9833-errors.sh new file mode 100755 index 0000000000..9ba892de7a --- /dev/null +++ b/t/t9833-errors.sh @@ -0,0 +1,78 @@ +#!/bin/sh + +test_description='git p4 errors' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'add p4 files' ' + ( + cd "$cli" && + echo file1 >file1 && + p4 add file1 && + p4 submit -d "file1" + ) +' + +# after this test, the default user requires a password +test_expect_success 'error handling' ' + git p4 clone --dest="$git" //depot@all && + ( + cd "$git" && + P4PORT=: test_must_fail git p4 submit 2>errmsg + ) && + p4 passwd -P newpassword && + ( + P4PASSWD=badpassword test_must_fail git p4 clone //depot/foo 2>errmsg && + grep -q "failure accessing depot.*P4PASSWD" errmsg + ) +' + +test_expect_success 'ticket logged out' ' + P4TICKETS="$cli/tickets" && + echo "newpassword" | p4 login && + ( + cd "$git" && + test_commit "ticket-auth-check" && + p4 logout && + test_must_fail git p4 submit 2>errmsg && + grep -q "failure accessing depot" errmsg + ) +' + +test_expect_success 'create group with short ticket expiry' ' + P4TICKETS="$cli/tickets" && + echo "newpassword" | p4 login && + p4_add_user short_expiry_user && + p4 -u short_expiry_user passwd -P password && + p4 group -i <<-EOF && + Group: testgroup + Timeout: 3 + Users: short_expiry_user + EOF + + p4 users | grep short_expiry_user +' + +test_expect_success 'git operation with expired ticket' ' + P4TICKETS="$cli/tickets" && + P4USER=short_expiry_user && + echo "password" | p4 login && + ( + cd "$git" && + git p4 sync && + sleep 5 && + test_must_fail git p4 sync 2>errmsg && + grep "failure accessing depot" errmsg + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + + +test_done diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index a34e55f874..a28640ce1a 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -13,7 +13,7 @@ complete () return 0 } -# Be careful when updating this list: +# Be careful when updating these lists: # # (1) The build tree may have build artifact from different branch, or # the user's $PATH may have a random executable that may begin @@ -30,7 +30,8 @@ complete () # completion for "git <TAB>", and a plumbing is excluded. "add", # "filter-branch" and "ls-files" are listed for this. -GIT_TESTING_COMMAND_COMPLETION='add checkout check-attr filter-branch ls-files' +GIT_TESTING_ALL_COMMAND_LIST='add checkout check-attr filter-branch ls-files' +GIT_TESTING_PORCELAIN_COMMAND_LIST='add checkout filter-branch' . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" @@ -84,10 +85,11 @@ test_completion () then printf '%s\n' "$2" >expected else - sed -e 's/Z$//' >expected + sed -e 's/Z$//' |sort >expected fi && run_completion "$1" && - test_cmp expected out + sort out >out_sorted && + test_cmp expected out_sorted } # Test __gitcomp. @@ -98,7 +100,7 @@ test_gitcomp () { local -a COMPREPLY && sed -e 's/Z$//' >expected && - cur="$1" && + local cur="$1" && shift && __gitcomp "$@" && print_comp && @@ -113,7 +115,7 @@ test_gitcomp_nl () { local -a COMPREPLY && sed -e 's/Z$//' >expected && - cur="$1" && + local cur="$1" && shift && __gitcomp_nl "$@" && print_comp && @@ -124,140 +126,336 @@ 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_expect_success '__git_find_repo_path - .git directory in cwd' ' + echo ".git" >expected && + ( + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" + ) && test_cmp expected "$actual" ' -test_expect_success '__gitdir - remote as argument' ' - echo "remote" >expected && - __gitdir "remote" >"$actual" && +test_expect_success '__git_find_repo_path - .git directory in parent' ' + echo "$ROOT/.git" >expected && + ( + cd subdir/subsubdir && + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" + ) && test_cmp expected "$actual" ' -test_expect_success '__gitdir - .git directory in cwd' ' - echo ".git" >expected && - __gitdir >"$actual" && +test_expect_success '__git_find_repo_path - cwd is a .git directory' ' + echo "." >expected && + ( + cd .git && + __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 - parent is a .git directory' ' + echo "$ROOT/.git" >expected && ( - cd subdir/subsubdir && - __gitdir >"$actual" + cd .git/objects && + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" ) && test_cmp expected "$actual" ' -test_expect_success '__gitdir - cwd is a .git directory' ' - echo "." >expected && +test_expect_success '__git_find_repo_path - $GIT_DIR set while .git directory in cwd' ' + echo "$ROOT/otherrepo/.git" >expected && ( - cd .git && - __gitdir >"$actual" + GIT_DIR="$ROOT/otherrepo/.git" && + export GIT_DIR && + __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 - $GIT_DIR set while .git directory in parent' ' + echo "$ROOT/otherrepo/.git" >expected && ( - cd .git/refs/heads && - __gitdir >"$actual" + GIT_DIR="$ROOT/otherrepo/.git" && + export GIT_DIR && + cd subdir && + __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 - from command line while "git -C"' ' + echo "$ROOT/.git" >expected && ( - GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" && + __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 '__git_find_repo_path - $GIT_DIR set while "git -C"' ' + echo "$ROOT/.git" >expected && + ( + GIT_DIR="$ROOT/.git" && export GIT_DIR && - __gitdir >"$actual" + __git_C_args=(-C otherrepo) && + __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 - relative dir in $GIT_DIR and "git -C"' ' + echo "$ROOT/otherrepo/.git" >expected && ( - GIT_DIR="$TRASH_DIRECTORY/otherrepo/.git" && + 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 '__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 && - __gitdir >"$actual" + __git_C_args=(-C .. -C otherrepo) && + __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 - non-existing path in "git -C"' ' ( - GIT_DIR="$TRASH_DIRECTORY/non-existing" && - export GIT_DIR && - test_must_fail __gitdir - ) + __git_C_args=(-C non-existing) && + test_must_fail __git_find_repo_path && + printf "$__git_repo_path" >"$actual" + ) && + test_must_be_empty "$actual" ' -function pwd_P_W () { - if test_have_prereq MINGW - then - pwd -W - else - pwd -P - fi -} +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 '__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 - 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 && + __git_find_repo_path && + echo "$__git_repo_path" >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success '__git_find_repo_path - not a git repository' ' + ( + cd non-repo && + GIT_CEILING_DIRECTORIES="$ROOT" && + export GIT_CEILING_DIRECTORIES && + 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 - not a git repository' ' - nongit test_must_fail __gitdir + +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 '__git_dequote - plain unquoted word' ' + __git_dequote unquoted-word && + verbose test unquoted-word = "$dequoted_word" +' + +# input: b\a\c\k\'\\\"s\l\a\s\h\es +# expected: back'\"slashes +test_expect_success '__git_dequote - backslash escaped' ' + __git_dequote "b\a\c\k\\'\''\\\\\\\"s\l\a\s\h\es" && + verbose test "back'\''\\\"slashes" = "$dequoted_word" +' + +# input: sin'gle\' '"quo'ted +# expected: single\ "quoted +test_expect_success '__git_dequote - single quoted' ' + __git_dequote "'"sin'gle\\\\' '\\\"quo'ted"'" && + verbose test '\''single\ "quoted'\'' = "$dequoted_word" +' + +# input: dou"ble\\" "\"\quot"ed +# expected: double\ "\quoted +test_expect_success '__git_dequote - double quoted' ' + __git_dequote '\''dou"ble\\" "\"\quot"ed'\'' && + verbose test '\''double\ "\quoted'\'' = "$dequoted_word" +' + +# input: 'open single quote +test_expect_success '__git_dequote - open single quote' ' + __git_dequote "'\''open single quote" && + verbose test "open single quote" = "$dequoted_word" +' + +# input: "open double quote +test_expect_success '__git_dequote - open double quote' ' + __git_dequote "\"open double quote" && + verbose test "open double quote" = "$dequoted_word" +' + + +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' ' @@ -361,10 +559,775 @@ 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_path_completion () +{ + test $# = 2 || error "bug in the test script: not 2 parameters to test_path_completion" + + local cur="$1" expected="$2" + echo "$expected" >expected && + ( + # In the following tests calling this function we only + # care about how __git_complete_index_file() deals with + # unusual characters in path names. By requesting only + # untracked files we dont have to bother adding any + # paths to the index in those tests. + __git_complete_index_file --others && + print_comp + ) && + test_cmp expected out +} + +test_expect_success 'setup for path completion tests' ' + mkdir simple-dir \ + "spaces in dir" \ + árvíztűrő && + touch simple-dir/simple-file \ + "spaces in dir/spaces in file" \ + "árvíztűrő/Сайн яваарай" && + if test_have_prereq !MINGW && + mkdir BS\\dir \ + '$'separators\034in\035dir'' && + touch BS\\dir/DQ\"file \ + '$'separators\034in\035dir/sep\036in\037file'' + then + test_set_prereq FUNNYNAMES + else + rm -rf BS\\dir '$'separators\034in\035dir'' + fi +' + +test_expect_success '__git_complete_index_file - simple' ' + test_path_completion simple simple-dir && # Bash is supposed to + # add the trailing /. + test_path_completion simple-dir/simple simple-dir/simple-file +' + +test_expect_success \ + '__git_complete_index_file - escaped characters on cmdline' ' + test_path_completion spac "spaces in dir" && # Bash will turn this + # into "spaces\ in\ dir" + test_path_completion "spaces\\ i" \ + "spaces in dir" && + test_path_completion "spaces\\ in\\ dir/s" \ + "spaces in dir/spaces in file" && + test_path_completion "spaces\\ in\\ dir/spaces\\ i" \ + "spaces in dir/spaces in file" +' + +test_expect_success \ + '__git_complete_index_file - quoted characters on cmdline' ' + # Testing with an opening but without a corresponding closing + # double quote is important. + test_path_completion \"spac "spaces in dir" && + test_path_completion "\"spaces i" \ + "spaces in dir" && + test_path_completion "\"spaces in dir/s" \ + "spaces in dir/spaces in file" && + test_path_completion "\"spaces in dir/spaces i" \ + "spaces in dir/spaces in file" +' + +test_expect_success '__git_complete_index_file - UTF-8 in ls-files output' ' + test_path_completion á árvíztűrő && + test_path_completion árvíztűrő/С "árvíztűrő/Сайн яваарай" +' + +test_expect_success FUNNYNAMES \ + '__git_complete_index_file - C-style escapes in ls-files output' ' + test_path_completion BS \ + BS\\dir && + test_path_completion BS\\\\d \ + BS\\dir && + test_path_completion BS\\\\dir/DQ \ + BS\\dir/DQ\"file && + test_path_completion BS\\\\dir/DQ\\\"f \ + BS\\dir/DQ\"file +' + +test_expect_success FUNNYNAMES \ + '__git_complete_index_file - \nnn-escaped characters in ls-files output' ' + test_path_completion sep '$'separators\034in\035dir'' && + test_path_completion '$'separators\034i'' \ + '$'separators\034in\035dir'' && + test_path_completion '$'separators\034in\035dir/sep'' \ + '$'separators\034in\035dir/sep\036in\037file'' && + test_path_completion '$'separators\034in\035dir/sep\036i'' \ + '$'separators\034in\035dir/sep\036in\037file'' +' + +test_expect_success FUNNYNAMES \ + '__git_complete_index_file - removing repeated quoted path components' ' + test_when_finished rm -r repeated-quoted && + mkdir repeated-quoted && # A directory whose name in itself + # would not be quoted ... + >repeated-quoted/0-file && + >repeated-quoted/1\"file && # ... but here the file makes the + # dirname quoted ... + >repeated-quoted/2-file && + >repeated-quoted/3\"file && # ... and here, too. + + # Still, we shold only list the directory name only once. + test_path_completion repeated repeated-quoted +' + +test_expect_success 'teardown after path completion tests' ' + rm -rf simple-dir "spaces in dir" árvíztűrő \ + BS\\dir '$'separators\034in\035dir'' +' + + test_expect_success '__git_get_config_variables' ' cat >expect <<-EOF && name-1 @@ -388,17 +1351,6 @@ test_expect_success '__git_pretty_aliases' ' test_cmp expect actual ' -test_expect_success '__git_aliases' ' - cat >expect <<-EOF && - ci - co - EOF - test_config alias.ci commit && - test_config alias.co checkout && - __git_aliases >actual && - test_cmp expect actual -' - test_expect_success 'basic' ' run_completion "git " && # built-in @@ -434,14 +1386,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 ' @@ -475,7 +1433,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' ' @@ -483,10 +1446,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 @@ -500,6 +1463,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 @@ -517,7 +1486,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 @@ -526,7 +1495,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 @@ -545,6 +1514,7 @@ test_expect_success 'complete files' ' echo "expected" > .gitignore && echo "out" >> .gitignore && + echo "out_sorted" >> .gitignore && git add .gitignore && test_completion "git commit " ".gitignore" && @@ -634,6 +1604,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" && @@ -643,4 +1619,62 @@ 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 !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 97c9b32c2e..c3b89ae783 100755 --- a/t/t9903-bash-prompt.sh +++ b/t/t9903-bash-prompt.sh @@ -148,7 +148,7 @@ test_expect_success 'prompt - inside .git directory' ' test_expect_success 'prompt - deep inside .git directory' ' printf " (GIT_DIR!)" >expected && ( - cd .git/refs/heads && + cd .git/objects && __git_ps1 >"$actual" ) && test_cmp expected "$actual" @@ -735,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 adab7f51f4..2b2181dca0 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"; @@ -136,37 +137,53 @@ 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. +# Wrap git with a debugger. Adding this to a command can make it easier +# to understand what is going on in a failing test. # -# Example: "debug git checkout master". +# Examples: +# debug git checkout master +# debug --debugger=nemiver git $ARGS +# debug -d "valgrind --tool=memcheck --track-origins=yes" git $ARGS debug () { - GIT_TEST_GDB=1 "$@" + case "$1" in + -d) + GIT_DEBUGGER="$2" && + shift 2 + ;; + --debugger=*) + GIT_DEBUGGER="${1#*=}" && + shift 1 + ;; + *) + GIT_DEBUGGER=1 + ;; + esac && + GIT_DEBUGGER="${GIT_DEBUGGER}" "$@" <&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 @@ -176,21 +193,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> @@ -211,6 +233,11 @@ 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 () { config_dir= @@ -267,8 +294,20 @@ write_script () { # The single parameter is the prerequisite tag (a simple word, in all # capital letters by convention). +test_unset_prereq () { + ! test_have_prereq "$1" || + satisfied_prereq="${satisfied_prereq% $1 *} ${satisfied_prereq#* $1 }" +} + test_set_prereq () { - satisfied_prereq="$satisfied_prereq$1 " + case "$1" in + !*) + test_unset_prereq "${1#!}" + ;; + *) + satisfied_prereq="$satisfied_prereq$1 " + ;; + esac } satisfied_prereq=" " lazily_testable_prereq= lazily_tested_prereq= @@ -599,6 +638,14 @@ list_contains () { # # 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 @@ -610,30 +657,30 @@ test_must_fail () { _test_ok= ;; esac - "$@" + "$@" 2>&7 exit_code=$? if test $exit_code -eq 0 && ! list_contains "$_test_ok" success then - echo >&2 "test_must_fail: command succeeded: $*" + echo >&4 "test_must_fail: command succeeded: $*" return 1 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 >&2 "test_must_fail: died by signal $(($exit_code - 128)): $*" + echo >&4 "test_must_fail: died by signal $(($exit_code - 128)): $*" return 1 elif test $exit_code -eq 127 then - echo >&2 "test_must_fail: command not found: $*" + echo >&4 "test_must_fail: command not found: $*" return 1 elif test $exit_code -eq 126 then - echo >&2 "test_must_fail: valgrind error: $*" + 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: @@ -645,10 +692,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 () { - test_must_fail ok=success "$@" -} + 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: @@ -660,16 +709,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: @@ -694,12 +743,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 } @@ -707,6 +810,7 @@ verbose () { # otherwise. test_must_be_empty () { + test_path_is_file "$1" && if test -s "$1" then echo "'$1' is not empty, it contains:" @@ -817,8 +921,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 () { @@ -958,13 +1062,13 @@ test_env () { 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. @@ -989,6 +1093,7 @@ test_copy_bytes () { my $s; my $nread = sysread(STDIN, $s, $len); die "cannot read: $!" unless defined($nread); + last unless $nread; print $s; $len -= $nread; } @@ -1005,6 +1110,40 @@ nongit () { 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 cde7fc7fcf..28315706be 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -36,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 @@ -67,7 +80,7 @@ done,*) # from any previous runs. >"$GIT_TEST_TEE_OUTPUT_FILE" - (GIT_TEST_TEE_STARTED=done ${SHELL_PATH} "$0" "$@" 2>&1; + (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 @@ -91,7 +104,6 @@ unset VISUAL EMAIL LANGUAGE COLUMNS $("$PERL_PATH" -e ' my $ok = join("|", qw( TRACE DEBUG - USE_LOOKUP TEST .*_TEST PROVE @@ -104,6 +116,7 @@ unset VISUAL EMAIL LANGUAGE COLUMNS $("$PERL_PATH" -e ' 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 @@ -148,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 @@ -166,13 +176,16 @@ 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 +OID_REGEX="$_x40" +ZERO_OID=$_z40 EMPTY_TREE=4b825dc642cb6eb9a060e54bf8d69288fbee4904 EMPTY_BLOB=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 @@ -184,7 +197,7 @@ LF=' # when case-folding filenames u200c=$(printf '\342\200\214') -export _x05 _x40 _z40 LF u200c EMPTY_TREE EMPTY_BLOB +export _x05 _x35 _x40 _z40 LF u200c EMPTY_TREE EMPTY_BLOB ZERO_OID OID_REGEX # Each test should start with something like this, after copyright notices: # @@ -253,8 +266,24 @@ 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 @@ -270,6 +299,11 @@ 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 @@ -342,6 +376,7 @@ fi exec 5>&1 exec 6<&0 +exec 7>&2 if test "$verbose_log" = "t" then exec 3>>"$GIT_TEST_TEE_OUTPUT_FILE" 4>&3 @@ -575,7 +610,9 @@ maybe_setup_valgrind () { } want_trace () { - test "$trace" = t && test "$verbose" = t + test "$trace" = t && { + test "$verbose" = t || test "$verbose_log" = t + } } # This is a separate function because some tests use @@ -590,26 +627,40 @@ test_eval_inner_ () { } 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 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_ } @@ -624,9 +675,9 @@ test_run_ () { 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 @@ -744,26 +795,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 ;; @@ -898,16 +959,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"/t/helper/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 t/helper/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 @@ -918,7 +979,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" @@ -966,7 +1026,8 @@ yes () { } # 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 () { @@ -975,9 +1036,6 @@ case $(uname -s) in find () { /usr/bin/find "$@" } - sum () { - md5sum "$@" - } # git sees Windows-style pwd pwd () { builtin pwd -W @@ -1007,8 +1065,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? @@ -1021,42 +1082,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*|MINGW*) - false - ;; - *) - rm -f testfifo && mkfifo testfifo - ;; - esac + test_have_prereq !MINGW,!CYGWIN && + rm -f testfifo && mkfifo testfifo ' test_lazy_prereq SYMLINKS ' @@ -1079,12 +1108,7 @@ test_lazy_prereq UTF8_NFD_TO_NFC ' auml=$(printf "\303\244") aumlcdiar=$(printf "\141\314\210") >"$auml" && - case "$(echo *)" in - "$aumlcdiar") - true ;; - *) - false ;; - esac + test -f "$aumlcdiar" ' test_lazy_prereq AUTOIDENT ' @@ -1097,6 +1121,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 ' @@ -1141,6 +1169,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 @@ -1151,7 +1180,19 @@ 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 | @@ -1161,3 +1202,17 @@ build_option () { 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 +' + +# SHA1 is a test if the hash algorithm in use is SHA-1. This is both for tests +# which will not work with other hash algorithms and tests that work but don't +# test anything meaningful (e.g. special values which cause short collisions). +test_lazy_prereq SHA1 ' + test $(git hash-object /dev/null) = e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 +' diff --git a/t/test-terminal.perl b/t/test-terminal.perl index 96b6a03e1c..46bf618479 100755 --- a/t/test-terminal.perl +++ b/t/test-terminal.perl @@ -80,6 +80,7 @@ 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; |