diff options
Diffstat (limited to 't')
238 files changed, 11018 insertions, 1767 deletions
diff --git a/t/.gitattributes b/t/.gitattributes index e7acedabe1..df05434d32 100644 --- a/t/.gitattributes +++ b/t/.gitattributes @@ -16,6 +16,7 @@ t[0-9][0-9][0-9][0-9]/* -whitespace /t4135/* eol=lf /t4211/* eol=lf /t4252/* eol=lf +/t4256/1/* eol=lf /t5100/* eol=lf /t5515/* eol=lf /t556x_common eol=lf diff --git a/t/.gitignore b/t/.gitignore index 348715f0e4..91cf5772fe 100644 --- a/t/.gitignore +++ b/t/.gitignore @@ -2,3 +2,4 @@ /test-results /.prove /chainlinttmp +/out/ @@ -170,6 +170,15 @@ appropriately before running "make". implied by other options like --valgrind and GIT_TEST_INSTALLED. +--no-bin-wrappers:: + By default, the test suite uses the wrappers in + `../bin-wrappers/` to execute `git` and friends. With this option, + `../git` and friends are run directly. This is not recommended + in general, as the wrappers contain safeguards to ensure that no + files from an installed Git are used, but can speed up test runs + especially on platforms where running shell scripts is expensive + (most notably, Windows). + --root=<directory>:: Create "trash" directories used to store all temporary data during testing under <directory>, instead of the t/ directory. @@ -186,6 +195,22 @@ appropriately before running "make". this feature by setting the GIT_TEST_CHAIN_LINT environment variable to "1" or "0", respectively. +--stress:: +--stress=<N>:: + Run the test script repeatedly in multiple parallel jobs until + one of them fails. Useful for reproducing rare failures in + flaky tests. The number of parallel jobs is, in order of + precedence: <N>, or the value of the GIT_TEST_STRESS_LOAD + environment variable, or twice the number of available + processors (as shown by the 'getconf' utility), or 8. + Implies `--verbose -x --immediate` to get the most information + about the failure. Note that the verbose output of each test + job is saved to 't/test-results/$TEST_NAME.stress-<nr>.out', + and only the output of the failed test job is shown on the + terminal. The names of the trash directories get a + '.stress-<nr>' suffix, and the trash directory of the failed + test job is renamed to end with a '.stress-failed' suffix. + You can also set the GIT_TEST_INSTALLED environment variable to the bindir of an existing git installation to test that installation. You still need to have built this git sandbox, from which various @@ -302,6 +327,12 @@ 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_GETTEXT_POISON=<non-empty?> turns all strings marked for +translation into gibberish if non-empty (think "test -n"). Used for +spotting those tests that need to be marked with a C_LOCALE_OUTPUT +prerequisite when adding more strings for translation. See "Testing +marked strings" in po/README for details. + GIT_TEST_SPLIT_INDEX=<boolean> forces split-index mode on the whole test suite. Accept any boolean values that are accepted by git-config. @@ -316,10 +347,51 @@ 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. -GIT_TEST_OE_DELTA_SIZE=<n> exercises the uncomon pack-objects code +GIT_TEST_OE_DELTA_SIZE=<n> exercises the uncommon pack-objects code path where deltas larger than this limit require extra memory allocation for bookkeeping. +GIT_TEST_VALIDATE_INDEX_CACHE_ENTRIES=<boolean> checks that cache-tree +records are valid when the index is written out or after a merge. This +is mostly to catch missing invalidation. Default is true. + +GIT_TEST_COMMIT_GRAPH=<boolean>, when true, forces the commit-graph to +be written after every 'git commit' command, and overrides the +'core.commitGraph' setting to true. + +GIT_TEST_FSMONITOR=$PWD/t7519/fsmonitor-all exercises the fsmonitor +code path for utilizing a file system monitor to speed up detecting +new or changed files. + +GIT_TEST_INDEX_VERSION=<n> exercises the index read/write code path +for the index version specified. Can be set to any valid version +(currently 2, 3, or 4). + +GIT_TEST_PACK_SPARSE=<boolean> if enabled will default the pack-objects +builtin to use the sparse object walk. This can still be overridden by +the --no-sparse command-line argument. + +GIT_TEST_PRELOAD_INDEX=<boolean> exercises the preload-index code path +by overriding the minimum number of cache entries required per thread. + +GIT_TEST_REBASE_USE_BUILTIN=<boolean>, when false, disables the +builtin version of git-rebase. See 'rebase.useBuiltin' in +git-config(1). + +GIT_TEST_INDEX_THREADS=<n> enables exercising the multi-threaded loading +of the index for the whole test suite by bypassing the default number of +cache entries and thread minimums. Setting this to 1 will make the +index loading single threaded. + +GIT_TEST_MULTI_PACK_INDEX=<boolean>, when true, forces the multi-pack- +index to be written after every 'git repack' command, and overrides the +'core.multiPackIndex' setting to true. + +GIT_TEST_SIDEBAND_ALL=<boolean>, when true, overrides the +'uploadpack.allowSidebandAll' setting to true, and when false, forces +fetch-pack to not request sideband-all (even if the server advertises +sideband-all). + Naming Tests ------------ @@ -387,20 +459,21 @@ This test harness library does the following things: - Creates an empty test directory with an empty .git/objects database and chdir(2) into it. This directory is 't/trash directory.$test_name_without_dotsh', with t/ subject to change by - the --root option documented above. + the --root option documented above, and a '.stress-<N>' suffix + appended by the --stress option. - Defines standard test helper functions for your scripts to use. These functions are designed to make all scripts behave consistently when command line arguments --verbose (or -v), --debug (or -d), and --immediate (or -i) is given. -Do's, don'ts & things to keep in mind -------------------------------------- +Do's & don'ts +------------- Here are a few examples of things you probably should and shouldn't do when writing tests. -Do: +Here are the "do's:" - Put all code inside test_expect_success and other assertions. @@ -445,16 +518,21 @@ Do: Windows, where the shell (MSYS bash) mangles absolute path names. For details, see the commit message of 4114156ae9. -Don't: + - Remember that 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 debug the tests. + +And here are the "don'ts:" - - exit() within a <script> part. + - Don't exit() within a <script> part. The harness will catch this as a programming error of the test. Use test_done instead if you need to stop the tests early (see "Skipping tests" below). - - use '! git cmd' when you want to make sure the git command exits - with failure in a controlled way by calling "die()". Instead, + - Don't use '! git cmd' when you want to make sure the git command + exits with failure in a controlled way by calling "die()". Instead, use 'test_must_fail git cmd'. This will signal a failure if git dies in an unexpected way (e.g. segfault). @@ -462,8 +540,35 @@ Don't: platform commands; just use '! cmd'. We are not in the business of verifying that the world given to us sanely works. - - use perl without spelling it as "$PERL_PATH". This is to help our - friends on Windows where the platform Perl often adds CR before + - Don't feed the output of a git command to a pipe, as in: + + git -C repo ls-files | + xargs -n 1 basename | + grep foo + + which will discard git's exit code and may mask a crash. In the + above example, all exit codes are ignored except grep's. + + Instead, write the output of that command to a temporary + file with ">" or assign it to a variable with "x=$(git ...)" rather + than pipe it. + + - Don't use command substitution in a way that discards git's exit + code. When assigning to a variable, the exit code is not discarded, + e.g.: + + x=$(git cat-file -p $sha) && + ... + + is OK because a crash in "git cat-file" will cause the "&&" chain + to fail, but: + + test "refs/heads/foo" = "$(git symbolic-ref HEAD)" + + is not OK and a crash in git could go undetected. + + - Don't use perl without spelling it as "$PERL_PATH". This is to help + our friends on Windows where the platform Perl often adds CR before the end of line, and they bundle Git with a version of Perl that does not do so, whose path is specified with $PERL_PATH. Note that we provide a "perl" function which uses $PERL_PATH under the hood, so @@ -471,17 +576,17 @@ Don't: (but you do, for example, on a shebang line or in a sub script created via "write_script"). - - use sh without spelling it as "$SHELL_PATH", when the script can - be misinterpreted by broken platform shell (e.g. Solaris). + - Don't use sh without spelling it as "$SHELL_PATH", when the script + can be misinterpreted by broken platform shell (e.g. Solaris). - - chdir around in tests. It is not sufficient to chdir to + - Don't chdir around in tests. It is not sufficient to chdir to somewhere and then chdir back to the original location later in the test, as any intermediate step can fail and abort the test, 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 + - Don't 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 && @@ -496,7 +601,7 @@ Don't: ( cd dir && git cmd 2>../error ) && test_cmp expect error - - Break the TAP output + - Don't break the TAP output The raw output from your test may be interpreted by a TAP harness. TAP harnesses will ignore everything they don't know about, but don't step @@ -516,13 +621,6 @@ Don't: but the best indication is to just run the tests with prove(1), it'll complain if anything is amiss. -Keep in mind: - - - 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. - Skipping tests -------------- @@ -807,6 +905,28 @@ library for your script to use. the symbolic link in the file system and a part that does; then only the latter part need be protected by a SYMLINKS prerequisite (see below). + - test_oid_init + + This function loads facts and useful object IDs related to the hash + algorithm(s) in use from the files in t/oid-info. + + - test_oid_cache + + This function reads per-hash algorithm information from standard + input (usually a heredoc) in the format described in + t/oid-info/README. This is useful for test-specific values, such as + object IDs, which must vary based on the hash algorithm. + + Certain fixed values, such as hash sizes and common placeholder + object IDs, can be loaded with test_oid_init (described above). + + - test_oid <key> + + This function looks up a value for the hash algorithm in use, based + on the key given. The value must have been loaded using + test_oid_init or test_oid_cache. Providing an unknown key is an + error. + Prerequisites ------------- diff --git a/t/check-non-portable-shell.pl b/t/check-non-portable-shell.pl index b45bdac688..166d64d4a2 100755 --- a/t/check-non-portable-shell.pl +++ b/t/check-non-portable-shell.pl @@ -35,7 +35,8 @@ while (<>) { chomp; } - /\bsed\s+-i/ and err 'sed -i is not portable'; + /\bcp\s+-a/ and err 'cp -a is not portable'; + /\bsed\s+-[^efn]\s+/ and err 'sed option not portable (use only -n, -e, -f)'; /\becho\s+-[neE]/ and err 'echo with option is not portable (use printf)'; /^\s*declare\s+/ and err 'arrays/declare not portable'; /^\s*[^#]\s*which\s/ and err 'which is not portable (use type)'; diff --git a/t/helper/test-date.c b/t/helper/test-date.c index a0837371ab..b3253803ac 100644 --- a/t/helper/test-date.c +++ b/t/helper/test-date.c @@ -3,10 +3,12 @@ static const char *usage_msg = "\n" " test-tool date relative [time_t]...\n" +" test-tool date human [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 getnanos [start-nanos]\n" " test-tool date is64bit\n" " test-tool date time_t-is64bit\n"; @@ -16,12 +18,20 @@ static void show_relative_dates(const char **argv, struct timeval *now) for (; *argv; argv++) { time_t t = atoi(*argv); - show_date_relative(t, 0, now, &buf); + show_date_relative(t, now, &buf); printf("%s -> %s\n", *argv, buf.buf); } strbuf_release(&buf); } +static void show_human_dates(const char **argv) +{ + for (; *argv; argv++) { + time_t t = atoi(*argv); + printf("%s -> %s\n", *argv, show_date(t, 0, DATE_MODE(HUMAN))); + } +} + static void show_dates(const char **argv, const char *format) { struct date_mode mode; @@ -82,12 +92,21 @@ static void parse_approx_timestamp(const char **argv, struct timeval *now) } } +static void getnanos(const char **argv) +{ + double seconds = getnanotime() / 1.0e9; + + if (*argv) + seconds -= strtod(*argv, NULL); + printf("%lf\n", seconds); +} + int cmd__date(int argc, const char **argv) { struct timeval now; const char *x; - x = getenv("TEST_DATE_NOW"); + x = getenv("GIT_TEST_DATE_NOW"); if (x) { now.tv_sec = atoi(x); now.tv_usec = 0; @@ -100,6 +119,8 @@ int cmd__date(int argc, const char **argv) usage(usage_msg); if (!strcmp(*argv, "relative")) show_relative_dates(argv+1, &now); + else if (!strcmp(*argv, "human")) + show_human_dates(argv+1); else if (skip_prefix(*argv, "show:", &x)) show_dates(argv+1, x); else if (!strcmp(*argv, "parse")) @@ -108,6 +129,8 @@ int cmd__date(int argc, const char **argv) parse_approxidate(argv+1, &now); else if (!strcmp(*argv, "timestamp")) parse_approx_timestamp(argv+1, &now); + else if (!strcmp(*argv, "getnanos")) + getnanos(argv+1); else if (!strcmp(*argv, "is64bit")) return sizeof(timestamp_t) == 8 ? 0 : 1; else if (!strcmp(*argv, "time_t-is64bit")) diff --git a/t/helper/test-delta.c b/t/helper/test-delta.c index 34c7259248..e749a49c88 100644 --- a/t/helper/test-delta.c +++ b/t/helper/test-delta.c @@ -34,8 +34,8 @@ int cmd__delta(int argc, const char **argv) return 1; } from_size = st.st_size; - from_buf = mmap(NULL, from_size, PROT_READ, MAP_PRIVATE, fd, 0); - if (from_buf == MAP_FAILED) { + from_buf = xmalloc(from_size); + if (read_in_full(fd, from_buf, from_size) < 0) { perror(argv[2]); close(fd); return 1; @@ -48,8 +48,8 @@ int cmd__delta(int argc, const char **argv) return 1; } data_size = st.st_size; - data_buf = mmap(NULL, data_size, PROT_READ, MAP_PRIVATE, fd, 0); - if (data_buf == MAP_FAILED) { + data_buf = xmalloc(data_size); + if (read_in_full(fd, data_buf, data_size) < 0) { perror(argv[3]); close(fd); return 1; diff --git a/t/helper/test-dump-cache-tree.c b/t/helper/test-dump-cache-tree.c index 98a4891f1d..6a3f88f5f5 100644 --- a/t/helper/test-dump-cache-tree.c +++ b/t/helper/test-dump-cache-tree.c @@ -33,7 +33,7 @@ static int dump_cache_tree(struct cache_tree *it, } else { dump_one(it, pfx, ""); - if (oidcmp(&it->oid, &ref->oid) || + if (!oideq(&it->oid, &ref->oid) || ref->entry_count != it->entry_count || ref->subtree_nr != it->subtree_nr) { /* claims to be valid but is lying */ diff --git a/t/helper/test-dump-fsmonitor.c b/t/helper/test-dump-fsmonitor.c index ad452707e8..2786f47088 100644 --- a/t/helper/test-dump-fsmonitor.c +++ b/t/helper/test-dump-fsmonitor.c @@ -1,12 +1,13 @@ +#include "test-tool.h" #include "cache.h" -int cmd_main(int ac, const char **av) +int cmd__dump_fsmonitor(int ac, const char **av) { - struct index_state *istate = &the_index; + struct index_state *istate = the_repository->index; int i; setup_git_directory(); - if (do_read_index(istate, get_index_file(), 0) < 0) + if (do_read_index(istate, the_repository->index_file, 0) < 0) die("unable to read index file"); if (!istate->fsmonitor_last_update) { printf("no fsmonitor\n"); diff --git a/t/helper/test-dump-untracked-cache.c b/t/helper/test-dump-untracked-cache.c index bd92fb305a..cf0f2c7228 100644 --- a/t/helper/test-dump-untracked-cache.c +++ b/t/helper/test-dump-untracked-cache.c @@ -1,3 +1,5 @@ +#define USE_THE_INDEX_COMPATIBILITY_MACROS +#include "test-tool.h" #include "cache.h" #include "dir.h" @@ -38,7 +40,7 @@ static void dump(struct untracked_cache_dir *ucd, struct strbuf *base) strbuf_setlen(base, len); } -int cmd_main(int ac, const char **av) +int cmd__dump_untracked_cache(int ac, const char **av) { struct untracked_cache *uc; struct strbuf base = STRBUF_INIT; diff --git a/t/helper/test-hash-speed.c b/t/helper/test-hash-speed.c new file mode 100644 index 0000000000..432233c7f0 --- /dev/null +++ b/t/helper/test-hash-speed.c @@ -0,0 +1,61 @@ +#include "test-tool.h" +#include "cache.h" + +#define NUM_SECONDS 3 + +static inline void compute_hash(const struct git_hash_algo *algo, git_hash_ctx *ctx, uint8_t *final, const void *p, size_t len) +{ + algo->init_fn(ctx); + algo->update_fn(ctx, p, len); + algo->final_fn(final, ctx); +} + +int cmd__hash_speed(int ac, const char **av) +{ + git_hash_ctx ctx; + unsigned char hash[GIT_MAX_RAWSZ]; + clock_t initial, start, end; + unsigned bufsizes[] = { 64, 256, 1024, 8192, 16384 }; + int i; + void *p; + const struct git_hash_algo *algo = NULL; + + if (ac == 2) { + for (i = 1; i < GIT_HASH_NALGOS; i++) { + if (!strcmp(av[1], hash_algos[i].name)) { + algo = &hash_algos[i]; + break; + } + } + } + if (!algo) + die("usage: test-tool hash-speed algo_name"); + + /* Use this as an offset to make overflow less likely. */ + initial = clock(); + + printf("algo: %s\n", algo->name); + + for (i = 0; i < ARRAY_SIZE(bufsizes); i++) { + unsigned long j, kb; + double kb_per_sec; + p = xcalloc(1, bufsizes[i]); + start = end = clock() - initial; + for (j = 0; ((end - start) / CLOCKS_PER_SEC) < NUM_SECONDS; j++) { + compute_hash(algo, &ctx, hash, p, bufsizes[i]); + + /* + * Only check elapsed time every 128 iterations to avoid + * dominating the runtime with system calls. + */ + if (!(j & 127)) + end = clock() - initial; + } + kb = j * bufsizes[i]; + kb_per_sec = kb / (1024 * ((double)end - start) / CLOCKS_PER_SEC); + printf("size %u: %lu iters; %lu KiB; %0.2f KiB/s\n", bufsizes[i], j, kb, kb_per_sec); + free(p); + } + + exit(0); +} diff --git a/t/helper/test-hash.c b/t/helper/test-hash.c new file mode 100644 index 0000000000..0a31de66f3 --- /dev/null +++ b/t/helper/test-hash.c @@ -0,0 +1,58 @@ +#include "test-tool.h" +#include "cache.h" + +int cmd_hash_impl(int ac, const char **av, int algo) +{ + git_hash_ctx ctx; + unsigned char hash[GIT_MAX_HEXSZ]; + unsigned bufsz = 8192; + int binary = 0; + char *buffer; + const struct git_hash_algo *algop = &hash_algos[algo]; + + if (ac == 2) { + if (!strcmp(av[1], "-b")) + binary = 1; + else + bufsz = strtoul(av[1], NULL, 10) * 1024 * 1024; + } + + if (!bufsz) + bufsz = 8192; + + while ((buffer = malloc(bufsz)) == NULL) { + fprintf(stderr, "bufsz %u is too big, halving...\n", bufsz); + bufsz /= 2; + if (bufsz < 1024) + die("OOPS"); + } + + algop->init_fn(&ctx); + + while (1) { + ssize_t sz, this_sz; + char *cp = buffer; + unsigned room = bufsz; + this_sz = 0; + while (room) { + sz = xread(0, cp, room); + if (sz == 0) + break; + if (sz < 0) + die_errno("test-hash"); + this_sz += sz; + cp += sz; + room -= sz; + } + if (this_sz == 0) + break; + algop->update_fn(&ctx, buffer, this_sz); + } + algop->final_fn(hash, &ctx); + + if (binary) + fwrite(hash, 1, algop->rawsz, stdout); + else + puts(hash_to_hex_algop(hash, algop)); + exit(0); +} diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c index 630c76d127..47fee660b8 100644 --- a/t/helper/test-parse-options.c +++ b/t/helper/test-parse-options.c @@ -1,3 +1,4 @@ +#include "test-tool.h" #include "cache.h" #include "parse-options.h" #include "string-list.h" @@ -35,6 +36,7 @@ static int length_callback(const struct option *opt, const char *arg, int unset) static int number_callback(const struct option *opt, const char *arg, int unset) { + BUG_ON_OPT_NEG(unset); *(int *)opt->value = strtol(arg, NULL, 10); return 0; } @@ -94,11 +96,11 @@ static void show(struct string_list *expect, int *status, const char *fmt, ...) strbuf_release(&buf); } -int cmd_main(int argc, const char **argv) +int cmd__parse_options(int argc, const char **argv) { const char *prefix = "prefix/"; const char *usage[] = { - "test-parse-options <options>", + "test-tool parse-options <options>", "", "A helper function for the parse-options API.", NULL @@ -118,7 +120,6 @@ int cmd_main(int argc, const char **argv) OPT_INTEGER('j', NULL, &integer, "get a integer, too"), OPT_MAGNITUDE('m', "magnitude", &magnitude, "get a magnitude"), OPT_SET_INT(0, "set23", &integer, "set integer to 23", 23), - OPT_DATE('t', NULL, ×tamp, "get timestamp of <time>"), OPT_CALLBACK('L', "length", &integer, "str", "get length of <str>", length_callback), OPT_FILENAME('F', "file", &file, "set file to <file>"), diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c index ae091d9b3e..5d543ad21f 100644 --- a/t/helper/test-path-utils.c +++ b/t/helper/test-path-utils.c @@ -177,6 +177,14 @@ static int is_dotgitmodules(const char *path) return is_hfs_dotgitmodules(path) || is_ntfs_dotgitmodules(path); } +static int cmp_by_st_size(const void *a, const void *b) +{ + intptr_t x = (intptr_t)((struct string_list_item *)a)->util; + intptr_t y = (intptr_t)((struct string_list_item *)b)->util; + + return x > y ? -1 : (x < y ? +1 : 0); +} + int cmd__path_utils(int argc, const char **argv) { if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) { @@ -291,6 +299,62 @@ int cmd__path_utils(int argc, const char **argv) return !!res; } + if (argc > 2 && !strcmp(argv[1], "file-size")) { + int res = 0, i; + struct stat st; + + for (i = 2; i < argc; i++) + if (stat(argv[i], &st)) + res = error_errno("Cannot stat '%s'", argv[i]); + else + printf("%"PRIuMAX"\n", (uintmax_t)st.st_size); + return !!res; + } + + if (argc == 4 && !strcmp(argv[1], "skip-n-bytes")) { + int fd = open(argv[2], O_RDONLY), offset = atoi(argv[3]); + char buffer[65536]; + + if (fd < 0) + die_errno("could not open '%s'", argv[2]); + if (lseek(fd, offset, SEEK_SET) < 0) + die_errno("could not skip %d bytes", offset); + for (;;) { + ssize_t count = read(fd, buffer, sizeof(buffer)); + if (count < 0) + die_errno("could not read '%s'", argv[2]); + if (!count) + break; + if (write(1, buffer, count) < 0) + die_errno("could not write to stdout"); + } + close(fd); + return 0; + } + + if (argc > 5 && !strcmp(argv[1], "slice-tests")) { + int res = 0; + long offset, stride, i; + struct string_list list = STRING_LIST_INIT_NODUP; + struct stat st; + + offset = strtol(argv[2], NULL, 10); + stride = strtol(argv[3], NULL, 10); + if (stride < 1) + stride = 1; + for (i = 4; i < argc; i++) + if (stat(argv[i], &st)) + res = error_errno("Cannot stat '%s'", argv[i]); + else + string_list_append(&list, argv[i])->util = + (void *)(intptr_t)st.st_size; + QSORT(list.items, list.nr, cmp_by_st_size); + for (i = offset; i < list.nr; i+= stride) + printf("%s\n", list.items[i].string); + + return !!res; + } + 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-pkt-line.c b/t/helper/test-pkt-line.c index 30775f986f..282d536384 100644 --- a/t/helper/test-pkt-line.c +++ b/t/helper/test-pkt-line.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "test-tool.h" #include "pkt-line.h" static void pack_line(const char *line) @@ -79,7 +80,7 @@ static void unpack_sideband(void) } } -int cmd_main(int argc, const char **argv) +int cmd__pkt_line(int argc, const char **argv) { if (argc < 2) die("too few arguments"); diff --git a/t/helper/test-prio-queue.c b/t/helper/test-prio-queue.c index 9807b649b1..5bc9c46ea5 100644 --- a/t/helper/test-prio-queue.c +++ b/t/helper/test-prio-queue.c @@ -22,14 +22,24 @@ int cmd__prio_queue(int argc, const char **argv) struct prio_queue pq = { intcmp }; while (*++argv) { - if (!strcmp(*argv, "get")) - show(prio_queue_get(&pq)); - else if (!strcmp(*argv, "dump")) { - int *v; - while ((v = prio_queue_get(&pq))) - show(v); - } - else { + if (!strcmp(*argv, "get")) { + void *peek = prio_queue_peek(&pq); + void *get = prio_queue_get(&pq); + if (peek != get) + BUG("peek and get results do not match"); + show(get); + } else if (!strcmp(*argv, "dump")) { + void *peek; + void *get; + while ((peek = prio_queue_peek(&pq))) { + get = prio_queue_get(&pq); + if (peek != get) + BUG("peek and get results do not match"); + show(get); + } + } else if (!strcmp(*argv, "stack")) { + pq.compare = NULL; + } else { int *v = malloc(sizeof(*v)); *v = atoi(*argv); prio_queue_put(&pq, v); diff --git a/t/helper/test-reach.c b/t/helper/test-reach.c new file mode 100644 index 0000000000..a0272178b7 --- /dev/null +++ b/t/helper/test-reach.c @@ -0,0 +1,168 @@ +#include "test-tool.h" +#include "cache.h" +#include "commit.h" +#include "commit-reach.h" +#include "config.h" +#include "parse-options.h" +#include "ref-filter.h" +#include "string-list.h" +#include "tag.h" + +static void print_sorted_commit_ids(struct commit_list *list) +{ + int i; + struct string_list s = STRING_LIST_INIT_DUP; + + while (list) { + string_list_append(&s, oid_to_hex(&list->item->object.oid)); + list = list->next; + } + + string_list_sort(&s); + + for (i = 0; i < s.nr; i++) + printf("%s\n", s.items[i].string); + + string_list_clear(&s, 0); +} + +int cmd__reach(int ac, const char **av) +{ + struct object_id oid_A, oid_B; + struct commit *A, *B; + struct commit_list *X, *Y; + struct object_array X_obj = OBJECT_ARRAY_INIT; + struct commit **X_array, **Y_array; + int X_nr, X_alloc, Y_nr, Y_alloc; + struct strbuf buf = STRBUF_INIT; + struct repository *r = the_repository; + + setup_git_directory(); + + if (ac < 2) + exit(1); + + A = B = NULL; + X = Y = NULL; + X_nr = Y_nr = 0; + X_alloc = Y_alloc = 16; + ALLOC_ARRAY(X_array, X_alloc); + ALLOC_ARRAY(Y_array, Y_alloc); + + while (strbuf_getline(&buf, stdin) != EOF) { + struct object_id oid; + struct object *orig; + struct object *peeled; + struct commit *c; + if (buf.len < 3) + continue; + + if (get_oid_committish(buf.buf + 2, &oid)) + die("failed to resolve %s", buf.buf + 2); + + orig = parse_object(r, &oid); + peeled = deref_tag_noverify(orig); + + if (!peeled) + die("failed to load commit for input %s resulting in oid %s\n", + buf.buf, oid_to_hex(&oid)); + + c = object_as_type(r, peeled, OBJ_COMMIT, 0); + + if (!c) + die("failed to load commit for input %s resulting in oid %s\n", + buf.buf, oid_to_hex(&oid)); + + switch (buf.buf[0]) { + case 'A': + oidcpy(&oid_A, &oid); + A = c; + break; + + case 'B': + oidcpy(&oid_B, &oid); + B = c; + break; + + case 'X': + commit_list_insert(c, &X); + ALLOC_GROW(X_array, X_nr + 1, X_alloc); + X_array[X_nr++] = c; + add_object_array(orig, NULL, &X_obj); + break; + + case 'Y': + commit_list_insert(c, &Y); + ALLOC_GROW(Y_array, Y_nr + 1, Y_alloc); + Y_array[Y_nr++] = c; + break; + + default: + die("unexpected start of line: %c", buf.buf[0]); + } + } + strbuf_release(&buf); + + if (!strcmp(av[1], "ref_newer")) + printf("%s(A,B):%d\n", av[1], ref_newer(&oid_A, &oid_B)); + else if (!strcmp(av[1], "in_merge_bases")) + printf("%s(A,B):%d\n", av[1], in_merge_bases(A, B)); + else if (!strcmp(av[1], "is_descendant_of")) + printf("%s(A,X):%d\n", av[1], is_descendant_of(A, X)); + else if (!strcmp(av[1], "get_merge_bases_many")) { + struct commit_list *list = get_merge_bases_many(A, X_nr, X_array); + printf("%s(A,X):\n", av[1]); + print_sorted_commit_ids(list); + } else if (!strcmp(av[1], "reduce_heads")) { + struct commit_list *list = reduce_heads(X); + printf("%s(X):\n", av[1]); + print_sorted_commit_ids(list); + } else if (!strcmp(av[1], "can_all_from_reach")) { + printf("%s(X,Y):%d\n", av[1], can_all_from_reach(X, Y, 1)); + } else if (!strcmp(av[1], "can_all_from_reach_with_flag")) { + struct commit_list *iter = Y; + + while (iter) { + iter->item->object.flags |= 2; + iter = iter->next; + } + + printf("%s(X,_,_,0,0):%d\n", av[1], can_all_from_reach_with_flag(&X_obj, 2, 4, 0, 0)); + } else if (!strcmp(av[1], "commit_contains")) { + struct ref_filter filter; + struct contains_cache cache; + init_contains_cache(&cache); + + if (ac > 2 && !strcmp(av[2], "--tag")) + filter.with_commit_tag_algo = 1; + else + filter.with_commit_tag_algo = 0; + + printf("%s(_,A,X,_):%d\n", av[1], commit_contains(&filter, A, X, &cache)); + } else if (!strcmp(av[1], "get_reachable_subset")) { + const int reachable_flag = 1; + int i, count = 0; + struct commit_list *current; + struct commit_list *list = get_reachable_subset(X_array, X_nr, + Y_array, Y_nr, + reachable_flag); + printf("get_reachable_subset(X,Y)\n"); + for (current = list; current; current = current->next) { + if (!(list->item->object.flags & reachable_flag)) + die(_("commit %s is not marked reachable"), + oid_to_hex(&list->item->object.oid)); + count++; + } + for (i = 0; i < Y_nr; i++) { + if (Y_array[i]->object.flags & reachable_flag) + count--; + } + + if (count < 0) + die(_("too many commits marked reachable")); + + print_sorted_commit_ids(list); + } + + exit(0); +} diff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c new file mode 100644 index 0000000000..831b586d02 --- /dev/null +++ b/t/helper/test-read-midx.c @@ -0,0 +1,51 @@ +#include "test-tool.h" +#include "cache.h" +#include "midx.h" +#include "repository.h" +#include "object-store.h" + +static int read_midx_file(const char *object_dir) +{ + uint32_t i; + struct multi_pack_index *m = load_multi_pack_index(object_dir, 1); + + if (!m) + return 1; + + printf("header: %08x %d %d %d\n", + m->signature, + m->version, + m->num_chunks, + m->num_packs); + + printf("chunks:"); + + if (m->chunk_pack_names) + printf(" pack-names"); + if (m->chunk_oid_fanout) + printf(" oid-fanout"); + if (m->chunk_oid_lookup) + printf(" oid-lookup"); + if (m->chunk_object_offsets) + printf(" object-offsets"); + if (m->chunk_large_offsets) + printf(" large-offsets"); + + printf("\nnum_objects: %d\n", m->num_objects); + + printf("packs:\n"); + for (i = 0; i < m->num_packs; i++) + printf("%s\n", m->pack_names[i]); + + printf("object-dir: %s\n", m->object_dir); + + return 0; +} + +int cmd__read_midx(int argc, const char **argv) +{ + if (argc != 2) + usage("read-midx <object-dir>"); + + return read_midx_file(argv[1]); +} diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index e9e0541276..799fc00aa1 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c @@ -233,7 +233,7 @@ 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 *new_sha1_buf = notnull(*argv++, "new-sha1"); const char *old_sha1_buf = notnull(*argv++, "old-sha1"); unsigned int flags = arg_flags(*argv++, "flags"); struct object_id old_oid; diff --git a/t/helper/test-repository.c b/t/helper/test-repository.c index 6a84a53efb..f7f8618445 100644 --- a/t/helper/test-repository.c +++ b/t/helper/test-repository.c @@ -17,6 +17,11 @@ static void test_parse_commit_in_graph(const char *gitdir, const char *worktree, setup_git_env(gitdir); + memset(the_repository, 0, sizeof(*the_repository)); + + /* TODO: Needed for temporary hack in hashcmp, see 183a638b7da. */ + repo_set_hash_algo(the_repository, GIT_HASH_SHA1); + if (repo_init(&r, gitdir, worktree)) die("Couldn't init repo"); @@ -43,6 +48,11 @@ static void test_get_commit_tree_in_graph(const char *gitdir, setup_git_env(gitdir); + memset(the_repository, 0, sizeof(*the_repository)); + + /* TODO: Needed for temporary hack in hashcmp, see 183a638b7da. */ + repo_set_hash_algo(the_repository, GIT_HASH_SHA1); + if (repo_init(&r, gitdir, worktree)) die("Couldn't init repo"); diff --git a/t/helper/test-revision-walking.c b/t/helper/test-revision-walking.c index 4f8bc75821..625b2dbf82 100644 --- a/t/helper/test-revision-walking.c +++ b/t/helper/test-revision-walking.c @@ -32,7 +32,7 @@ static int run_revision_walk(void) int argc = ARRAY_SIZE(argv) - 1; int got_revision = 0; - init_revisions(&rev, NULL); + repo_init_revisions(the_repository, &rev, NULL); setup_revisions(argc, argv, &rev, NULL); if (prepare_revision_walk(&rev)) die("revision walk setup failed"); diff --git a/t/helper/test-sha1.c b/t/helper/test-sha1.c index 1ba0675c75..d860c387c3 100644 --- a/t/helper/test-sha1.c +++ b/t/helper/test-sha1.c @@ -3,55 +3,5 @@ int cmd__sha1(int ac, const char **av) { - git_SHA_CTX ctx; - unsigned char sha1[20]; - unsigned bufsz = 8192; - int binary = 0; - char *buffer; - - if (ac == 2) { - if (!strcmp(av[1], "-b")) - binary = 1; - else - bufsz = strtoul(av[1], NULL, 10) * 1024 * 1024; - } - - if (!bufsz) - bufsz = 8192; - - while ((buffer = malloc(bufsz)) == NULL) { - fprintf(stderr, "bufsz %u is too big, halving...\n", bufsz); - bufsz /= 2; - if (bufsz < 1024) - die("OOPS"); - } - - git_SHA1_Init(&ctx); - - while (1) { - ssize_t sz, this_sz; - char *cp = buffer; - unsigned room = bufsz; - this_sz = 0; - while (room) { - sz = xread(0, cp, room); - if (sz == 0) - break; - if (sz < 0) - die_errno("test-sha1"); - this_sz += sz; - cp += sz; - room -= sz; - } - if (this_sz == 0) - break; - git_SHA1_Update(&ctx, buffer, this_sz); - } - git_SHA1_Final(sha1, &ctx); - - if (binary) - fwrite(sha1, 1, 20, stdout); - else - puts(sha1_to_hex(sha1)); - exit(0); + return cmd_hash_impl(ac, av, GIT_HASH_SHA1); } diff --git a/t/helper/test-sha256.c b/t/helper/test-sha256.c new file mode 100644 index 0000000000..0ac6a99d5f --- /dev/null +++ b/t/helper/test-sha256.c @@ -0,0 +1,7 @@ +#include "test-tool.h" +#include "cache.h" + +int cmd__sha256(int ac, const char **av) +{ + return cmd_hash_impl(ac, av, GIT_HASH_SHA256); +} diff --git a/t/helper/test-sigchain.c b/t/helper/test-sigchain.c index 77ac5bc33f..d013bccdda 100644 --- a/t/helper/test-sigchain.c +++ b/t/helper/test-sigchain.c @@ -14,7 +14,8 @@ X(two) X(three) #undef X -int cmd__sigchain(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-submodule-nested-repo-config.c b/t/helper/test-submodule-nested-repo-config.c new file mode 100644 index 0000000000..bc97929bbc --- /dev/null +++ b/t/helper/test-submodule-nested-repo-config.c @@ -0,0 +1,32 @@ +#include "test-tool.h" +#include "submodule-config.h" + +static void die_usage(int argc, const char **argv, const char *msg) +{ + fprintf(stderr, "%s\n", msg); + fprintf(stderr, "Usage: %s <submodulepath> <config name>\n", argv[0]); + exit(1); +} + +int cmd__submodule_nested_repo_config(int argc, const char **argv) +{ + struct repository subrepo; + const struct submodule *sub; + + if (argc < 3) + die_usage(argc, argv, "Wrong number of arguments."); + + setup_git_directory(); + + sub = submodule_from_path(the_repository, &null_oid, argv[1]); + if (repo_submodule_init(&subrepo, the_repository, sub)) { + die_usage(argc, argv, "Submodule not found."); + } + + /* Read the config of _child_ submodules. */ + print_config_from_gitmodules(&subrepo, argv[2]); + + submodule_free(the_repository); + + return 0; +} diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c index db19131f71..50c55f8b1a 100644 --- a/t/helper/test-tool.c +++ b/t/helper/test-tool.c @@ -14,10 +14,13 @@ static struct test_cmd cmds[] = { { "delta", cmd__delta }, { "drop-caches", cmd__drop_caches }, { "dump-cache-tree", cmd__dump_cache_tree }, + { "dump-fsmonitor", cmd__dump_fsmonitor }, { "dump-split-index", cmd__dump_split_index }, + { "dump-untracked-cache", cmd__dump_untracked_cache }, { "example-decorate", cmd__example_decorate }, { "genrandom", cmd__genrandom }, { "hashmap", cmd__hashmap }, + { "hash-speed", cmd__hash_speed }, { "index-version", cmd__index_version }, { "json-writer", cmd__json_writer }, { "lazy-init-name-hash", cmd__lazy_init_name_hash }, @@ -25,23 +28,30 @@ static struct test_cmd cmds[] = { { "mergesort", cmd__mergesort }, { "mktemp", cmd__mktemp }, { "online-cpus", cmd__online_cpus }, + { "parse-options", cmd__parse_options }, { "path-utils", cmd__path_utils }, + { "pkt-line", cmd__pkt_line }, { "prio-queue", cmd__prio_queue }, + { "reach", cmd__reach }, { "read-cache", cmd__read_cache }, + { "read-midx", cmd__read_midx }, { "ref-store", cmd__ref_store }, { "regex", cmd__regex }, { "repository", cmd__repository }, { "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 }, + { "sha1-array", cmd__sha1_array }, + { "sha256", cmd__sha256 }, { "sigchain", cmd__sigchain }, { "strcmp-offset", cmd__strcmp_offset }, { "string-list", cmd__string_list }, { "submodule-config", cmd__submodule_config }, + { "submodule-nested-repo-config", cmd__submodule_nested_repo_config }, { "subprocess", cmd__subprocess }, { "urlmatch-normalization", cmd__urlmatch_normalization }, + { "xml-encode", cmd__xml_encode }, { "wildmatch", cmd__wildmatch }, #ifdef GIT_WINDOWS_NATIVE { "windows-named-pipe", cmd__windows_named_pipe }, @@ -49,13 +59,23 @@ static struct test_cmd cmds[] = { { "write-cache", cmd__write_cache }, }; +static NORETURN void die_usage(void) +{ + size_t i; + + fprintf(stderr, "usage: test-tool <toolname> [args]\n"); + for (i = 0; i < ARRAY_SIZE(cmds); i++) + fprintf(stderr, " %s\n", cmds[i].name); + exit(128); +} + int cmd_main(int argc, const char **argv) { int i; BUG_exit_code = 99; if (argc < 2) - die("I need a test name!"); + die_usage(); for (i = 0; i < ARRAY_SIZE(cmds); i++) { if (!strcmp(cmds[i].name, argv[1])) { @@ -64,5 +84,6 @@ int cmd_main(int argc, const char **argv) return cmds[i].fn(argc, argv); } } - die("There is no test named '%s'", argv[1]); + error("there is no tool named '%s'", argv[1]); + die_usage(); } diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h index 4914001086..a563df49bf 100644 --- a/t/helper/test-tool.h +++ b/t/helper/test-tool.h @@ -1,6 +1,7 @@ -#ifndef __TEST_TOOL_H__ -#define __TEST_TOOL_H__ +#ifndef TEST_TOOL_H +#define TEST_TOOL_H +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "git-compat-util.h" int cmd__chmtime(int argc, const char **argv); @@ -10,10 +11,13 @@ 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_fsmonitor(int argc, const char **argv); int cmd__dump_split_index(int argc, const char **argv); +int cmd__dump_untracked_cache(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__hash_speed(int argc, const char **argv); int cmd__index_version(int argc, const char **argv); int cmd__json_writer(int argc, const char **argv); int cmd__lazy_init_name_hash(int argc, const char **argv); @@ -21,27 +25,36 @@ 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__parse_options(int argc, const char **argv); int cmd__path_utils(int argc, const char **argv); +int cmd__pkt_line(int argc, const char **argv); int cmd__prio_queue(int argc, const char **argv); +int cmd__reach(int argc, const char **argv); int cmd__read_cache(int argc, const char **argv); +int cmd__read_midx(int argc, const char **argv); int cmd__ref_store(int argc, const char **argv); int cmd__regex(int argc, const char **argv); int cmd__repository(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__sha1_array(int argc, const char **argv); +int cmd__sha256(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__submodule_nested_repo_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__xml_encode(int argc, const char **argv); int cmd__wildmatch(int argc, const char **argv); #ifdef GIT_WINDOWS_NATIVE int cmd__windows_named_pipe(int argc, const char **argv); #endif int cmd__write_cache(int argc, const char **argv); +int cmd_hash_impl(int ac, const char **av, int algo); + #endif diff --git a/t/helper/test-xml-encode.c b/t/helper/test-xml-encode.c new file mode 100644 index 0000000000..a648bbd961 --- /dev/null +++ b/t/helper/test-xml-encode.c @@ -0,0 +1,80 @@ +#include "test-tool.h" + +static const char *utf8_replace_character = "�"; + +/* + * Encodes (possibly incorrect) UTF-8 on <stdin> to <stdout>, to be embedded + * in an XML file. + */ +int cmd__xml_encode(int argc, const char **argv) +{ + unsigned char buf[1024], tmp[4], *tmp2 = NULL; + ssize_t cur = 0, len = 1, remaining = 0; + unsigned char ch; + + for (;;) { + if (++cur == len) { + len = xread(0, buf, sizeof(buf)); + if (!len) + return 0; + if (len < 0) + die_errno("Could not read <stdin>"); + cur = 0; + } + ch = buf[cur]; + + if (tmp2) { + if ((ch & 0xc0) != 0x80) { + fputs(utf8_replace_character, stdout); + tmp2 = NULL; + cur--; + continue; + } + *tmp2 = ch; + tmp2++; + if (--remaining == 0) { + fwrite(tmp, tmp2 - tmp, 1, stdout); + tmp2 = NULL; + } + continue; + } + + if (!(ch & 0x80)) { + /* 0xxxxxxx */ + if (ch == '&') + fputs("&", stdout); + else if (ch == '\'') + fputs("'", stdout); + else if (ch == '"') + fputs(""", stdout); + else if (ch == '<') + fputs("<", stdout); + else if (ch == '>') + fputs(">", stdout); + else if (ch >= 0x20) + fputc(ch, stdout); + else if (ch == 0x09 || ch == 0x0a || ch == 0x0d) + fprintf(stdout, "&#x%02x;", ch); + else + fputs(utf8_replace_character, stdout); + } else if ((ch & 0xe0) == 0xc0) { + /* 110XXXXx 10xxxxxx */ + tmp[0] = ch; + remaining = 1; + tmp2 = tmp + 1; + } else if ((ch & 0xf0) == 0xe0) { + /* 1110XXXX 10Xxxxxx 10xxxxxx */ + tmp[0] = ch; + remaining = 2; + tmp2 = tmp + 1; + } else if ((ch & 0xf8) == 0xf0) { + /* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */ + tmp[0] = ch; + remaining = 3; + tmp2 = tmp + 1; + } else + fputs(utf8_replace_character, stdout); + } + + return 0; +} diff --git a/t/lib-gettext.sh b/t/lib-gettext.sh index eec757f104..2139b427ca 100644 --- a/t/lib-gettext.sh +++ b/t/lib-gettext.sh @@ -10,9 +10,14 @@ GIT_TEXTDOMAINDIR="$GIT_BUILD_DIR/po/build/locale" GIT_PO_PATH="$GIT_BUILD_DIR/po" export GIT_TEXTDOMAINDIR GIT_PO_PATH -. "$GIT_BUILD_DIR"/git-sh-i18n +if test -n "$GIT_TEST_INSTALLED" +then + . "$(git --exec-path)"/git-sh-i18n +else + . "$GIT_BUILD_DIR"/git-sh-i18n +fi -if test_have_prereq GETTEXT && ! test_have_prereq GETTEXT_POISON +if test_have_prereq GETTEXT && test_have_prereq C_LOCALE_OUTPUT then # is_IS.UTF-8 on Solaris and FreeBSD, is_IS.utf8 on Debian is_IS_locale=$(locale -a 2>/dev/null | diff --git a/t/lib-git-daemon.sh b/t/lib-git-daemon.sh index edbea2d986..79db3b7ae5 100644 --- a/t/lib-git-daemon.sh +++ b/t/lib-git-daemon.sh @@ -28,7 +28,7 @@ then test_skip_or_die $GIT_TEST_GIT_DAEMON "file system does not support FIFOs" fi -LIB_GIT_DAEMON_PORT=${LIB_GIT_DAEMON_PORT-${this_test#t}} +test_set_port LIB_GIT_DAEMON_PORT GIT_DAEMON_PID= GIT_DAEMON_DOCUMENT_ROOT_PATH="$PWD"/repo @@ -54,19 +54,11 @@ start_git_daemon() { "$@" "$GIT_DAEMON_DOCUMENT_ROOT_PATH" \ >&3 2>git_daemon_output & GIT_DAEMON_PID=$! - >daemon.log { 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" && + printf "%s\n" "$line" >&4 + cat <&7 >&4 & + } 7<git_daemon_output && # Check expected output if test x"$(expr "$line" : "\[[0-9]*\] \(.*\)")" != x"Ready to rumble" @@ -92,7 +84,7 @@ stop_git_daemon() { kill "$GIT_DAEMON_PID" wait "$GIT_DAEMON_PID" >&3 2>&4 ret=$? - if test_match_signal 15 $? + if ! test_match_signal 15 $ret then error "git daemon exited with status: $ret" fi diff --git a/t/lib-git-p4.sh b/t/lib-git-p4.sh index c27599474c..b3be3ba011 100644 --- a/t/lib-git-p4.sh +++ b/t/lib-git-p4.sh @@ -53,14 +53,7 @@ time_in_seconds () { (cd / && "$PYTHON_PATH" -c 'import time; print(int(time.time()))') } -# Try to pick a unique port: guess a large number, then hope -# no more than one of each test is running. -# -# This does not handle the case where somebody else is running the -# same tests and has chosen the same ports. -testid=${this_test#t} -git_p4_test_start=9800 -P4DPORT=$((10669 + ($testid - $git_p4_test_start))) +test_set_port P4DPORT P4PORT=localhost:$P4DPORT P4CLIENT=client diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index a8130f9119..f3b478c307 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -13,6 +13,7 @@ fi GIT_DIR=$PWD/.git GIT_SVN_DIR=$GIT_DIR/svn/refs/remotes/git-svn SVN_TREE=$GIT_SVN_DIR/svn-tree +test_set_port SVNSERVE_PORT svn >/dev/null 2>&1 if test $? -ne 1 @@ -119,7 +120,6 @@ require_svnserve () { } start_svnserve () { - SVNSERVE_PORT=${SVNSERVE_PORT-${this_test#t}} svnserve --listen-port $SVNSERVE_PORT \ --root "$rawsvnrepo" \ --listen-once \ diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh index 3fe02876c1..f1277bef4f 100755 --- a/t/lib-gpg.sh +++ b/t/lib-gpg.sh @@ -57,9 +57,12 @@ then echo | gpgsm --homedir "${GNUPGHOME}" 2>/dev/null \ --passphrase-fd 0 --pinentry-mode loopback \ --import "$TEST_DIRECTORY"/lib-gpg/gpgsm_cert.p12 && - gpgsm --homedir "${GNUPGHOME}" 2>/dev/null -K \ - | grep fingerprint: | cut -d" " -f4 | tr -d '\n' > \ - ${GNUPGHOME}/trustlist.txt && + + gpgsm --homedir "${GNUPGHOME}" 2>/dev/null -K | + grep fingerprint: | + cut -d" " -f4 | + tr -d '\n' >"${GNUPGHOME}/trustlist.txt" && + echo " S relax" >> ${GNUPGHOME}/trustlist.txt && (gpgconf --kill gpg-agent >/dev/null 2>&1 || : ) && echo hello | gpgsm --homedir "${GNUPGHOME}" >/dev/null \ diff --git a/t/lib-gpg/keyring.gpg b/t/lib-gpg/keyring.gpg index d4754a1f19..918dfce332 100644 --- a/t/lib-gpg/keyring.gpg +++ b/t/lib-gpg/keyring.gpg @@ -30,7 +30,6 @@ Cezx4Q2khACcCs+/LtE8Lb9hC+2cvr3uH5p82AI= =aEiU -----END PGP PRIVATE KEY BLOCK----- -----BEGIN PGP PRIVATE KEY BLOCK----- -Version: GnuPG v1 lQOYBFFMlkcBCADJi/xnAF8yI34PHilSCbM7VtOFO17oFMkpu4cgN2QpPuM5MVjy cvrzKSguZFvPCDLzeAFJW1uPxL4SHaHSkisCrFhijH7OJWcOPNPSFCwu+inAoAsv @@ -83,11 +82,43 @@ fn1sY/IG5atoKK+ypmV/TlBlMZqFQzuPIJQT8VLbmxtLlDhJG04LbI6c8axIZxOO ZKLy5nTTSy16ztqEeS7eifHLPZg1UFFyEEIQ1XW0CNDAeuWKh90ERjyl4Cg7PnWS Z9Ei+zj6JD5Pcdi3BJhQo9WOLOVEJ0NHmewTYqk9QVXH/0v1Hdl4LMJtgcbdbDWk 4UTkXbg9pn3umCgkNJ3Vs8fWnIWO9Izdr2/wrFY2JvUT7Yvl+wsNIWatvOEzGy7n -BOW78WUxzhu0YJTLKy+iKCjg5HS5dx6OC+e4aEEgfhNPCMkbvDsJjtQ= -=hieJ +BOW78WUxzhu0YJTLKy+iKCjg5HS5dx6OC+e4aEEgfhNPCMkbvDsJjtSdA5gEW967 +3AEIAKjseT0sTQjyN39fOn0fzxWp89REMUUKgLigb01MKuuNI3cedBZsz3hpFOKV +cii5rldw8uf3yS3Okht2DfHPSD4NrGzLGEzSTpQ10S8N2q0DUYwyLU6C0U8HnMZm +/n+lCGBbUoxvnruohAvKAjpHO3rmJ8D4De9hlWg/fwdAxQQ0Sve0kN8Vwk2p1GuO +OWQKV1SU9c+kBiou7dewQmbilPRanKmP5ZSU4emhpTOMlJFXF+kmYSODQk1cMvWW +Ob3ttll2llX0Gul7Sjf+haq/FcRyRk7Tw5MHwZjr5aWiCny0/0+byvfF6SBIfzyE +qlyWURQ2gHZUqSiG3QPMZiYr04cAEQEAAQAH/Am4rv/oQF6wodgz5y4zc6JJiTDA +4+nKdIuR7OKqUxk1oo7eZjJML/xvMumygNyUvJ9nodl1SlMKilOhdAswfkKj9gJY +BdDJLm1OufhW3pJwy6ahbjeqEgwJFVENtSPF0zkuyED9kElrpbD2ZTGfzwdM0e9D +10ZDFWtODCw8rzOFcijujgI8oilLtxSNrkkTKW+25WJFRNPSHgIkMIm8UlPAG+rj +3Yj9UqodeXTSvXwG2zceOxjFJadV77sOFJDgwWslN6J8El4+GcgwFVepJxoZEj7e +cKkmVr0Dc9/Q04D5dWATc1FYcIhZbTu3oImCAh45ep4u9WYLUV5PGyeMviEEAMwo +mJbYBxWuPjpNa722HQcbvMUiZWWDwHfLCib/SaP0AgfDahid8/PcZwxOPHPByBrm +GDi0z7ibn/pgJr07kpp1Cic9ntfc2FvkI0QMzG0EuiekzQyPEnzjoDHF+V4nJIj2 +GWVjLYYqlZWEmhsfKt1CnlPXBunKoDJ30ABPcHJ/BADT0WxAIVKF4lO2HlrDVP44 +bufBEG9Ct7dl/G08Qve4Ag3VEZpT82vEFp0LzX0mTCDIUKJUYAYLxAIPhP7IvIfc +EZXrwyDUxU7YSgKTHMKo9nFC6fIc1GeGPRalIF1gmTY32qlYJC6y5BTDhZNV5ydG +u8QL2P/orP7XuRrJyeyK+QP/XTekr/DS6Jkct826MPA52ciIkWVgYLatH5fO4HCq +ssDU8vz7FbbvGs0G1Xn7GA4m9dNYVOZtKwX++3nf2IEOpgPiZVTn/nP2u3HutpJb +/HMLlcfZGiGdxS6n/vdz6wsEobJoi6STkHkA+VFNOSZmdsw6eKl3X911tpCTYfOG +2U47/IkCbAQYAQgAIBYhBNS+IjEa0xMeXtoppGEJLoW3InGJBQJb3rvcAhsCAUAJ +EGEJLoW3InGJwHQgBBkBCAAdFiEE+DZKWeB//p9NYwBaZaDuoC4wytcFAlveu9wA +CgkQZaDuoC4wytcD9gf/WigtHl7lFyl8RaE/uqROFEelZyM00v1h55fd/IGRG88E +tN0Lr4FaqBqPkMZjU/LN9UMBaTd+748vHlHaweZqljXJu99CO9Id7Y4w7WzF3C3Y +yQsGZ92EGxthsPK0+rhHV0MbaINupI1oO9gATFglSxq17o83FJatGRjaXCZau8jr +57/By1MGtjk+Iq1NkzGkrX778LdRQGLKDw2Qa7lsdHY8d3lUPAH8mbb97ELmIc9t +PG2aM7ATJL7nBmFuTHo6hmEcIw32Ei9KK1zxM0ZylEYkjBjHAlklWmKb9MiayMC5 +uHW7Iyhjl+NbgbIEr2JTamW/9tL6UrIIxiDEdqaHNfCaB/9D+V31Upcohc9azwB4 +AF8diQwt5nfiVpnVeF/W8+eS1By2W6QrwLNthNRabYFnuSf9USHAY6atDWe+egId +MLIv4ce0i3ykoczSu0oMoUCMxdl9kQrsNHZCqWX/OiDDLSb05u/P/3he900y6tSB +15MbIPA6i5Bw/693nHguqxS1ASbBB/LiIu3vCXdFEs9RMvIJ+qkP3xQA96oImQiK +R3U6OGv593eONKijUINNqHRq6+UxIyJ+OCAi+L2QTidAhJLRCp6EZD96u02cthYq +8KA8j1+rx9BcbeacVVHepeG1JsgxsXX8BTJ7ZuS5VVndZOjag8URW/9nJMf01w/h +el64 +=Iv7W -----END PGP PRIVATE KEY BLOCK----- -----BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v1 mQGiBEZnyykRBACzCPjIpTYNL7Y2tQqlEGTTDlvZcWNLjF5f7ZzuyOqNOidLUgFD 36qch1LZLSZkShdR3Gae+bsolyjxrlFuFP0eXRPMtqK20aLw7WZvPFpEV1ThMne+ @@ -137,6 +168,25 @@ bGPyBuWraCivsqZlf05QZTGahUM7jyCUE/FS25sbS5Q4SRtOC2yOnPGsSGcTjmSi 8uZ000stes7ahHku3onxyz2YNVBRchBCENV1tAjQwHrliofdBEY8peAoOz51kmfR Ivs4+iQ+T3HYtwSYUKPVjizlRCdDR5nsE2KpPUFVx/9L9R3ZeCzCbYHG3Ww1pOFE 5F24PaZ97pgoJDSd1bPH1pyFjvSM3a9v8KxWNib1E+2L5fsLDSFmrbzhMxsu5wTl -u/FlMc4btGCUyysvoigo4OR0uXcejgvnuGhBIH4TTwjJG7w7CY7U -=iYv/ +u/FlMc4btGCUyysvoigo4OR0uXcejgvnuGhBIH4TTwjJG7w7CY7UuQENBFveu9wB +CACo7Hk9LE0I8jd/Xzp9H88VqfPURDFFCoC4oG9NTCrrjSN3HnQWbM94aRTilXIo +ua5XcPLn98ktzpIbdg3xz0g+DaxsyxhM0k6UNdEvDdqtA1GMMi1OgtFPB5zGZv5/ +pQhgW1KMb567qIQLygI6Rzt65ifA+A3vYZVoP38HQMUENEr3tJDfFcJNqdRrjjlk +CldUlPXPpAYqLu3XsEJm4pT0Wpypj+WUlOHpoaUzjJSRVxfpJmEjg0JNXDL1ljm9 +7bZZdpZV9Brpe0o3/oWqvxXEckZO08OTB8GY6+Wlogp8tP9Pm8r3xekgSH88hKpc +llEUNoB2VKkoht0DzGYmK9OHABEBAAGJAmwEGAEIACAWIQTUviIxGtMTHl7aKaRh +CS6FtyJxiQUCW9673AIbAgFACRBhCS6FtyJxicB0IAQZAQgAHRYhBPg2Slngf/6f +TWMAWmWg7qAuMMrXBQJb3rvcAAoJEGWg7qAuMMrXA/YH/1ooLR5e5RcpfEWhP7qk +ThRHpWcjNNL9YeeX3fyBkRvPBLTdC6+BWqgaj5DGY1PyzfVDAWk3fu+PLx5R2sHm +apY1ybvfQjvSHe2OMO1sxdwt2MkLBmfdhBsbYbDytPq4R1dDG2iDbqSNaDvYAExY +JUsate6PNxSWrRkY2lwmWrvI6+e/wctTBrY5PiKtTZMxpK1++/C3UUBiyg8NkGu5 +bHR2PHd5VDwB/Jm2/exC5iHPbTxtmjOwEyS+5wZhbkx6OoZhHCMN9hIvSitc8TNG +cpRGJIwYxwJZJVpim/TImsjAubh1uyMoY5fjW4GyBK9iU2plv/bS+lKyCMYgxHam +hzXwmgf/Q/ld9VKXKIXPWs8AeABfHYkMLeZ34laZ1Xhf1vPnktQctlukK8CzbYTU +Wm2BZ7kn/VEhwGOmrQ1nvnoCHTCyL+HHtIt8pKHM0rtKDKFAjMXZfZEK7DR2Qqll +/zogwy0m9Obvz/94XvdNMurUgdeTGyDwOouQcP+vd5x4LqsUtQEmwQfy4iLt7wl3 +RRLPUTLyCfqpD98UAPeqCJkIikd1Ojhr+fd3jjSoo1CDTah0auvlMSMifjggIvi9 +kE4nQISS0QqehGQ/ertNnLYWKvCgPI9fq8fQXG3mnFVR3qXhtSbIMbF1/AUye2bk +uVVZ3WTo2oPFEVv/ZyTH9NcP4XpeuA== +=KRyT -----END PGP PUBLIC KEY BLOCK----- diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index a8729f8232..e465116ef9 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -82,7 +82,7 @@ case $(uname) in esac LIB_HTTPD_PATH=${LIB_HTTPD_PATH-"$DEFAULT_HTTPD_PATH"} -LIB_HTTPD_PORT=${LIB_HTTPD_PORT-${this_test#t}} +test_set_port LIB_HTTPD_PORT TEST_PATH="$TEST_DIRECTORY"/lib-httpd HTTPD_ROOT_PATH="$PWD"/httpd diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 581c010d8f..5d63ed90c5 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -78,6 +78,7 @@ PassEnv GNUPGHOME PassEnv ASAN_OPTIONS PassEnv GIT_TRACE PassEnv GIT_CONFIG_NOSYSTEM +PassEnv GIT_TEST_SIDEBAND_ALL SetEnvIf Git-Protocol ".*" GIT_PROTOCOL=$0 @@ -115,6 +116,7 @@ Alias /auth/dumb/ www/auth/dumb/ SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH} SetEnv GIT_HTTP_EXPORT_ALL </LocationMatch> +ScriptAliasMatch /error_git_upload_pack/(.*)/git-upload-pack error.sh/ ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1 ScriptAlias /broken_smart/ broken-smart-http.sh/ ScriptAlias /error/ error.sh/ diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh index 0c93d00bdd..7ea30e5006 100644 --- a/t/lib-rebase.sh +++ b/t/lib-rebase.sh @@ -49,7 +49,7 @@ set_fake_editor () { case $line in pick|p|squash|s|fixup|f|edit|e|reword|r|drop|d) action="$line";; - exec_*|x_*) + exec_*|x_*|break|b) echo "$line" | sed 's/_/ /g' >> "$1";; "#") echo '# comment' >> "$1";; diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh index 016391723c..5b56b23166 100755 --- a/t/lib-submodule-update.sh +++ b/t/lib-submodule-update.sh @@ -235,7 +235,7 @@ reset_work_tree_to_interested () { 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 + # core.worktree is unset for sub2 as it is not checked out fi && # indicate we are interested in the submodule: git -C submodule_update config submodule.sub1.url "bogus" && @@ -709,7 +709,8 @@ test_submodule_recursing_with_args_common() { git branch -t remove_sub1 origin/remove_sub1 && $command remove_sub1 && test_superproject_content origin/remove_sub1 && - ! test -e sub1 + ! test -e sub1 && + test_must_fail git config -f .git/modules/sub1/config core.worktree ) ' # ... absorbing a .git directory along the way. diff --git a/t/oid-info/README b/t/oid-info/README new file mode 100644 index 0000000000..27f843fc00 --- /dev/null +++ b/t/oid-info/README @@ -0,0 +1,19 @@ +This directory contains various per-hash values that are used in the testsuite. + +Each file contains lines containing a key-value pair; blank lines and lines +starting with `#` are ignored. The key and value are separated by whitespace +(specifically, those whitespace in the default `$IFS`). The key consists only +of shell identifier characters, and the value consists of a hash algorithm, +colon, and value. The hash algorithm also consists only of shell identifier +characters; it should match the value in sha1-file.c. + +For example, the following lines map the key "rawsz" to "20" if SHA-1 is in use +and to "32" if SHA-256 is in use: + +---- +rawsz sha1:20 +rawsz sha256:32 +---- + +The keys and values used here are loaded by `test_oid_init` (see the README file +in the "t" directory) and are used by calling `test_oid`. diff --git a/t/oid-info/hash-info b/t/oid-info/hash-info new file mode 100644 index 0000000000..ccdbfdf974 --- /dev/null +++ b/t/oid-info/hash-info @@ -0,0 +1,8 @@ +rawsz sha1:20 +rawsz sha256:32 + +hexsz sha1:40 +hexsz sha256:64 + +zero sha1:0000000000000000000000000000000000000000 +zero sha256:0000000000000000000000000000000000000000000000000000000000000000 diff --git a/t/oid-info/oid b/t/oid-info/oid new file mode 100644 index 0000000000..a754970523 --- /dev/null +++ b/t/oid-info/oid @@ -0,0 +1,29 @@ +# These are some common invalid and partial object IDs used in tests. +001 sha1:0000000000000000000000000000000000000001 +001 sha256:0000000000000000000000000000000000000000000000000000000000000001 +002 sha1:0000000000000000000000000000000000000002 +002 sha256:0000000000000000000000000000000000000000000000000000000000000002 +003 sha1:0000000000000000000000000000000000000003 +003 sha256:0000000000000000000000000000000000000000000000000000000000000003 +004 sha1:0000000000000000000000000000000000000004 +004 sha256:0000000000000000000000000000000000000000000000000000000000000004 +005 sha1:0000000000000000000000000000000000000005 +005 sha256:0000000000000000000000000000000000000000000000000000000000000005 +006 sha1:0000000000000000000000000000000000000006 +006 sha256:0000000000000000000000000000000000000000000000000000000000000006 +007 sha1:0000000000000000000000000000000000000007 +007 sha256:0000000000000000000000000000000000000000000000000000000000000007 +# All zeros or Fs missing one or two hex segments. +zero_1 sha1:000000000000000000000000000000000000000 +zero_1 sha256:000000000000000000000000000000000000000000000000000000000000000 +zero_2 sha1:00000000000000000000000000000000000000 +zero_2 sha256:00000000000000000000000000000000000000000000000000000000000000 +ff_1 sha1:fffffffffffffffffffffffffffffffffffffff +ff_1 sha256:fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ff_2 sha1:ffffffffffffffffffffffffffffffffffffff +ff_2 sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +# More various invalid OIDs. +numeric sha1:0123456789012345678901234567890123456789 +numeric sha256:0123456789012345678901234567890123456789012345678901234567890123 +deadbeef sha1:deadbeefdeadbeefdeadbeefdeadbeefdeadbeef +deadbeef sha256:deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef diff --git a/t/perf/README b/t/perf/README index 21321a0f36..be12090c38 100644 --- a/t/perf/README +++ b/t/perf/README @@ -168,3 +168,28 @@ that While we have tried to make sure that it can cope with embedded whitespace and other special characters, it will not work with multi-line data. + +Rather than tracking the performance by run-time as `test_perf` does, you +may also track output size by using `test_size`. The stdout of the +function should be a single numeric value, which will be captured and +shown in the aggregated output. For example: + + test_perf 'time foo' ' + ./foo >foo.out + ' + + test_size 'output size' + wc -c <foo.out + ' + +might produce output like: + + Test origin HEAD + ------------------------------------------------------------- + 1234.1 time foo 0.37(0.79+0.02) 0.26(0.51+0.02) -29.7% + 1234.2 output size 4.3M 3.6M -14.7% + +The item being measured (and its units) is up to the test; the context +and the test title should make it clear to the user whether bigger or +smaller numbers are better. Unlike test_perf, the test code will only be +run once, since output sizes tend to be more deterministic than timings. diff --git a/t/perf/aggregate.perl b/t/perf/aggregate.perl index bc865160e7..494907a892 100755 --- a/t/perf/aggregate.perl +++ b/t/perf/aggregate.perl @@ -13,27 +13,42 @@ sub get_times { my $line = <$fh>; return undef if not defined $line; close $fh or die "cannot close $name: $!"; - $line =~ /^(?:(\d+):)?(\d+):(\d+(?:\.\d+)?) (\d+(?:\.\d+)?) (\d+(?:\.\d+)?)$/ - or die "bad input line: $line"; - my $rt = ((defined $1 ? $1 : 0.0)*60+$2)*60+$3; - return ($rt, $4, $5); + # times + if ($line =~ /^(?:(\d+):)?(\d+):(\d+(?:\.\d+)?) (\d+(?:\.\d+)?) (\d+(?:\.\d+)?)$/) { + my $rt = ((defined $1 ? $1 : 0.0)*60+$2)*60+$3; + return ($rt, $4, $5); + # size + } elsif ($line =~ /^\d+$/) { + return $&; + } else { + die "bad input line: $line"; + } +} + +sub relative_change { + my ($r, $firstr) = @_; + if ($firstr > 0) { + return sprintf "%+.1f%%", 100.0*($r-$firstr)/$firstr; + } elsif ($r == 0) { + return "="; + } else { + return "+inf"; + } } sub format_times { my ($r, $u, $s, $firstr) = @_; + # no value means we did not finish the test if (!defined $r) { return "<missing>"; } - my $out = sprintf "%.2f(%.2f+%.2f)", $r, $u, $s; - if (defined $firstr) { - if ($firstr > 0) { - $out .= sprintf " %+.1f%%", 100.0*($r-$firstr)/$firstr; - } elsif ($r == 0) { - $out .= " ="; - } else { - $out .= " +inf"; - } + # a single value means we have a size, not times + if (!defined $u) { + return format_size($r, $firstr); } + # otherwise, we have real/user/system times + my $out = sprintf "%.2f(%.2f+%.2f)", $r, $u, $s; + $out .= ' ' . relative_change($r, $firstr) if defined $firstr; return $out; } @@ -51,6 +66,25 @@ EOT exit(1); } +sub human_size { + my $n = shift; + my @units = ('', qw(K M G)); + while ($n > 900 && @units > 1) { + $n /= 1000; + shift @units; + } + return $n unless length $units[0]; + return sprintf '%.1f%s', $n, $units[0]; +} + +sub format_size { + my ($size, $first) = @_; + # match the width of a time: 0.00(0.00+0.00) + my $out = sprintf '%15s', human_size($size); + $out .= ' ' . relative_change($size, $first) if defined $first; + return $out; +} + my (@dirs, %dirnames, %dirabbrevs, %prefixes, @tests, $codespeed, $sortby, $subsection, $reponame); @@ -181,7 +215,14 @@ sub print_default_results { my $firstr; for my $i (0..$#dirs) { my $d = $dirs[$i]; - $times{$prefixes{$d}.$t} = [get_times("$resultsdir/$prefixes{$d}$t.times")]; + my $base = "$resultsdir/$prefixes{$d}$t"; + $times{$prefixes{$d}.$t} = []; + foreach my $type (qw(times size)) { + if (-e "$base.$type") { + $times{$prefixes{$d}.$t} = [get_times("$base.$type")]; + last; + } + } my ($r,$u,$s) = @{$times{$prefixes{$d}.$t}}; my $w = length format_times($r,$u,$s,$firstr); $colwidth[$i] = $w if $w > $colwidth[$i]; diff --git a/t/perf/p1450-fsck.sh b/t/perf/p1450-fsck.sh new file mode 100755 index 0000000000..ae1b84198b --- /dev/null +++ b/t/perf/p1450-fsck.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +test_description='Test fsck performance' + +. ./perf-lib.sh + +test_perf_large_repo + +test_perf 'fsck' ' + git fsck +' + +test_done diff --git a/t/perf/p1451-fsck-skip-list.sh b/t/perf/p1451-fsck-skip-list.sh new file mode 100755 index 0000000000..c2b97d2487 --- /dev/null +++ b/t/perf/p1451-fsck-skip-list.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +test_description='Test fsck skipList performance' + +. ./perf-lib.sh + +test_perf_fresh_repo + +n=1000000 + +test_expect_success "setup $n bad commits" ' + for i in $(test_seq 1 $n) + do + echo "commit refs/heads/master" && + echo "committer C <c@example.com> 1234567890 +0000" && + echo "data <<EOF" && + echo "$i.Q." && + echo "EOF" + done | q_to_nul | git fast-import +' + +skip=0 +while test $skip -le $n +do + test_expect_success "create skipList for $skip bad commits" ' + git log --format=%H --max-count=$skip | + sort >skiplist + ' + + test_perf "fsck with $skip skipped bad commits" ' + git -c fsck.skipList=skiplist fsck + ' + + case $skip in + 0) skip=1 ;; + *) skip=${skip}0 ;; + esac +done + +test_done diff --git a/t/perf/p3400-rebase.sh b/t/perf/p3400-rebase.sh index ce271ca4c1..d202aaed06 100755 --- a/t/perf/p3400-rebase.sh +++ b/t/perf/p3400-rebase.sh @@ -6,9 +6,9 @@ test_description='Tests rebase performance' test_perf_default_repo test_expect_success 'setup rebasing on top of a lot of changes' ' - git checkout -f -b base && - git checkout -b to-rebase && - git checkout -b upstream && + git checkout -f -B base && + git checkout -B to-rebase && + git checkout -B upstream && for i in $(seq 100) do # simulate huge diffs @@ -35,8 +35,8 @@ test_perf 'rebase on top of a lot of unrelated changes' ' 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 + git checkout -B upstream2 to-rebase && + git checkout -B to-rebase2 upstream ' test_perf 'rebase a lot of unrelated changes without split-index' ' diff --git a/t/perf/p5311-pack-bitmaps-fetch.sh b/t/perf/p5311-pack-bitmaps-fetch.sh new file mode 100755 index 0000000000..b04575951f --- /dev/null +++ b/t/perf/p5311-pack-bitmaps-fetch.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +test_description='performance of fetches from bitmapped packs' +. ./perf-lib.sh + +test_perf_default_repo + +test_expect_success 'create bitmapped server repo' ' + git config pack.writebitmaps true && + git config pack.writebitmaphashcache true && + git repack -ad +' + +# simulate a fetch from a repository that last fetched N days ago, for +# various values of N. We do so by following the first-parent chain, +# and assume the first entry in the chain that is N days older than the current +# HEAD is where the HEAD would have been then. +for days in 1 2 4 8 16 32 64 128; do + title=$(printf '%10s' "($days days)") + test_expect_success "setup revs from $days days ago" ' + now=$(git log -1 --format=%ct HEAD) && + then=$(($now - ($days * 86400))) && + tip=$(git rev-list -1 --first-parent --until=$then HEAD) && + { + echo HEAD && + echo ^$tip + } >revs + ' + + test_perf "server $title" ' + git pack-objects --stdout --revs \ + --thin --delta-base-offset \ + <revs >tmp.pack + ' + + test_size "size $title" ' + wc -c <tmp.pack + ' + + test_perf "client $title" ' + git index-pack --stdin --fix-thin <tmp.pack + ' +done + +test_done diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh index e4c343a6b7..2e33ab3ec3 100644 --- a/t/perf/perf-lib.sh +++ b/t/perf/perf-lib.sh @@ -82,7 +82,7 @@ test_perf_do_repo_symlink_config_ () { test_perf_create_repo_from () { test "$#" = 2 || - error "bug in the test script: not 2 parameters to test-create-repo" + BUG "not 2 parameters to test-create-repo" repo="$1" source="$2" source_git="$("$MODERN_GIT" -C "$source" rev-parse --git-dir)" @@ -179,47 +179,69 @@ exit $ret' >&3 2>&4 return "$eval_ret" } - -test_perf () { +test_wrapper_ () { + test_wrapper_func_=$1; shift test_start_ test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq= test "$#" = 2 || - error "bug in the test script: not 2 or 3 parameters to test-expect-success" + BUG "not 2 or 3 parameters to test-expect-success" export test_prereq if ! test_skip "$@" then base=$(basename "$0" .sh) echo "$test_count" >>"$perf_results_dir"/$base.subtests echo "$1" >"$perf_results_dir"/$base.$test_count.descr - if test -z "$verbose"; then - printf "%s" "perf $test_count - $1:" - else - echo "perf $test_count - $1:" - fi - for i in $(test_seq 1 $GIT_PERF_REPEAT_COUNT); do - say >&3 "running: $2" - if test_run_perf_ "$2" - then - if test -z "$verbose"; then - printf " %s" "$i" - else - echo "* timing run $i/$GIT_PERF_REPEAT_COUNT:" - fi + base="$perf_results_dir"/"$perf_results_prefix$(basename "$0" .sh)"."$test_count" + "$test_wrapper_func_" "$@" + fi + + test_finish_ +} + +test_perf_ () { + if test -z "$verbose"; then + printf "%s" "perf $test_count - $1:" + else + echo "perf $test_count - $1:" + fi + for i in $(test_seq 1 $GIT_PERF_REPEAT_COUNT); do + say >&3 "running: $2" + if test_run_perf_ "$2" + then + if test -z "$verbose"; then + printf " %s" "$i" else - test -z "$verbose" && echo - test_failure_ "$@" - break + echo "* timing run $i/$GIT_PERF_REPEAT_COUNT:" fi - done - if test -z "$verbose"; then - echo " ok" else - test_ok_ "$1" + test -z "$verbose" && echo + test_failure_ "$@" + break fi - base="$perf_results_dir"/"$perf_results_prefix$(basename "$0" .sh)"."$test_count" - "$TEST_DIRECTORY"/perf/min_time.perl test_time.* >"$base".times + done + if test -z "$verbose"; then + echo " ok" + else + test_ok_ "$1" fi - test_finish_ + "$TEST_DIRECTORY"/perf/min_time.perl test_time.* >"$base".times +} + +test_perf () { + test_wrapper_ test_perf_ "$@" +} + +test_size_ () { + say >&3 "running: $2" + if test_eval_ "$2" 3>"$base".size; then + test_ok_ "$1" + else + test_failure_ "$@" + fi +} + +test_size () { + test_wrapper_ test_size_ "$@" } # We extend test_done to print timings at the end (./run disables this diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 850f651e4e..b6566003dd 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -87,6 +87,10 @@ _run_sub_test_lib_test_common () { passing metrics ' + # Tell the framework that we are self-testing to make sure + # it yields a stable result. + GIT_TEST_FRAMEWORK_SELFTEST=t && + # Point to the t/test-lib.sh, which isn't in ../ as usual . "\$TEST_DIRECTORY"/test-lib.sh EOF @@ -270,7 +274,7 @@ test_expect_success 'pretend we have a mix of all possible results' " EOF " -test_expect_success 'test --verbose' ' +test_expect_success C_LOCALE_OUTPUT 'test --verbose' ' test_must_fail run_sub_test_lib_test \ test-verbose "test verbose" --verbose <<-\EOF && test_expect_success "passing test" true @@ -821,9 +825,87 @@ test_expect_success 'tests clean up even on failures' " EOF " +test_expect_success 'test_oid setup' ' + test_oid_init +' + +test_expect_success 'test_oid provides sane info by default' ' + test_oid zero >actual && + grep "^00*\$" actual && + rawsz="$(test_oid rawsz)" && + hexsz="$(test_oid hexsz)" && + test "$hexsz" -eq $(wc -c <actual) && + test $(( $rawsz * 2)) -eq "$hexsz" +' + +test_expect_success 'test_oid can look up data for SHA-1' ' + test_when_finished "test_detect_hash" && + test_set_hash sha1 && + test_oid zero >actual && + grep "^00*\$" actual && + rawsz="$(test_oid rawsz)" && + hexsz="$(test_oid hexsz)" && + test $(wc -c <actual) -eq 40 && + test "$rawsz" -eq 20 && + test "$hexsz" -eq 40 +' + +test_expect_success 'test_oid can look up data for SHA-256' ' + test_when_finished "test_detect_hash" && + test_set_hash sha256 && + test_oid zero >actual && + grep "^00*\$" actual && + rawsz="$(test_oid rawsz)" && + hexsz="$(test_oid hexsz)" && + test $(wc -c <actual) -eq 64 && + test "$rawsz" -eq 32 && + test "$hexsz" -eq 64 +' + ################################################################ # Basics of the basics +test_oid_cache <<\EOF +path0f sha1:f87290f8eb2cbbea7857214459a0739927eab154 +path0f sha256:638106af7c38be056f3212cbd7ac65bc1bac74f420ca5a436ff006a9d025d17d + +path0s sha1:15a98433ae33114b085f3eb3bb03b832b3180a01 +path0s sha256:3a24cc53cf68edddac490bbf94a418a52932130541361f685df685e41dd6c363 + +path2f sha1:3feff949ed00a62d9f7af97c15cd8a30595e7ac7 +path2f sha256:2a7f36571c6fdbaf0e3f62751a0b25a3f4c54d2d1137b3f4af9cb794bb498e5f + +path2s sha1:d8ce161addc5173867a3c3c730924388daedbc38 +path2s sha256:18fd611b787c2e938ddcc248fabe4d66a150f9364763e9ec133dd01d5bb7c65a + +path2d sha1:58a09c23e2ca152193f2786e06986b7b6712bdbe +path2d sha256:00e4b32b96e7e3d65d79112dcbea53238a22715f896933a62b811377e2650c17 + +path3f sha1:0aa34cae68d0878578ad119c86ca2b5ed5b28376 +path3f sha256:09f58616b951bd571b8cb9dc76d372fbb09ab99db2393f5ab3189d26c45099ad + +path3s sha1:8599103969b43aff7e430efea79ca4636466794f +path3s sha256:fce1aed087c053306f3f74c32c1a838c662bbc4551a7ac2420f5d6eb061374d0 + +path3d sha1:21ae8269cacbe57ae09138dcc3a2887f904d02b3 +path3d sha256:9b60497be959cb830bf3f0dc82bcc9ad9e925a24e480837ade46b2295e47efe1 + +subp3f sha1:00fb5908cb97c2564a9783c0c64087333b3b464f +subp3f sha256:a1a9e16998c988453f18313d10375ee1d0ddefe757e710dcae0d66aa1e0c58b3 + +subp3s sha1:6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c +subp3s sha256:81759d9f5e93c6546ecfcadb560c1ff057314b09f93fe8ec06e2d8610d34ef10 + +subp3d sha1:3c5e5399f3a333eddecce7a9b9465b63f65f51e2 +subp3d sha256:76b4ef482d4fa1c754390344cf3851c7f883b27cf9bc999c6547928c46aeafb7 + +root sha1:087704a96baf1c2d1c869a8b084481e121c88b5b +root sha256:9481b52abab1b2ffeedbf9de63ce422b929f179c1b98ff7bee5f8f1bc0710751 + +simpletree sha1:7bb943559a305bdd6bdee2cef6e5df2413c3d30a +simpletree sha256:1710c07a6c86f9a3c7376364df04c47ee39e5a5e221fcdd84b743bc9bb7e2bc5 +EOF + # updating a new file without --add should fail. test_expect_success 'git update-index without --add should fail adding' ' test_must_fail git update-index should-be-empty @@ -839,8 +921,8 @@ 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 SHA1 'validate object ID of a known tree' ' - test "$tree" = 7bb943559a305bdd6bdee2cef6e5df2413c3d30a +test_expect_success 'validate object ID of a known tree' ' + test "$tree" = "$(test_oid simpletree)" ' # Removing paths. @@ -882,16 +964,16 @@ test_expect_success 'showing stage with git ls-files --stage' ' git ls-files --stage >current ' -test_expect_success SHA1 'validate git ls-files output for a known tree' ' - cat >expected <<-\EOF && - 100644 f87290f8eb2cbbea7857214459a0739927eab154 0 path0 - 120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0 path0sym - 100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0 path2/file2 - 120000 d8ce161addc5173867a3c3c730924388daedbc38 0 path2/file2sym - 100644 0aa34cae68d0878578ad119c86ca2b5ed5b28376 0 path3/file3 - 120000 8599103969b43aff7e430efea79ca4636466794f 0 path3/file3sym - 100644 00fb5908cb97c2564a9783c0c64087333b3b464f 0 path3/subp3/file3 - 120000 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c 0 path3/subp3/file3sym +test_expect_success 'validate git ls-files output for a known tree' ' + cat >expected <<-EOF && + 100644 $(test_oid path0f) 0 path0 + 120000 $(test_oid path0s) 0 path0sym + 100644 $(test_oid path2f) 0 path2/file2 + 120000 $(test_oid path2s) 0 path2/file2sym + 100644 $(test_oid path3f) 0 path3/file3 + 120000 $(test_oid path3s) 0 path3/file3sym + 100644 $(test_oid subp3f) 0 path3/subp3/file3 + 120000 $(test_oid subp3s) 0 path3/subp3/file3sym EOF test_cmp expected current ' @@ -900,20 +982,20 @@ test_expect_success 'writing tree out with git write-tree' ' tree=$(git write-tree) ' -test_expect_success SHA1 'validate object ID for a known tree' ' - test "$tree" = 087704a96baf1c2d1c869a8b084481e121c88b5b +test_expect_success 'validate object ID for a known tree' ' + test "$tree" = "$(test_oid root)" ' test_expect_success 'showing tree with git ls-tree' ' git ls-tree $tree >current ' -test_expect_success SHA1 'git ls-tree output for a known tree' ' - cat >expected <<-\EOF && - 100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0 - 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym - 040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe path2 - 040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3 path3 +test_expect_success 'git ls-tree output for a known tree' ' + cat >expected <<-EOF && + 100644 blob $(test_oid path0f) path0 + 120000 blob $(test_oid path0s) path0sym + 040000 tree $(test_oid path2d) path2 + 040000 tree $(test_oid path3d) path3 EOF test_cmp expected current ' @@ -924,16 +1006,16 @@ test_expect_success 'showing tree with git ls-tree -r' ' git ls-tree -r $tree >current ' -test_expect_success SHA1 'git ls-tree -r output for a known tree' ' - cat >expected <<-\EOF && - 100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0 - 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym - 100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 path2/file2 - 120000 blob d8ce161addc5173867a3c3c730924388daedbc38 path2/file2sym - 100644 blob 0aa34cae68d0878578ad119c86ca2b5ed5b28376 path3/file3 - 120000 blob 8599103969b43aff7e430efea79ca4636466794f path3/file3sym - 100644 blob 00fb5908cb97c2564a9783c0c64087333b3b464f path3/subp3/file3 - 120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c path3/subp3/file3sym +test_expect_success 'git ls-tree -r output for a known tree' ' + cat >expected <<-EOF && + 100644 blob $(test_oid path0f) path0 + 120000 blob $(test_oid path0s) path0sym + 100644 blob $(test_oid path2f) path2/file2 + 120000 blob $(test_oid path2s) path2/file2sym + 100644 blob $(test_oid path3f) path3/file3 + 120000 blob $(test_oid path3s) path3/file3sym + 100644 blob $(test_oid subp3f) path3/subp3/file3 + 120000 blob $(test_oid subp3s) path3/subp3/file3sym EOF test_cmp expected current ' @@ -943,19 +1025,19 @@ test_expect_success 'showing tree with git ls-tree -r -t' ' git ls-tree -r -t $tree >current ' -test_expect_success SHA1 'git ls-tree -r output for a known tree' ' - cat >expected <<-\EOF && - 100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0 - 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym - 040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe path2 - 100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 path2/file2 - 120000 blob d8ce161addc5173867a3c3c730924388daedbc38 path2/file2sym - 040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3 path3 - 100644 blob 0aa34cae68d0878578ad119c86ca2b5ed5b28376 path3/file3 - 120000 blob 8599103969b43aff7e430efea79ca4636466794f path3/file3sym - 040000 tree 3c5e5399f3a333eddecce7a9b9465b63f65f51e2 path3/subp3 - 100644 blob 00fb5908cb97c2564a9783c0c64087333b3b464f path3/subp3/file3 - 120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c path3/subp3/file3sym +test_expect_success 'git ls-tree -r output for a known tree' ' + cat >expected <<-EOF && + 100644 blob $(test_oid path0f) path0 + 120000 blob $(test_oid path0s) path0sym + 040000 tree $(test_oid path2d) path2 + 100644 blob $(test_oid path2f) path2/file2 + 120000 blob $(test_oid path2s) path2/file2sym + 040000 tree $(test_oid path3d) path3 + 100644 blob $(test_oid path3f) path3/file3 + 120000 blob $(test_oid path3s) path3/file3sym + 040000 tree $(test_oid subp3d) path3/subp3 + 100644 blob $(test_oid subp3f) path3/subp3/file3 + 120000 blob $(test_oid subp3s) path3/subp3/file3sym EOF test_cmp expected current ' @@ -964,26 +1046,27 @@ test_expect_success 'writing partial tree out with git write-tree --prefix' ' ptree=$(git write-tree --prefix=path3) ' -test_expect_success SHA1 'validate object ID for a known tree' ' - test "$ptree" = 21ae8269cacbe57ae09138dcc3a2887f904d02b3 +test_expect_success 'validate object ID for a known tree' ' + test "$ptree" = $(test_oid path3d) ' test_expect_success 'writing partial tree out with git write-tree --prefix' ' ptree=$(git write-tree --prefix=path3/subp3) ' -test_expect_success SHA1 'validate object ID for a known tree' ' - test "$ptree" = 3c5e5399f3a333eddecce7a9b9465b63f65f51e2 +test_expect_success 'validate object ID for a known tree' ' + test "$ptree" = $(test_oid subp3d) ' test_expect_success 'put invalid objects into the index' ' rm -f .git/index && - cat >badobjects <<-\EOF && - 100644 blob 1000000000000000000000000000000000000000 dir/file1 - 100644 blob 2000000000000000000000000000000000000000 dir/file2 - 100644 blob 3000000000000000000000000000000000000000 dir/file3 - 100644 blob 4000000000000000000000000000000000000000 dir/file4 - 100644 blob 5000000000000000000000000000000000000000 dir/file5 + suffix=$(echo $ZERO_OID | sed -e "s/^.//") && + cat >badobjects <<-EOF && + 100644 blob $(test_oid 001) dir/file1 + 100644 blob $(test_oid 002) dir/file2 + 100644 blob $(test_oid 003) dir/file3 + 100644 blob $(test_oid 004) dir/file4 + 100644 blob $(test_oid 005) dir/file5 EOF git update-index --index-info <badobjects ' @@ -1006,19 +1089,19 @@ test_expect_success 'git read-tree followed by write-tree should be idempotent' test "$newtree" = "$tree" ' -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 -:100644 100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0000000000000000000000000000000000000000 M path2/file2 -:120000 120000 d8ce161addc5173867a3c3c730924388daedbc38 0000000000000000000000000000000000000000 M path2/file2sym -:100644 100644 0aa34cae68d0878578ad119c86ca2b5ed5b28376 0000000000000000000000000000000000000000 M path3/file3 -:120000 120000 8599103969b43aff7e430efea79ca4636466794f 0000000000000000000000000000000000000000 M path3/file3sym -:100644 100644 00fb5908cb97c2564a9783c0c64087333b3b464f 0000000000000000000000000000000000000000 M path3/subp3/file3 -:120000 120000 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c 0000000000000000000000000000000000000000 M path3/subp3/file3sym +test_expect_success 'validate git diff-files output for a know cache/work tree state' ' + cat >expected <<EOF && +:100644 100644 $(test_oid path0f) $ZERO_OID M path0 +:120000 120000 $(test_oid path0s) $ZERO_OID M path0sym +:100644 100644 $(test_oid path2f) $ZERO_OID M path2/file2 +:120000 120000 $(test_oid path2s) $ZERO_OID M path2/file2sym +:100644 100644 $(test_oid path3f) $ZERO_OID M path3/file3 +:120000 120000 $(test_oid path3s) $ZERO_OID M path3/file3sym +:100644 100644 $(test_oid subp3f) $ZERO_OID M path3/subp3/file3 +:120000 120000 $(test_oid subp3s) $ZERO_OID M path3/subp3/file3sym EOF git diff-files >current && - test_cmp current expected + test_cmp expected current ' test_expect_success 'git update-index --refresh should succeed' ' @@ -1031,23 +1114,23 @@ test_expect_success 'no diff after checkout and git update-index --refresh' ' ' ################################################################ -P=087704a96baf1c2d1c869a8b084481e121c88b5b +P=$(test_oid root) -test_expect_success SHA1 'git commit-tree records the correct tree in a commit' ' +test_expect_success '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 SHA1 'git commit-tree records the correct parent in a commit' ' +test_expect_success '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 SHA1 'git commit-tree omits duplicated parent in a commit' ' +test_expect_success '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 182da069f1..42a263cada 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -319,14 +319,14 @@ 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" + BUG "cannot prepare $base" (cd $base/dir && /bin/pwd -P) status=$? chmod 700 $base && rm -rf $base || - error "bug in test script: cannot clean $base" + BUG "cannot clean $base" return $status ' diff --git a/t/t0002-gitfile.sh b/t/t0002-gitfile.sh index 3691023d51..0aa9908ea1 100755 --- a/t/t0002-gitfile.sh +++ b/t/t0002-gitfile.sh @@ -92,11 +92,12 @@ test_expect_success 'enter_repo non-strict mode' ' mv .git .realgit && echo "gitdir: .realgit" >.git ) && + head=$(git -C enter_repo rev-parse HEAD) && git ls-remote enter_repo >actual && - cat >expected <<-\EOF && - 946e985ab20de757ca5b872b16d64e92ff3803a9 HEAD - 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/heads/master - 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/tags/foo + cat >expected <<-EOF && + $head HEAD + $head refs/heads/master + $head refs/tags/foo EOF test_cmp expected actual ' @@ -106,21 +107,23 @@ test_expect_success 'enter_repo linked checkout' ' cd enter_repo && git worktree add ../foo refs/tags/foo ) && + head=$(git -C enter_repo rev-parse HEAD) && git ls-remote foo >actual && - cat >expected <<-\EOF && - 946e985ab20de757ca5b872b16d64e92ff3803a9 HEAD - 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/heads/master - 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/tags/foo + cat >expected <<-EOF && + $head HEAD + $head refs/heads/master + $head refs/tags/foo EOF test_cmp expected actual ' test_expect_success 'enter_repo strict mode' ' + head=$(git -C enter_repo rev-parse HEAD) && git ls-remote --upload-pack="git upload-pack --strict" foo/.git >actual && - cat >expected <<-\EOF && - 946e985ab20de757ca5b872b16d64e92ff3803a9 HEAD - 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/heads/master - 946e985ab20de757ca5b872b16d64e92ff3803a9 refs/tags/foo + cat >expected <<-EOF && + $head HEAD + $head refs/heads/master + $head refs/tags/foo EOF test_cmp expected actual ' diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh index 22499bce5f..71e63d8b50 100755 --- a/t/t0003-attributes.sh +++ b/t/t0003-attributes.sh @@ -322,4 +322,24 @@ test_expect_success 'bare repository: test info/attributes' ' ) ' +test_expect_success 'binary macro expanded by -a' ' + echo "file binary" >.gitattributes && + cat >expect <<-\EOF && + file: binary: set + file: diff: unset + file: merge: unset + file: text: unset + EOF + git check-attr -a file >actual && + test_cmp expect actual +' + + +test_expect_success 'query binary macro directly' ' + echo "file binary" >.gitattributes && + echo file: binary: set >expect && + git check-attr binary file >actual && + test_cmp expect actual +' + test_done diff --git a/t/t0006-date.sh b/t/t0006-date.sh index 64ff86df8e..d9fcc829a9 100755 --- a/t/t0006-date.sh +++ b/t/t0006-date.sh @@ -4,10 +4,10 @@ test_description='test date parsing and printing' . ./test-lib.sh # arbitrary reference time: 2009-08-30 19:20:00 -TEST_DATE_NOW=1251660000; export TEST_DATE_NOW +GIT_TEST_DATE_NOW=1251660000; export GIT_TEST_DATE_NOW check_relative() { - t=$(($TEST_DATE_NOW - $1)) + t=$(($GIT_TEST_DATE_NOW - $1)) echo "$t -> $2" >expect test_expect_${3:-success} "relative date ($2)" " test-tool date relative $t >actual && @@ -113,6 +113,8 @@ check_approxidate '3:00' '2009-08-30 03:00:00' check_approxidate '15:00' '2009-08-30 15:00:00' check_approxidate 'noon today' '2009-08-30 12:00:00' check_approxidate 'noon yesterday' '2009-08-29 12:00:00' +check_approxidate 'January 5th noon pm' '2009-01-05 12:00:00' +check_approxidate '10am noon' '2009-08-29 12:00:00' check_approxidate 'last tuesday' '2009-08-25 19:20:00' check_approxidate 'July 5th' '2009-07-05 19:20:00' @@ -126,4 +128,22 @@ check_approxidate '6AM, June 7, 2009' '2009-06-07 06:00:00' check_approxidate '2008-12-01' '2008-12-01 19:20:00' check_approxidate '2009-12-01' '2009-12-01 19:20:00' +check_date_format_human() { + t=$(($GIT_TEST_DATE_NOW - $1)) + echo "$t -> $2" >expect + test_expect_success "human date $t" ' + test-tool date human $t >actual && + test_i18ncmp expect actual +' +} + +check_date_format_human 18000 "5 hours ago" # 5 hours ago +check_date_format_human 432000 "Tue Aug 25 19:20" # 5 days ago +check_date_format_human 1728000 "Mon Aug 10 19:20" # 3 weeks ago +check_date_format_human 13000000 "Thu Apr 2 08:13" # 5 months ago +check_date_format_human 31449600 "Aug 31 2008" # 12 months ago +check_date_format_human 37500000 "Jun 22 2008" # 1 year, 2 months ago +check_date_format_human 55188000 "Dec 1 2007" # 1 year, 9 months ago +check_date_format_human 630000000 "Sep 13 1989" # 20 years ago + test_done diff --git a/t/t0009-prio-queue.sh b/t/t0009-prio-queue.sh index e56dfce668..3941ad2528 100755 --- a/t/t0009-prio-queue.sh +++ b/t/t0009-prio-queue.sh @@ -47,4 +47,18 @@ test_expect_success 'notice empty queue' ' test_cmp expect actual ' +cat >expect <<'EOF' +3 +2 +6 +4 +5 +1 +8 +EOF +test_expect_success 'stack order' ' + test-tool prio-queue stack 8 1 5 4 6 2 3 dump >actual && + test_cmp expect actual +' + test_done diff --git a/t/t0012-help.sh b/t/t0012-help.sh index bc27df7f38..e8ef7300ec 100755 --- a/t/t0012-help.sh +++ b/t/t0012-help.sh @@ -29,9 +29,9 @@ test_expect_success "setup" ' # to verify test_expect_success 'basic help commands' ' git help >/dev/null && - git help -a >/dev/null && + git help -a --no-verbose >/dev/null && git help -g >/dev/null && - git help -av >/dev/null + git help -a >/dev/null ' test_expect_success "works for commands and guides by default" ' diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh new file mode 100755 index 0000000000..a070e645d7 --- /dev/null +++ b/t/t0014-alias.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +test_description='git command aliasing' + +. ./test-lib.sh + +test_expect_success 'nested aliases - internal execution' ' + git config alias.nested-internal-1 nested-internal-2 && + git config alias.nested-internal-2 status && + git nested-internal-1 >output && + test_i18ngrep "^On branch " output +' + +test_expect_success 'nested aliases - mixed execution' ' + git config alias.nested-external-1 nested-external-2 && + git config alias.nested-external-2 "!git nested-external-3" && + git config alias.nested-external-3 status && + git nested-external-1 >output && + test_i18ngrep "^On branch " output +' + +test_expect_success 'looping aliases - internal execution' ' + git config alias.loop-internal-1 loop-internal-2 && + git config alias.loop-internal-2 loop-internal-3 && + git config alias.loop-internal-3 loop-internal-2 && + test_must_fail git loop-internal-1 2>output && + test_i18ngrep "^fatal: alias loop detected: expansion of" output +' + +# This test is disabled until external loops are fixed, because would block +# the test suite for a full minute. +# +#test_expect_failure 'looping aliases - mixed execution' ' +# git config alias.loop-mixed-1 loop-mixed-2 && +# git config alias.loop-mixed-2 "!git loop-mixed-1" && +# test_must_fail git loop-mixed-1 2>output && +# test_i18ngrep "^fatal: alias loop detected: expansion of" output +#' + +test_done diff --git a/t/t0015-hash.sh b/t/t0015-hash.sh new file mode 100755 index 0000000000..291e9061f3 --- /dev/null +++ b/t/t0015-hash.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +test_description='test basic hash implementation' +. ./test-lib.sh + + +test_expect_success 'test basic SHA-1 hash values' ' + test-tool sha1 </dev/null >actual && + grep da39a3ee5e6b4b0d3255bfef95601890afd80709 actual && + printf "a" | test-tool sha1 >actual && + grep 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 actual && + printf "abc" | test-tool sha1 >actual && + grep a9993e364706816aba3e25717850c26c9cd0d89d actual && + printf "message digest" | test-tool sha1 >actual && + grep c12252ceda8be8994d5fa0290a47231c1d16aae3 actual && + printf "abcdefghijklmnopqrstuvwxyz" | test-tool sha1 >actual && + grep 32d10c7b8cf96570ca04ce37f2a19d84240d3a89 actual && + perl -e "$| = 1; print q{aaaaaaaaaa} for 1..100000;" | \ + test-tool sha1 >actual && + grep 34aa973cd4c4daa4f61eeb2bdbad27316534016f actual && + printf "blob 0\0" | test-tool sha1 >actual && + grep e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 actual && + printf "blob 3\0abc" | test-tool sha1 >actual && + grep f2ba8f84ab5c1bce84a7b441cb1959cfc7093b7f actual && + printf "tree 0\0" | test-tool sha1 >actual && + grep 4b825dc642cb6eb9a060e54bf8d69288fbee4904 actual +' + +test_expect_success 'test basic SHA-256 hash values' ' + test-tool sha256 </dev/null >actual && + grep e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 actual && + printf "a" | test-tool sha256 >actual && + grep ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb actual && + printf "abc" | test-tool sha256 >actual && + grep ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad actual && + printf "message digest" | test-tool sha256 >actual && + grep f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650 actual && + printf "abcdefghijklmnopqrstuvwxyz" | test-tool sha256 >actual && + grep 71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73 actual && + # Try to exercise the chunking code by turning autoflush on. + perl -e "$| = 1; print q{aaaaaaaaaa} for 1..100000;" | \ + test-tool sha256 >actual && + grep cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0 actual && + perl -e "$| = 1; print q{abcdefghijklmnopqrstuvwxyz} for 1..100000;" | \ + test-tool sha256 >actual && + grep e406ba321ca712ad35a698bf0af8d61fc4dc40eca6bdcea4697962724ccbde35 actual && + printf "blob 0\0" | test-tool sha256 >actual && + grep 473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813 actual && + printf "blob 3\0abc" | test-tool sha256 >actual && + grep c1cf6e465077930e88dc5136641d402f72a229ddd996f627d60e9639eaba35a6 actual && + printf "tree 0\0" | test-tool sha256 >actual && + grep 6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321 actual +' + +test_done diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh index 308cd28f3b..e10f5f787f 100755 --- a/t/t0021-conversion.sh +++ b/t/t0021-conversion.sh @@ -24,7 +24,7 @@ generate_random_characters () { } file_size () { - perl -e 'print -s $ARGV[0]' "$1" + test-tool path-utils file-size "$1" } filter_git () { @@ -166,10 +166,10 @@ test_expect_success expanded_in_repo ' rm -f expanded-keywords expanded-keywords-crlf && git checkout -- expanded-keywords && - test_cmp expanded-keywords expected-output && + test_cmp expected-output expanded-keywords && git checkout -- expanded-keywords-crlf && - test_cmp expanded-keywords-crlf expected-output-crlf + test_cmp expected-output-crlf expanded-keywords-crlf ' # The use of %f in a filter definition is expanded to the path to diff --git a/t/t0027-auto-crlf.sh b/t/t0027-auto-crlf.sh index beb5927f77..3587e454f1 100755 --- a/t/t0027-auto-crlf.sh +++ b/t/t0027-auto-crlf.sh @@ -293,9 +293,9 @@ checkout_files () { do rm crlf_false_attr__$f.txt && if test -z "$ceol"; then - git checkout crlf_false_attr__$f.txt + git checkout -- crlf_false_attr__$f.txt else - git -c core.eol=$ceol checkout crlf_false_attr__$f.txt + git -c core.eol=$ceol checkout -- crlf_false_attr__$f.txt fi done diff --git a/t/t0028-working-tree-encoding.sh b/t/t0028-working-tree-encoding.sh index 7e87b5a200..e58ecbfc44 100755 --- a/t/t0028-working-tree-encoding.sh +++ b/t/t0028-working-tree-encoding.sh @@ -11,9 +11,12 @@ test_expect_success 'setup test files' ' text="hallo there!\ncan you read me?" && echo "*.utf16 text working-tree-encoding=utf-16" >.gitattributes && + echo "*.utf16lebom text working-tree-encoding=UTF-16LE-BOM" >>.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 && + printf "\377\376" >test.utf16lebom.raw && + printf "$text" | iconv -f UTF-8 -t UTF-32LE >>test.utf16lebom.raw && # Line ending tests printf "one\ntwo\nthree\n" >lf.utf8.raw && @@ -32,7 +35,8 @@ test_expect_success 'setup test files' ' # 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 && + cp test.utf16lebom.raw test.utf16lebom && + git add .gitattributes test.utf16 test.utf16lebom && git commit -m initial ' @@ -51,6 +55,12 @@ test_expect_success 're-encode to UTF-16 on checkout' ' test_cmp_bin test.utf16.raw test.utf16 ' +test_expect_success 're-encode to UTF-16-LE-BOM on checkout' ' + rm test.utf16lebom && + git checkout test.utf16lebom && + test_cmp_bin test.utf16lebom.raw test.utf16lebom +' + test_expect_success 'check $GIT_DIR/info/attributes support' ' test_when_finished "rm -f test.utf32.git" && test_when_finished "git reset --hard HEAD" && diff --git a/t/t0029-core-unsetenvvars.sh b/t/t0029-core-unsetenvvars.sh new file mode 100755 index 0000000000..24ce46a6ea --- /dev/null +++ b/t/t0029-core-unsetenvvars.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +test_description='test the Windows-only core.unsetenvvars setting' + +. ./test-lib.sh + +if ! test_have_prereq MINGW +then + skip_all='skipping Windows-specific tests' + test_done +fi + +test_expect_success 'setup' ' + mkdir -p "$TRASH_DIRECTORY/.git/hooks" && + write_script "$TRASH_DIRECTORY/.git/hooks/pre-commit" <<-\EOF + echo $HOBBES >&2 + EOF +' + +test_expect_success 'core.unsetenvvars works' ' + HOBBES=Calvin && + export HOBBES && + git commit --allow-empty -m with 2>err && + grep Calvin err && + git -c core.unsetenvvars=FINDUS,HOBBES,CALVIN \ + commit --allow-empty -m without 2>err && + ! grep Calvin err +' + +test_done diff --git a/t/t0030-stripspace.sh b/t/t0030-stripspace.sh index 5ce47e8af5..0c24a0f9a3 100755 --- a/t/t0030-stripspace.sh +++ b/t/t0030-stripspace.sh @@ -430,9 +430,15 @@ test_expect_success '-c with changed comment char' ' test_expect_success '-c with comment char defined in .git/config' ' test_config core.commentchar = && printf "= foo\n" >expect && - printf "foo" | ( - mkdir sub && cd sub && git stripspace -c - ) >actual && + rm -fr sub && + mkdir sub && + printf "foo" | git -C sub stripspace -c >actual && + test_cmp expect actual +' + +test_expect_success '-c outside git repository' ' + printf "# foo\n" >expect && + printf "foo" | nongit git stripspace -c >actual && test_cmp expect actual ' diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index 5b0560fa20..b8f366c442 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -8,7 +8,7 @@ test_description='our own option parser' . ./test-lib.sh cat >expect <<\EOF -usage: test-parse-options <options> +usage: test-tool parse-options <options> A helper function for the parse-options API. @@ -23,7 +23,6 @@ usage: test-parse-options <options> -j <n> get a integer, too -m, --magnitude <n> get a magnitude --set23 set integer to 23 - -t <time> get timestamp of <time> -L, --length <str> get length of <str> -F, --file <file> set file to <file> @@ -52,7 +51,7 @@ Standard options EOF test_expect_success 'test help' ' - test_must_fail test-parse-options -h >output 2>output.err && + test_must_fail test-tool parse-options -h >output 2>output.err && test_must_be_empty output.err && test_i18ncmp expect output ' @@ -64,7 +63,7 @@ check () { shift && expect="$1" && shift && - test-parse-options --expect="$what $expect" "$@" + test-tool parse-options --expect="$what $expect" "$@" } check_unknown_i18n() { @@ -75,7 +74,7 @@ check_unknown_i18n() { echo error: unknown switch \`${1#-}\' >expect ;; esac && cat expect.err >>expect && - test_must_fail test-parse-options $* >output 2>output.err && + test_must_fail test-tool parse-options $* >output 2>output.err && test_must_be_empty output && test_i18ncmp expect output.err } @@ -133,7 +132,7 @@ file: prefix/my.file EOF test_expect_success 'short options' ' - test-parse-options -s123 -b -i 1729 -m 16k -b -vv -n -F my.file \ + test-tool parse-options -s123 -b -i 1729 -m 16k -b -vv -n -F my.file \ >output 2>output.err && test_cmp expect output && test_must_be_empty output.err @@ -153,7 +152,7 @@ file: prefix/fi.le EOF test_expect_success 'long options' ' - test-parse-options --boolean --integer 1729 --magnitude 16k \ + test-tool parse-options --boolean --integer 1729 --magnitude 16k \ --boolean --string2=321 --verbose --verbose --no-dry-run \ --abbrev=10 --file fi.le --obsolete \ >output 2>output.err && @@ -162,9 +161,9 @@ test_expect_success 'long options' ' ' test_expect_success 'missing required value' ' - test_expect_code 129 test-parse-options -s && - test_expect_code 129 test-parse-options --string && - test_expect_code 129 test-parse-options --file + test_expect_code 129 test-tool parse-options -s && + test_expect_code 129 test-tool parse-options --string && + test_expect_code 129 test-tool parse-options --file ' cat >expect <<\EOF @@ -184,7 +183,7 @@ arg 02: --boolean EOF test_expect_success 'intermingled arguments' ' - test-parse-options a1 --string 123 b1 --boolean -j 13 -- --boolean \ + test-tool parse-options a1 --string 123 b1 --boolean -j 13 -- --boolean \ >output 2>output.err && test_must_be_empty output.err && test_cmp expect output @@ -204,21 +203,21 @@ file: (not set) EOF test_expect_success 'unambiguously abbreviated option' ' - test-parse-options --int 2 --boolean --no-bo >output 2>output.err && + test-tool parse-options --int 2 --boolean --no-bo >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' test_expect_success 'unambiguously abbreviated option with "="' ' - test-parse-options --expect="integer: 2" --int=2 + test-tool parse-options --expect="integer: 2" --int=2 ' test_expect_success 'ambiguously abbreviated option' ' - test_expect_code 129 test-parse-options --strin 123 + test_expect_code 129 test-tool parse-options --strin 123 ' test_expect_success 'non ambiguous option (after two options it abbreviates)' ' - test-parse-options --expect="string: 123" --st 123 + test-tool parse-options --expect="string: 123" --st 123 ' cat >typo.err <<\EOF @@ -226,9 +225,9 @@ error: did you mean `--boolean` (with two dashes ?) EOF test_expect_success 'detect possible typos' ' - test_must_fail test-parse-options -boolean >output 2>output.err && + test_must_fail test-tool parse-options -boolean >output 2>output.err && test_must_be_empty output && - test_cmp typo.err output.err + test_i18ncmp typo.err output.err ' cat >typo.err <<\EOF @@ -236,34 +235,13 @@ error: did you mean `--ambiguous` (with two dashes ?) EOF test_expect_success 'detect possible typos' ' - test_must_fail test-parse-options -ambiguous >output 2>output.err && + test_must_fail test-tool parse-options -ambiguous >output 2>output.err && test_must_be_empty output && - test_cmp typo.err output.err + test_i18ncmp typo.err output.err ' test_expect_success 'keep some options as arguments' ' - test-parse-options --expect="arg 00: --quux" --quux -' - -cat >expect <<\EOF -boolean: 0 -integer: 0 -magnitude: 0 -timestamp: 1 -string: (not set) -abbrev: 7 -verbose: -1 -quiet: 1 -dry run: no -file: (not set) -arg 00: foo -EOF - -test_expect_success 'OPT_DATE() works' ' - test-parse-options -t "1970-01-01 00:00:01 +0000" \ - foo -q >output 2>output.err && - test_must_be_empty output.err && - test_cmp expect output + test-tool parse-options --expect="arg 00: --quux" --quux ' cat >expect <<\EOF @@ -281,13 +259,13 @@ file: (not set) EOF test_expect_success 'OPT_CALLBACK() and OPT_BIT() work' ' - test-parse-options --length=four -b -4 >output 2>output.err && + test-tool parse-options --length=four -b -4 >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' test_expect_success 'OPT_CALLBACK() and callback errors work' ' - test_must_fail test-parse-options --no-length >output 2>output.err && + test_must_fail test-tool parse-options --no-length >output 2>output.err && test_must_be_empty output && test_must_be_empty output.err ' @@ -306,31 +284,31 @@ file: (not set) EOF test_expect_success 'OPT_BIT() and OPT_SET_INT() work' ' - test-parse-options --set23 -bbbbb --no-or4 >output 2>output.err && + test-tool parse-options --set23 -bbbbb --no-or4 >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' test_expect_success 'OPT_NEGBIT() and OPT_SET_INT() work' ' - test-parse-options --set23 -bbbbb --neg-or4 >output 2>output.err && + test-tool parse-options --set23 -bbbbb --neg-or4 >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' test_expect_success 'OPT_BIT() works' ' - test-parse-options --expect="boolean: 6" -bb --or4 + test-tool parse-options --expect="boolean: 6" -bb --or4 ' test_expect_success 'OPT_NEGBIT() works' ' - test-parse-options --expect="boolean: 6" -bb --no-neg-or4 + test-tool parse-options --expect="boolean: 6" -bb --no-neg-or4 ' test_expect_success 'OPT_COUNTUP() with PARSE_OPT_NODASH works' ' - test-parse-options --expect="boolean: 6" + + + + + + + test-tool parse-options --expect="boolean: 6" + + + + + + ' test_expect_success 'OPT_NUMBER_CALLBACK() works' ' - test-parse-options --expect="integer: 12345" -12345 + test-tool parse-options --expect="integer: 12345" -12345 ' cat >expect <<\EOF @@ -347,7 +325,7 @@ file: (not set) EOF test_expect_success 'negation of OPT_NONEG flags is not ambiguous' ' - test-parse-options --no-ambig >output 2>output.err && + test-tool parse-options --no-ambig >output 2>output.err && test_must_be_empty output.err && test_cmp expect output ' @@ -358,38 +336,38 @@ list: bar list: baz EOF test_expect_success '--list keeps list of strings' ' - test-parse-options --list foo --list=bar --list=baz >output && + test-tool parse-options --list foo --list=bar --list=baz >output && test_cmp expect output ' test_expect_success '--no-list resets list' ' - test-parse-options --list=other --list=irrelevant --list=options \ + test-tool parse-options --list=other --list=irrelevant --list=options \ --no-list --list=foo --list=bar --list=baz >output && test_cmp expect output ' test_expect_success 'multiple quiet levels' ' - test-parse-options --expect="quiet: 3" -q -q -q + test-tool parse-options --expect="quiet: 3" -q -q -q ' test_expect_success 'multiple verbose levels' ' - test-parse-options --expect="verbose: 3" -v -v -v + test-tool parse-options --expect="verbose: 3" -v -v -v ' test_expect_success '--no-quiet sets --quiet to 0' ' - test-parse-options --expect="quiet: 0" --no-quiet + test-tool parse-options --expect="quiet: 0" --no-quiet ' test_expect_success '--no-quiet resets multiple -q to 0' ' - test-parse-options --expect="quiet: 0" -q -q -q --no-quiet + test-tool parse-options --expect="quiet: 0" -q -q -q --no-quiet ' test_expect_success '--no-verbose sets verbose to 0' ' - test-parse-options --expect="verbose: 0" --no-verbose + test-tool parse-options --expect="verbose: 0" --no-verbose ' test_expect_success '--no-verbose resets multiple verbose to 0' ' - test-parse-options --expect="verbose: 0" -v -v -v --no-verbose + test-tool parse-options --expect="verbose: 0" -v -v -v --no-verbose ' test_done diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index cd74c0a471..c7b53e494b 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -306,6 +306,8 @@ test_git_path GIT_COMMON_DIR=bar hooks/me bar/hooks/me test_git_path GIT_COMMON_DIR=bar config bar/config test_git_path GIT_COMMON_DIR=bar packed-refs bar/packed-refs test_git_path GIT_COMMON_DIR=bar shallow bar/shallow +test_git_path GIT_COMMON_DIR=bar common bar/common +test_git_path GIT_COMMON_DIR=bar common/file bar/common/file # In the tests below, $(pwd) must be used because it is a native path on # Windows and avoids MSYS's path mangling (which simplifies "foo/../bar" and diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh index cf932c8514..ebc49561ac 100755 --- a/t/t0061-run-command.sh +++ b/t/t0061-run-command.sh @@ -13,11 +13,13 @@ cat >hello-script <<-EOF EOF test_expect_success 'start_command reports ENOENT (slash)' ' - test-tool run-command start-command-ENOENT ./does-not-exist + test-tool run-command start-command-ENOENT ./does-not-exist 2>err && + test_i18ngrep "\./does-not-exist" err ' test_expect_success 'start_command reports ENOENT (no slash)' ' - test-tool run-command start-command-ENOENT does-not-exist + test-tool run-command start-command-ENOENT does-not-exist 2>err && + test_i18ngrep "does-not-exist" err ' test_expect_success 'run_command can run a command' ' @@ -29,11 +31,20 @@ test_expect_success 'run_command can run a command' ' test_must_be_empty err ' -test_expect_success 'run_command is restricted to PATH' ' + +test_lazy_prereq RUNS_COMMANDS_FROM_PWD ' + write_script runs-commands-from-pwd <<-\EOF && + true + EOF + runs-commands-from-pwd >/dev/null 2>&1 +' + +test_expect_success !RUNS_COMMANDS_FROM_PWD 'run_command is restricted to PATH' ' write_script should-not-run <<-\EOF && echo yikes EOF - test_must_fail test-tool run-command run-command should-not-run + test_must_fail test-tool run-command run-command should-not-run 2>err && + test_i18ngrep "should-not-run" err ' test_expect_success !MINGW 'run_command can run a script without a #! line' ' @@ -155,7 +166,8 @@ 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 && + sed -e 's/.* run_command: //' -e '/trace: .*/d' \ + -e '/RUNTIME_PREFIX requested/d' >actual && echo "$expect true" >expect && test_cmp expect actual } @@ -188,4 +200,14 @@ test_expect_success 'GIT_TRACE with environment variables' ' ) ' +test_expect_success MINGW 'verify curlies are quoted properly' ' + : force the rev-parse through the MSYS2 Bash && + git -c alias.r="!git rev-parse" r -- a{b}c >actual && + cat >expect <<-\EOF && + -- + a{b}c + EOF + test_cmp expect actual +' + test_done diff --git a/t/t0064-sha1-array.sh b/t/t0064-sha1-array.sh index 67484502a0..5dda570b9a 100755 --- a/t/t0064-sha1-array.sh +++ b/t/t0064-sha1-array.sh @@ -3,30 +3,30 @@ test_description='basic tests for the SHA1 array implementation' . ./test-lib.sh -echo20 () { +echoid () { prefix="${1:+$1 }" shift while test $# -gt 0 do - echo "$prefix$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1$1" + echo "$prefix$ZERO_OID" | sed -e "s/00/$1/g" shift done } test_expect_success 'ordered enumeration' ' - echo20 "" 44 55 88 aa >expect && + echoid "" 44 55 88 aa >expect && { - echo20 append 88 44 aa 55 && + echoid append 88 44 aa 55 && echo for_each_unique } | test-tool sha1-array >actual && test_cmp expect actual ' test_expect_success 'ordered enumeration with duplicate suppression' ' - echo20 "" 44 55 88 aa >expect && + echoid "" 44 55 88 aa >expect && { - echo20 append 88 44 aa 55 && - echo20 append 88 44 aa 55 && + echoid append 88 44 aa 55 && + echoid append 88 44 aa 55 && echo for_each_unique } | test-tool sha1-array >actual && test_cmp expect actual @@ -34,8 +34,8 @@ test_expect_success 'ordered enumeration with duplicate suppression' ' test_expect_success 'lookup' ' { - echo20 append 88 44 aa 55 && - echo20 lookup 55 + echoid append 88 44 aa 55 && + echoid lookup 55 } | test-tool sha1-array >actual && n=$(cat actual) && test "$n" -eq 1 @@ -43,8 +43,8 @@ test_expect_success 'lookup' ' test_expect_success 'lookup non-existing entry' ' { - echo20 append 88 44 aa 55 && - echo20 lookup 33 + echoid append 88 44 aa 55 && + echoid lookup 33 } | test-tool sha1-array >actual && n=$(cat actual) && test "$n" -lt 0 @@ -52,9 +52,9 @@ test_expect_success 'lookup non-existing entry' ' test_expect_success 'lookup with duplicates' ' { - echo20 append 88 44 aa 55 && - echo20 append 88 44 aa 55 && - echo20 lookup 55 + echoid append 88 44 aa 55 && + echoid append 88 44 aa 55 && + echoid lookup 55 } | test-tool sha1-array >actual && n=$(cat actual) && test "$n" -ge 2 && @@ -63,19 +63,24 @@ test_expect_success 'lookup with duplicates' ' test_expect_success 'lookup non-existing entry with duplicates' ' { - echo20 append 88 44 aa 55 && - echo20 append 88 44 aa 55 && - echo20 lookup 66 + echoid append 88 44 aa 55 && + echoid append 88 44 aa 55 && + echoid lookup 66 } | test-tool sha1-array >actual && n=$(cat actual) && test "$n" -lt 0 ' test_expect_success 'lookup with almost duplicate values' ' + # n-1 5s + root=$(echoid "" 55) && + root=${root%5} && { - echo "append 5555555555555555555555555555555555555555" && - echo "append 555555555555555555555555555555555555555f" && - echo20 lookup 55 + id1="${root}5" && + id2="${root}f" && + echo "append $id1" && + echo "append $id2" && + echoid lookup 55 } | test-tool sha1-array >actual && n=$(cat actual) && test "$n" -eq 0 @@ -83,8 +88,8 @@ test_expect_success 'lookup with almost duplicate values' ' test_expect_success 'lookup with single duplicate value' ' { - echo20 append 55 55 && - echo20 lookup 55 + echoid append 55 55 && + echoid lookup 55 } | test-tool sha1-array >actual && n=$(cat actual) && test "$n" -ge 0 && diff --git a/t/t0205-gettext-poison.sh b/t/t0205-gettext-poison.sh index 438e778d6a..a06269f38a 100755 --- a/t/t0205-gettext-poison.sh +++ b/t/t0205-gettext-poison.sh @@ -5,13 +5,15 @@ test_description='Gettext Shell poison' +GIT_TEST_GETTEXT_POISON=YesPlease +export GIT_TEST_GETTEXT_POISON . ./lib-gettext.sh -test_expect_success GETTEXT_POISON 'sanity: $GIT_INTERNAL_GETTEXT_SH_SCHEME" is poison' ' +test_expect_success 'sanity: $GIT_INTERNAL_GETTEXT_SH_SCHEME" is poison' ' test "$GIT_INTERNAL_GETTEXT_SH_SCHEME" = "poison" ' -test_expect_success GETTEXT_POISON 'gettext: our gettext() fallback has poison semantics' ' +test_expect_success 'gettext: our gettext() fallback has poison semantics' ' printf "# GETTEXT POISON #" >expect && gettext "test" >actual && test_cmp expect actual && @@ -20,7 +22,7 @@ test_expect_success GETTEXT_POISON 'gettext: our gettext() fallback has poison s test_cmp expect actual ' -test_expect_success GETTEXT_POISON 'eval_gettext: our eval_gettext() fallback has poison semantics' ' +test_expect_success 'eval_gettext: our eval_gettext() fallback has poison semantics' ' printf "# GETTEXT POISON #" >expect && eval_gettext "test" >actual && test_cmp expect actual && diff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh index 7762f89b3d..bce02788e6 100755 --- a/t/t0410-partial-clone.sh +++ b/t/t0410-partial-clone.sh @@ -234,11 +234,56 @@ test_expect_success 'rev-list stops traversal at missing and promised commit' ' 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 && + GIT_TEST_COMMIT_GRAPH=0 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 'missing tree objects with --missing=allow-promisor and --exclude-promisor-objects' ' + rm -rf repo && + test_create_repo repo && + test_commit -C repo foo && + test_commit -C repo bar && + test_commit -C repo baz && + + promise_and_delete $(git -C repo rev-parse bar^{tree}) && + promise_and_delete $(git -C repo rev-parse foo^{tree}) && + + git -C repo config core.repositoryformatversion 1 && + git -C repo config extensions.partialclone "arbitrary string" && + + git -C repo rev-list --missing=allow-promisor --objects HEAD >objs 2>rev_list_err && + test_must_be_empty rev_list_err && + # 3 commits, 3 blobs, and 1 tree + test_line_count = 7 objs && + + # Do the same for --exclude-promisor-objects, but with all trees gone. + promise_and_delete $(git -C repo rev-parse baz^{tree}) && + git -C repo rev-list --exclude-promisor-objects --objects HEAD >objs 2>rev_list_err && + test_must_be_empty rev_list_err && + # 3 commits, no blobs or trees + test_line_count = 3 objs +' + +test_expect_success 'missing non-root tree object and rev-list' ' + rm -rf repo && + test_create_repo repo && + mkdir repo/dir && + echo foo >repo/dir/foo && + git -C repo add dir/foo && + git -C repo commit -m "commit dir/foo" && + + promise_and_delete $(git -C repo rev-parse HEAD:dir) && + + git -C repo config core.repositoryformatversion 1 && + git -C repo config extensions.partialclone "arbitrary string" && + + git -C repo rev-list --missing=allow-any --objects HEAD >objs 2>rev_list_err && + test_must_be_empty rev_list_err && + # 1 commit and 1 tree + test_line_count = 2 objs +' + test_expect_success 'rev-list stops traversal at missing and promised tree' ' rm -rf repo && test_create_repo repo && @@ -304,7 +349,7 @@ test_expect_success 'rev-list stops traversal at promisor commit, tree, and blob 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' ' +test_expect_success 'rev-list dies for missing objects on cmd line' ' rm -rf repo && test_create_repo repo && test_commit -C repo foo && @@ -321,7 +366,19 @@ test_expect_success 'rev-list accepts missing and promised objects on command li 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" + + for OBJ in "$COMMIT" "$TREE" "$BLOB"; do + test_must_fail git -C repo rev-list --objects \ + --exclude-promisor-objects "$OBJ" && + test_must_fail git -C repo rev-list --objects-edge-aggressive \ + --exclude-promisor-objects "$OBJ" && + + # Do not die or crash when --ignore-missing is passed. + git -C repo rev-list --ignore-missing --objects \ + --exclude-promisor-objects "$OBJ" && + git -C repo rev-list --ignore-missing --objects-edge-aggressive \ + --exclude-promisor-objects "$OBJ" + done ' test_expect_success 'gc repacks promisor objects separately from non-promisor objects' ' @@ -435,7 +492,6 @@ test_expect_success 'gc stops traversal when a missing but promised object is re ! grep "$TREE_HASH" out ' -LIB_HTTPD_PORT=12345 # default port, 410, cannot be used as non-root . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index 7f19d591f2..43c4be1e5e 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -140,15 +140,17 @@ test_expect_success '--batch-check without %(rest) considers whole line' ' test_cmp expect actual ' +test_oid_init + tree_sha1=$(git write-tree) -tree_size=33 +tree_size=$(($(test_oid rawsz) + 13)) tree_pretty_content="100644 blob $hello_sha1 hello" run_tests 'tree' $tree_sha1 $tree_size "" "$tree_pretty_content" commit_message="Initial commit" commit_sha1=$(echo_without_newline "$commit_message" | git commit-tree $tree_sha1) -commit_size=177 +commit_size=$(($(test_oid hexsz) + 137)) commit_content="tree $tree_sha1 author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> 0000000000 +0000 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 0000000000 +0000 @@ -218,8 +220,8 @@ test_expect_success "--batch-check for a non-existent hash" ' test "0000000000000000000000000000000000000042 missing 0000000000000000000000000000000000000084 missing" = \ "$( ( echo 0000000000000000000000000000000000000042; - echo_without_newline 0000000000000000000000000000000000000084; ) \ - | git cat-file --batch-check)" + echo_without_newline 0000000000000000000000000000000000000084; ) | + git cat-file --batch-check)" ' test_expect_success "--batch for an existent and a non-existent hash" ' @@ -227,8 +229,8 @@ test_expect_success "--batch for an existent and a non-existent hash" ' $tag_content 0000000000000000000000000000000000000000 missing" = \ "$( ( echo $tag_sha1; - echo_without_newline 0000000000000000000000000000000000000000; ) \ - | git cat-file --batch)" + echo_without_newline 0000000000000000000000000000000000000000; ) | + git cat-file --batch)" ' test_expect_success "--batch-check for an empty line" ' diff --git a/t/t1050-large.sh b/t/t1050-large.sh index 1a9b21b293..dcb4dbba67 100755 --- a/t/t1050-large.sh +++ b/t/t1050-large.sh @@ -8,7 +8,7 @@ test_description='adding and checking out large blobs' # This should be moved to test-lib.sh together with the # copy in t0021 after both topics have graduated to 'master'. file_size () { - perl -e 'print -s $ARGV[0]' "$1" + test-tool path-utils file-size "$1" } test_expect_success setup ' diff --git a/t/t1060-object-corruption.sh b/t/t1060-object-corruption.sh index ac1f189fd2..4feb65157d 100755 --- a/t/t1060-object-corruption.sh +++ b/t/t1060-object-corruption.sh @@ -117,8 +117,10 @@ test_expect_failure 'clone --local detects misnamed objects' ' ' test_expect_success 'fetch into corrupted repo with index-pack' ' + cp -R bit-error bit-error-cp && + test_when_finished "rm -rf bit-error-cp" && ( - cd bit-error && + cd bit-error-cp && test_must_fail git -c transfer.unpackLimit=1 \ fetch ../no-bit-error 2>stderr && test_i18ngrep ! -i collision stderr diff --git a/t/t1090-sparse-checkout-scope.sh b/t/t1090-sparse-checkout-scope.sh index 1f61eb3e88..090b7fc3d3 100755 --- a/t/t1090-sparse-checkout-scope.sh +++ b/t/t1090-sparse-checkout-scope.sh @@ -31,6 +31,20 @@ test_expect_success 'perform sparse checkout of master' ' test_path_is_file c ' +test_expect_success 'checkout -b checkout.optimizeNewBranch interaction' ' + cp .git/info/sparse-checkout .git/info/sparse-checkout.bak && + test_when_finished " + mv -f .git/info/sparse-checkout.bak .git/info/sparse-checkout + git checkout master + " && + echo "/b" >>.git/info/sparse-checkout && + test "$(git ls-files -t b)" = "S b" && + git -c checkout.optimizeNewBranch=true checkout -b fast && + test "$(git ls-files -t b)" = "S b" && + git checkout -b slow && + test "$(git ls-files -t b)" = "H b" +' + test_expect_success 'merge feature branch into sparse checkout of master' ' git merge feature && test_path_is_file a && @@ -49,4 +63,37 @@ test_expect_success 'return to full checkout of master' ' test "$(cat b)" = "modified" ' +test_expect_success 'in partial clone, sparse checkout only fetches needed blobs' ' + test_create_repo server && + git clone "file://$(pwd)/server" client && + + test_config -C server uploadpack.allowfilter 1 && + test_config -C server uploadpack.allowanysha1inwant 1 && + echo a >server/a && + echo bb >server/b && + mkdir server/c && + echo ccc >server/c/c && + git -C server add a b c/c && + git -C server commit -m message && + + test_config -C client core.sparsecheckout 1 && + test_config -C client extensions.partialclone origin && + echo "!/*" >client/.git/info/sparse-checkout && + echo "/a" >>client/.git/info/sparse-checkout && + git -C client fetch --filter=blob:none origin && + git -C client checkout FETCH_HEAD && + + git -C client rev-list HEAD \ + --quiet --objects --missing=print >unsorted_actual && + ( + printf "?" && + git hash-object server/b && + printf "?" && + git hash-object server/c/c + ) >unsorted_expect && + sort unsorted_actual >actual && + sort unsorted_expect >expect && + test_cmp expect actual +' + test_done diff --git a/t/t1300-config.sh b/t/t1300-config.sh index cdf1fed5d1..9652b241c7 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -76,15 +76,11 @@ EOF test_expect_success 'non-match result' 'test_cmp expect .git/config' test_expect_success 'find mixed-case key by canonical name' ' - echo Second >expect && - git config cores.whatever >actual && - test_cmp expect actual + test_cmp_config Second cores.whatever ' test_expect_success 'find mixed-case key by non-canonical name' ' - echo Second >expect && - git config CoReS.WhAtEvEr >actual && - test_cmp expect actual + test_cmp_config Second CoReS.WhAtEvEr ' test_expect_success 'subsections are not canonicalized by git-config' ' @@ -94,12 +90,8 @@ test_expect_success 'subsections are not canonicalized by git-config' ' [section "SubSection"] key = two EOF - echo one >expect && - git config section.subsection.key >actual && - test_cmp expect actual && - echo two >expect && - git config section.SubSection.key >actual && - test_cmp expect actual + test_cmp_config one section.subsection.key && + test_cmp_config two section.SubSection.key ' cat > .git/config <<\EOF @@ -212,9 +204,7 @@ test_expect_success 'really really mean test' ' ' test_expect_success 'get value' ' - echo alpha >expect && - git config beta.haha >actual && - test_cmp expect actual + test_cmp_config alpha beta.haha ' cat > expect << EOF @@ -251,15 +241,11 @@ test_expect_success 'non-match' ' ' test_expect_success 'non-match value' ' - echo wow >expect && - git config --get nextsection.nonewline !for >actual && - test_cmp expect actual + test_cmp_config wow --get nextsection.nonewline !for ' test_expect_success 'multi-valued get returns final one' ' - echo "wow2 for me" >expect && - git config --get nextsection.nonewline >actual && - test_cmp expect actual + test_cmp_config "wow2 for me" --get nextsection.nonewline ' test_expect_success 'multi-valued get-all returns all' ' @@ -520,21 +506,11 @@ test_expect_success 'editing stdin is an error' ' test_expect_success 'refer config from subdirectory' ' mkdir x && - ( - cd x && - echo strasse >expect && - git config --get --file ../other-config ein.bahn >actual && - test_cmp expect actual - ) - + test_cmp_config -C x strasse --get --file ../other-config ein.bahn ' test_expect_success 'refer config from subdirectory via --file' ' - ( - cd x && - git config --file=../other-config --get ein.bahn >actual && - test_cmp expect actual - ) + test_cmp_config -C x strasse --file=../other-config --get ein.bahn ' cat > expect << EOF @@ -688,16 +664,13 @@ test_expect_success numbers ' test_expect_success '--int is at least 64 bits' ' git config giga.watts 121g && - echo 129922760704 >expect && - git config --int --get giga.watts >actual && - test_cmp expect actual + echo >expect && + test_cmp_config 129922760704 --int --get giga.watts ' test_expect_success 'invalid unit' ' git config aninvalid.unit "1auto" && - echo 1auto >expect && - git config aninvalid.unit >actual && - test_cmp expect actual && + test_cmp_config 1auto aninvalid.unit && test_must_fail git config --int --get aninvalid.unit 2>actual && test_i18ngrep "bad numeric config value .1auto. for .aninvalid.unit. in file .git/config: invalid unit" actual ' @@ -1001,7 +974,7 @@ EOF test_expect_success 'value continued on next line' ' git config --list > result && - test_cmp result expect + test_cmp expect result ' cat > .git/config <<\EOF @@ -1039,9 +1012,7 @@ test_expect_success '--null --get-regexp' ' test_expect_success 'inner whitespace kept verbatim' ' git config section.val "foo bar" && - echo "foo bar" >expect && - git config section.val >actual && - test_cmp expect actual + test_cmp_config "foo bar" section.val ' test_expect_success SYMLINKS 'symlinked configuration' ' @@ -1770,8 +1741,9 @@ test_expect_success '--show-origin stdin with file include' ' cat >expect <<-EOF && file:$INCLUDE_DIR/stdin.include include EOF - echo "[include]path=\"$INCLUDE_DIR\"/stdin.include" \ - | git config --show-origin --includes --file - user.stdin >output && + echo "[include]path=\"$INCLUDE_DIR\"/stdin.include" | + git config --show-origin --includes --file - user.stdin >output && + test_cmp expect output ' @@ -1808,21 +1780,15 @@ 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_cmp_config 1048576 --type=int --type=int core.big ' test_expect_success 'identical legacy --type specifiers are allowed' ' - git config --int --int core.big >actual && - echo 1048576 >expect && - test_cmp expect actual + test_cmp_config 1048576 --int --int core.big ' 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_cmp_config 1048576 --int --type=int core.big ' test_expect_success 'non-identical modern --type specifiers are not allowed' ' @@ -1841,21 +1807,15 @@ test_expect_success 'non-identical mixed --type specifiers are not allowed' ' ' test_expect_success '--type allows valid type specifiers' ' - echo "true" >expect && - git config --type=bool core.foo >actual && - test_cmp expect actual + test_cmp_config true --type=bool core.foo ' 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_cmp_config 10 --type=bool --no-type core.number ' 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_cmp_config 1048576 --type=bool --no-type --type=int core.big ' test_expect_success '--type rejects unknown specifiers' ' @@ -1881,7 +1841,7 @@ test_expect_success '--replace-all does not invent newlines' ' Qkey = b EOF git config --replace-all abc.key b && - test_cmp .git/config expect + test_cmp expect .git/config ' test_done diff --git a/t/t1303-wacky-config.sh b/t/t1303-wacky-config.sh index 3b92083e19..0000e664e7 100755 --- a/t/t1303-wacky-config.sh +++ b/t/t1303-wacky-config.sh @@ -14,7 +14,7 @@ setup() { check() { echo "$2" >expected git config --get "$1" >actual 2>&1 - test_cmp actual expected + test_cmp expected actual } # 'check section.key regex value' verifies that the entry for @@ -22,7 +22,7 @@ check() { check_regex() { echo "$3" >expected git config --get "$1" "$2" >actual 2>&1 - test_cmp actual expected + test_cmp expected actual } test_expect_success 'modify same key' ' diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index b72beebe42..1fbd940408 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -346,7 +346,7 @@ test_expect_success "verifying $m's log (logged by config)" ' git update-ref $m $D cat >.git/logs/$m <<EOF -0000000000000000000000000000000000000000 $C $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500 +$Z $C $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500 $C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150350 -0500 $A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500 $F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500 diff --git a/t/t1404-update-ref-errors.sh b/t/t1404-update-ref-errors.sh index 2a42a589a4..51a4f4c0ac 100755 --- a/t/t1404-update-ref-errors.sh +++ b/t/t1404-update-ref-errors.sh @@ -559,9 +559,9 @@ test_expect_success 'no bogus intermediate values during delete' ' { # 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 & + # attempts to acquire the `packed-refs` lock for much longer + # than it takes for us to do the check then delete it: + git -c core.packedrefstimeout=30000 update-ref -d $prefix/foo & } && pid2=$! && # Give update-ref plenty of time to get to the point where it tries diff --git a/t/t1405-main-ref-store.sh b/t/t1405-main-ref-store.sh index a74c38b5fb..331899ddc4 100755 --- a/t/t1405-main-ref-store.sh +++ b/t/t1405-main-ref-store.sh @@ -54,7 +54,7 @@ test_expect_success 'for_each_ref(refs/heads/)' ' ' test_expect_success 'for_each_ref() is sorted' ' - $RUN for-each-ref refs/heads/ | cut -c 42- >actual && + $RUN for-each-ref refs/heads/ | cut -d" " -f 2- >actual && sort actual > expected && test_cmp expected actual ' @@ -71,7 +71,7 @@ test_expect_success 'verify_ref(new-master)' ' ' test_expect_success 'for_each_reflog()' ' - $RUN for-each-reflog | sort -k2 | cut -c 42- >actual && + $RUN for-each-reflog | sort -k2 | cut -d" " -f 2- >actual && cat >expected <<-\EOF && HEAD 0x1 refs/heads/master 0x0 diff --git a/t/t1406-submodule-ref-store.sh b/t/t1406-submodule-ref-store.sh index e093782cc3..d199d872fb 100755 --- a/t/t1406-submodule-ref-store.sh +++ b/t/t1406-submodule-ref-store.sh @@ -39,7 +39,7 @@ test_expect_success 'rename_refs() not allowed' ' ' test_expect_success 'for_each_ref(refs/heads/)' ' - $RUN for-each-ref refs/heads/ | cut -c 42- >actual && + $RUN for-each-ref refs/heads/ | cut -d" " -f 2- >actual && cat >expected <<-\EOF && master 0x0 new-master 0x0 @@ -48,7 +48,7 @@ test_expect_success 'for_each_ref(refs/heads/)' ' ' test_expect_success 'for_each_ref() is sorted' ' - $RUN for-each-ref refs/heads/ | cut -c 42- >actual && + $RUN for-each-ref refs/heads/ | cut -d" " -f 2- >actual && sort actual > expected && test_cmp expected actual ' @@ -65,7 +65,7 @@ test_expect_success 'verify_ref(new-master)' ' ' test_expect_success 'for_each_reflog()' ' - $RUN for-each-reflog | sort | cut -c 42- >actual && + $RUN for-each-reflog | sort | cut -d" " -f 2- >actual && cat >expected <<-\EOF && HEAD 0x1 refs/heads/master 0x0 diff --git a/t/t1407-worktree-ref-store.sh b/t/t1407-worktree-ref-store.sh index 4623ae15c4..9a84858118 100755 --- a/t/t1407-worktree-ref-store.sh +++ b/t/t1407-worktree-ref-store.sh @@ -58,7 +58,7 @@ test_expect_success 'for_each_reflog()' ' 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 && + $RWT for-each-reflog | cut -d" " -f 2- | sort >actual && cat >expected <<-\EOF && HEAD 0x1 PSEUDO-WT 0x0 @@ -68,7 +68,7 @@ test_expect_success 'for_each_reflog()' ' EOF test_cmp expected actual && - $RMAIN for-each-reflog | cut -c 42- | sort >actual && + $RMAIN for-each-reflog | cut -d" " -f 2- | sort >actual && cat >expected <<-\EOF && HEAD 0x1 PSEUDO-MAIN 0x0 diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh index 388b0611d8..ae8a448e34 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh @@ -20,12 +20,12 @@ check_have () { } check_fsck () { - output=$(git fsck --full) + git fsck --full >fsck.output case "$1" in '') - test -z "$output" ;; + test_must_be_empty fsck.output ;; *) - echo "$output" | grep "$1" ;; + test_i18ngrep "$1" fsck.output ;; esac } @@ -368,4 +368,19 @@ test_expect_success 'continue walking past root commits' ' ) ' +test_expect_success 'expire with multiple worktrees' ' + git init main-wt && + ( + cd main-wt && + test_tick && + test_commit foo && + git worktree add link-wt && + test_tick && + test_commit -C link-wt foobar && + test_tick && + git reflog expire --verbose --all --expire=$test_tick && + test_must_be_empty .git/worktrees/link-wt/logs/HEAD + ) +' + test_done diff --git a/t/t1415-worktree-refs.sh b/t/t1415-worktree-refs.sh new file mode 100755 index 0000000000..b664e51250 --- /dev/null +++ b/t/t1415-worktree-refs.sh @@ -0,0 +1,79 @@ +#!/bin/sh + +test_description='per-worktree refs' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit initial && + test_commit wt1 && + test_commit wt2 && + git worktree add wt1 wt1 && + git worktree add wt2 wt2 && + git checkout initial && + git update-ref refs/worktree/foo HEAD && + git -C wt1 update-ref refs/worktree/foo HEAD && + git -C wt2 update-ref refs/worktree/foo HEAD +' + +test_expect_success 'refs/worktree must not be packed' ' + git pack-refs --all && + test_path_is_missing .git/refs/tags/wt1 && + test_path_is_file .git/refs/worktree/foo && + test_path_is_file .git/worktrees/wt1/refs/worktree/foo && + test_path_is_file .git/worktrees/wt2/refs/worktree/foo +' + +test_expect_success 'refs/worktree are per-worktree' ' + test_cmp_rev worktree/foo initial && + ( cd wt1 && test_cmp_rev worktree/foo wt1 ) && + ( cd wt2 && test_cmp_rev worktree/foo wt2 ) +' + +test_expect_success 'resolve main-worktree/HEAD' ' + test_cmp_rev main-worktree/HEAD initial && + ( cd wt1 && test_cmp_rev main-worktree/HEAD initial ) && + ( cd wt2 && test_cmp_rev main-worktree/HEAD initial ) +' + +test_expect_success 'ambiguous main-worktree/HEAD' ' + mkdir -p .git/refs/heads/main-worktree && + test_when_finished rm -f .git/refs/heads/main-worktree/HEAD && + cp .git/HEAD .git/refs/heads/main-worktree/HEAD && + git rev-parse main-worktree/HEAD 2>warn && + grep "main-worktree/HEAD.*ambiguous" warn +' + +test_expect_success 'resolve worktrees/xx/HEAD' ' + test_cmp_rev worktrees/wt1/HEAD wt1 && + ( cd wt1 && test_cmp_rev worktrees/wt1/HEAD wt1 ) && + ( cd wt2 && test_cmp_rev worktrees/wt1/HEAD wt1 ) +' + +test_expect_success 'ambiguous worktrees/xx/HEAD' ' + mkdir -p .git/refs/heads/worktrees/wt1 && + test_when_finished rm -f .git/refs/heads/worktrees/wt1/HEAD && + cp .git/HEAD .git/refs/heads/worktrees/wt1/HEAD && + git rev-parse worktrees/wt1/HEAD 2>warn && + grep "worktrees/wt1/HEAD.*ambiguous" warn +' + +test_expect_success 'reflog of main-worktree/HEAD' ' + git reflog HEAD | sed "s/HEAD/main-worktree\/HEAD/" >expected && + git reflog main-worktree/HEAD >actual && + test_cmp expected actual && + git -C wt1 reflog main-worktree/HEAD >actual.wt1 && + test_cmp expected actual.wt1 +' + +test_expect_success 'reflog of worktrees/xx/HEAD' ' + git -C wt2 reflog HEAD | sed "s/HEAD/worktrees\/wt2\/HEAD/" >expected && + git reflog worktrees/wt2/HEAD >actual && + test_cmp expected actual && + git -C wt1 reflog worktrees/wt2/HEAD >actual.wt1 && + test_cmp expected actual.wt1 && + git -C wt2 reflog worktrees/wt2/HEAD >actual.wt2 && + test_cmp expected actual.wt2 +' + +test_done diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index b5677d26a4..c61f972141 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -70,7 +70,7 @@ test_expect_success 'object with bad sha1' ' test_must_fail git fsck 2>out && cat out && - grep "$sha.*corrupt" out + test_i18ngrep "$sha.*corrupt" out ' test_expect_success 'branch pointing to non-commit' ' @@ -78,7 +78,7 @@ test_expect_success 'branch pointing to non-commit' ' test_when_finished "git update-ref -d refs/heads/invalid" && test_must_fail git fsck 2>out && cat out && - grep "not a commit" out + test_i18ngrep "not a commit" out ' test_expect_success 'HEAD link pointing at a funny object' ' @@ -88,7 +88,7 @@ test_expect_success 'HEAD link pointing at a funny object' ' # avoid corrupt/broken HEAD from interfering with repo discovery test_must_fail env GIT_DIR=.git git fsck 2>out && cat out && - grep "detached HEAD points" out + test_i18ngrep "detached HEAD points" out ' test_expect_success 'HEAD link pointing at a funny place' ' @@ -98,7 +98,42 @@ test_expect_success 'HEAD link pointing at a funny place' ' # avoid corrupt/broken HEAD from interfering with repo discovery test_must_fail env GIT_DIR=.git git fsck 2>out && cat out && - grep "HEAD points to something strange" out + test_i18ngrep "HEAD points to something strange" out +' + +test_expect_success 'HEAD link pointing at a funny object (from different wt)' ' + test_when_finished "mv .git/SAVED_HEAD .git/HEAD" && + test_when_finished "rm -rf .git/worktrees wt" && + git worktree add wt && + mv .git/HEAD .git/SAVED_HEAD && + echo $ZERO_OID >.git/HEAD && + # avoid corrupt/broken HEAD from interfering with repo discovery + test_must_fail git -C wt fsck 2>out && + test_i18ngrep "main-worktree/HEAD: detached HEAD points" out +' + +test_expect_success 'other worktree HEAD link pointing at a funny object' ' + test_when_finished "rm -rf .git/worktrees other" && + git worktree add other && + echo $ZERO_OID >.git/worktrees/other/HEAD && + test_must_fail git fsck 2>out && + test_i18ngrep "worktrees/other/HEAD: detached HEAD points" out +' + +test_expect_success 'other worktree HEAD link pointing at missing object' ' + test_when_finished "rm -rf .git/worktrees other" && + git worktree add other && + echo "Contents missing from repo" | git hash-object --stdin >.git/worktrees/other/HEAD && + test_must_fail git fsck 2>out && + test_i18ngrep "worktrees/other/HEAD: invalid sha1 pointer" out +' + +test_expect_success 'other worktree HEAD link pointing at a funny place' ' + test_when_finished "rm -rf .git/worktrees other" && + git worktree add other && + echo "ref: refs/funny/place" >.git/worktrees/other/HEAD && + test_must_fail git fsck 2>out && + test_i18ngrep "worktrees/other/HEAD points to something strange" out ' test_expect_success 'email without @ is okay' ' @@ -122,7 +157,7 @@ test_expect_success 'email with embedded > is not okay' ' test_when_finished "git update-ref -d refs/heads/bogus" && test_must_fail git fsck 2>out && cat out && - grep "error in commit $new" out + test_i18ngrep "error in commit $new" out ' test_expect_success 'missing < email delimiter is reported nicely' ' @@ -134,7 +169,7 @@ test_expect_success 'missing < email delimiter is reported nicely' ' test_when_finished "git update-ref -d refs/heads/bogus" && test_must_fail git fsck 2>out && cat out && - grep "error in commit $new.* - bad name" out + test_i18ngrep "error in commit $new.* - bad name" out ' test_expect_success 'missing email is reported nicely' ' @@ -146,7 +181,7 @@ test_expect_success 'missing email is reported nicely' ' test_when_finished "git update-ref -d refs/heads/bogus" && test_must_fail git fsck 2>out && cat out && - grep "error in commit $new.* - missing email" out + test_i18ngrep "error in commit $new.* - missing email" out ' test_expect_success '> in name is reported' ' @@ -158,7 +193,7 @@ test_expect_success '> in name is reported' ' test_when_finished "git update-ref -d refs/heads/bogus" && test_must_fail git fsck 2>out && cat out && - grep "error in commit $new" out + test_i18ngrep "error in commit $new" out ' # date is 2^64 + 1 @@ -172,7 +207,7 @@ test_expect_success 'integer overflow in timestamps is reported' ' test_when_finished "git update-ref -d refs/heads/bogus" && test_must_fail git fsck 2>out && cat out && - grep "error in commit $new.*integer overflow" out + test_i18ngrep "error in commit $new.*integer overflow" out ' test_expect_success 'commit with NUL in header' ' @@ -184,7 +219,7 @@ test_expect_success 'commit with NUL in header' ' test_when_finished "git update-ref -d refs/heads/bogus" && test_must_fail git fsck 2>out && cat out && - grep "error in commit $new.*unterminated header: NUL at offset" out + test_i18ngrep "error in commit $new.*unterminated header: NUL at offset" out ' test_expect_success 'tree object with duplicate entries' ' @@ -205,7 +240,7 @@ test_expect_success 'tree object with duplicate entries' ' git hash-object -w -t tree --stdin ) && test_must_fail git fsck 2>out && - grep "error in tree .*contains duplicate file entries" out + test_i18ngrep "error in tree .*contains duplicate file entries" out ' test_expect_success 'unparseable tree object' ' @@ -259,7 +294,7 @@ test_expect_success 'tag pointing to nonexistent' ' test_when_finished "git update-ref -d refs/tags/invalid" && test_must_fail git fsck --tags >out && cat out && - grep "broken link" out + test_i18ngrep "broken link" out ' test_expect_success 'tag pointing to something else than its type' ' @@ -301,7 +336,7 @@ test_expect_success 'tag with incorrect tag name & missing tagger' ' warning in tag $tag: badTagName: invalid '\''tag'\'' name: wrong name format warning in tag $tag: missingTaggerEntry: invalid format - expected '\''tagger'\'' line EOF - test_cmp expect out + test_i18ncmp expect out ' test_expect_success 'tag with bad tagger' ' @@ -320,7 +355,7 @@ test_expect_success 'tag with bad tagger' ' echo $tag >.git/refs/tags/wrong && test_when_finished "git update-ref -d refs/tags/wrong" && test_must_fail git fsck --tags 2>out && - grep "error in tag .*: invalid author/committer" out + test_i18ngrep "error in tag .*: invalid author/committer" out ' test_expect_success 'tag with NUL in header' ' @@ -340,7 +375,7 @@ test_expect_success 'tag with NUL in header' ' test_when_finished "git update-ref -d refs/tags/wrong" && test_must_fail git fsck --tags 2>out && cat out && - grep "error in tag $tag.*unterminated header: NUL at offset" out + test_i18ngrep "error in tag $tag.*unterminated header: NUL at offset" out ' test_expect_success 'cleaned up' ' @@ -371,7 +406,7 @@ test_expect_success 'rev-list --verify-objects with bad sha1' ' test_might_fail git rev-list --verify-objects refs/heads/bogus >/dev/null 2>out && cat out && - test_i18ngrep -q "error: sha1 mismatch 63ffffffffffffffffffffffffffffffffffffff" out + test_i18ngrep -q "error: hash mismatch 63ffffffffffffffffffffffffffffffffffffff" out ' test_expect_success 'force fsck to ignore double author' ' @@ -396,7 +431,7 @@ test_expect_success 'fsck notices blob entry pointing to null sha1' ' git hash-object -w --stdin -t tree) && git fsck 2>out && cat out && - grep "warning.*null sha1" out + test_i18ngrep "warning.*null sha1" out ) ' @@ -407,7 +442,7 @@ test_expect_success 'fsck notices submodule entry pointing to null sha1' ' git hash-object -w --stdin -t tree) && git fsck 2>out && cat out && - grep "warning.*null sha1" out + test_i18ngrep "warning.*null sha1" out ) ' @@ -428,7 +463,7 @@ while read name path pretty; do bad_tree=$(git mktree <bad) && git fsck 2>out && cat out && - grep "warning.*tree $bad_tree" out + test_i18ngrep "warning.*tree $bad_tree" out )' done <<-\EOF 100644 blob @@ -474,9 +509,9 @@ test_expect_success 'NUL in commit' ' git branch bad $(cat name) && test_must_fail git -c fsck.nulInCommit=error fsck 2>warn.1 && - grep nulInCommit warn.1 && + test_i18ngrep nulInCommit warn.1 && git fsck 2>warn.2 && - grep nulInCommit warn.2 + test_i18ngrep nulInCommit warn.2 ) ' @@ -594,7 +629,7 @@ test_expect_success 'fsck --name-objects' ' remove_object $(git rev-parse julius:caesar.t) && test_must_fail git fsck --name-objects >out && tree=$(git rev-parse --verify julius:) && - egrep "$tree \((refs/heads/master|HEAD)@\{[0-9]*\}:" out + test_i18ngrep -E "$tree \((refs/heads/master|HEAD)@\{[0-9]*\}:" out ) ' @@ -605,7 +640,7 @@ test_expect_success 'alternate objects are correctly blamed' ' mkdir alt.git/objects/12 && >alt.git/objects/12/34567890123456789012345678901234567890 && test_must_fail git fsck >out 2>&1 && - grep alt.git out + test_i18ngrep alt.git out ' test_expect_success 'fsck errors in packed objects' ' @@ -624,8 +659,8 @@ test_expect_success 'fsck errors in packed objects' ' 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 && + test_i18ngrep "error in commit $one.* - bad name" out && + test_i18ngrep "error in commit $two.* - bad name" out && ! grep corrupt out ' @@ -725,7 +760,7 @@ test_expect_success 'fsck notices dangling objects' ' 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_i18ncmp expect actual.sorted ) ' @@ -773,7 +808,7 @@ test_expect_success 'detect corrupt index file in fsck' ' 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_i18ngrep "bad index file" errors ' test_done diff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh index e4d5b56014..c19fb500cb 100755 --- a/t/t1512-rev-parse-disambiguation.sh +++ b/t/t1512-rev-parse-disambiguation.sh @@ -388,4 +388,14 @@ test_expect_success C_LOCALE_OUTPUT 'ambiguous commits are printed by type first done ' +test_expect_success 'cat-file --batch and --batch-check show ambiguous' ' + echo "0000 ambiguous" >expect && + echo 0000 | git cat-file --batch-check >actual 2>err && + test_cmp expect actual && + test_i18ngrep hint: err && + echo 0000 | git cat-file --batch >actual 2>err && + test_cmp expect actual && + test_i18ngrep hint: err +' + test_done diff --git a/t/t1700-split-index.sh b/t/t1700-split-index.sh index f053bf83eb..4667e1a190 100755 --- a/t/t1700-split-index.sh +++ b/t/t1700-split-index.sh @@ -6,10 +6,12 @@ test_description='split index mode tests' # We need total control of index splitting here sane_unset GIT_TEST_SPLIT_INDEX -# A couple of tests expect the index to have a specific checksum, -# but the presence of the optional FSMN extension would interfere -# with those checks, so disable it in this test script. -sane_unset GIT_FSMONITOR_TEST + +# Testing a hard coded SHA against an index with an extension +# that can vary from run to run is problematic so we disable +# those extensions. +sane_unset GIT_TEST_FSMONITOR +sane_unset GIT_TEST_INDEX_THREADS # Create a file named as $1 with content read from stdin. # Set the file's mtime to a few seconds in the past to avoid racy situations. @@ -23,6 +25,8 @@ test_expect_success 'enable split index' ' git update-index --split-index && test-tool dump-split-index .git/index >actual && indexversion=$(test-tool index-version <.git/index) && + + # NEEDSWORK: Stop hard-coding checksums. if test "$indexversion" = "4" then own=432ef4b63f32193984f339431fd50ca796493569 @@ -31,6 +35,7 @@ test_expect_success 'enable split index' ' own=8299b0bcd1ac364e5f1d7768efb62fa2da79a339 base=39d890139ee5356c7ef572216cebcd27aa41f9df fi && + cat >expect <<-EOF && own $own base $base @@ -379,6 +384,26 @@ test_expect_success 'check splitIndex.sharedIndexExpire set to "never" and "now" test $(ls .git/sharedindex.* | wc -l) -le 2 ' +test_expect_success POSIXPERM 'same mode for index & split index' ' + git init same-mode && + ( + cd same-mode && + test_commit A && + test_modebits .git/index >index_mode && + test_must_fail git config core.sharedRepository && + git -c core.splitIndex=true status && + shared=$(ls .git/sharedindex.*) && + case "$shared" in + *" "*) + # we have more than one??? + false ;; + *) + test_modebits "$shared" >split_index_mode && + test_cmp index_mode split_index_mode ;; + esac + ) +' + while read -r mode modebits do test_expect_success POSIXPERM "split index respects core.sharedrepository $mode" ' diff --git a/t/t2000-checkout-cache-clash.sh b/t/t2000-checkout-cache-clash.sh deleted file mode 100755 index de3edb5d57..0000000000 --- a/t/t2000-checkout-cache-clash.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2005 Junio C Hamano -# - -test_description='git checkout-index test. - -This test registers the following filesystem structure in the -cache: - - path0 - a file - path1/file1 - a file in a directory - -And then tries to checkout in a work tree that has the following: - - path0/file0 - a file in a directory - path1 - a file - -The git checkout-index command should fail when attempting to checkout -path0, finding it is occupied by a directory, and path1/file1, finding -path1 is occupied by a non-directory. With "-f" flag, it should remove -the conflicting paths and succeed. -' -. ./test-lib.sh - -date >path0 -mkdir path1 -date >path1/file1 - -test_expect_success \ - 'git update-index --add various paths.' \ - 'git update-index --add path0 path1/file1' - -rm -fr path0 path1 -mkdir path0 -date >path0/file0 -date >path1 - -test_expect_success \ - 'git checkout-index without -f should fail on conflicting work tree.' \ - 'test_must_fail git checkout-index -a' - -test_expect_success \ - 'git checkout-index with -f should succeed.' \ - 'git checkout-index -f -a' - -test_expect_success \ - 'git checkout-index conflicting paths.' \ - 'test -f path0 && test -d path1 && test -f path1/file1' - -test_expect_success SYMLINKS 'checkout-index -f twice with --prefix' ' - mkdir -p tar/get && - ln -s tar/get there && - echo first && - git checkout-index -a -f --prefix=there/ && - echo second && - git checkout-index -a -f --prefix=there/ -' - -test_done diff --git a/t/t2000-conflict-when-checking-files-out.sh b/t/t2000-conflict-when-checking-files-out.sh new file mode 100755 index 0000000000..f18616ad2b --- /dev/null +++ b/t/t2000-conflict-when-checking-files-out.sh @@ -0,0 +1,135 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# + +test_description='git conflicts when checking files out test.' + +# The first test registers the following filesystem structure in the +# cache: +# +# path0 - a file +# path1/file1 - a file in a directory +# +# And then tries to checkout in a work tree that has the following: +# +# path0/file0 - a file in a directory +# path1 - a file +# +# The git checkout-index command should fail when attempting to checkout +# path0, finding it is occupied by a directory, and path1/file1, finding +# path1 is occupied by a non-directory. With "-f" flag, it should remove +# the conflicting paths and succeed. + +. ./test-lib.sh + +show_files() { + # show filesystem files, just [-dl] for type and name + find path? -ls | + sed -e 's/^[0-9]* * [0-9]* * \([-bcdl]\)[^ ]* *[0-9]* *[^ ]* *[^ ]* *[0-9]* [A-Z][a-z][a-z] [0-9][0-9] [^ ]* /fs: \1 /' + # what's in the cache, just mode and name + git ls-files --stage | + sed -e 's/^\([0-9]*\) [0-9a-f]* [0-3] /ca: \1 /' + # what's in the tree, just mode and name. + git ls-tree -r "$1" | + sed -e 's/^\([0-9]*\) [^ ]* [0-9a-f]* /tr: \1 /' +} + +date >path0 +mkdir path1 +date >path1/file1 + +test_expect_success \ + 'git update-index --add various paths.' \ + 'git update-index --add path0 path1/file1' + +rm -fr path0 path1 +mkdir path0 +date >path0/file0 +date >path1 + +test_expect_success \ + 'git checkout-index without -f should fail on conflicting work tree.' \ + 'test_must_fail git checkout-index -a' + +test_expect_success \ + 'git checkout-index with -f should succeed.' \ + 'git checkout-index -f -a' + +test_expect_success \ + 'git checkout-index conflicting paths.' \ + 'test -f path0 && test -d path1 && test -f path1/file1' + +test_expect_success SYMLINKS 'checkout-index -f twice with --prefix' ' + mkdir -p tar/get && + ln -s tar/get there && + echo first && + git checkout-index -a -f --prefix=there/ && + echo second && + git checkout-index -a -f --prefix=there/ +' + +# The second test registers the following filesystem structure in the cache: +# +# path2/file0 - a file in a directory +# path3/file1 - a file in a directory +# +# and attempts to check it out when the work tree has: +# +# path2/file0 - a file in a directory +# path3 - a symlink pointing at "path2" +# +# Checkout cache should fail to extract path3/file1 because the leading +# path path3 is occupied by a non-directory. With "-f" it should remove +# the symlink path3 and create directory path3 and file path3/file1. + +mkdir path2 +date >path2/file0 +test_expect_success \ + 'git update-index --add path2/file0' \ + 'git update-index --add path2/file0' +test_expect_success \ + 'writing tree out with git write-tree' \ + 'tree1=$(git write-tree)' +test_debug 'show_files $tree1' + +mkdir path3 +date >path3/file1 +test_expect_success \ + 'git update-index --add path3/file1' \ + 'git update-index --add path3/file1' +test_expect_success \ + 'writing tree out with git write-tree' \ + 'tree2=$(git write-tree)' +test_debug 'show_files $tree2' + +rm -fr path3 +test_expect_success \ + 'read previously written tree and checkout.' \ + 'git read-tree -m $tree1 && git checkout-index -f -a' +test_debug 'show_files $tree1' + +test_expect_success \ + 'add a symlink' \ + 'test_ln_s_add path2 path3' +test_expect_success \ + 'writing tree out with git write-tree' \ + 'tree3=$(git write-tree)' +test_debug 'show_files $tree3' + +# Morten says "Got that?" here. +# Test begins. + +test_expect_success \ + 'read previously written tree and checkout.' \ + 'git read-tree $tree2 && git checkout-index -f -a' +test_debug 'show_files $tree2' + +test_expect_success \ + 'checking out conflicting path with -f' \ + 'test ! -h path2 && test -d path2 && + test ! -h path3 && test -d path3 && + test ! -h path2/file0 && test -f path2/file0 && + test ! -h path3/file1 && test -f path3/file1' + +test_done diff --git a/t/t2001-checkout-cache-clash.sh b/t/t2001-checkout-cache-clash.sh deleted file mode 100755 index 1fc8e634b7..0000000000 --- a/t/t2001-checkout-cache-clash.sh +++ /dev/null @@ -1,85 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2005 Junio C Hamano -# - -test_description='git checkout-index test. - -This test registers the following filesystem structure in the cache: - - path0/file0 - a file in a directory - path1/file1 - a file in a directory - -and attempts to check it out when the work tree has: - - path0/file0 - a file in a directory - path1 - a symlink pointing at "path0" - -Checkout cache should fail to extract path1/file1 because the leading -path path1 is occupied by a non-directory. With "-f" it should remove -the symlink path1 and create directory path1 and file path1/file1. -' -. ./test-lib.sh - -show_files() { - # show filesystem files, just [-dl] for type and name - find path? -ls | - sed -e 's/^[0-9]* * [0-9]* * \([-bcdl]\)[^ ]* *[0-9]* *[^ ]* *[^ ]* *[0-9]* [A-Z][a-z][a-z] [0-9][0-9] [^ ]* /fs: \1 /' - # what's in the cache, just mode and name - git ls-files --stage | - sed -e 's/^\([0-9]*\) [0-9a-f]* [0-3] /ca: \1 /' - # what's in the tree, just mode and name. - git ls-tree -r "$1" | - sed -e 's/^\([0-9]*\) [^ ]* [0-9a-f]* /tr: \1 /' -} - -mkdir path0 -date >path0/file0 -test_expect_success \ - 'git update-index --add path0/file0' \ - 'git update-index --add path0/file0' -test_expect_success \ - 'writing tree out with git write-tree' \ - 'tree1=$(git write-tree)' -test_debug 'show_files $tree1' - -mkdir path1 -date >path1/file1 -test_expect_success \ - 'git update-index --add path1/file1' \ - 'git update-index --add path1/file1' -test_expect_success \ - 'writing tree out with git write-tree' \ - 'tree2=$(git write-tree)' -test_debug 'show_files $tree2' - -rm -fr path1 -test_expect_success \ - 'read previously written tree and checkout.' \ - 'git read-tree -m $tree1 && git checkout-index -f -a' -test_debug 'show_files $tree1' - -test_expect_success \ - 'add a symlink' \ - 'test_ln_s_add path0 path1' -test_expect_success \ - 'writing tree out with git write-tree' \ - 'tree3=$(git write-tree)' -test_debug 'show_files $tree3' - -# Morten says "Got that?" here. -# Test begins. - -test_expect_success \ - 'read previously written tree and checkout.' \ - 'git read-tree $tree2 && git checkout-index -f -a' -test_debug 'show_files $tree2' - -test_expect_success \ - 'checking out conflicting path with -f' \ - 'test ! -h path0 && test -d path0 && - test ! -h path1 && test -d path1 && - test ! -h path0/file0 && test -f path0/file0 && - test ! -h path1/file1 && test -f path1/file1' - -test_done diff --git a/t/t2018-checkout-branch.sh b/t/t2018-checkout-branch.sh index 2131fb2a56..c5014ad9a6 100755 --- a/t/t2018-checkout-branch.sh +++ b/t/t2018-checkout-branch.sh @@ -198,4 +198,13 @@ test_expect_success 'checkout -B to the current branch works' ' test_dirty_mergeable ' +test_expect_success 'checkout -b after clone --no-checkout does a checkout of HEAD' ' + git init src && + test_commit -C src a && + rev="$(git -C src rev-parse HEAD)" && + git clone --no-checkout src dest && + git -C dest checkout "$rev" -b branch && + test_path_is_file dest/a.t +' + test_done diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh index 69b6774d10..fa0718c730 100755 --- a/t/t2024-checkout-dwim.sh +++ b/t/t2024-checkout-dwim.sh @@ -278,4 +278,35 @@ test_expect_success 'loosely defined local base branch is reported correctly' ' test_cmp expect actual ' +test_expect_success 'reject when arg could be part of dwim branch' ' + git remote add foo file://non-existent-place && + git update-ref refs/remotes/foo/dwim-arg HEAD && + echo foo >dwim-arg && + git add dwim-arg && + echo bar >dwim-arg && + test_must_fail git checkout dwim-arg && + test_must_fail git rev-parse refs/heads/dwim-arg -- && + grep bar dwim-arg +' + +test_expect_success 'disambiguate dwim branch and checkout path (1)' ' + git update-ref refs/remotes/foo/dwim-arg1 HEAD && + echo foo >dwim-arg1 && + git add dwim-arg1 && + echo bar >dwim-arg1 && + git checkout -- dwim-arg1 && + test_must_fail git rev-parse refs/heads/dwim-arg1 -- && + grep foo dwim-arg1 +' + +test_expect_success 'disambiguate dwim branch and checkout path (2)' ' + git update-ref refs/remotes/foo/dwim-arg2 HEAD && + echo foo >dwim-arg2 && + git add dwim-arg2 && + echo bar >dwim-arg2 && + git checkout dwim-arg2 -- && + git rev-parse refs/heads/dwim-arg2 -- && + grep bar dwim-arg2 +' + test_done diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh index 07d292317c..286bba35d8 100755 --- a/t/t2025-worktree-add.sh +++ b/t/t2025-worktree-add.sh @@ -552,4 +552,22 @@ test_expect_success '"add" in bare repo invokes post-checkout hook' ' test_cmp hook.expect goozy/hook.actual ' +test_expect_success '"add" an existing but missing worktree' ' + git worktree add --detach pneu && + test_must_fail git worktree add --detach pneu && + rm -fr pneu && + test_must_fail git worktree add --detach pneu && + git worktree add --force --detach pneu +' + +test_expect_success '"add" an existing locked but missing worktree' ' + git worktree add --detach gnoo && + git worktree lock gnoo && + test_when_finished "git worktree unlock gnoo || :" && + rm -fr gnoo && + test_must_fail git worktree add --detach gnoo && + test_must_fail git worktree add --force --detach gnoo && + git worktree add --force --force --detach gnoo +' + test_done diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh index 5f7d45b7b7..939d18d728 100755 --- a/t/t2028-worktree-move.sh +++ b/t/t2028-worktree-move.sh @@ -98,6 +98,40 @@ test_expect_success 'move worktree to another dir' ' test_cmp expected2 actual2 ' +test_expect_success 'move locked worktree (force)' ' + test_when_finished " + git worktree unlock flump || : + git worktree remove flump || : + git worktree unlock ploof || : + git worktree remove ploof || : + " && + git worktree add --detach flump && + git worktree lock flump && + test_must_fail git worktree move flump ploof" && + test_must_fail git worktree move --force flump ploof" && + git worktree move --force --force flump ploof +' + +test_expect_success 'move a repo with uninitialized submodule' ' + git init withsub && + ( + cd withsub && + test_commit initial && + git submodule add "$PWD"/.git sub && + git commit -m withsub && + git worktree add second HEAD && + git worktree move second third + ) +' + +test_expect_success 'not move a repo with initialized submodule' ' + ( + cd withsub && + git -C third submodule update && + test_must_fail git worktree move third forth + ) +' + test_expect_success 'remove main worktree' ' test_must_fail git worktree remove . ' @@ -141,4 +175,51 @@ test_expect_success 'NOT remove missing-but-locked worktree' ' test_path_is_dir .git/worktrees/gone-but-locked ' +test_expect_success 'proper error when worktree not found' ' + for i in noodle noodle/bork + do + test_must_fail git worktree lock $i 2>err && + test_i18ngrep "not a working tree" err || return 1 + done +' + +test_expect_success 'remove locked worktree (force)' ' + git worktree add --detach gumby && + test_when_finished "git worktree remove gumby || :" && + git worktree lock gumby && + test_when_finished "git worktree unlock gumby || :" && + test_must_fail git worktree remove gumby && + test_must_fail git worktree remove --force gumby && + git worktree remove --force --force gumby +' + +test_expect_success 'remove cleans up .git/worktrees when empty' ' + git init moog && + ( + cd moog && + test_commit bim && + git worktree add --detach goom && + test_path_exists .git/worktrees && + git worktree remove goom && + test_path_is_missing .git/worktrees + ) +' + +test_expect_success 'remove a repo with uninitialized submodule' ' + ( + cd withsub && + git worktree add to-remove HEAD && + git worktree remove to-remove + ) +' + +test_expect_success 'not remove a repo with initialized submodule' ' + ( + cd withsub && + git worktree add to-remove HEAD && + git -C to-remove submodule update && + test_must_fail git worktree remove to-remove + ) +' + test_done diff --git a/t/t2029-worktree-config.sh b/t/t2029-worktree-config.sh new file mode 100755 index 0000000000..286121d8de --- /dev/null +++ b/t/t2029-worktree-config.sh @@ -0,0 +1,79 @@ +#!/bin/sh + +test_description="config file in multi worktree" + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit start +' + +test_expect_success 'config --worktree in single worktree' ' + git config --worktree foo.bar true && + test_cmp_config true foo.bar +' + +test_expect_success 'add worktrees' ' + git worktree add wt1 && + git worktree add wt2 +' + +test_expect_success 'config --worktree without extension' ' + test_must_fail git config --worktree foo.bar false +' + +test_expect_success 'enable worktreeConfig extension' ' + git config extensions.worktreeConfig true && + test_cmp_config true extensions.worktreeConfig +' + +test_expect_success 'config is shared as before' ' + git config this.is shared && + test_cmp_config shared this.is && + test_cmp_config -C wt1 shared this.is && + test_cmp_config -C wt2 shared this.is +' + +test_expect_success 'config is shared (set from another worktree)' ' + git -C wt1 config that.is also-shared && + test_cmp_config also-shared that.is && + test_cmp_config -C wt1 also-shared that.is && + test_cmp_config -C wt2 also-shared that.is +' + +test_expect_success 'config private to main worktree' ' + git config --worktree this.is for-main && + test_cmp_config for-main this.is && + test_cmp_config -C wt1 shared this.is && + test_cmp_config -C wt2 shared this.is +' + +test_expect_success 'config private to linked worktree' ' + git -C wt1 config --worktree this.is for-wt1 && + test_cmp_config for-main this.is && + test_cmp_config -C wt1 for-wt1 this.is && + test_cmp_config -C wt2 shared this.is +' + +test_expect_success 'core.bare no longer for main only' ' + test_config core.bare true && + test "$(git rev-parse --is-bare-repository)" = true && + test "$(git -C wt1 rev-parse --is-bare-repository)" = true && + test "$(git -C wt2 rev-parse --is-bare-repository)" = true +' + +test_expect_success 'per-worktree core.bare is picked up' ' + git -C wt1 config --worktree core.bare true && + test "$(git rev-parse --is-bare-repository)" = false && + test "$(git -C wt1 rev-parse --is-bare-repository)" = true && + test "$(git -C wt2 rev-parse --is-bare-repository)" = false +' + +test_expect_success 'config.worktree no longer read without extension' ' + git config --unset extensions.worktreeConfig && + test_cmp_config shared this.is && + test_cmp_config -C wt1 shared this.is && + test_cmp_config -C wt2 shared this.is +' + +test_done diff --git a/t/t2101-update-index-reupdate.sh b/t/t2101-update-index-reupdate.sh index 685ec45639..6c32d42c8c 100755 --- a/t/t2101-update-index-reupdate.sh +++ b/t/t2101-update-index-reupdate.sh @@ -73,7 +73,7 @@ test_expect_success 'update-index --update from subdir' ' 100644 $(git hash-object dir1/file3) 0 dir1/file3 100644 $file2 0 file2 EOF - test_cmp current expected + test_cmp expected current ' test_expect_success 'update-index --update with pathspec' ' diff --git a/t/t3070-wildmatch.sh b/t/t3070-wildmatch.sh index 46aca0af10..891d4d7cb9 100755 --- a/t/t3070-wildmatch.sh +++ b/t/t3070-wildmatch.sh @@ -237,7 +237,7 @@ 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 1 1 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' @@ -253,7 +253,7 @@ match 1 1 1 1 ']' ']' # Extended slash-matching features 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 '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' diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 93f21ab078..478b82cf9b 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -1221,7 +1221,7 @@ test_expect_success 'use --edit-description' ' EOF EDITOR=./editor git branch --edit-description && echo "New contents" >expect && - test_cmp EDITOR_OUTPUT expect + test_cmp expect EDITOR_OUTPUT ' test_expect_success 'detect typo in branch name when using --edit-description' ' diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh index fb4c13a84a..048feaf6dd 100755 --- a/t/t3206-range-diff.sh +++ b/t/t3206-range-diff.sh @@ -122,6 +122,70 @@ test_expect_success 'changed commit' ' test_cmp expected actual ' +test_expect_success 'changed commit with --no-patch diff option' ' + git range-diff --no-color --no-patch topic...changed >actual && + cat >expected <<-EOF && + 1: 4de457d = 1: a4b3333 s/5/A/ + 2: fccce22 = 2: f51d370 s/4/A/ + 3: 147e64e ! 3: 0559556 s/11/B/ + 4: a63e992 ! 4: d966c5c s/12/B/ + EOF + test_cmp expected actual +' + +test_expect_success 'changed commit with --stat diff option' ' + git range-diff --no-color --stat topic...changed >actual && + cat >expected <<-EOF && + 1: 4de457d = 1: a4b3333 s/5/A/ + a => b | 0 + 1 file changed, 0 insertions(+), 0 deletions(-) + 2: fccce22 = 2: f51d370 s/4/A/ + a => b | 0 + 1 file changed, 0 insertions(+), 0 deletions(-) + 3: 147e64e ! 3: 0559556 s/11/B/ + a => b | 0 + 1 file changed, 0 insertions(+), 0 deletions(-) + 4: a63e992 ! 4: d966c5c s/12/B/ + a => b | 0 + 1 file changed, 0 insertions(+), 0 deletions(-) + EOF + test_cmp expected actual +' + +test_expect_success 'changed commit with sm config' ' + git range-diff --no-color --submodule=log topic...changed >actual && + cat >expected <<-EOF && + 1: 4de457d = 1: a4b3333 s/5/A/ + 2: fccce22 = 2: f51d370 s/4/A/ + 3: 147e64e ! 3: 0559556 s/11/B/ + @@ -10,7 +10,7 @@ + 9 + 10 + -11 + -+B + ++BB + 12 + 13 + 14 + 4: a63e992 ! 4: d966c5c s/12/B/ + @@ -8,7 +8,7 @@ + @@ + 9 + 10 + - B + + BB + -12 + +B + 13 + EOF + test_cmp expected actual +' + +test_expect_success 'no commits on one side' ' + git commit --amend -m "new message" && + git range-diff master HEAD@{1} HEAD +' + test_expect_success 'changed message' ' git range-diff --no-color topic...changed-message >actual && sed s/Z/\ /g >expected <<-EOF && @@ -133,18 +197,75 @@ test_expect_success 'changed message' ' Z + Also a silly comment here! + - Zdiff --git a/file b/file - Z--- a/file - Z+++ b/file + Z diff --git a/file b/file + Z --- a/file + Z +++ b/file 3: 147e64e = 3: b9cb956 s/11/B/ 4: a63e992 = 4: 8add5f1 s/12/B/ EOF test_cmp expected actual ' -test_expect_success 'no commits on one side' ' - git commit --amend -m "new message" && - git range-diff master HEAD@{1} HEAD +test_expect_success 'dual-coloring' ' + sed -e "s|^:||" >expect <<-\EOF && + :<YELLOW>1: a4b3333 = 1: f686024 s/5/A/<RESET> + :<RED>2: f51d370 <RESET><YELLOW>!<RESET><GREEN> 2: 4ab067d<RESET><YELLOW> s/4/A/<RESET> + : <REVERSE><CYAN>@@ -2,6 +2,8 @@<RESET> + : <RESET> + : s/4/A/<RESET> + : <RESET> + : <REVERSE><GREEN>+<RESET><BOLD> Also a silly comment here!<RESET> + : <REVERSE><GREEN>+<RESET> + : diff --git a/file b/file<RESET> + : --- a/file<RESET> + : +++ b/file<RESET> + :<RED>3: 0559556 <RESET><YELLOW>!<RESET><GREEN> 3: b9cb956<RESET><YELLOW> s/11/B/<RESET> + : <REVERSE><CYAN>@@ -10,7 +10,7 @@<RESET> + : 9<RESET> + : 10<RESET> + : <RED> -11<RESET> + : <REVERSE><RED>-<RESET><FAINT;GREEN>+BB<RESET> + : <REVERSE><GREEN>+<RESET><BOLD;GREEN>+B<RESET> + : 12<RESET> + : 13<RESET> + : 14<RESET> + :<RED>4: d966c5c <RESET><YELLOW>!<RESET><GREEN> 4: 8add5f1<RESET><YELLOW> s/12/B/<RESET> + : <REVERSE><CYAN>@@ -8,7 +8,7 @@<RESET> + : <CYAN> @@<RESET> + : 9<RESET> + : 10<RESET> + : <REVERSE><RED>-<RESET><FAINT> BB<RESET> + : <REVERSE><GREEN>+<RESET><BOLD> B<RESET> + : <RED> -12<RESET> + : <GREEN> +B<RESET> + : 13<RESET> + EOF + git range-diff changed...changed-message --color --dual-color >actual.raw && + test_decode_color >actual <actual.raw && + test_cmp expect actual +' + +for prev in topic master..topic +do + test_expect_success "format-patch --range-diff=$prev" ' + git format-patch --cover-letter --range-diff=$prev \ + master..unmodified >actual && + test_when_finished "rm 000?-*" && + test_line_count = 5 actual && + test_i18ngrep "^Range-diff:$" 0000-* && + grep "= 1: .* s/5/A" 0000-* && + grep "= 2: .* s/4/A" 0000-* && + grep "= 3: .* s/11/B" 0000-* && + grep "= 4: .* s/12/B" 0000-* + ' +done + +test_expect_success 'format-patch --range-diff as commentary' ' + git format-patch --range-diff=HEAD~1 HEAD~1 >actual && + test_when_finished "rm 0001-*" && + test_line_count = 1 actual && + test_i18ngrep "^Range-diff:$" 0001-* && + grep "> 1: .* new message" 0001-* ' test_done diff --git a/t/t3320-notes-merge-worktrees.sh b/t/t3320-notes-merge-worktrees.sh index 10bfc8b947..823fdbda1f 100755 --- a/t/t3320-notes-merge-worktrees.sh +++ b/t/t3320-notes-merge-worktrees.sh @@ -44,7 +44,7 @@ test_expect_success 'merge z into y fails and sets NOTES_MERGE_REF' ' git config core.notesRef refs/notes/y && test_must_fail git notes merge z && echo "ref: refs/notes/y" >expect && - test_cmp .git/NOTES_MERGE_REF expect + test_cmp expect .git/NOTES_MERGE_REF ' test_expect_success 'merge z into y while mid-merge in another workdir fails' ' @@ -66,7 +66,7 @@ test_expect_success 'merge z into x while mid-merge on y succeeds' ' grep -v "A notes merge into refs/notes/x is already in-progress in" out ) && echo "ref: refs/notes/x" >expect && - test_cmp .git/worktrees/worktree2/NOTES_MERGE_REF expect + test_cmp expect .git/worktrees/worktree2/NOTES_MERGE_REF ' test_done diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 3996ee0135..3e73f7584c 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -183,13 +183,13 @@ test_expect_success 'cherry-picked commits and fork-point work together' ' test_commit final_B B "Final B" && git rebase && echo Amended >expect && - test_cmp A expect && + test_cmp expect A && echo "Final B" >expect && - test_cmp B expect && + test_cmp expect B && echo C >expect && - test_cmp C expect && + test_cmp expect C && echo D >expect && - test_cmp D expect + test_cmp expect D ' test_expect_success 'rebase -q is quiet' ' diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 0075ac5c2a..52fa41c707 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -75,6 +75,16 @@ test_expect_success 'rebase --keep-empty' ' test_line_count = 6 actual ' +cat > expect <<EOF +error: nothing to do +EOF + +test_expect_success 'rebase -i with empty HEAD' ' + set_fake_editor && + test_must_fail env FAKE_LINES="1 exec_true" git rebase -i HEAD^ >actual 2>&1 && + test_i18ncmp expect actual +' + test_expect_success 'rebase -i with the exec command' ' git checkout master && ( @@ -137,6 +147,25 @@ test_expect_success 'rebase -i with the exec command checks tree cleanness' ' git rebase --continue ' +test_expect_success 'rebase -x with empty command fails' ' + test_when_finished "git rebase --abort ||:" && + test_must_fail git rebase -x "" @ 2>actual && + test_write_lines "error: empty exec command" >expected && + test_i18ncmp expected actual && + test_must_fail git rebase -x " " @ 2>actual && + test_i18ncmp expected actual +' + +LF=' +' +test_expect_success 'rebase -x with newline in command fails' ' + test_when_finished "git rebase --abort ||:" && + test_must_fail git rebase -x "a${LF}b" @ 2>actual && + test_write_lines "error: exec commands cannot contain newlines" \ + >expected && + test_i18ncmp expected actual +' + test_expect_success 'rebase -i with exec of inexistent command' ' git checkout master && test_when_finished "git rebase --abort" && @@ -146,6 +175,11 @@ test_expect_success 'rebase -i with exec of inexistent command' ' ! grep "Maybe git-rebase is broken" actual ' +test_expect_success 'implicit interactive rebase does not invoke sequence editor' ' + test_when_finished "git rebase --abort ||:" && + GIT_SEQUENCE_EDITOR="echo bad >" git rebase -x"echo one" @^ +' + test_expect_success 'no changes are a nop' ' git checkout branch2 && set_fake_editor && @@ -312,7 +346,7 @@ test_expect_success 'retain authorship when squashing' ' git show HEAD | grep "^Author: Twerp Snog" ' -test_expect_success '-p handles "no changes" gracefully' ' +test_expect_success REBASE_P '-p handles "no changes" gracefully' ' HEAD=$(git rev-parse HEAD) && set_fake_editor && git rebase -i -p HEAD^ && @@ -322,7 +356,7 @@ test_expect_success '-p handles "no changes" gracefully' ' test $HEAD = $(git rev-parse HEAD) ' -test_expect_failure 'exchange two commits with -p' ' +test_expect_failure REBASE_P 'exchange two commits with -p' ' git checkout H && set_fake_editor && FAKE_LINES="2 1" git rebase -i -p HEAD~2 && @@ -330,7 +364,7 @@ test_expect_failure 'exchange two commits with -p' ' test G = $(git cat-file commit HEAD | sed -ne \$p) ' -test_expect_success 'preserve merges with -p' ' +test_expect_success REBASE_P 'preserve merges with -p' ' git checkout -b to-be-preserved master^ && : > unrelated-file && git add unrelated-file && @@ -373,7 +407,7 @@ test_expect_success 'preserve merges with -p' ' test $(git show HEAD:unrelated-file) = 1 ' -test_expect_success 'edit ancestor with -p' ' +test_expect_success REBASE_P 'edit ancestor with -p' ' set_fake_editor && FAKE_LINES="1 2 edit 3 4" git rebase -i -p HEAD~3 && echo 2 > unrelated-file && @@ -387,6 +421,7 @@ test_expect_success 'edit ancestor with -p' ' ' test_expect_success '--continue tries to commit' ' + git reset --hard D && test_tick && set_fake_editor && test_must_fail git rebase -i --onto new-branch1 HEAD^ && @@ -426,7 +461,7 @@ test_expect_success C_LOCALE_OUTPUT 'multi-fixup does not fire up editor' ' git rebase -i $base && test $base = $(git rev-parse HEAD^) && test 0 = $(git show | grep NEVER | wc -l) && - git checkout to-be-rebased && + git checkout @{-1} && git branch -D multi-fixup ' @@ -441,7 +476,7 @@ test_expect_success 'commit message used after conflict' ' git rebase --continue && test $base = $(git rev-parse HEAD^) && test 1 = $(git show | grep ONCE | wc -l) && - git checkout to-be-rebased && + git checkout @{-1} && git branch -D conflict-fixup ' @@ -456,7 +491,7 @@ test_expect_success 'commit message retained after conflict' ' git rebase --continue && test $base = $(git rev-parse HEAD^) && test 2 = $(git show | grep TWICE | wc -l) && - git checkout to-be-rebased && + git checkout @{-1} && git branch -D conflict-squash ' @@ -481,7 +516,7 @@ test_expect_success C_LOCALE_OUTPUT 'squash and fixup generate correct log messa 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 checkout @{-1} && git branch -D squash-fixup ' @@ -494,7 +529,7 @@ test_expect_success C_LOCALE_OUTPUT 'squash ignores comments' ' git rebase -i $base && test $base = $(git rev-parse HEAD^) && test 1 = $(git show | grep ONCE | wc -l) && - git checkout to-be-rebased && + git checkout @{-1} && git branch -D skip-comments ' @@ -507,7 +542,7 @@ test_expect_success C_LOCALE_OUTPUT 'squash ignores blank lines' ' git rebase -i $base && test $base = $(git rev-parse HEAD^) && test 1 = $(git show | grep ONCE | wc -l) && - git checkout to-be-rebased && + git checkout @{-1} && git branch -D skip-blank-lines ' @@ -648,7 +683,7 @@ test_expect_success 'rebase with a file named HEAD in worktree' ' ) && set_fake_editor && - FAKE_LINES="1 squash 2" git rebase -i to-be-rebased && + FAKE_LINES="1 squash 2" git rebase -i @{-1} && test "$(git show -s --pretty=format:%an)" = "Squashed Away" ' diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh index 0392e36d23..b393e1e9fe 100755 --- a/t/t3406-rebase-message.sh +++ b/t/t3406-rebase-message.sh @@ -17,14 +17,9 @@ test_expect_success 'setup' ' git tag start ' -cat >expect <<\EOF -Already applied: 0001 A -Already applied: 0002 B -Committed: 0003 Z -EOF - test_expect_success 'rebase -m' ' git rebase -m master >report && + >expect && sed -n -e "/^Already applied: /p" \ -e "/^Committed: /p" report >actual && test_cmp expect actual @@ -77,11 +72,54 @@ test_expect_success 'rebase -n overrides config rebase.stat config' ' # "Does not point to a valid commit: invalid-ref" # # NEEDSWORK: This "grep" is fine in real non-C locales, but -# GETTEXT_POISON poisons the refname along with the enclosing +# GIT_TEST_GETTEXT_POISON poisons the refname along with the enclosing # error message. test_expect_success 'rebase --onto outputs the invalid ref' ' test_must_fail git rebase --onto invalid-ref HEAD HEAD 2>err && test_i18ngrep "invalid-ref" err ' +test_expect_success 'error out early upon -C<n> or --whitespace=<bad>' ' + test_must_fail git rebase -Cnot-a-number HEAD 2>err && + test_i18ngrep "numerical value" err && + test_must_fail git rebase --whitespace=bad HEAD 2>err && + test_i18ngrep "Invalid whitespace option" err +' + +test_expect_success 'GIT_REFLOG_ACTION' ' + git checkout start && + test_commit reflog-onto && + git checkout -b reflog-topic start && + test_commit reflog-to-rebase && + + git rebase reflog-onto && + git log -g --format=%gs -3 >actual && + cat >expect <<-\EOF && + rebase finished: returning to refs/heads/reflog-topic + rebase: reflog-to-rebase + rebase: checkout reflog-onto + EOF + test_cmp expect actual && + + git checkout -b reflog-prefix reflog-to-rebase && + GIT_REFLOG_ACTION=change-the-reflog git rebase reflog-onto && + git log -g --format=%gs -3 >actual && + cat >expect <<-\EOF && + rebase finished: returning to refs/heads/reflog-prefix + change-the-reflog: reflog-to-rebase + change-the-reflog: checkout reflog-onto + EOF + test_cmp expect actual +' + +test_expect_success 'rebase -i onto unrelated history' ' + git init unrelated && + test_commit -C unrelated 1 && + git -C unrelated remote add -f origin "$PWD" && + git -C unrelated branch --set-upstream-to=origin/master && + git -C unrelated -c core.editor=true rebase -i -v --stat >actual && + test_i18ngrep "Changes to " actual && + test_i18ngrep "5 files changed" actual +' + test_done diff --git a/t/t3408-rebase-multi-line.sh b/t/t3408-rebase-multi-line.sh index e7292f5b9b..d2bd7c17b0 100755 --- a/t/t3408-rebase-multi-line.sh +++ b/t/t3408-rebase-multi-line.sh @@ -52,7 +52,7 @@ test_expect_success rebase ' test_cmp expect actual ' -test_expect_success rebasep ' +test_expect_success REBASE_P rebasep ' git checkout side-merge && git rebase -p side && diff --git a/t/t3409-rebase-preserve-merges.sh b/t/t3409-rebase-preserve-merges.sh index 8c251c57a6..3b340f1ece 100755 --- a/t/t3409-rebase-preserve-merges.sh +++ b/t/t3409-rebase-preserve-merges.sh @@ -8,6 +8,11 @@ Run "git rebase -p" and check that merges are properly carried along ' . ./test-lib.sh +if ! test_have_prereq REBASE_P; then + skip_all='skipping git rebase -p tests, as asked for' + test_done +fi + GIT_AUTHOR_EMAIL=bogus_email_address export GIT_AUTHOR_EMAIL diff --git a/t/t3410-rebase-preserve-dropped-merges.sh b/t/t3410-rebase-preserve-dropped-merges.sh index 6f73b95558..2e29866993 100755 --- a/t/t3410-rebase-preserve-dropped-merges.sh +++ b/t/t3410-rebase-preserve-dropped-merges.sh @@ -11,6 +11,11 @@ rewritten. ' . ./test-lib.sh +if ! test_have_prereq REBASE_P; then + skip_all='skipping git rebase -p tests, as asked for' + test_done +fi + # set up two branches like this: # # A - B - C - D - E diff --git a/t/t3411-rebase-preserve-around-merges.sh b/t/t3411-rebase-preserve-around-merges.sh index dc81bf27eb..fb45e7bf7b 100755 --- a/t/t3411-rebase-preserve-around-merges.sh +++ b/t/t3411-rebase-preserve-around-merges.sh @@ -10,6 +10,11 @@ a merge to before the merge. ' . ./test-lib.sh +if ! test_have_prereq REBASE_P; then + skip_all='skipping git rebase -p tests, as asked for' + test_done +fi + . "$TEST_DIRECTORY"/lib-rebase.sh set_fake_editor diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh index 73a39f2923..21632a984e 100755 --- a/t/t3412-rebase-root.sh +++ b/t/t3412-rebase-root.sh @@ -86,14 +86,14 @@ test_expect_success 'pre-rebase got correct input (4)' ' test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work4 ' -test_expect_success 'rebase -i -p with linear history' ' +test_expect_success REBASE_P 'rebase -i -p with linear history' ' git checkout -b work5 other && git rebase -i -p --root --onto master && git log --pretty=tformat:"%s" > rebased5 && test_cmp expect rebased5 ' -test_expect_success 'pre-rebase got correct input (5)' ' +test_expect_success REBASE_P 'pre-rebase got correct input (5)' ' test "z$(cat .git/PRE-REBASE-INPUT)" = z--root, ' @@ -120,7 +120,7 @@ commit work6~4 1 EOF -test_expect_success 'rebase -i -p with merge' ' +test_expect_success REBASE_P 'rebase -i -p with merge' ' git checkout -b work6 other && git rebase -i -p --root --onto master && log_with_names work6 > rebased6 && @@ -155,7 +155,7 @@ commit work7~5 1 EOF -test_expect_success 'rebase -i -p with two roots' ' +test_expect_success REBASE_P 'rebase -i -p with two roots' ' git checkout -b work7 other && git rebase -i -p --root --onto master && log_with_names work7 > rebased7 && @@ -261,7 +261,7 @@ commit conflict3~6 1 EOF -test_expect_success 'rebase -i -p --root with conflict (first part)' ' +test_expect_success REBASE_P 'rebase -i -p --root with conflict (first part)' ' git checkout -b conflict3 other && test_must_fail git rebase -i -p --root --onto master && git ls-files -u | grep "B$" @@ -272,7 +272,7 @@ test_expect_success 'fix the conflict' ' git add B ' -test_expect_success 'rebase -i -p --root with conflict (second part)' ' +test_expect_success REBASE_P 'rebase -i -p --root with conflict (second part)' ' git rebase --continue && log_with_names conflict3 >out && test_cmp expect-conflict-p out diff --git a/t/t3414-rebase-preserve-onto.sh b/t/t3414-rebase-preserve-onto.sh index ee0a6cccfd..72e04b5386 100755 --- a/t/t3414-rebase-preserve-onto.sh +++ b/t/t3414-rebase-preserve-onto.sh @@ -10,6 +10,11 @@ aren'"'"'t on top of $ONTO, even if they are on top of $UPSTREAM. ' . ./test-lib.sh +if ! test_have_prereq REBASE_P; then + skip_all='skipping git rebase -p tests, as asked for' + test_done +fi + . "$TEST_DIRECTORY"/lib-rebase.sh # Set up branches like this: diff --git a/t/t3417-rebase-whitespace-fix.sh b/t/t3417-rebase-whitespace-fix.sh index 1fb3e499b4..e85cdc7037 100755 --- a/t/t3417-rebase-whitespace-fix.sh +++ b/t/t3417-rebase-whitespace-fix.sh @@ -55,7 +55,7 @@ test_expect_success 'blank line at end of file; extend at end of file' ' git add file && git commit -m second && git rebase --whitespace=fix HEAD^^ && git diff --exit-code HEAD^:file expect-first && - test_cmp file expect-second + test_cmp expect-second file ' # prepare third revision of "file" @@ -82,7 +82,7 @@ test_expect_success 'two blanks line at end of file; extend at end of file' ' cp third file && git add file && git commit -m third && git rebase --whitespace=fix HEAD^^ && git diff --exit-code HEAD^:file expect-second && - test_cmp file expect-third + test_cmp expect-third file ' test_expect_success 'same, but do not remove trailing spaces' ' @@ -120,7 +120,7 @@ test_expect_success 'at beginning of file' ' done >> file && git commit -m more file && git rebase --whitespace=fix HEAD^^ && - test_cmp file expect-beginning + test_cmp expect-beginning file ' test_done diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh index 25099d715c..25aaacacfc 100755 --- a/t/t3418-rebase-continue.sh +++ b/t/t3418-rebase-continue.sh @@ -106,7 +106,7 @@ test_expect_success 'rebase -i --continue handles merge strategy and options' ' test -f funny.was.run ' -test_expect_success 'rebase passes merge strategy options correctly' ' +test_expect_success REBASE_P '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 && @@ -177,6 +177,7 @@ test_expect_success 'setup rerere database' ' git checkout master && test_commit "commit-new-file-F3" F3 3 && test_config rerere.enabled true && + git update-ref refs/heads/topic commit-new-file-F3-on-topic-branch && test_must_fail git rebase -m master topic && echo "Resolved" >F2 && cp F2 expected-F2 && @@ -240,6 +241,31 @@ test_rerere_autoupdate test_rerere_autoupdate -m GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR test_rerere_autoupdate -i -test_rerere_autoupdate --preserve-merges +test_have_prereq !REBASE_P || test_rerere_autoupdate --preserve-merges +unset GIT_SEQUENCE_EDITOR + +test_expect_success 'the todo command "break" works' ' + rm -f execed && + FAKE_LINES="break b exec_>execed" git rebase -i HEAD && + test_path_is_missing execed && + git rebase --continue && + test_path_is_missing execed && + git rebase --continue && + test_path_is_file execed +' + +test_expect_success '--reschedule-failed-exec' ' + test_when_finished "git rebase --abort" && + test_must_fail git rebase -x false --reschedule-failed-exec HEAD^ && + grep "^exec false" .git/rebase-merge/git-rebase-todo && + git rebase --abort && + test_must_fail git -c rebase.rescheduleFailedExec=true \ + rebase -x false HEAD^ 2>err && + grep "^exec false" .git/rebase-merge/git-rebase-todo && + test_i18ngrep "has been rescheduled" err && + git rebase --abort && + test_must_fail git rebase -y false HEAD^ 2>err && + test_i18ngrep "has been rescheduled" err +' test_done diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh index 0c4eefec76..2d1094e483 100755 --- a/t/t3420-rebase-autostash.sh +++ b/t/t3420-rebase-autostash.sh @@ -53,41 +53,6 @@ create_expected_success_interactive () { 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) @@ -112,43 +77,6 @@ create_expected_failure_interactive () { 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 @@ -177,6 +105,9 @@ testrebase () { test_expect_success "rebase$type --autostash: check output" ' test_when_finished git branch -D rebased-feature-branch && suffix=${type#\ --} && suffix=${suffix:-am} && + if test ${suffix} = "merge"; then + suffix=interactive + fi && create_expected_success_$suffix && test_i18ncmp expected actual ' @@ -274,6 +205,9 @@ testrebase () { test_expect_success "rebase$type: check output with conflicting stash" ' test_when_finished git branch -D rebased-feature-branch && suffix=${type#\ --} && suffix=${suffix:-am} && + if test ${suffix} = "merge"; then + suffix=interactive + fi && create_expected_failure_$suffix && test_i18ncmp expected actual ' @@ -351,4 +285,22 @@ test_expect_success 'autostash is saved on editor failure with conflict' ' test_cmp expected file0 ' +test_expect_success 'autostash with dirty submodules' ' + test_when_finished "git reset --hard && git checkout master" && + git checkout -b with-submodule && + git submodule add ./ sub && + test_tick && + git commit -m add-submodule && + echo changed >sub/file0 && + git rebase -i --autostash HEAD +' + +test_expect_success 'branch is left alone when possible' ' + git checkout -b unchanged-branch && + echo changed >file0 && + git rebase --autostash unchanged-branch && + test changed = "$(cat file0)" && + test unchanged-branch = "$(git rev-parse --abbrev-ref HEAD)" +' + test_done diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh index 99b2aac921..7274dca40b 100755 --- a/t/t3421-rebase-topology-linear.sh +++ b/t/t3421-rebase-topology-linear.sh @@ -29,7 +29,7 @@ test_run_rebase () { test_run_rebase success '' test_run_rebase success -m test_run_rebase success -i -test_run_rebase success -p +test_have_prereq !REBASE_P || test_run_rebase success -p test_run_rebase () { result=$1 @@ -43,7 +43,7 @@ test_run_rebase () { test_run_rebase success '' test_run_rebase success -m test_run_rebase success -i -test_run_rebase success -p +test_have_prereq !REBASE_P || test_run_rebase success -p test_run_rebase () { result=$1 @@ -59,7 +59,7 @@ test_run_rebase () { test_run_rebase success '' test_run_rebase success -m test_run_rebase success -i -test_run_rebase failure -p +test_have_prereq !REBASE_P || test_run_rebase failure -p test_run_rebase () { result=$1 @@ -73,7 +73,7 @@ test_run_rebase () { test_run_rebase success '' test_run_rebase success -m test_run_rebase success -i -test_run_rebase success -p +test_have_prereq !REBASE_P || test_run_rebase success -p # f # / @@ -111,9 +111,9 @@ test_run_rebase () { " } test_run_rebase success '' -test_run_rebase failure -m +test_run_rebase success -m test_run_rebase success -i -test_run_rebase success -p +test_have_prereq !REBASE_P || test_run_rebase success -p test_run_rebase () { result=$1 @@ -126,9 +126,9 @@ test_run_rebase () { " } test_run_rebase success '' -test_run_rebase failure -m +test_run_rebase success -m test_run_rebase success -i -test_run_rebase success -p +test_have_prereq !REBASE_P || test_run_rebase success -p test_run_rebase () { result=$1 @@ -141,9 +141,9 @@ test_run_rebase () { " } test_run_rebase success '' -test_run_rebase failure -m +test_run_rebase success -m test_run_rebase success -i -test_run_rebase success -p +test_have_prereq !REBASE_P || test_run_rebase success -p test_run_rebase () { result=$1 @@ -158,7 +158,7 @@ test_run_rebase () { test_run_rebase success '' test_run_rebase success -m test_run_rebase success -i -test_run_rebase success -p +test_have_prereq !REBASE_P || test_run_rebase success -p # a---b---c---j! # \ @@ -186,7 +186,7 @@ test_run_rebase () { test_run_rebase success '' test_run_rebase success -m test_run_rebase success -i -test_run_rebase success -p +test_have_prereq !REBASE_P || test_run_rebase success -p test_run_rebase () { result=$1 @@ -201,7 +201,7 @@ test_run_rebase () { test_run_rebase success '' test_run_rebase success -m test_run_rebase success -i -test_run_rebase failure -p +test_have_prereq !REBASE_P || test_run_rebase failure -p test_run_rebase () { result=$1 @@ -216,7 +216,7 @@ test_run_rebase () { test_run_rebase success '' test_run_rebase success -m test_run_rebase success -i -test_run_rebase failure -p +test_have_prereq !REBASE_P || test_run_rebase failure -p test_run_rebase success --rebase-merges # m @@ -256,7 +256,7 @@ test_run_rebase () { test_run_rebase success '' test_run_rebase success -m test_run_rebase success -i -test_run_rebase success -p +test_have_prereq !REBASE_P || test_run_rebase success -p test_run_rebase () { result=$1 @@ -271,7 +271,7 @@ test_run_rebase () { test_run_rebase success '' test_run_rebase success -m test_run_rebase success -i -test_run_rebase failure -p +test_have_prereq !REBASE_P || test_run_rebase failure -p test_run_rebase () { result=$1 @@ -284,9 +284,9 @@ test_run_rebase () { " } test_run_rebase success '' -test_run_rebase failure -m +test_run_rebase success -m test_run_rebase success -i -test_run_rebase success -p +test_have_prereq !REBASE_P || test_run_rebase success -p test_run_rebase () { result=$1 @@ -302,7 +302,7 @@ test_run_rebase () { test_run_rebase success '' test_run_rebase success -m test_run_rebase success -i -test_run_rebase failure -p +test_have_prereq !REBASE_P || test_run_rebase failure -p test_run_rebase () { result=$1 @@ -315,9 +315,9 @@ 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 +test_have_prereq !REBASE_P || test_run_rebase failure -p test_run_rebase () { result=$1 @@ -331,7 +331,7 @@ test_run_rebase () { test_run_rebase success '' test_run_rebase success -m test_run_rebase success -i -test_run_rebase failure -p +test_have_prereq !REBASE_P || test_run_rebase failure -p test_run_rebase () { result=$1 @@ -346,6 +346,6 @@ test_run_rebase () { test_run_rebase success '' test_run_rebase success -m test_run_rebase success -i -test_run_rebase success -p +test_have_prereq !REBASE_P || test_run_rebase success -p test_done diff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh index 846f85c27e..fd8efe84fe 100755 --- a/t/t3425-rebase-topology-merges.sh +++ b/t/t3425-rebase-topology-merges.sh @@ -70,9 +70,8 @@ test_run_rebase () { test_linear_range "\'"$expected"\'" d.. " } -#TODO: make order consistent across all flavors of rebase -test_run_rebase success 'e n o' '' -test_run_rebase success 'e n o' -m +test_run_rebase success 'n o e' '' +test_run_rebase success 'n o e' -m test_run_rebase success 'n o e' -i test_run_rebase () { @@ -87,9 +86,8 @@ test_run_rebase () { test_linear_range "\'"$expected"\'" c.. " } -#TODO: make order consistent across all flavors of rebase -test_run_rebase success 'd e n o' '' -test_run_rebase success 'd e n o' -m +test_run_rebase success 'd n o e' '' +test_run_rebase success 'd n o e' -m test_run_rebase success 'd n o e' -i test_run_rebase () { @@ -104,11 +102,15 @@ test_run_rebase () { test_linear_range "\'"$expected"\'" c.. " } -#TODO: make order consistent across all flavors of rebase -test_run_rebase success 'd e n o' '' -test_run_rebase success 'd e n o' -m +test_run_rebase success 'd n o e' '' +test_run_rebase success 'd n o e' -m test_run_rebase success 'd n o e' -i +if ! test_have_prereq REBASE_P; then + skip_all='skipping git rebase -p tests, as asked for' + test_done +fi + test_expect_success "rebase -p is no-op in non-linear history" " reset_rebase && git rebase -p d w && diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh index aa7bfc88ec..4c69255ee6 100755 --- a/t/t3430-rebase-merges.sh +++ b/t/t3430-rebase-merges.sh @@ -125,7 +125,7 @@ test_expect_success '`reset` refuses to overwrite untracked files' ' : >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 && + test_must_fail git rebase -ir HEAD && git rebase --abort ' @@ -396,4 +396,20 @@ test_expect_success 'with --autosquash and --exec' ' grep "G: +G" actual ' +test_expect_success '--continue after resolving conflicts after a merge' ' + git checkout -b already-has-g E && + git cherry-pick E..G && + test_commit H2 && + + git checkout -b conflicts-in-merge H && + test_commit H2 H2.t conflicts H2-conflict && + test_must_fail git rebase -r already-has-g && + grep conflicts H2.t && + echo resolved >H2.t && + git add -u && + git rebase --continue && + test_must_fail git rev-parse --verify HEAD^2 && + test_path_is_missing .git/MERGE_HEAD +' + test_done diff --git a/t/t3502-cherry-pick-merge.sh b/t/t3502-cherry-pick-merge.sh index b1602718f8..8b635a196d 100755 --- a/t/t3502-cherry-pick-merge.sh +++ b/t/t3502-cherry-pick-merge.sh @@ -40,12 +40,12 @@ test_expect_success 'cherry-pick -m complains of bogus numbers' ' test_expect_code 129 git cherry-pick -m 0 b ' -test_expect_success 'cherry-pick a non-merge with -m should fail' ' +test_expect_success 'cherry-pick explicit first parent of a non-merge' ' git reset --hard && git checkout a^0 && - test_expect_code 128 git cherry-pick -m 1 b && - git diff --exit-code a -- + git cherry-pick -m 1 b && + git diff --exit-code c -- ' @@ -84,12 +84,12 @@ test_expect_success 'cherry pick a merge relative to nonexistent parent should f ' -test_expect_success 'revert a non-merge with -m should fail' ' +test_expect_success 'revert explicit first parent of a non-merge' ' git reset --hard && git checkout c^0 && - test_must_fail git revert -m 1 b && - git diff --exit-code c + git revert -m 1 b && + git diff --exit-code a -- ' diff --git a/t/t3506-cherry-pick-ff.sh b/t/t3506-cherry-pick-ff.sh index fb889ac6f0..127dd0082f 100755 --- a/t/t3506-cherry-pick-ff.sh +++ b/t/t3506-cherry-pick-ff.sh @@ -64,10 +64,10 @@ test_expect_success 'merge setup' ' git checkout -b new A ' -test_expect_success 'cherry-pick a non-merge with --ff and -m should fail' ' +test_expect_success 'cherry-pick explicit first parent of a non-merge with --ff' ' git reset --hard A -- && - test_must_fail git cherry-pick --ff -m 1 B && - git diff --exit-code A -- + git cherry-pick --ff -m 1 B && + git diff --exit-code C -- ' test_expect_success 'cherry pick a merge with --ff but without -m should fail' ' diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh index c84eeefdc9..941d5026da 100755 --- a/t/t3510-cherry-pick-sequence.sh +++ b/t/t3510-cherry-pick-sequence.sh @@ -61,7 +61,11 @@ test_expect_success 'cherry-pick mid-cherry-pick-sequence' ' test_expect_success 'cherry-pick persists opts correctly' ' pristine_detach initial && - test_expect_code 128 git cherry-pick -s -m 1 --strategy=recursive -X patience -X ours initial..anotherpick && + # to make sure that the session to cherry-pick a sequence + # gets interrupted, use a high-enough number that is larger + # than the number of parents of any commit we have created + mainline=4 && + test_expect_code 128 git cherry-pick -s -m $mainline --strategy=recursive -X patience -X ours initial..anotherpick && test_path_is_dir .git/sequencer && test_path_is_file .git/sequencer/head && test_path_is_file .git/sequencer/todo && @@ -69,7 +73,7 @@ test_expect_success 'cherry-pick persists opts correctly' ' echo "true" >expect && git config --file=.git/sequencer/opts --get-all options.signoff >actual && test_cmp expect actual && - echo "1" >expect && + echo "$mainline" >expect && git config --file=.git/sequencer/opts --get-all options.mainline >actual && test_cmp expect actual && echo "recursive" >expect && diff --git a/t/t3700-add.sh b/t/t3700-add.sh index 37729ba258..be582a513b 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -402,4 +402,11 @@ test_expect_success 'all statuses changed in folder if . is given' ' test $(git ls-files --stage | grep ^100755 | wc -l) -eq 0 ' +test_expect_success CASE_INSENSITIVE_FS 'path is case-insensitive' ' + path="$(pwd)/BLUB" && + touch "$path" && + downcased="$(echo "$path" | tr A-Z a-z)" && + git add "$downcased" +' + test_done diff --git a/t/t3702-add-edit.sh b/t/t3702-add-edit.sh index c6af7f82b5..6c676645d8 100755 --- a/t/t3702-add-edit.sh +++ b/t/t3702-add-edit.sh @@ -110,10 +110,10 @@ test_expect_success 'add -e' ' cp second-part file && git add -e && test_cmp second-part file && - test_cmp orig-patch expected-patch && + test_cmp expected-patch orig-patch && git diff --cached >actual && grep -v index actual >out && - test_cmp out expected + test_cmp expected out ' diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 6450bc6698..5f8272b6f9 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -36,7 +36,7 @@ EOF test_expect_success 'parents of stash' ' test $(git rev-parse stash^) = $(git rev-parse HEAD) && git diff stash^2..stash > output && - test_cmp output expect + test_cmp expect output ' test_expect_success 'applying bogus stash does nothing' ' @@ -210,9 +210,9 @@ test_expect_success 'stash branch' ' test refs/heads/stashbranch = $(git symbolic-ref HEAD) && test $(git rev-parse HEAD) = $(git rev-parse master^) && git diff --cached > output && - test_cmp output expect && + test_cmp expect output && git diff > output && - test_cmp output expect1 && + test_cmp expect1 output && git add file && git commit -m alternate\ second && git diff master..stashbranch > output && @@ -710,7 +710,7 @@ test_expect_success 'stash where working directory contains "HEAD" file' ' git diff-index --cached --quiet HEAD && test "$(git rev-parse stash^)" = "$(git rev-parse HEAD)" && git diff stash^..stash > output && - test_cmp output expect + test_cmp expect output ' test_expect_success 'store called with invalid commit' ' @@ -1096,4 +1096,32 @@ test_expect_success 'stash -- <subdir> works with binary files' ' test_path_is_file subdir/untracked ' +test_expect_success 'stash works when user.name and user.email are not set' ' + git reset && + >1 && + git add 1 && + echo "$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" >expect && + git stash && + git show -s --format="%an <%ae>" refs/stash >actual && + test_cmp expect actual && + >2 && + git add 2 && + test_config user.useconfigonly true && + test_config stash.usebuiltin true && + ( + sane_unset GIT_AUTHOR_NAME && + sane_unset GIT_AUTHOR_EMAIL && + sane_unset GIT_COMMITTER_NAME && + sane_unset GIT_COMMITTER_EMAIL && + test_unconfig user.email && + test_unconfig user.name && + test_must_fail git commit -m "should fail" && + echo "git stash <git@stash>" >expect && + >2 && + git stash && + git show -s --format="%an <%ae>" refs/stash >actual && + test_cmp expect actual + ) +' + test_done diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh index 597b0637d1..cc1c8a7bb2 100755 --- a/t/t3905-stash-include-untracked.sh +++ b/t/t3905-stash-include-untracked.sh @@ -142,7 +142,7 @@ test_expect_success 'stash save --include-untracked removed files' ' rm -f file && git stash save --include-untracked && echo 1 > expect && - test_cmp file expect + test_cmp expect file ' rm -f expect diff --git a/t/t4006-diff-mode.sh b/t/t4006-diff-mode.sh index a8e01eccd1..03489aff14 100755 --- a/t/t4006-diff-mode.sh +++ b/t/t4006-diff-mode.sh @@ -32,28 +32,37 @@ test_expect_success 'prepare binary file' ' git commit -m binbin ' -# test_expect_success '--stat output after text chmod' ' -# test_chmod -x rezrov && -# echo " 0 files changed" >expect && -# git diff HEAD --stat >actual && -# test_i18ncmp expect actual -# ' -# -# test_expect_success '--shortstat output after text chmod' ' -# git diff HEAD --shortstat >actual && -# test_i18ncmp expect actual -# ' -# -# test_expect_success '--stat output after binary chmod' ' -# test_chmod +x binbin && -# echo " 0 files changed" >expect && -# git diff HEAD --stat >actual && -# test_i18ncmp expect actual -# ' -# -# test_expect_success '--shortstat output after binary chmod' ' -# git diff HEAD --shortstat >actual && -# test_i18ncmp expect actual -# ' +test_expect_success '--stat output after text chmod' ' + test_chmod -x rezrov && + cat >expect <<-\EOF && + rezrov | 0 + 1 file changed, 0 insertions(+), 0 deletions(-) + EOF + git diff HEAD --stat >actual && + test_i18ncmp expect actual +' + +test_expect_success '--shortstat output after text chmod' ' + tail -n 1 <expect >expect.short && + git diff HEAD --shortstat >actual && + test_i18ncmp expect.short actual +' + +test_expect_success '--stat output after binary chmod' ' + test_chmod +x binbin && + cat >expect <<-EOF && + binbin | Bin + rezrov | 0 + 2 files changed, 0 insertions(+), 0 deletions(-) + EOF + git diff HEAD --stat >actual && + test_i18ncmp expect actual +' + +test_expect_success '--shortstat output after binary chmod' ' + tail -n 1 <expect >expect.short && + git diff HEAD --shortstat >actual && + test_i18ncmp expect.short actual +' test_done diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 73f7038253..9f8f0e84ad 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -98,6 +98,12 @@ test_expect_success setup ' git commit -m "update mode" && git checkout -f master && + # Same merge as master, but with parents reversed. Hide it in a + # pseudo-ref to avoid impacting tests with --all. + commit=$(echo reverse | + git commit-tree -p master^2 -p master^1 master^{tree}) && + git update-ref REVERSE $commit && + git config diff.renames false && git show-branch @@ -129,7 +135,7 @@ do case "$magic" in noellipses) ;; *) - die "bug in t4103: unknown magic $magic" ;; + BUG "unknown magic $magic" ;; esac ;; *) cmd="$magic $cmd" magic= @@ -239,6 +245,8 @@ diff-tree --cc --stat --summary master # stat summary should show the diffstat and summary with the first parent diff-tree -c --stat --summary side diff-tree --cc --stat --summary side +diff-tree --cc --shortstat master +diff-tree --cc --summary REVERSE # improved by Timo's patch diff-tree --cc --patch-with-stat master # improved by Timo's patch @@ -350,6 +358,7 @@ diff --line-prefix=abc master master^ side diff --dirstat master~1 master~2 diff --dirstat initial rearrange diff --dirstat-by-file initial rearrange +diff --dirstat --cc master~1 master # No-index --abbrev and --no-abbrev diff --raw initial :noellipses diff --raw initial diff --git a/t/t4013/diff.diff-tree_--cc_--shortstat_master b/t/t4013/diff.diff-tree_--cc_--shortstat_master new file mode 100644 index 0000000000..a4ca42df2a --- /dev/null +++ b/t/t4013/diff.diff-tree_--cc_--shortstat_master @@ -0,0 +1,4 @@ +$ git diff-tree --cc --shortstat master +59d314ad6f356dd08601a4cd5e530381da3e3c64 + 2 files changed, 5 insertions(+) +$ diff --git a/t/t4013/diff.diff-tree_--cc_--summary_REVERSE b/t/t4013/diff.diff-tree_--cc_--summary_REVERSE new file mode 100644 index 0000000000..e208dd5682 --- /dev/null +++ b/t/t4013/diff.diff-tree_--cc_--summary_REVERSE @@ -0,0 +1,6 @@ +$ git diff-tree --cc --summary REVERSE +2562325a7ee916efb2481da93073b82cec801cbc + create mode 100644 file1 + delete mode 100644 file2 + delete mode 100644 file3 +$ diff --git a/t/t4013/diff.diff_--dirstat_--cc_master~1_master b/t/t4013/diff.diff_--dirstat_--cc_master~1_master new file mode 100644 index 0000000000..fba4e34175 --- /dev/null +++ b/t/t4013/diff.diff_--dirstat_--cc_master~1_master @@ -0,0 +1,3 @@ +$ git diff --dirstat --cc master~1 master + 40.0% dir/ +$ diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 53880da7bb..909c743c13 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -1717,4 +1717,38 @@ test_expect_success 'format-patch --pretty=mboxrd' ' test_cmp expect actual ' +test_expect_success 'interdiff: setup' ' + git checkout -b boop master && + test_commit fnorp blorp && + test_commit fleep blorp +' + +test_expect_success 'interdiff: cover-letter' ' + sed "y/q/ /" >expect <<-\EOF && + +fleep + --q + EOF + git format-patch --cover-letter --interdiff=boop~2 -1 boop && + test_i18ngrep "^Interdiff:$" 0000-cover-letter.patch && + test_i18ngrep ! "^Interdiff:$" 0001-fleep.patch && + sed "1,/^@@ /d; /^-- $/q" <0000-cover-letter.patch >actual && + test_cmp expect actual +' + +test_expect_success 'interdiff: reroll-count' ' + git format-patch --cover-letter --interdiff=boop~2 -v2 -1 boop && + test_i18ngrep "^Interdiff ..* v1:$" v2-0000-cover-letter.patch +' + +test_expect_success 'interdiff: solo-patch' ' + cat >expect <<-\EOF && + +fleep + + EOF + git format-patch --interdiff=boop~2 -1 boop && + test_i18ngrep "^Interdiff:$" 0001-fleep.patch && + sed "1,/^ @@ /d; /^$/q" <0001-fleep.patch >actual && + test_cmp expect actual +' + test_done diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index a9fb226c5a..ab4670d236 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -1802,8 +1802,8 @@ test_expect_success 'only move detection ignores white spaces' ' <BOLD;MAGENTA>-a long line to exceed per-line minimum<RESET> <BOLD;MAGENTA>-another long line to exceed per-line minimum<RESET> <RED>-original file<RESET> - <BOLD;YELLOW>+<RESET>Q<BOLD;YELLOW>a long line to exceed per-line minimum<RESET> - <BOLD;YELLOW>+<RESET>Q<BOLD;YELLOW>another long line to exceed per-line minimum<RESET> + <BOLD;CYAN>+<RESET>Q<BOLD;CYAN>a long line to exceed per-line minimum<RESET> + <BOLD;CYAN>+<RESET>Q<BOLD;CYAN>another long line to exceed per-line minimum<RESET> <GREEN>+<RESET><GREEN>new file<RESET> EOF test_cmp expected actual @@ -1827,6 +1827,7 @@ test_expect_success 'compare whitespace delta across moved blocks' ' QQQthat has similar lines QQQto previous blocks, but with different indent QQQYetQAnotherQoutlierQ + QLine with internal w h i t e s p a c e change EOF git add text.txt && @@ -1847,6 +1848,7 @@ test_expect_success 'compare whitespace delta across moved blocks' ' QQthat has similar lines QQto previous blocks, but with different indent QQYetQAnotherQoutlier + QLine with internal whitespace change EOF git diff --color --color-moved --color-moved-ws=allow-indentation-change >actual.raw && @@ -1856,7 +1858,7 @@ test_expect_success 'compare whitespace delta across moved blocks' ' <BOLD>diff --git a/text.txt b/text.txt<RESET> <BOLD>--- a/text.txt<RESET> <BOLD>+++ b/text.txt<RESET> - <CYAN>@@ -1,14 +1,14 @@<RESET> + <CYAN>@@ -1,15 +1,15 @@<RESET> <BOLD;MAGENTA>-QIndented<RESET> <BOLD;MAGENTA>-QText across<RESET> <BOLD;MAGENTA>-Qsome lines<RESET> @@ -1871,6 +1873,7 @@ test_expect_success 'compare whitespace delta across moved blocks' ' <BOLD;MAGENTA>-QQQthat has similar lines<RESET> <BOLD;MAGENTA>-QQQto previous blocks, but with different indent<RESET> <RED>-QQQYetQAnotherQoutlierQ<RESET> + <RED>-QLine with internal w h i t e s p a c e change<RESET> <BOLD;CYAN>+<RESET>QQ<BOLD;CYAN>Indented<RESET> <BOLD;CYAN>+<RESET>QQ<BOLD;CYAN>Text across<RESET> <BOLD;CYAN>+<RESET>QQ<BOLD;CYAN>some lines<RESET> @@ -1885,11 +1888,30 @@ test_expect_success 'compare whitespace delta across moved blocks' ' <BOLD;CYAN>+<RESET>QQ<BOLD;CYAN>that has similar lines<RESET> <BOLD;CYAN>+<RESET>QQ<BOLD;CYAN>to previous blocks, but with different indent<RESET> <GREEN>+<RESET>QQ<GREEN>YetQAnotherQoutlier<RESET> + <GREEN>+<RESET>Q<GREEN>Line with internal whitespace change<RESET> EOF test_cmp expected actual ' +test_expect_success 'bogus settings in move detection erroring out' ' + test_must_fail git diff --color-moved=bogus 2>err && + test_i18ngrep "must be one of" err && + test_i18ngrep bogus err && + + test_must_fail git -c diff.colormoved=bogus diff 2>err && + test_i18ngrep "must be one of" err && + test_i18ngrep "from command-line config" err && + + test_must_fail git diff --color-moved-ws=bogus 2>err && + test_i18ngrep "possible values" err && + test_i18ngrep bogus err && + + test_must_fail git -c diff.colormovedws=bogus diff 2>err && + test_i18ngrep "possible values" err && + test_i18ngrep "from command-line config" err +' + test_expect_success 'compare whitespace delta incompatible with other space options' ' test_must_fail git diff \ --color-moved-ws=allow-indentation-change,ignore-all-space \ @@ -1897,4 +1919,93 @@ test_expect_success 'compare whitespace delta incompatible with other space opti test_i18ngrep allow-indentation-change err ' +EMPTY='' +test_expect_success 'compare mixed whitespace delta across moved blocks' ' + + git reset --hard && + tr Q_ "\t " <<-EOF >text.txt && + ${EMPTY} + ____too short without + ${EMPTY} + ___being grouped across blank line + ${EMPTY} + context + lines + to + anchor + ____Indented text to + _Q____be further indented by four spaces across + ____Qseveral lines + QQ____These two lines have had their + ____indentation reduced by four spaces + Qdifferent indentation change + ____too short + EOF + + git add text.txt && + git commit -m "add text.txt" && + + tr Q_ "\t " <<-EOF >text.txt && + context + lines + to + anchor + QIndented text to + QQbe further indented by four spaces across + Q____several lines + ${EMPTY} + QQtoo short without + ${EMPTY} + Q_______being grouped across blank line + ${EMPTY} + Q_QThese two lines have had their + indentation reduced by four spaces + QQdifferent indentation change + __Qtoo short + EOF + + git -c color.diff.whitespace="normal red" \ + -c core.whitespace=space-before-tab \ + diff --color --color-moved --ws-error-highlight=all \ + --color-moved-ws=allow-indentation-change >actual.raw && + grep -v "index" actual.raw | test_decode_color >actual && + + cat <<-\EOF >expected && + <BOLD>diff --git a/text.txt b/text.txt<RESET> + <BOLD>--- a/text.txt<RESET> + <BOLD>+++ b/text.txt<RESET> + <CYAN>@@ -1,16 +1,16 @@<RESET> + <BOLD;MAGENTA>-<RESET> + <BOLD;MAGENTA>-<RESET><BOLD;MAGENTA> too short without<RESET> + <BOLD;MAGENTA>-<RESET> + <BOLD;MAGENTA>-<RESET><BOLD;MAGENTA> being grouped across blank line<RESET> + <BOLD;MAGENTA>-<RESET> + <RESET>context<RESET> + <RESET>lines<RESET> + <RESET>to<RESET> + <RESET>anchor<RESET> + <BOLD;MAGENTA>-<RESET><BOLD;MAGENTA> Indented text to<RESET> + <BOLD;MAGENTA>-<RESET><BRED> <RESET> <BOLD;MAGENTA> be further indented by four spaces across<RESET> + <BOLD;MAGENTA>-<RESET><BRED> <RESET> <BOLD;MAGENTA>several lines<RESET> + <BOLD;BLUE>-<RESET> <BOLD;BLUE> These two lines have had their<RESET> + <BOLD;BLUE>-<RESET><BOLD;BLUE> indentation reduced by four spaces<RESET> + <BOLD;MAGENTA>-<RESET> <BOLD;MAGENTA>different indentation change<RESET> + <RED>-<RESET><RED> too short<RESET> + <BOLD;CYAN>+<RESET> <BOLD;CYAN>Indented text to<RESET> + <BOLD;CYAN>+<RESET> <BOLD;CYAN>be further indented by four spaces across<RESET> + <BOLD;CYAN>+<RESET> <BOLD;CYAN> several lines<RESET> + <BOLD;YELLOW>+<RESET> + <BOLD;YELLOW>+<RESET> <BOLD;YELLOW>too short without<RESET> + <BOLD;YELLOW>+<RESET> + <BOLD;YELLOW>+<RESET> <BOLD;YELLOW> being grouped across blank line<RESET> + <BOLD;YELLOW>+<RESET> + <BOLD;CYAN>+<RESET> <BRED> <RESET> <BOLD;CYAN>These two lines have had their<RESET> + <BOLD;CYAN>+<RESET><BOLD;CYAN>indentation reduced by four spaces<RESET> + <BOLD;YELLOW>+<RESET> <BOLD;YELLOW>different indentation change<RESET> + <GREEN>+<RESET><BRED> <RESET> <GREEN>too short<RESET> + EOF + + test_cmp expected actual +' + test_done diff --git a/t/t4025-hunk-header.sh b/t/t4025-hunk-header.sh index fa44e78869..35578f2bb9 100755 --- a/t/t4025-hunk-header.sh +++ b/t/t4025-hunk-header.sh @@ -37,7 +37,7 @@ test_expect_success 'hunk header truncation with an overly long line' ' echo " A $N$N$N$N$N$N$N$N$N2" && echo " L $N$N$N$N$N$N$N$N$N1" ) >expected && - test_cmp actual expected + test_cmp expected actual ' diff --git a/t/t4052-stat-output.sh b/t/t4052-stat-output.sh index 6e2cf933f7..28c053849a 100755 --- a/t/t4052-stat-output.sh +++ b/t/t4052-stat-output.sh @@ -44,42 +44,50 @@ show --stat log -1 --stat EOF -while read cmd args +cat >expect.60 <<-'EOF' + ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 + +EOF +cat >expect.6030 <<-'EOF' + ...aaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 + +EOF +cat >expect2.60 <<-'EOF' + ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 + + ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 + +EOF +cat >expect2.6030 <<-'EOF' + ...aaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 + + ...aaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 + +EOF +while read expect cmd args do - cat >expect <<-'EOF' - ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 + - EOF test_expect_success "$cmd --stat=width: a long name is given more room when the bar is short" ' git $cmd $args --stat=40 >output && grep " | " output >actual && - test_cmp expect actual + test_cmp $expect.60 actual ' test_expect_success "$cmd --stat-width=width with long name" ' git $cmd $args --stat-width=40 >output && grep " | " output >actual && - test_cmp expect actual + test_cmp $expect.60 actual ' - cat >expect <<-'EOF' - ...aaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 + - EOF test_expect_success "$cmd --stat=...,name-width with long name" ' git $cmd $args --stat=60,30 >output && grep " | " output >actual && - test_cmp expect actual + test_cmp $expect.6030 actual ' test_expect_success "$cmd --stat-name-width with long name" ' git $cmd $args --stat-name-width=30 >output && grep " | " output >actual && - test_cmp expect actual + test_cmp $expect.6030 actual ' done <<\EOF -format-patch -1 --stdout -diff HEAD^ HEAD --stat -show --stat -log -1 --stat +expect2 format-patch --cover-letter -1 --stdout +expect diff HEAD^ HEAD --stat +expect show --stat +expect log -1 --stat EOF @@ -97,6 +105,16 @@ test_expect_success 'preparation for big change tests' ' cat >expect72 <<'EOF' abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +EOF +test_expect_success "format-patch --cover-letter ignores COLUMNS (big change)" ' + COLUMNS=200 git format-patch -1 --stdout --cover-letter >output && + grep " | " output >actual && + test_cmp expect72 actual +' + +cat >expect72 <<'EOF' + abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ EOF cat >expect72-graph <<'EOF' | abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/t/t4066-diff-emit-delay.sh b/t/t4066-diff-emit-delay.sh new file mode 100755 index 0000000000..5df6b5e64e --- /dev/null +++ b/t/t4066-diff-emit-delay.sh @@ -0,0 +1,79 @@ +#!/bin/sh + +test_description='test combined/stat/moved interaction' +. ./test-lib.sh + +# This test covers a weird 3-way interaction between "--cc -p", which will run +# the combined diff code, along with "--stat", which will be computed as a +# first-parent stat during the combined diff, and "--color-moved", which +# enables the emitted_symbols list to store the diff in memory. + +test_expect_success 'set up history with a merge' ' + test_commit A && + test_commit B && + git checkout -b side HEAD^ && + test_commit C && + git merge -m M master && + test_commit D +' + +test_expect_success 'log --cc -p --stat --color-moved' ' + cat >expect <<-\EOF && + commit D + --- + D.t | 1 + + 1 file changed, 1 insertion(+) + + diff --git a/D.t b/D.t + new file mode 100644 + index 0000000..1784810 + --- /dev/null + +++ b/D.t + @@ -0,0 +1 @@ + +D + commit M + + B.t | 1 + + 1 file changed, 1 insertion(+) + commit C + --- + C.t | 1 + + 1 file changed, 1 insertion(+) + + diff --git a/C.t b/C.t + new file mode 100644 + index 0000000..3cc58df + --- /dev/null + +++ b/C.t + @@ -0,0 +1 @@ + +C + commit B + --- + B.t | 1 + + 1 file changed, 1 insertion(+) + + diff --git a/B.t b/B.t + new file mode 100644 + index 0000000..223b783 + --- /dev/null + +++ b/B.t + @@ -0,0 +1 @@ + +B + commit A + --- + A.t | 1 + + 1 file changed, 1 insertion(+) + + diff --git a/A.t b/A.t + new file mode 100644 + index 0000000..f70f10e + --- /dev/null + +++ b/A.t + @@ -0,0 +1 @@ + +A + EOF + git log --format="commit %s" --cc -p --stat --color-moved >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t4117-apply-reject.sh b/t/t4117-apply-reject.sh index d80187de94..f7de6f077a 100755 --- a/t/t4117-apply-reject.sh +++ b/t/t4117-apply-reject.sh @@ -72,7 +72,7 @@ test_expect_success 'apply with --reject should fail but update the file' ' rm -f file1.rej file2.rej && test_must_fail git apply --reject patch.1 && - test_cmp file1 expected && + test_cmp expected file1 && cat file1.rej && test_path_is_missing file2.rej @@ -85,7 +85,7 @@ test_expect_success 'apply with --reject should fail but update the file' ' test_must_fail git apply --reject patch.2 >rejects && test_path_is_missing file1 && - test_cmp file2 expected && + test_cmp expected file2 && cat file2.rej && test_path_is_missing file1.rej @@ -99,7 +99,7 @@ test_expect_success 'the same test with --verbose' ' test_must_fail git apply --reject --verbose patch.2 >rejects && test_path_is_missing file1 && - test_cmp file2 expected && + test_cmp expected file2 && cat file2.rej && test_path_is_missing file1.rej diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index 7e32237a2a..ff51e9e789 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -313,9 +313,9 @@ test_expect_success 'applying beyond EOF requires one non-blank context line' ' { echo a; echo; } >one && cp one expect && test_must_fail git apply --whitespace=fix patch && - test_cmp one expect && + test_cmp expect one && test_must_fail git apply --ignore-space-change --whitespace=fix patch && - test_cmp one expect + test_cmp expect one ' test_expect_success 'tons of blanks at EOF should not apply' ' @@ -342,10 +342,10 @@ test_expect_success 'missing blank line at end with --whitespace=fix' ' cp one saved-one && test_must_fail git apply patch && git apply --whitespace=fix patch && - test_cmp one expect && + test_cmp expect one && mv saved-one one && git apply --ignore-space-change --whitespace=fix patch && - test_cmp one expect + test_cmp expect one ' test_expect_success 'two missing blank lines at end with --whitespace=fix' ' @@ -360,11 +360,11 @@ test_expect_success 'two missing blank lines at end with --whitespace=fix' ' cp no-blank-lines one && test_must_fail git apply patch && git apply --whitespace=fix patch && - test_cmp one expect && + test_cmp expect one && mv no-blank-lines one && test_must_fail git apply patch && git apply --ignore-space-change --whitespace=fix patch && - test_cmp one expect + test_cmp expect one ' test_expect_success 'missing blank line at end, insert before end, --whitespace=fix' ' @@ -376,7 +376,7 @@ test_expect_success 'missing blank line at end, insert before end, --whitespace= echo a >one && test_must_fail git apply patch && git apply --whitespace=fix patch && - test_cmp one expect + test_cmp expect one ' test_expect_success 'shrink file with tons of missing blanks at end of file' ' @@ -392,10 +392,10 @@ test_expect_success 'shrink file with tons of missing blanks at end of file' ' cp no-blank-lines one && test_must_fail git apply patch && git apply --whitespace=fix patch && - test_cmp one expect && + test_cmp expect one && mv no-blank-lines one && git apply --ignore-space-change --whitespace=fix patch && - test_cmp one expect + test_cmp expect one ' test_expect_success 'missing blanks at EOF must only match blank lines' ' @@ -427,7 +427,7 @@ test_expect_success 'missing blank line should match context line with spaces' ' git add one && git apply --whitespace=fix patch && - test_cmp one expect + test_cmp expect one ' sed -e's/Z//' >one <<EOF @@ -447,7 +447,7 @@ test_expect_success 'same, but with the --ignore-space-option' ' git checkout-index -f one && git apply --ignore-space-change --whitespace=fix patch && - test_cmp one expect + test_cmp expect one ' test_expect_success 'same, but with CR-LF line endings && cr-at-eol set' ' @@ -464,7 +464,7 @@ test_expect_success 'same, but with CR-LF line endings && cr-at-eol set' ' mv save-one one && git apply --ignore-space-change --whitespace=fix patch && - test_cmp one expect + test_cmp expect one ' test_expect_success 'CR-LF line endings && add line && text=auto' ' @@ -478,7 +478,7 @@ test_expect_success 'CR-LF line endings && add line && text=auto' ' mv save-one one && echo "one text=auto" >.gitattributes && git apply patch && - test_cmp one expect + test_cmp expect one ' test_expect_success 'CR-LF line endings && change line && text=auto' ' @@ -491,7 +491,7 @@ test_expect_success 'CR-LF line endings && change line && text=auto' ' mv save-one one && echo "one text=auto" >.gitattributes && git apply patch && - test_cmp one expect + test_cmp expect one ' test_expect_success 'LF in repo, CRLF in worktree && change line && text=auto' ' @@ -503,7 +503,7 @@ test_expect_success 'LF in repo, CRLF in worktree && change line && text=auto' ' echo "one text=auto" >.gitattributes && git -c core.eol=CRLF apply patch && printf "b\r\n" >expect && - test_cmp one expect + test_cmp expect one ' test_expect_success 'whitespace=fix to expand' ' diff --git a/t/t4136-apply-check.sh b/t/t4136-apply-check.sh index 6d92872318..4c3f264a63 100755 --- a/t/t4136-apply-check.sh +++ b/t/t4136-apply-check.sh @@ -29,6 +29,18 @@ test_expect_success 'apply exits non-zero with no-op patch' ' test_must_fail git apply --check input ' +test_expect_success '`apply --recount` allows no-op patch' ' + echo 1 >1 && + git apply --recount --check <<-\EOF + diff --get a/1 b/1 + index 6696ea4..606eddd 100644 + --- a/1 + +++ b/1 + @@ -1,1 +1,1 @@ + 1 + EOF +' + test_expect_success 'invalid combination: create and copy' ' test_must_fail git apply --check - <<-\EOF diff --git a/1 b/2 diff --git a/t/t4138-apply-ws-expansion.sh b/t/t4138-apply-ws-expansion.sh index 0ffe33fbef..3b636a63a3 100755 --- a/t/t4138-apply-ws-expansion.sh +++ b/t/t4138-apply-ws-expansion.sh @@ -114,7 +114,7 @@ for t in 1 2 3 4 do test_expect_success 'apply with ws expansion (t=$t)' ' git apply patch$t.patch && - test_cmp test-$t expect-$t + test_cmp expect-$t test-$t ' done diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index 313222d0d6..55b7750ade 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh @@ -577,6 +577,71 @@ test_expect_success 'multiple identical conflicts' ' count_pre_post 0 0 ' +test_expect_success 'rerere with unexpected conflict markers does not crash' ' + git reset --hard && + + git checkout -b branch-1 master && + echo "bar" >test && + git add test && + git commit -q -m two && + + git reset --hard && + git checkout -b branch-2 master && + echo "foo" >test && + git add test && + git commit -q -a -m one && + + test_must_fail git merge branch-1 && + echo "<<<<<<< a" >test && + git rerere && + + git rerere clear +' + +test_expect_success 'rerere with inner conflict markers' ' + git reset --hard && + + git checkout -b A master && + echo "bar" >test && + git add test && + git commit -q -m two && + echo "baz" >test && + git add test && + git commit -q -m three && + + git reset --hard && + git checkout -b B master && + echo "foo" >test && + git add test && + git commit -q -a -m one && + + test_must_fail git merge A~ && + git add test && + git commit -q -m "will solve conflicts later" && + test_must_fail git merge A && + + echo "resolved" >test && + git add test && + git commit -q -m "solved conflict" && + + echo "resolved" >expect && + + git reset --hard HEAD~~ && + test_must_fail git merge A~ && + git add test && + git commit -q -m "will solve conflicts later" && + test_must_fail git merge A && + cat test >actual && + test_cmp expect actual && + + git add test && + git commit -m "rerere solved conflict" && + git reset --hard HEAD~ && + test_must_fail git merge A && + cat test >actual && + test_cmp expect actual +' + test_expect_success 'setup simple stage 1 handling' ' test_create_repo stage_1_handling && ( diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh index 978a8a66ff..7df8c3d4ec 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh @@ -621,4 +621,54 @@ test_expect_success 'trailer parsing not fooled by --- line' ' test_cmp expect actual ' +test_expect_success 'set up %S tests' ' + git checkout --orphan source-a && + test_commit one && + test_commit two && + git checkout -b source-b HEAD^ && + test_commit three +' + +test_expect_success 'log --format=%S paints branch names' ' + cat >expect <<-\EOF && + source-b + source-a + source-b + EOF + git log --format=%S source-a source-b >actual && + test_cmp expect actual +' + +test_expect_success 'log --format=%S paints tag names' ' + git tag -m tagged source-tag && + cat >expect <<-\EOF && + source-tag + source-a + source-tag + EOF + git log --format=%S source-tag source-a >actual && + test_cmp expect actual +' + +test_expect_success 'log --format=%S paints symmetric ranges' ' + cat >expect <<-\EOF && + source-b + source-a + EOF + git log --format=%S source-a...source-b >actual && + test_cmp expect actual +' + +test_expect_success '%S in git log --format works with other placeholders (part 1)' ' + git log --format="source-b %h" source-b >expect && + git log --format="%S %h" source-b >actual && + test_cmp expect actual +' + +test_expect_success '%S in git log --format works with other placeholders (part 2)' ' + git log --format="%h source-b" source-b >expect && + git log --format="%h %S" source-b >actual && + test_cmp expect actual +' + test_done diff --git a/t/t4209-log-pickaxe.sh b/t/t4209-log-pickaxe.sh index 844df760f7..5d06f5f45e 100755 --- a/t/t4209-log-pickaxe.sh +++ b/t/t4209-log-pickaxe.sh @@ -106,4 +106,39 @@ test_expect_success 'log -S --no-textconv (missing textconv tool)' ' rm .gitattributes ' +test_expect_success 'setup log -[GS] binary & --text' ' + git checkout --orphan GS-binary-and-text && + git read-tree --empty && + printf "a\na\0a\n" >data.bin && + git add data.bin && + git commit -m "create binary file" data.bin && + printf "a\na\0a\n" >>data.bin && + git commit -m "modify binary file" data.bin && + git rm data.bin && + git commit -m "delete binary file" data.bin && + git log >full-log +' + +test_expect_success 'log -G ignores binary files' ' + git log -Ga >log && + test_must_be_empty log +' + +test_expect_success 'log -G looks into binary files with -a' ' + git log -a -Ga >log && + test_cmp log full-log +' + +test_expect_success 'log -G looks into binary files with textconv filter' ' + test_when_finished "rm .gitattributes" && + echo "* diff=bin" >.gitattributes && + git -c diff.bin.textconv=cat log -Ga >log && + test_cmp log full-log +' + +test_expect_success 'log -S looks into binary files' ' + git log -Sa >log && + test_cmp log full-log +' + test_done diff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh index ef1322148e..bd5fe4d148 100755 --- a/t/t4211-line-log.sh +++ b/t/t4211-line-log.sh @@ -25,7 +25,7 @@ canned_test_failure () { test_bad_opts () { test_expect_success "invalid args: $1" " test_must_fail git log $1 2>errors && - grep '$2' errors + test_i18ngrep '$2' errors " } diff --git a/t/t4256-am-format-flowed.sh b/t/t4256-am-format-flowed.sh new file mode 100755 index 0000000000..6340310e9a --- /dev/null +++ b/t/t4256-am-format-flowed.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +test_description='test format=flowed support of git am' + +. ./test-lib.sh + +test_expect_success 'setup' ' + cp "$TEST_DIRECTORY/t4256/1/mailinfo.c.orig" mailinfo.c && + git add mailinfo.c && + git commit -m initial +' + +test_expect_success 'am with format=flowed' ' + git am <"$TEST_DIRECTORY/t4256/1/patch" >stdout 2>stderr && + test_i18ngrep "warning: Patch sent with format=flowed" stderr && + test_cmp "$TEST_DIRECTORY/t4256/1/mailinfo.c" mailinfo.c +' + +test_done diff --git a/t/t4256/1/mailinfo.c b/t/t4256/1/mailinfo.c new file mode 100644 index 0000000000..b395adbdf2 --- /dev/null +++ b/t/t4256/1/mailinfo.c @@ -0,0 +1,1245 @@ +#include "cache.h" +#include "config.h" +#include "utf8.h" +#include "strbuf.h" +#include "mailinfo.h" + +static void cleanup_space(struct strbuf *sb) +{ + size_t pos, cnt; + for (pos = 0; pos < sb->len; pos++) { + if (isspace(sb->buf[pos])) { + sb->buf[pos] = ' '; + for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++); + strbuf_remove(sb, pos + 1, cnt); + } + } +} + +static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email) +{ + struct strbuf *src = name; + if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') || + strchr(name->buf, '<') || strchr(name->buf, '>')) + src = email; + else if (name == out) + return; + strbuf_reset(out); + strbuf_addbuf(out, src); +} + +static void parse_bogus_from(struct mailinfo *mi, const struct strbuf *line) +{ + /* John Doe <johndoe> */ + + char *bra, *ket; + /* This is fallback, so do not bother if we already have an + * e-mail address. + */ + if (mi->email.len) + return; + + bra = strchr(line->buf, '<'); + if (!bra) + return; + ket = strchr(bra, '>'); + if (!ket) + return; + + strbuf_reset(&mi->email); + strbuf_add(&mi->email, bra + 1, ket - bra - 1); + + strbuf_reset(&mi->name); + strbuf_add(&mi->name, line->buf, bra - line->buf); + strbuf_trim(&mi->name); + get_sane_name(&mi->name, &mi->name, &mi->email); +} + +static const char *unquote_comment(struct strbuf *outbuf, const char *in) +{ + int c; + int take_next_literally = 0; + + strbuf_addch(outbuf, '('); + + while ((c = *in++) != 0) { + if (take_next_literally == 1) { + take_next_literally = 0; + } else { + switch (c) { + case '\\': + take_next_literally = 1; + continue; + case '(': + in = unquote_comment(outbuf, in); + continue; + case ')': + strbuf_addch(outbuf, ')'); + return in; + } + } + + strbuf_addch(outbuf, c); + } + + return in; +} + +static const char *unquote_quoted_string(struct strbuf *outbuf, const char *in) +{ + int c; + int take_next_literally = 0; + + while ((c = *in++) != 0) { + if (take_next_literally == 1) { + take_next_literally = 0; + } else { + switch (c) { + case '\\': + take_next_literally = 1; + continue; + case '"': + return in; + } + } + + strbuf_addch(outbuf, c); + } + + return in; +} + +static void unquote_quoted_pair(struct strbuf *line) +{ + struct strbuf outbuf; + const char *in = line->buf; + int c; + + strbuf_init(&outbuf, line->len); + + while ((c = *in++) != 0) { + switch (c) { + case '"': + in = unquote_quoted_string(&outbuf, in); + continue; + case '(': + in = unquote_comment(&outbuf, in); + continue; + } + + strbuf_addch(&outbuf, c); + } + + strbuf_swap(&outbuf, line); + strbuf_release(&outbuf); + +} + +static void handle_from(struct mailinfo *mi, const struct strbuf *from) +{ + char *at; + size_t el; + struct strbuf f; + + strbuf_init(&f, from->len); + strbuf_addbuf(&f, from); + + unquote_quoted_pair(&f); + + at = strchr(f.buf, '@'); + if (!at) { + parse_bogus_from(mi, from); + goto out; + } + + /* + * If we already have one email, don't take any confusing lines + */ + if (mi->email.len && strchr(at + 1, '@')) + goto out; + + /* Pick up the string around '@', possibly delimited with <> + * pair; that is the email part. + */ + while (at > f.buf) { + char c = at[-1]; + if (isspace(c)) + break; + if (c == '<') { + at[-1] = ' '; + break; + } + at--; + } + el = strcspn(at, " \n\t\r\v\f>"); + strbuf_reset(&mi->email); + strbuf_add(&mi->email, at, el); + strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0)); + + /* The remainder is name. It could be + * + * - "John Doe <john.doe@xz>" (a), or + * - "john.doe@xz (John Doe)" (b), or + * - "John (zzz) Doe <john.doe@xz> (Comment)" (c) + * + * but we have removed the email part, so + * + * - remove extra spaces which could stay after email (case 'c'), and + * - trim from both ends, possibly removing the () pair at the end + * (cases 'a' and 'b'). + */ + cleanup_space(&f); + strbuf_trim(&f); + if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') { + strbuf_remove(&f, 0, 1); + strbuf_setlen(&f, f.len - 1); + } + + get_sane_name(&mi->name, &f, &mi->email); +out: + strbuf_release(&f); +} + +static void handle_header(struct strbuf **out, const struct strbuf *line) +{ + if (!*out) { + *out = xmalloc(sizeof(struct strbuf)); + strbuf_init(*out, line->len); + } else + strbuf_reset(*out); + + strbuf_addbuf(*out, line); +} + +/* NOTE NOTE NOTE. We do not claim we do full MIME. We just attempt + * to have enough heuristics to grok MIME encoded patches often found + * on our mailing lists. For example, we do not even treat header lines + * case insensitively. + */ + +static int slurp_attr(const char *line, const char *name, struct strbuf *attr) +{ + const char *ends, *ap = strcasestr(line, name); + size_t sz; + + strbuf_setlen(attr, 0); + if (!ap) + return 0; + ap += strlen(name); + if (*ap == '"') { + ap++; + ends = "\""; + } + else + ends = "; \t"; + sz = strcspn(ap, ends); + strbuf_add(attr, ap, sz); + return 1; +} + +static int has_attr_value(const char *line, const char *name, const char *value) +{ + struct strbuf sb = STRBUF_INIT; + int rc = slurp_attr(line, name, &sb) && !strcasecmp(sb.buf, value); + strbuf_release(&sb); + return rc; +} + +static void handle_content_type(struct mailinfo *mi, struct strbuf *line) +{ + struct strbuf *boundary = xmalloc(sizeof(struct strbuf)); + strbuf_init(boundary, line->len); + + mi->format_flowed = has_attr_value(line->buf, "format=", "flowed"); + mi->delsp = has_attr_value(line->buf, "delsp=", "yes"); + + if (slurp_attr(line->buf, "boundary=", boundary)) { + strbuf_insert(boundary, 0, "--", 2); + if (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) { + error("Too many boundaries to handle"); + mi->input_error = -1; + mi->content_top = &mi->content[MAX_BOUNDARIES] - 1; + return; + } + *(mi->content_top) = boundary; + boundary = NULL; + } + slurp_attr(line->buf, "charset=", &mi->charset); + + if (boundary) { + strbuf_release(boundary); + free(boundary); + } +} + +static void handle_content_transfer_encoding(struct mailinfo *mi, + const struct strbuf *line) +{ + if (strcasestr(line->buf, "base64")) + mi->transfer_encoding = TE_BASE64; + else if (strcasestr(line->buf, "quoted-printable")) + mi->transfer_encoding = TE_QP; + else + mi->transfer_encoding = TE_DONTCARE; +} + +static int is_multipart_boundary(struct mailinfo *mi, const struct strbuf *line) +{ + struct strbuf *content_top = *(mi->content_top); + + return ((content_top->len <= line->len) && + !memcmp(line->buf, content_top->buf, content_top->len)); +} + +static void cleanup_subject(struct mailinfo *mi, struct strbuf *subject) +{ + size_t at = 0; + + while (at < subject->len) { + char *pos; + size_t remove; + + switch (subject->buf[at]) { + case 'r': case 'R': + if (subject->len <= at + 3) + break; + if ((subject->buf[at + 1] == 'e' || + subject->buf[at + 1] == 'E') && + subject->buf[at + 2] == ':') { + strbuf_remove(subject, at, 3); + continue; + } + at++; + break; + case ' ': case '\t': case ':': + strbuf_remove(subject, at, 1); + continue; + case '[': + pos = strchr(subject->buf + at, ']'); + if (!pos) + break; + remove = pos - subject->buf + at + 1; + if (!mi->keep_non_patch_brackets_in_subject || + (7 <= remove && + memmem(subject->buf + at, remove, "PATCH", 5))) + strbuf_remove(subject, at, remove); + else { + at += remove; + /* + * If the input had a space after the ], keep + * it. We don't bother with finding the end of + * the space, since we later normalize it + * anyway. + */ + if (isspace(subject->buf[at])) + at += 1; + } + continue; + } + break; + } + strbuf_trim(subject); +} + +#define MAX_HDR_PARSED 10 +static const char *header[MAX_HDR_PARSED] = { + "From","Subject","Date", +}; + +static inline int cmp_header(const struct strbuf *line, const char *hdr) +{ + int len = strlen(hdr); + return !strncasecmp(line->buf, hdr, len) && line->len > len && + line->buf[len] == ':' && isspace(line->buf[len + 1]); +} + +static int is_format_patch_separator(const char *line, int len) +{ + static const char SAMPLE[] = + "From e6807f3efca28b30decfecb1732a56c7db1137ee Mon Sep 17 00:00:00 2001\n"; + const char *cp; + + if (len != strlen(SAMPLE)) + return 0; + if (!skip_prefix(line, "From ", &cp)) + return 0; + if (strspn(cp, "0123456789abcdef") != 40) + return 0; + cp += 40; + return !memcmp(SAMPLE + (cp - line), cp, strlen(SAMPLE) - (cp - line)); +} + +static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047) +{ + const char *in = q_seg->buf; + int c; + struct strbuf *out = xmalloc(sizeof(struct strbuf)); + strbuf_init(out, q_seg->len); + + while ((c = *in++) != 0) { + if (c == '=') { + int ch, d = *in; + if (d == '\n' || !d) + break; /* drop trailing newline */ + ch = hex2chr(in); + if (ch >= 0) { + strbuf_addch(out, ch); + in += 2; + continue; + } + /* garbage -- fall through */ + } + if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */ + c = 0x20; + strbuf_addch(out, c); + } + return out; +} + +static struct strbuf *decode_b_segment(const struct strbuf *b_seg) +{ + /* Decode in..ep, possibly in-place to ot */ + int c, pos = 0, acc = 0; + const char *in = b_seg->buf; + struct strbuf *out = xmalloc(sizeof(struct strbuf)); + strbuf_init(out, b_seg->len); + + while ((c = *in++) != 0) { + if (c == '+') + c = 62; + else if (c == '/') + c = 63; + else if ('A' <= c && c <= 'Z') + c -= 'A'; + else if ('a' <= c && c <= 'z') + c -= 'a' - 26; + else if ('0' <= c && c <= '9') + c -= '0' - 52; + else + continue; /* garbage */ + switch (pos++) { + case 0: + acc = (c << 2); + break; + case 1: + strbuf_addch(out, (acc | (c >> 4))); + acc = (c & 15) << 4; + break; + case 2: + strbuf_addch(out, (acc | (c >> 2))); + acc = (c & 3) << 6; + break; + case 3: + strbuf_addch(out, (acc | c)); + acc = pos = 0; + break; + } + } + return out; +} + +static int convert_to_utf8(struct mailinfo *mi, + struct strbuf *line, const char *charset) +{ + char *out; + + if (!mi->metainfo_charset || !charset || !*charset) + return 0; + + if (same_encoding(mi->metainfo_charset, charset)) + return 0; + out = reencode_string(line->buf, mi->metainfo_charset, charset); + if (!out) { + mi->input_error = -1; + return error("cannot convert from %s to %s", + charset, mi->metainfo_charset); + } + strbuf_attach(line, out, strlen(out), strlen(out)); + return 0; +} + +static void decode_header(struct mailinfo *mi, struct strbuf *it) +{ + char *in, *ep, *cp; + struct strbuf outbuf = STRBUF_INIT, *dec; + struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT; + int found_error = 1; /* pessimism */ + + in = it->buf; + while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) { + int encoding; + strbuf_reset(&charset_q); + strbuf_reset(&piecebuf); + + if (in != ep) { + /* + * We are about to process an encoded-word + * that begins at ep, but there is something + * before the encoded word. + */ + char *scan; + for (scan = in; scan < ep; scan++) + if (!isspace(*scan)) + break; + + if (scan != ep || in == it->buf) { + /* + * We should not lose that "something", + * unless we have just processed an + * encoded-word, and there is only LWS + * before the one we are about to process. + */ + strbuf_add(&outbuf, in, ep - in); + } + } + /* E.g. + * ep : "=?iso-2022-jp?B?GyR...?= foo" + * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz" + */ + ep += 2; + + if (ep - it->buf >= it->len || !(cp = strchr(ep, '?'))) + goto release_return; + + if (cp + 3 - it->buf > it->len) + goto release_return; + strbuf_add(&charset_q, ep, cp - ep); + + encoding = cp[1]; + if (!encoding || cp[2] != '?') + goto release_return; + ep = strstr(cp + 3, "?="); + if (!ep) + goto release_return; + strbuf_add(&piecebuf, cp + 3, ep - cp - 3); + switch (tolower(encoding)) { + default: + goto release_return; + case 'b': + dec = decode_b_segment(&piecebuf); + break; + case 'q': + dec = decode_q_segment(&piecebuf, 1); + break; + } + if (convert_to_utf8(mi, dec, charset_q.buf)) + goto release_return; + + strbuf_addbuf(&outbuf, dec); + strbuf_release(dec); + free(dec); + in = ep + 2; + } + strbuf_addstr(&outbuf, in); + strbuf_reset(it); + strbuf_addbuf(it, &outbuf); + found_error = 0; +release_return: + strbuf_release(&outbuf); + strbuf_release(&charset_q); + strbuf_release(&piecebuf); + + if (found_error) + mi->input_error = -1; +} + +static int check_header(struct mailinfo *mi, + const struct strbuf *line, + struct strbuf *hdr_data[], int overwrite) +{ + int i, ret = 0, len; + struct strbuf sb = STRBUF_INIT; + + /* search for the interesting parts */ + for (i = 0; header[i]; i++) { + int len = strlen(header[i]); + if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) { + /* Unwrap inline B and Q encoding, and optionally + * normalize the meta information to utf8. + */ + strbuf_add(&sb, line->buf + len + 2, line->len - len - 2); + decode_header(mi, &sb); + handle_header(&hdr_data[i], &sb); + ret = 1; + goto check_header_out; + } + } + + /* Content stuff */ + if (cmp_header(line, "Content-Type")) { + len = strlen("Content-Type: "); + strbuf_add(&sb, line->buf + len, line->len - len); + decode_header(mi, &sb); + strbuf_insert(&sb, 0, "Content-Type: ", len); + handle_content_type(mi, &sb); + ret = 1; + goto check_header_out; + } + if (cmp_header(line, "Content-Transfer-Encoding")) { + len = strlen("Content-Transfer-Encoding: "); + strbuf_add(&sb, line->buf + len, line->len - len); + decode_header(mi, &sb); + handle_content_transfer_encoding(mi, &sb); + ret = 1; + goto check_header_out; + } + if (cmp_header(line, "Message-Id")) { + len = strlen("Message-Id: "); + strbuf_add(&sb, line->buf + len, line->len - len); + decode_header(mi, &sb); + if (mi->add_message_id) + mi->message_id = strbuf_detach(&sb, NULL); + ret = 1; + goto check_header_out; + } + +check_header_out: + strbuf_release(&sb); + return ret; +} + +/* + * Returns 1 if the given line or any line beginning with the given line is an + * in-body header (that is, check_header will succeed when passed + * mi->s_hdr_data). + */ +static int is_inbody_header(const struct mailinfo *mi, + const struct strbuf *line) +{ + int i; + for (i = 0; header[i]; i++) + if (!mi->s_hdr_data[i] && cmp_header(line, header[i])) + return 1; + return 0; +} + +static void decode_transfer_encoding(struct mailinfo *mi, struct strbuf *line) +{ + struct strbuf *ret; + + switch (mi->transfer_encoding) { + case TE_QP: + ret = decode_q_segment(line, 0); + break; + case TE_BASE64: + ret = decode_b_segment(line); + break; + case TE_DONTCARE: + default: + return; + } + strbuf_reset(line); + strbuf_addbuf(line, ret); + strbuf_release(ret); + free(ret); +} + +static inline int patchbreak(const struct strbuf *line) +{ + size_t i; + + /* Beginning of a "diff -" header? */ + if (starts_with(line->buf, "diff -")) + return 1; + + /* CVS "Index: " line? */ + if (starts_with(line->buf, "Index: ")) + return 1; + + /* + * "--- <filename>" starts patches without headers + * "---<sp>*" is a manual separator + */ + if (line->len < 4) + return 0; + + if (starts_with(line->buf, "---")) { + /* space followed by a filename? */ + if (line->buf[3] == ' ' && !isspace(line->buf[4])) + return 1; + /* Just whitespace? */ + for (i = 3; i < line->len; i++) { + unsigned char c = line->buf[i]; + if (c == '\n') + return 1; + if (!isspace(c)) + break; + } + return 0; + } + return 0; +} + +static int is_scissors_line(const char *line) +{ + const char *c; + int scissors = 0, gap = 0; + const char *first_nonblank = NULL, *last_nonblank = NULL; + int visible, perforation = 0, in_perforation = 0; + + for (c = line; *c; c++) { + if (isspace(*c)) { + if (in_perforation) { + perforation++; + gap++; + } + continue; + } + last_nonblank = c; + if (first_nonblank == NULL) + first_nonblank = c; + if (*c == '-') { + in_perforation = 1; + perforation++; + continue; + } + if ((!memcmp(c, ">8", 2) || !memcmp(c, "8<", 2) || + !memcmp(c, ">%", 2) || !memcmp(c, "%<", 2))) { + in_perforation = 1; + perforation += 2; + scissors += 2; + c++; + continue; + } + in_perforation = 0; + } + + /* + * The mark must be at least 8 bytes long (e.g. "-- >8 --"). + * Even though there can be arbitrary cruft on the same line + * (e.g. "cut here"), in order to avoid misidentification, the + * perforation must occupy more than a third of the visible + * width of the line, and dashes and scissors must occupy more + * than half of the perforation. + */ + + if (first_nonblank && last_nonblank) + visible = last_nonblank - first_nonblank + 1; + else + visible = 0; + return (scissors && 8 <= visible && + visible < perforation * 3 && + gap * 2 < perforation); +} + +static void flush_inbody_header_accum(struct mailinfo *mi) +{ + if (!mi->inbody_header_accum.len) + return; + if (!check_header(mi, &mi->inbody_header_accum, mi->s_hdr_data, 0)) + BUG("inbody_header_accum, if not empty, must always contain a valid in-body header"); + strbuf_reset(&mi->inbody_header_accum); +} + +static int check_inbody_header(struct mailinfo *mi, const struct strbuf *line) +{ + if (mi->inbody_header_accum.len && + (line->buf[0] == ' ' || line->buf[0] == '\t')) { + if (mi->use_scissors && is_scissors_line(line->buf)) { + /* + * This is a scissors line; do not consider this line + * as a header continuation line. + */ + flush_inbody_header_accum(mi); + return 0; + } + strbuf_strip_suffix(&mi->inbody_header_accum, "\n"); + strbuf_addbuf(&mi->inbody_header_accum, line); + return 1; + } + + flush_inbody_header_accum(mi); + + if (starts_with(line->buf, ">From") && isspace(line->buf[5])) + return is_format_patch_separator(line->buf + 1, line->len - 1); + if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) { + int i; + for (i = 0; header[i]; i++) + if (!strcmp("Subject", header[i])) { + handle_header(&mi->s_hdr_data[i], line); + return 1; + } + return 0; + } + if (is_inbody_header(mi, line)) { + strbuf_addbuf(&mi->inbody_header_accum, line); + return 1; + } + return 0; +} + +static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line) +{ + assert(!mi->filter_stage); + + if (mi->header_stage) { + if (!line->len || (line->len == 1 && line->buf[0] == '\n')) { + if (mi->inbody_header_accum.len) { + flush_inbody_header_accum(mi); + mi->header_stage = 0; + } + return 0; + } + } + + if (mi->use_inbody_headers && mi->header_stage) { + mi->header_stage = check_inbody_header(mi, line); + if (mi->header_stage) + return 0; + } else + /* Only trim the first (blank) line of the commit message + * when ignoring in-body headers. + */ + mi->header_stage = 0; + + /* normalize the log message to UTF-8. */ + if (convert_to_utf8(mi, line, mi->charset.buf)) + return 0; /* mi->input_error already set */ + + if (mi->use_scissors && is_scissors_line(line->buf)) { + int i; + + strbuf_setlen(&mi->log_message, 0); + mi->header_stage = 1; + + /* + * We may have already read "secondary headers"; purge + * them to give ourselves a clean restart. + */ + for (i = 0; header[i]; i++) { + if (mi->s_hdr_data[i]) + strbuf_release(mi->s_hdr_data[i]); + mi->s_hdr_data[i] = NULL; + } + return 0; + } + + if (patchbreak(line)) { + if (mi->message_id) + strbuf_addf(&mi->log_message, + "Message-Id: %s\n", mi->message_id); + return 1; + } + + strbuf_addbuf(&mi->log_message, line); + return 0; +} + +static void handle_patch(struct mailinfo *mi, const struct strbuf *line) +{ + fwrite(line->buf, 1, line->len, mi->patchfile); + mi->patch_lines++; +} + +static void handle_filter(struct mailinfo *mi, struct strbuf *line) +{ + switch (mi->filter_stage) { + case 0: + if (!handle_commit_msg(mi, line)) + break; + mi->filter_stage++; + /* fallthrough */ + case 1: + handle_patch(mi, line); + break; + } +} + +static int is_rfc2822_header(const struct strbuf *line) +{ + /* + * The section that defines the loosest possible + * field name is "3.6.8 Optional fields". + * + * optional-field = field-name ":" unstructured CRLF + * field-name = 1*ftext + * ftext = %d33-57 / %59-126 + */ + int ch; + char *cp = line->buf; + + /* Count mbox From headers as headers */ + if (starts_with(cp, "From ") || starts_with(cp, ">From ")) + return 1; + + while ((ch = *cp++)) { + if (ch == ':') + return 1; + if ((33 <= ch && ch <= 57) || + (59 <= ch && ch <= 126)) + continue; + break; + } + return 0; +} + +static int read_one_header_line(struct strbuf *line, FILE *in) +{ + struct strbuf continuation = STRBUF_INIT; + + /* Get the first part of the line. */ + if (strbuf_getline_lf(line, in)) + return 0; + + /* + * Is it an empty line or not a valid rfc2822 header? + * If so, stop here, and return false ("not a header") + */ + strbuf_rtrim(line); + if (!line->len || !is_rfc2822_header(line)) { + /* Re-add the newline */ + strbuf_addch(line, '\n'); + return 0; + } + + /* + * Now we need to eat all the continuation lines.. + * Yuck, 2822 header "folding" + */ + for (;;) { + int peek; + + peek = fgetc(in); + if (peek == EOF) + break; + ungetc(peek, in); + if (peek != ' ' && peek != '\t') + break; + if (strbuf_getline_lf(&continuation, in)) + break; + continuation.buf[0] = ' '; + strbuf_rtrim(&continuation); + strbuf_addbuf(line, &continuation); + } + strbuf_release(&continuation); + + return 1; +} + +static int find_boundary(struct mailinfo *mi, struct strbuf *line) +{ + while (!strbuf_getline_lf(line, mi->input)) { + if (*(mi->content_top) && is_multipart_boundary(mi, line)) + return 1; + } + return 0; +} + +static int handle_boundary(struct mailinfo *mi, struct strbuf *line) +{ + struct strbuf newline = STRBUF_INIT; + + strbuf_addch(&newline, '\n'); +again: + if (line->len >= (*(mi->content_top))->len + 2 && + !memcmp(line->buf + (*(mi->content_top))->len, "--", 2)) { + /* we hit an end boundary */ + /* pop the current boundary off the stack */ + strbuf_release(*(mi->content_top)); + FREE_AND_NULL(*(mi->content_top)); + + /* technically won't happen as is_multipart_boundary() + will fail first. But just in case.. + */ + if (--mi->content_top < mi->content) { + error("Detected mismatched boundaries, can't recover"); + mi->input_error = -1; + mi->content_top = mi->content; + strbuf_release(&newline); + return 0; + } + handle_filter(mi, &newline); + strbuf_release(&newline); + if (mi->input_error) + return 0; + + /* skip to the next boundary */ + if (!find_boundary(mi, line)) + return 0; + goto again; + } + + /* set some defaults */ + mi->transfer_encoding = TE_DONTCARE; + strbuf_reset(&mi->charset); + + /* slurp in this section's info */ + while (read_one_header_line(line, mi->input)) + check_header(mi, line, mi->p_hdr_data, 0); + + strbuf_release(&newline); + /* replenish line */ + if (strbuf_getline_lf(line, mi->input)) + return 0; + strbuf_addch(line, '\n'); + return 1; +} + +static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line, + struct strbuf *prev) +{ + size_t len = line->len; + const char *rest; + + if (!mi->format_flowed) { + handle_filter(mi, line); + return; + } + + if (line->buf[len - 1] == '\n') { + len--; + if (len && line->buf[len - 1] == '\r') + len--; + } + + /* Keep signature separator as-is. */ + if (skip_prefix(line->buf, "-- ", &rest) && rest - line->buf == len) { + if (prev->len) { + handle_filter(mi, prev); + strbuf_reset(prev); + } + handle_filter(mi, line); + return; + } + + /* Unstuff space-stuffed line. */ + if (len && line->buf[0] == ' ') { + strbuf_remove(line, 0, 1); + len--; + } + + /* Save flowed line for later, but without the soft line break. */ + if (len && line->buf[len - 1] == ' ') { + strbuf_add(prev, line->buf, len - !!mi->delsp); + return; + } + + /* Prepend any previous partial lines */ + strbuf_insert(line, 0, prev->buf, prev->len); + strbuf_reset(prev); + + handle_filter(mi, line); +} + +static void handle_body(struct mailinfo *mi, struct strbuf *line) +{ + struct strbuf prev = STRBUF_INIT; + + /* Skip up to the first boundary */ + if (*(mi->content_top)) { + if (!find_boundary(mi, line)) + goto handle_body_out; + } + + do { + /* process any boundary lines */ + if (*(mi->content_top) && is_multipart_boundary(mi, line)) { + /* flush any leftover */ + if (prev.len) { + handle_filter(mi, &prev); + strbuf_reset(&prev); + } + if (!handle_boundary(mi, line)) + goto handle_body_out; + } + + /* Unwrap transfer encoding */ + decode_transfer_encoding(mi, line); + + switch (mi->transfer_encoding) { + case TE_BASE64: + case TE_QP: + { + struct strbuf **lines, **it, *sb; + + /* Prepend any previous partial lines */ + strbuf_insert(line, 0, prev.buf, prev.len); + strbuf_reset(&prev); + + /* + * This is a decoded line that may contain + * multiple new lines. Pass only one chunk + * at a time to handle_filter() + */ + lines = strbuf_split(line, '\n'); + for (it = lines; (sb = *it); it++) { + if (*(it + 1) == NULL) /* The last line */ + if (sb->buf[sb->len - 1] != '\n') { + /* Partial line, save it for later. */ + strbuf_addbuf(&prev, sb); + break; + } + handle_filter_flowed(mi, sb, &prev); + } + /* + * The partial chunk is saved in "prev" and will be + * appended by the next iteration of read_line_with_nul(). + */ + strbuf_list_free(lines); + break; + } + default: + handle_filter_flowed(mi, line, &prev); + } + + if (mi->input_error) + break; + } while (!strbuf_getwholeline(line, mi->input, '\n')); + + if (prev.len) + handle_filter(mi, &prev); + + flush_inbody_header_accum(mi); + +handle_body_out: + strbuf_release(&prev); +} + +static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data) +{ + const char *sp = data->buf; + while (1) { + char *ep = strchr(sp, '\n'); + int len; + if (!ep) + len = strlen(sp); + else + len = ep - sp; + fprintf(fout, "%s: %.*s\n", hdr, len, sp); + if (!ep) + break; + sp = ep + 1; + } +} + +static void handle_info(struct mailinfo *mi) +{ + struct strbuf *hdr; + int i; + + for (i = 0; header[i]; i++) { + /* only print inbody headers if we output a patch file */ + if (mi->patch_lines && mi->s_hdr_data[i]) + hdr = mi->s_hdr_data[i]; + else if (mi->p_hdr_data[i]) + hdr = mi->p_hdr_data[i]; + else + continue; + + if (!strcmp(header[i], "Subject")) { + if (!mi->keep_subject) { + cleanup_subject(mi, hdr); + cleanup_space(hdr); + } + output_header_lines(mi->output, "Subject", hdr); + } else if (!strcmp(header[i], "From")) { + cleanup_space(hdr); + handle_from(mi, hdr); + fprintf(mi->output, "Author: %s\n", mi->name.buf); + fprintf(mi->output, "Email: %s\n", mi->email.buf); + } else { + cleanup_space(hdr); + fprintf(mi->output, "%s: %s\n", header[i], hdr->buf); + } + } + fprintf(mi->output, "\n"); +} + +int mailinfo(struct mailinfo *mi, const char *msg, const char *patch) +{ + FILE *cmitmsg; + int peek; + struct strbuf line = STRBUF_INIT; + + cmitmsg = fopen(msg, "w"); + if (!cmitmsg) { + perror(msg); + return -1; + } + mi->patchfile = fopen(patch, "w"); + if (!mi->patchfile) { + perror(patch); + fclose(cmitmsg); + return -1; + } + + mi->p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->p_hdr_data))); + mi->s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->s_hdr_data))); + + do { + peek = fgetc(mi->input); + if (peek == EOF) { + fclose(cmitmsg); + return error("empty patch: '%s'", patch); + } + } while (isspace(peek)); + ungetc(peek, mi->input); + + /* process the email header */ + while (read_one_header_line(&line, mi->input)) + check_header(mi, &line, mi->p_hdr_data, 1); + + handle_body(mi, &line); + fwrite(mi->log_message.buf, 1, mi->log_message.len, cmitmsg); + fclose(cmitmsg); + fclose(mi->patchfile); + + handle_info(mi); + strbuf_release(&line); + return mi->input_error; +} + +static int git_mailinfo_config(const char *var, const char *value, void *mi_) +{ + struct mailinfo *mi = mi_; + + if (!starts_with(var, "mailinfo.")) + return git_default_config(var, value, NULL); + if (!strcmp(var, "mailinfo.scissors")) { + mi->use_scissors = git_config_bool(var, value); + return 0; + } + /* perhaps others here */ + return 0; +} + +void setup_mailinfo(struct mailinfo *mi) +{ + memset(mi, 0, sizeof(*mi)); + strbuf_init(&mi->name, 0); + strbuf_init(&mi->email, 0); + strbuf_init(&mi->charset, 0); + strbuf_init(&mi->log_message, 0); + strbuf_init(&mi->inbody_header_accum, 0); + mi->header_stage = 1; + mi->use_inbody_headers = 1; + mi->content_top = mi->content; + git_config(git_mailinfo_config, mi); +} + +void clear_mailinfo(struct mailinfo *mi) +{ + int i; + + strbuf_release(&mi->name); + strbuf_release(&mi->email); + strbuf_release(&mi->charset); + strbuf_release(&mi->inbody_header_accum); + free(mi->message_id); + + if (mi->p_hdr_data) + for (i = 0; mi->p_hdr_data[i]; i++) + strbuf_release(mi->p_hdr_data[i]); + free(mi->p_hdr_data); + if (mi->s_hdr_data) + for (i = 0; mi->s_hdr_data[i]; i++) + strbuf_release(mi->s_hdr_data[i]); + free(mi->s_hdr_data); + + while (mi->content < mi->content_top) { + free(*(mi->content_top)); + mi->content_top--; + } + + strbuf_release(&mi->log_message); +} diff --git a/t/t4256/1/mailinfo.c.orig b/t/t4256/1/mailinfo.c.orig new file mode 100644 index 0000000000..3281a37d51 --- /dev/null +++ b/t/t4256/1/mailinfo.c.orig @@ -0,0 +1,1185 @@ +#include "cache.h" +#include "config.h" +#include "utf8.h" +#include "strbuf.h" +#include "mailinfo.h" + +static void cleanup_space(struct strbuf *sb) +{ + size_t pos, cnt; + for (pos = 0; pos < sb->len; pos++) { + if (isspace(sb->buf[pos])) { + sb->buf[pos] = ' '; + for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++); + strbuf_remove(sb, pos + 1, cnt); + } + } +} + +static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email) +{ + struct strbuf *src = name; + if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') || + strchr(name->buf, '<') || strchr(name->buf, '>')) + src = email; + else if (name == out) + return; + strbuf_reset(out); + strbuf_addbuf(out, src); +} + +static void parse_bogus_from(struct mailinfo *mi, const struct strbuf *line) +{ + /* John Doe <johndoe> */ + + char *bra, *ket; + /* This is fallback, so do not bother if we already have an + * e-mail address. + */ + if (mi->email.len) + return; + + bra = strchr(line->buf, '<'); + if (!bra) + return; + ket = strchr(bra, '>'); + if (!ket) + return; + + strbuf_reset(&mi->email); + strbuf_add(&mi->email, bra + 1, ket - bra - 1); + + strbuf_reset(&mi->name); + strbuf_add(&mi->name, line->buf, bra - line->buf); + strbuf_trim(&mi->name); + get_sane_name(&mi->name, &mi->name, &mi->email); +} + +static const char *unquote_comment(struct strbuf *outbuf, const char *in) +{ + int c; + int take_next_literally = 0; + + strbuf_addch(outbuf, '('); + + while ((c = *in++) != 0) { + if (take_next_literally == 1) { + take_next_literally = 0; + } else { + switch (c) { + case '\\': + take_next_literally = 1; + continue; + case '(': + in = unquote_comment(outbuf, in); + continue; + case ')': + strbuf_addch(outbuf, ')'); + return in; + } + } + + strbuf_addch(outbuf, c); + } + + return in; +} + +static const char *unquote_quoted_string(struct strbuf *outbuf, const char *in) +{ + int c; + int take_next_literally = 0; + + while ((c = *in++) != 0) { + if (take_next_literally == 1) { + take_next_literally = 0; + } else { + switch (c) { + case '\\': + take_next_literally = 1; + continue; + case '"': + return in; + } + } + + strbuf_addch(outbuf, c); + } + + return in; +} + +static void unquote_quoted_pair(struct strbuf *line) +{ + struct strbuf outbuf; + const char *in = line->buf; + int c; + + strbuf_init(&outbuf, line->len); + + while ((c = *in++) != 0) { + switch (c) { + case '"': + in = unquote_quoted_string(&outbuf, in); + continue; + case '(': + in = unquote_comment(&outbuf, in); + continue; + } + + strbuf_addch(&outbuf, c); + } + + strbuf_swap(&outbuf, line); + strbuf_release(&outbuf); + +} + +static void handle_from(struct mailinfo *mi, const struct strbuf *from) +{ + char *at; + size_t el; + struct strbuf f; + + strbuf_init(&f, from->len); + strbuf_addbuf(&f, from); + + unquote_quoted_pair(&f); + + at = strchr(f.buf, '@'); + if (!at) { + parse_bogus_from(mi, from); + goto out; + } + + /* + * If we already have one email, don't take any confusing lines + */ + if (mi->email.len && strchr(at + 1, '@')) + goto out; + + /* Pick up the string around '@', possibly delimited with <> + * pair; that is the email part. + */ + while (at > f.buf) { + char c = at[-1]; + if (isspace(c)) + break; + if (c == '<') { + at[-1] = ' '; + break; + } + at--; + } + el = strcspn(at, " \n\t\r\v\f>"); + strbuf_reset(&mi->email); + strbuf_add(&mi->email, at, el); + strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0)); + + /* The remainder is name. It could be + * + * - "John Doe <john.doe@xz>" (a), or + * - "john.doe@xz (John Doe)" (b), or + * - "John (zzz) Doe <john.doe@xz> (Comment)" (c) + * + * but we have removed the email part, so + * + * - remove extra spaces which could stay after email (case 'c'), and + * - trim from both ends, possibly removing the () pair at the end + * (cases 'a' and 'b'). + */ + cleanup_space(&f); + strbuf_trim(&f); + if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') { + strbuf_remove(&f, 0, 1); + strbuf_setlen(&f, f.len - 1); + } + + get_sane_name(&mi->name, &f, &mi->email); +out: + strbuf_release(&f); +} + +static void handle_header(struct strbuf **out, const struct strbuf *line) +{ + if (!*out) { + *out = xmalloc(sizeof(struct strbuf)); + strbuf_init(*out, line->len); + } else + strbuf_reset(*out); + + strbuf_addbuf(*out, line); +} + +/* NOTE NOTE NOTE. We do not claim we do full MIME. We just attempt + * to have enough heuristics to grok MIME encoded patches often found + * on our mailing lists. For example, we do not even treat header lines + * case insensitively. + */ + +static int slurp_attr(const char *line, const char *name, struct strbuf *attr) +{ + const char *ends, *ap = strcasestr(line, name); + size_t sz; + + strbuf_setlen(attr, 0); + if (!ap) + return 0; + ap += strlen(name); + if (*ap == '"') { + ap++; + ends = "\""; + } + else + ends = "; \t"; + sz = strcspn(ap, ends); + strbuf_add(attr, ap, sz); + return 1; +} + +static void handle_content_type(struct mailinfo *mi, struct strbuf *line) +{ + struct strbuf *boundary = xmalloc(sizeof(struct strbuf)); + strbuf_init(boundary, line->len); + + if (slurp_attr(line->buf, "boundary=", boundary)) { + strbuf_insert(boundary, 0, "--", 2); + if (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) { + error("Too many boundaries to handle"); + mi->input_error = -1; + mi->content_top = &mi->content[MAX_BOUNDARIES] - 1; + return; + } + *(mi->content_top) = boundary; + boundary = NULL; + } + slurp_attr(line->buf, "charset=", &mi->charset); + + if (boundary) { + strbuf_release(boundary); + free(boundary); + } +} + +static void handle_content_transfer_encoding(struct mailinfo *mi, + const struct strbuf *line) +{ + if (strcasestr(line->buf, "base64")) + mi->transfer_encoding = TE_BASE64; + else if (strcasestr(line->buf, "quoted-printable")) + mi->transfer_encoding = TE_QP; + else + mi->transfer_encoding = TE_DONTCARE; +} + +static int is_multipart_boundary(struct mailinfo *mi, const struct strbuf *line) +{ + struct strbuf *content_top = *(mi->content_top); + + return ((content_top->len <= line->len) && + !memcmp(line->buf, content_top->buf, content_top->len)); +} + +static void cleanup_subject(struct mailinfo *mi, struct strbuf *subject) +{ + size_t at = 0; + + while (at < subject->len) { + char *pos; + size_t remove; + + switch (subject->buf[at]) { + case 'r': case 'R': + if (subject->len <= at + 3) + break; + if ((subject->buf[at + 1] == 'e' || + subject->buf[at + 1] == 'E') && + subject->buf[at + 2] == ':') { + strbuf_remove(subject, at, 3); + continue; + } + at++; + break; + case ' ': case '\t': case ':': + strbuf_remove(subject, at, 1); + continue; + case '[': + pos = strchr(subject->buf + at, ']'); + if (!pos) + break; + remove = pos - subject->buf + at + 1; + if (!mi->keep_non_patch_brackets_in_subject || + (7 <= remove && + memmem(subject->buf + at, remove, "PATCH", 5))) + strbuf_remove(subject, at, remove); + else { + at += remove; + /* + * If the input had a space after the ], keep + * it. We don't bother with finding the end of + * the space, since we later normalize it + * anyway. + */ + if (isspace(subject->buf[at])) + at += 1; + } + continue; + } + break; + } + strbuf_trim(subject); +} + +#define MAX_HDR_PARSED 10 +static const char *header[MAX_HDR_PARSED] = { + "From","Subject","Date", +}; + +static inline int cmp_header(const struct strbuf *line, const char *hdr) +{ + int len = strlen(hdr); + return !strncasecmp(line->buf, hdr, len) && line->len > len && + line->buf[len] == ':' && isspace(line->buf[len + 1]); +} + +static int is_format_patch_separator(const char *line, int len) +{ + static const char SAMPLE[] = + "From e6807f3efca28b30decfecb1732a56c7db1137ee Mon Sep 17 00:00:00 2001\n"; + const char *cp; + + if (len != strlen(SAMPLE)) + return 0; + if (!skip_prefix(line, "From ", &cp)) + return 0; + if (strspn(cp, "0123456789abcdef") != 40) + return 0; + cp += 40; + return !memcmp(SAMPLE + (cp - line), cp, strlen(SAMPLE) - (cp - line)); +} + +static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047) +{ + const char *in = q_seg->buf; + int c; + struct strbuf *out = xmalloc(sizeof(struct strbuf)); + strbuf_init(out, q_seg->len); + + while ((c = *in++) != 0) { + if (c == '=') { + int ch, d = *in; + if (d == '\n' || !d) + break; /* drop trailing newline */ + ch = hex2chr(in); + if (ch >= 0) { + strbuf_addch(out, ch); + in += 2; + continue; + } + /* garbage -- fall through */ + } + if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */ + c = 0x20; + strbuf_addch(out, c); + } + return out; +} + +static struct strbuf *decode_b_segment(const struct strbuf *b_seg) +{ + /* Decode in..ep, possibly in-place to ot */ + int c, pos = 0, acc = 0; + const char *in = b_seg->buf; + struct strbuf *out = xmalloc(sizeof(struct strbuf)); + strbuf_init(out, b_seg->len); + + while ((c = *in++) != 0) { + if (c == '+') + c = 62; + else if (c == '/') + c = 63; + else if ('A' <= c && c <= 'Z') + c -= 'A'; + else if ('a' <= c && c <= 'z') + c -= 'a' - 26; + else if ('0' <= c && c <= '9') + c -= '0' - 52; + else + continue; /* garbage */ + switch (pos++) { + case 0: + acc = (c << 2); + break; + case 1: + strbuf_addch(out, (acc | (c >> 4))); + acc = (c & 15) << 4; + break; + case 2: + strbuf_addch(out, (acc | (c >> 2))); + acc = (c & 3) << 6; + break; + case 3: + strbuf_addch(out, (acc | c)); + acc = pos = 0; + break; + } + } + return out; +} + +static int convert_to_utf8(struct mailinfo *mi, + struct strbuf *line, const char *charset) +{ + char *out; + + if (!mi->metainfo_charset || !charset || !*charset) + return 0; + + if (same_encoding(mi->metainfo_charset, charset)) + return 0; + out = reencode_string(line->buf, mi->metainfo_charset, charset); + if (!out) { + mi->input_error = -1; + return error("cannot convert from %s to %s", + charset, mi->metainfo_charset); + } + strbuf_attach(line, out, strlen(out), strlen(out)); + return 0; +} + +static void decode_header(struct mailinfo *mi, struct strbuf *it) +{ + char *in, *ep, *cp; + struct strbuf outbuf = STRBUF_INIT, *dec; + struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT; + int found_error = 1; /* pessimism */ + + in = it->buf; + while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) { + int encoding; + strbuf_reset(&charset_q); + strbuf_reset(&piecebuf); + + if (in != ep) { + /* + * We are about to process an encoded-word + * that begins at ep, but there is something + * before the encoded word. + */ + char *scan; + for (scan = in; scan < ep; scan++) + if (!isspace(*scan)) + break; + + if (scan != ep || in == it->buf) { + /* + * We should not lose that "something", + * unless we have just processed an + * encoded-word, and there is only LWS + * before the one we are about to process. + */ + strbuf_add(&outbuf, in, ep - in); + } + } + /* E.g. + * ep : "=?iso-2022-jp?B?GyR...?= foo" + * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz" + */ + ep += 2; + + if (ep - it->buf >= it->len || !(cp = strchr(ep, '?'))) + goto release_return; + + if (cp + 3 - it->buf > it->len) + goto release_return; + strbuf_add(&charset_q, ep, cp - ep); + + encoding = cp[1]; + if (!encoding || cp[2] != '?') + goto release_return; + ep = strstr(cp + 3, "?="); + if (!ep) + goto release_return; + strbuf_add(&piecebuf, cp + 3, ep - cp - 3); + switch (tolower(encoding)) { + default: + goto release_return; + case 'b': + dec = decode_b_segment(&piecebuf); + break; + case 'q': + dec = decode_q_segment(&piecebuf, 1); + break; + } + if (convert_to_utf8(mi, dec, charset_q.buf)) + goto release_return; + + strbuf_addbuf(&outbuf, dec); + strbuf_release(dec); + free(dec); + in = ep + 2; + } + strbuf_addstr(&outbuf, in); + strbuf_reset(it); + strbuf_addbuf(it, &outbuf); + found_error = 0; +release_return: + strbuf_release(&outbuf); + strbuf_release(&charset_q); + strbuf_release(&piecebuf); + + if (found_error) + mi->input_error = -1; +} + +static int check_header(struct mailinfo *mi, + const struct strbuf *line, + struct strbuf *hdr_data[], int overwrite) +{ + int i, ret = 0, len; + struct strbuf sb = STRBUF_INIT; + + /* search for the interesting parts */ + for (i = 0; header[i]; i++) { + int len = strlen(header[i]); + if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) { + /* Unwrap inline B and Q encoding, and optionally + * normalize the meta information to utf8. + */ + strbuf_add(&sb, line->buf + len + 2, line->len - len - 2); + decode_header(mi, &sb); + handle_header(&hdr_data[i], &sb); + ret = 1; + goto check_header_out; + } + } + + /* Content stuff */ + if (cmp_header(line, "Content-Type")) { + len = strlen("Content-Type: "); + strbuf_add(&sb, line->buf + len, line->len - len); + decode_header(mi, &sb); + strbuf_insert(&sb, 0, "Content-Type: ", len); + handle_content_type(mi, &sb); + ret = 1; + goto check_header_out; + } + if (cmp_header(line, "Content-Transfer-Encoding")) { + len = strlen("Content-Transfer-Encoding: "); + strbuf_add(&sb, line->buf + len, line->len - len); + decode_header(mi, &sb); + handle_content_transfer_encoding(mi, &sb); + ret = 1; + goto check_header_out; + } + if (cmp_header(line, "Message-Id")) { + len = strlen("Message-Id: "); + strbuf_add(&sb, line->buf + len, line->len - len); + decode_header(mi, &sb); + if (mi->add_message_id) + mi->message_id = strbuf_detach(&sb, NULL); + ret = 1; + goto check_header_out; + } + +check_header_out: + strbuf_release(&sb); + return ret; +} + +/* + * Returns 1 if the given line or any line beginning with the given line is an + * in-body header (that is, check_header will succeed when passed + * mi->s_hdr_data). + */ +static int is_inbody_header(const struct mailinfo *mi, + const struct strbuf *line) +{ + int i; + for (i = 0; header[i]; i++) + if (!mi->s_hdr_data[i] && cmp_header(line, header[i])) + return 1; + return 0; +} + +static void decode_transfer_encoding(struct mailinfo *mi, struct strbuf *line) +{ + struct strbuf *ret; + + switch (mi->transfer_encoding) { + case TE_QP: + ret = decode_q_segment(line, 0); + break; + case TE_BASE64: + ret = decode_b_segment(line); + break; + case TE_DONTCARE: + default: + return; + } + strbuf_reset(line); + strbuf_addbuf(line, ret); + strbuf_release(ret); + free(ret); +} + +static inline int patchbreak(const struct strbuf *line) +{ + size_t i; + + /* Beginning of a "diff -" header? */ + if (starts_with(line->buf, "diff -")) + return 1; + + /* CVS "Index: " line? */ + if (starts_with(line->buf, "Index: ")) + return 1; + + /* + * "--- <filename>" starts patches without headers + * "---<sp>*" is a manual separator + */ + if (line->len < 4) + return 0; + + if (starts_with(line->buf, "---")) { + /* space followed by a filename? */ + if (line->buf[3] == ' ' && !isspace(line->buf[4])) + return 1; + /* Just whitespace? */ + for (i = 3; i < line->len; i++) { + unsigned char c = line->buf[i]; + if (c == '\n') + return 1; + if (!isspace(c)) + break; + } + return 0; + } + return 0; +} + +static int is_scissors_line(const char *line) +{ + const char *c; + int scissors = 0, gap = 0; + const char *first_nonblank = NULL, *last_nonblank = NULL; + int visible, perforation = 0, in_perforation = 0; + + for (c = line; *c; c++) { + if (isspace(*c)) { + if (in_perforation) { + perforation++; + gap++; + } + continue; + } + last_nonblank = c; + if (first_nonblank == NULL) + first_nonblank = c; + if (*c == '-') { + in_perforation = 1; + perforation++; + continue; + } + if ((!memcmp(c, ">8", 2) || !memcmp(c, "8<", 2) || + !memcmp(c, ">%", 2) || !memcmp(c, "%<", 2))) { + in_perforation = 1; + perforation += 2; + scissors += 2; + c++; + continue; + } + in_perforation = 0; + } + + /* + * The mark must be at least 8 bytes long (e.g. "-- >8 --"). + * Even though there can be arbitrary cruft on the same line + * (e.g. "cut here"), in order to avoid misidentification, the + * perforation must occupy more than a third of the visible + * width of the line, and dashes and scissors must occupy more + * than half of the perforation. + */ + + if (first_nonblank && last_nonblank) + visible = last_nonblank - first_nonblank + 1; + else + visible = 0; + return (scissors && 8 <= visible && + visible < perforation * 3 && + gap * 2 < perforation); +} + +static void flush_inbody_header_accum(struct mailinfo *mi) +{ + if (!mi->inbody_header_accum.len) + return; + if (!check_header(mi, &mi->inbody_header_accum, mi->s_hdr_data, 0)) + BUG("inbody_header_accum, if not empty, must always contain a valid in-body header"); + strbuf_reset(&mi->inbody_header_accum); +} + +static int check_inbody_header(struct mailinfo *mi, const struct strbuf *line) +{ + if (mi->inbody_header_accum.len && + (line->buf[0] == ' ' || line->buf[0] == '\t')) { + if (mi->use_scissors && is_scissors_line(line->buf)) { + /* + * This is a scissors line; do not consider this line + * as a header continuation line. + */ + flush_inbody_header_accum(mi); + return 0; + } + strbuf_strip_suffix(&mi->inbody_header_accum, "\n"); + strbuf_addbuf(&mi->inbody_header_accum, line); + return 1; + } + + flush_inbody_header_accum(mi); + + if (starts_with(line->buf, ">From") && isspace(line->buf[5])) + return is_format_patch_separator(line->buf + 1, line->len - 1); + if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) { + int i; + for (i = 0; header[i]; i++) + if (!strcmp("Subject", header[i])) { + handle_header(&mi->s_hdr_data[i], line); + return 1; + } + return 0; + } + if (is_inbody_header(mi, line)) { + strbuf_addbuf(&mi->inbody_header_accum, line); + return 1; + } + return 0; +} + +static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line) +{ + assert(!mi->filter_stage); + + if (mi->header_stage) { + if (!line->len || (line->len == 1 && line->buf[0] == '\n')) { + if (mi->inbody_header_accum.len) { + flush_inbody_header_accum(mi); + mi->header_stage = 0; + } + return 0; + } + } + + if (mi->use_inbody_headers && mi->header_stage) { + mi->header_stage = check_inbody_header(mi, line); + if (mi->header_stage) + return 0; + } else + /* Only trim the first (blank) line of the commit message + * when ignoring in-body headers. + */ + mi->header_stage = 0; + + /* normalize the log message to UTF-8. */ + if (convert_to_utf8(mi, line, mi->charset.buf)) + return 0; /* mi->input_error already set */ + + if (mi->use_scissors && is_scissors_line(line->buf)) { + int i; + + strbuf_setlen(&mi->log_message, 0); + mi->header_stage = 1; + + /* + * We may have already read "secondary headers"; purge + * them to give ourselves a clean restart. + */ + for (i = 0; header[i]; i++) { + if (mi->s_hdr_data[i]) + strbuf_release(mi->s_hdr_data[i]); + mi->s_hdr_data[i] = NULL; + } + return 0; + } + + if (patchbreak(line)) { + if (mi->message_id) + strbuf_addf(&mi->log_message, + "Message-Id: %s\n", mi->message_id); + return 1; + } + + strbuf_addbuf(&mi->log_message, line); + return 0; +} + +static void handle_patch(struct mailinfo *mi, const struct strbuf *line) +{ + fwrite(line->buf, 1, line->len, mi->patchfile); + mi->patch_lines++; +} + +static void handle_filter(struct mailinfo *mi, struct strbuf *line) +{ + switch (mi->filter_stage) { + case 0: + if (!handle_commit_msg(mi, line)) + break; + mi->filter_stage++; + /* fallthrough */ + case 1: + handle_patch(mi, line); + break; + } +} + +static int is_rfc2822_header(const struct strbuf *line) +{ + /* + * The section that defines the loosest possible + * field name is "3.6.8 Optional fields". + * + * optional-field = field-name ":" unstructured CRLF + * field-name = 1*ftext + * ftext = %d33-57 / %59-126 + */ + int ch; + char *cp = line->buf; + + /* Count mbox From headers as headers */ + if (starts_with(cp, "From ") || starts_with(cp, ">From ")) + return 1; + + while ((ch = *cp++)) { + if (ch == ':') + return 1; + if ((33 <= ch && ch <= 57) || + (59 <= ch && ch <= 126)) + continue; + break; + } + return 0; +} + +static int read_one_header_line(struct strbuf *line, FILE *in) +{ + struct strbuf continuation = STRBUF_INIT; + + /* Get the first part of the line. */ + if (strbuf_getline_lf(line, in)) + return 0; + + /* + * Is it an empty line or not a valid rfc2822 header? + * If so, stop here, and return false ("not a header") + */ + strbuf_rtrim(line); + if (!line->len || !is_rfc2822_header(line)) { + /* Re-add the newline */ + strbuf_addch(line, '\n'); + return 0; + } + + /* + * Now we need to eat all the continuation lines.. + * Yuck, 2822 header "folding" + */ + for (;;) { + int peek; + + peek = fgetc(in); + if (peek == EOF) + break; + ungetc(peek, in); + if (peek != ' ' && peek != '\t') + break; + if (strbuf_getline_lf(&continuation, in)) + break; + continuation.buf[0] = ' '; + strbuf_rtrim(&continuation); + strbuf_addbuf(line, &continuation); + } + strbuf_release(&continuation); + + return 1; +} + +static int find_boundary(struct mailinfo *mi, struct strbuf *line) +{ + while (!strbuf_getline_lf(line, mi->input)) { + if (*(mi->content_top) && is_multipart_boundary(mi, line)) + return 1; + } + return 0; +} + +static int handle_boundary(struct mailinfo *mi, struct strbuf *line) +{ + struct strbuf newline = STRBUF_INIT; + + strbuf_addch(&newline, '\n'); +again: + if (line->len >= (*(mi->content_top))->len + 2 && + !memcmp(line->buf + (*(mi->content_top))->len, "--", 2)) { + /* we hit an end boundary */ + /* pop the current boundary off the stack */ + strbuf_release(*(mi->content_top)); + FREE_AND_NULL(*(mi->content_top)); + + /* technically won't happen as is_multipart_boundary() + will fail first. But just in case.. + */ + if (--mi->content_top < mi->content) { + error("Detected mismatched boundaries, can't recover"); + mi->input_error = -1; + mi->content_top = mi->content; + strbuf_release(&newline); + return 0; + } + handle_filter(mi, &newline); + strbuf_release(&newline); + if (mi->input_error) + return 0; + + /* skip to the next boundary */ + if (!find_boundary(mi, line)) + return 0; + goto again; + } + + /* set some defaults */ + mi->transfer_encoding = TE_DONTCARE; + strbuf_reset(&mi->charset); + + /* slurp in this section's info */ + while (read_one_header_line(line, mi->input)) + check_header(mi, line, mi->p_hdr_data, 0); + + strbuf_release(&newline); + /* replenish line */ + if (strbuf_getline_lf(line, mi->input)) + return 0; + strbuf_addch(line, '\n'); + return 1; +} + +static void handle_body(struct mailinfo *mi, struct strbuf *line) +{ + struct strbuf prev = STRBUF_INIT; + + /* Skip up to the first boundary */ + if (*(mi->content_top)) { + if (!find_boundary(mi, line)) + goto handle_body_out; + } + + do { + /* process any boundary lines */ + if (*(mi->content_top) && is_multipart_boundary(mi, line)) { + /* flush any leftover */ + if (prev.len) { + handle_filter(mi, &prev); + strbuf_reset(&prev); + } + if (!handle_boundary(mi, line)) + goto handle_body_out; + } + + /* Unwrap transfer encoding */ + decode_transfer_encoding(mi, line); + + switch (mi->transfer_encoding) { + case TE_BASE64: + case TE_QP: + { + struct strbuf **lines, **it, *sb; + + /* Prepend any previous partial lines */ + strbuf_insert(line, 0, prev.buf, prev.len); + strbuf_reset(&prev); + + /* + * This is a decoded line that may contain + * multiple new lines. Pass only one chunk + * at a time to handle_filter() + */ + lines = strbuf_split(line, '\n'); + for (it = lines; (sb = *it); it++) { + if (*(it + 1) == NULL) /* The last line */ + if (sb->buf[sb->len - 1] != '\n') { + /* Partial line, save it for later. */ + strbuf_addbuf(&prev, sb); + break; + } + handle_filter(mi, sb); + } + /* + * The partial chunk is saved in "prev" and will be + * appended by the next iteration of read_line_with_nul(). + */ + strbuf_list_free(lines); + break; + } + default: + handle_filter(mi, line); + } + + if (mi->input_error) + break; + } while (!strbuf_getwholeline(line, mi->input, '\n')); + + flush_inbody_header_accum(mi); + +handle_body_out: + strbuf_release(&prev); +} + +static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data) +{ + const char *sp = data->buf; + while (1) { + char *ep = strchr(sp, '\n'); + int len; + if (!ep) + len = strlen(sp); + else + len = ep - sp; + fprintf(fout, "%s: %.*s\n", hdr, len, sp); + if (!ep) + break; + sp = ep + 1; + } +} + +static void handle_info(struct mailinfo *mi) +{ + struct strbuf *hdr; + int i; + + for (i = 0; header[i]; i++) { + /* only print inbody headers if we output a patch file */ + if (mi->patch_lines && mi->s_hdr_data[i]) + hdr = mi->s_hdr_data[i]; + else if (mi->p_hdr_data[i]) + hdr = mi->p_hdr_data[i]; + else + continue; + + if (!strcmp(header[i], "Subject")) { + if (!mi->keep_subject) { + cleanup_subject(mi, hdr); + cleanup_space(hdr); + } + output_header_lines(mi->output, "Subject", hdr); + } else if (!strcmp(header[i], "From")) { + cleanup_space(hdr); + handle_from(mi, hdr); + fprintf(mi->output, "Author: %s\n", mi->name.buf); + fprintf(mi->output, "Email: %s\n", mi->email.buf); + } else { + cleanup_space(hdr); + fprintf(mi->output, "%s: %s\n", header[i], hdr->buf); + } + } + fprintf(mi->output, "\n"); +} + +int mailinfo(struct mailinfo *mi, const char *msg, const char *patch) +{ + FILE *cmitmsg; + int peek; + struct strbuf line = STRBUF_INIT; + + cmitmsg = fopen(msg, "w"); + if (!cmitmsg) { + perror(msg); + return -1; + } + mi->patchfile = fopen(patch, "w"); + if (!mi->patchfile) { + perror(patch); + fclose(cmitmsg); + return -1; + } + + mi->p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->p_hdr_data))); + mi->s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->s_hdr_data))); + + do { + peek = fgetc(mi->input); + if (peek == EOF) { + fclose(cmitmsg); + return error("empty patch: '%s'", patch); + } + } while (isspace(peek)); + ungetc(peek, mi->input); + + /* process the email header */ + while (read_one_header_line(&line, mi->input)) + check_header(mi, &line, mi->p_hdr_data, 1); + + handle_body(mi, &line); + fwrite(mi->log_message.buf, 1, mi->log_message.len, cmitmsg); + fclose(cmitmsg); + fclose(mi->patchfile); + + handle_info(mi); + strbuf_release(&line); + return mi->input_error; +} + +static int git_mailinfo_config(const char *var, const char *value, void *mi_) +{ + struct mailinfo *mi = mi_; + + if (!starts_with(var, "mailinfo.")) + return git_default_config(var, value, NULL); + if (!strcmp(var, "mailinfo.scissors")) { + mi->use_scissors = git_config_bool(var, value); + return 0; + } + /* perhaps others here */ + return 0; +} + +void setup_mailinfo(struct mailinfo *mi) +{ + memset(mi, 0, sizeof(*mi)); + strbuf_init(&mi->name, 0); + strbuf_init(&mi->email, 0); + strbuf_init(&mi->charset, 0); + strbuf_init(&mi->log_message, 0); + strbuf_init(&mi->inbody_header_accum, 0); + mi->header_stage = 1; + mi->use_inbody_headers = 1; + mi->content_top = mi->content; + git_config(git_mailinfo_config, mi); +} + +void clear_mailinfo(struct mailinfo *mi) +{ + int i; + + strbuf_release(&mi->name); + strbuf_release(&mi->email); + strbuf_release(&mi->charset); + strbuf_release(&mi->inbody_header_accum); + free(mi->message_id); + + if (mi->p_hdr_data) + for (i = 0; mi->p_hdr_data[i]; i++) + strbuf_release(mi->p_hdr_data[i]); + free(mi->p_hdr_data); + if (mi->s_hdr_data) + for (i = 0; mi->s_hdr_data[i]; i++) + strbuf_release(mi->s_hdr_data[i]); + free(mi->s_hdr_data); + + while (mi->content < mi->content_top) { + free(*(mi->content_top)); + mi->content_top--; + } + + strbuf_release(&mi->log_message); +} diff --git a/t/t4256/1/patch b/t/t4256/1/patch new file mode 100644 index 0000000000..bd0d8b0251 --- /dev/null +++ b/t/t4256/1/patch @@ -0,0 +1,129 @@ +From: A <author@example.com> +Subject: [PATCH] mailinfo: support format=flowed +Message-ID: <aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa@example.com> +Date: Sat, 25 Aug 2018 22:04:50 +0200 +User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:60.0) Gecko/20100101 + Thunderbird/60.0 +MIME-Version: 1.0 +Content-Type: text/plain; charset=utf-8; format=flowed +Content-Language: en-US +Content-Transfer-Encoding: 7bit + +--- + mailinfo.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- + 1 file changed, 62 insertions(+), 2 deletions(-) + +diff --git a/mailinfo.c b/mailinfo.c +index 3281a37d51..b395adbdf2 100644 +--- a/mailinfo.c ++++ b/mailinfo.c +@@ -237,11 +237,22 @@ static int slurp_attr(const char *line, const char +*name, struct strbuf *attr) + return 1; + } + ++static int has_attr_value(const char *line, const char *name, const +char *value) ++{ ++ struct strbuf sb = STRBUF_INIT; ++ int rc = slurp_attr(line, name, &sb) && !strcasecmp(sb.buf, value); ++ strbuf_release(&sb); ++ return rc; ++} ++ + static void handle_content_type(struct mailinfo *mi, struct strbuf *line) + { + struct strbuf *boundary = xmalloc(sizeof(struct strbuf)); + strbuf_init(boundary, line->len); + ++ mi->format_flowed = has_attr_value(line->buf, "format=", "flowed"); ++ mi->delsp = has_attr_value(line->buf, "delsp=", "yes"); ++ + if (slurp_attr(line->buf, "boundary=", boundary)) { + strbuf_insert(boundary, 0, "--", 2); + if (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) { +@@ -964,6 +975,52 @@ static int handle_boundary(struct mailinfo *mi, +struct strbuf *line) + return 1; + } + ++static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line, ++ struct strbuf *prev) ++{ ++ size_t len = line->len; ++ const char *rest; ++ ++ if (!mi->format_flowed) { ++ handle_filter(mi, line); ++ return; ++ } ++ ++ if (line->buf[len - 1] == '\n') { ++ len--; ++ if (len && line->buf[len - 1] == '\r') ++ len--; ++ } ++ ++ /* Keep signature separator as-is. */ ++ if (skip_prefix(line->buf, "-- ", &rest) && rest - line->buf == len) { ++ if (prev->len) { ++ handle_filter(mi, prev); ++ strbuf_reset(prev); ++ } ++ handle_filter(mi, line); ++ return; ++ } ++ ++ /* Unstuff space-stuffed line. */ ++ if (len && line->buf[0] == ' ') { ++ strbuf_remove(line, 0, 1); ++ len--; ++ } ++ ++ /* Save flowed line for later, but without the soft line break. */ ++ if (len && line->buf[len - 1] == ' ') { ++ strbuf_add(prev, line->buf, len - !!mi->delsp); ++ return; ++ } ++ ++ /* Prepend any previous partial lines */ ++ strbuf_insert(line, 0, prev->buf, prev->len); ++ strbuf_reset(prev); ++ ++ handle_filter(mi, line); ++} ++ + static void handle_body(struct mailinfo *mi, struct strbuf *line) + { + struct strbuf prev = STRBUF_INIT; +@@ -1012,7 +1069,7 @@ static void handle_body(struct mailinfo *mi, +struct strbuf *line) + strbuf_addbuf(&prev, sb); + break; + } +- handle_filter(mi, sb); ++ handle_filter_flowed(mi, sb, &prev); + } + /* + * The partial chunk is saved in "prev" and will be +@@ -1022,13 +1079,16 @@ static void handle_body(struct mailinfo *mi, +struct strbuf *line) + break; + } + default: +- handle_filter(mi, line); ++ handle_filter_flowed(mi, line, &prev); + } + + if (mi->input_error) + break; + } while (!strbuf_getwholeline(line, mi->input, '\n')); + ++ if (prev.len) ++ handle_filter(mi, &prev); ++ + flush_inbody_header_accum(mi); + + handle_body_out: +-- +2.18.0 diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 2a97b27b0a..602bfd9574 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -206,6 +206,12 @@ test_expect_success 'git archive with --output, override inferred format' ' test_cmp_bin b.tar d4.zip ' +test_expect_success GZIP 'git archive with --output and --remote creates .tgz' ' + git archive --output=d5.tgz --remote=. HEAD && + gzip -d -c <d5.tgz >d5.tar && + test_cmp_bin b.tar d5.tar +' + test_expect_success 'git archive --list outside of a git repo' ' nongit git archive --list ' diff --git a/t/t5003-archive-zip.sh b/t/t5003-archive-zip.sh index 55c7870997..106eddbd85 100755 --- a/t/t5003-archive-zip.sh +++ b/t/t5003-archive-zip.sh @@ -158,11 +158,16 @@ test_expect_success 'git archive --format=zip with --output' \ 'git archive --format=zip --output=d2.zip HEAD && test_cmp_bin d.zip d2.zip' -test_expect_success 'git archive with --output, inferring format' ' +test_expect_success 'git archive with --output, inferring format (local)' ' git archive --output=d3.zip HEAD && test_cmp_bin d.zip d3.zip ' +test_expect_success 'git archive with --output, inferring format (remote)' ' + git archive --remote=. --output=d4.zip HEAD && + test_cmp_bin d.zip d4.zip +' + test_expect_success \ 'git archive --format=zip with prefix' \ 'git archive --format=zip --prefix=prefix/ HEAD >e.zip' diff --git a/t/t5004-archive-corner-cases.sh b/t/t5004-archive-corner-cases.sh index ced44355ca..271eb5a1fd 100755 --- a/t/t5004-archive-corner-cases.sh +++ b/t/t5004-archive-corner-cases.sh @@ -3,8 +3,12 @@ test_description='test corner cases of git-archive' . ./test-lib.sh -test_expect_success 'create commit with empty tree' ' - git commit --allow-empty -m foo +# the 10knuls.tar file is used to test for an empty git generated tar +# without having to invoke tar because an otherwise valid empty GNU tar +# will be considered broken by {Open,Net}BSD tar +test_expect_success 'create commit with empty tree and fake empty tar' ' + git commit --allow-empty -m foo && + perl -e "print \"\\0\" x 10240" >10knuls.tar ' # Make a dir and clean it up afterwards @@ -47,7 +51,6 @@ test_expect_success HEADER_ONLY_TAR_OK 'tar archive of commit with empty tree' ' test_expect_success 'tar archive of empty tree is empty' ' git archive --format=tar HEAD: >empty.tar && - perl -e "print \"\\0\" x 10240" >10knuls.tar && test_cmp_bin 10knuls.tar empty.tar ' @@ -106,16 +109,12 @@ test_expect_success 'create a commit with an empty subtree' ' 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 + test_cmp_bin 10knuls.tar subtree-all.tar ' 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 + test_cmp_bin 10knuls.tar subtree-path.tar ' ZIPINFO=zipinfo diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index 6c620cd540..410a09b0dd 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -468,29 +468,32 @@ test_expect_success 'pack-objects in too-many-packs mode' ' git fsck ' -# -# WARNING! -# -# The following test is destructive. Please keep the next -# two tests at the end of this file. -# - -test_expect_success \ - 'fake a SHA1 hash collision' \ - '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 'setup: fake a SHA1 hash collision' ' + git init corrupt && + ( + cd corrupt && + long_a=$(git hash-object -w ../a | sed -e "s!^..!&/!") && + long_b=$(git hash-object -w ../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' \ - 'test_must_fail git index-pack -o bad.idx test-3.pack 2>msg && - test_i18ngrep "SHA1 COLLISION FOUND" msg' +test_expect_success 'make sure index-pack detects the SHA1 collision' ' + ( + cd corrupt && + test_must_fail git index-pack -o ../bad.idx ../test-3.pack 2>msg && + test_i18ngrep "SHA1 COLLISION FOUND" msg + ) +' -test_expect_success \ - 'make sure index-pack detects the SHA1 collision (large blobs)' \ - 'test_must_fail git -c core.bigfilethreshold=1 index-pack -o bad.idx test-3.pack 2>msg && - test_i18ngrep "SHA1 COLLISION FOUND" msg' +test_expect_success 'make sure index-pack detects the SHA1 collision (large blobs)' ' + ( + cd corrupt && + test_must_fail git -c core.bigfilethreshold=1 index-pack -o ../bad.idx ../test-3.pack 2>msg && + test_i18ngrep "SHA1 COLLISION FOUND" msg + ) +' test_done diff --git a/t/t5303-pack-corruption-resilience.sh b/t/t5303-pack-corruption-resilience.sh index 3634e258f8..41e6dc4dcf 100755 --- a/t/t5303-pack-corruption-resilience.sh +++ b/t/t5303-pack-corruption-resilience.sh @@ -311,4 +311,93 @@ test_expect_success \ test_must_fail git cat-file blob $blob_2 > /dev/null && test_must_fail git cat-file blob $blob_3 > /dev/null' +# \0 - empty base +# \1 - one byte in result +# \1 - one literal byte (X) +test_expect_success \ + 'apply good minimal delta' \ + 'printf "\0\1\1X" > minimal_delta && + test-tool delta -p /dev/null minimal_delta /dev/null' + +# \0 - empty base +# \1 - 1 byte in result +# \2 - two literal bytes (one too many) +test_expect_success \ + 'apply delta with too many literal bytes' \ + 'printf "\0\1\2XX" > too_big_literal && + test_must_fail test-tool delta -p /dev/null too_big_literal /dev/null' + +# \4 - four bytes in base +# \1 - one byte in result +# \221 - copy, one byte offset, one byte size +# \0 - copy from offset 0 +# \2 - copy two bytes (one too many) +test_expect_success \ + 'apply delta with too many copied bytes' \ + 'printf "\4\1\221\0\2" > too_big_copy && + printf base >base && + test_must_fail test-tool delta -p base too_big_copy /dev/null' + +# \0 - empty base +# \2 - two bytes in result +# \2 - two literal bytes (we are short one) +test_expect_success \ + 'apply delta with too few literal bytes' \ + 'printf "\0\2\2X" > truncated_delta && + test_must_fail test-tool delta -p /dev/null truncated_delta /dev/null' + +# \0 - empty base +# \1 - one byte in result +# \221 - copy, one byte offset, one byte size +# \0 - copy from offset 0 +# \1 - copy one byte (we are short one) +test_expect_success \ + 'apply delta with too few bytes in base' \ + 'printf "\0\1\221\0\1" > truncated_base && + test_must_fail test-tool delta -p /dev/null truncated_base /dev/null' + +# \4 - four bytes in base +# \2 - two bytes in result +# \1 - one literal byte (X) +# \221 - copy, one byte offset, one byte size +# (offset/size missing) +# +# Note that the literal byte is necessary to get past the uninteresting minimum +# delta size check. +test_expect_success \ + 'apply delta with truncated copy parameters' \ + 'printf "\4\2\1X\221" > truncated_copy_delta && + printf base >base && + test_must_fail test-tool delta -p base truncated_copy_delta /dev/null' + +# \0 - empty base +# \1 - one byte in result +# \1 - one literal byte (X) +# \1 - trailing garbage command +test_expect_success \ + 'apply delta with trailing garbage literal' \ + 'printf "\0\1\1X\1" > tail_garbage_literal && + test_must_fail test-tool delta -p /dev/null tail_garbage_literal /dev/null' + +# \4 - four bytes in base +# \1 - one byte in result +# \1 - one literal byte (X) +# \221 - copy, one byte offset, one byte size +# \0 - copy from offset 0 +# \1 - copy 1 byte +test_expect_success \ + 'apply delta with trailing garbage copy' \ + 'printf "\4\1\1X\221\0\1" > tail_garbage_copy && + printf base >base && + test_must_fail test-tool delta -p /dev/null tail_garbage_copy /dev/null' + +# \0 - empty base +# \1 - one byte in result +# \1 - one literal byte (X) +# \0 - bogus opcode +test_expect_success \ + 'apply delta with trailing garbage opcode' \ + 'printf "\0\1\1X\0" > tail_garbage_opcode && + test_must_fail test-tool delta -p /dev/null tail_garbage_opcode /dev/null' + test_done diff --git a/t/t5307-pack-missing-commit.sh b/t/t5307-pack-missing-commit.sh index ae52a1882d..dacb440b27 100755 --- a/t/t5307-pack-missing-commit.sh +++ b/t/t5307-pack-missing-commit.sh @@ -24,11 +24,11 @@ test_expect_success 'check corruption' ' ' test_expect_success 'rev-list notices corruption (1)' ' - test_must_fail git rev-list HEAD + test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git rev-list HEAD ' test_expect_success 'rev-list notices corruption (2)' ' - test_must_fail git rev-list --objects HEAD + test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git rev-list --objects HEAD ' test_expect_success 'pack-objects notices corruption' ' diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh index 7bff7923f2..82d7f7f6a5 100755 --- a/t/t5310-pack-bitmaps.sh +++ b/t/t5310-pack-bitmaps.sh @@ -191,6 +191,7 @@ test_expect_success 'pack-objects respects --honor-pack-keep (local bitmapped pa test_expect_success 'pack-objects respects --local (non-local bitmapped pack)' ' mv .git/objects/pack/$packbitmap.* alt.git/objects/pack/ && + rm -f .git/objects/pack/multi-pack-index && test_when_finished "mv alt.git/objects/pack/$packbitmap.* .git/objects/pack/" && echo HEAD | git pack-objects --local --stdout --revs >3b.pack && git index-pack 3b.pack && @@ -342,4 +343,97 @@ test_expect_success 'truncated bitmap fails gracefully' ' test_i18ngrep corrupt stderr ' +# have_delta <obj> <expected_base> +# +# Note that because this relies on cat-file, it might find _any_ copy of an +# object in the repository. The caller is responsible for making sure +# there's only one (e.g., via "repack -ad", or having just fetched a copy). +have_delta () { + echo $2 >expect && + echo $1 | git cat-file --batch-check="%(deltabase)" >actual && + test_cmp expect actual +} + +# Create a state of history with these properties: +# +# - refs that allow a client to fetch some new history, while sharing some old +# history with the server; we use branches delta-reuse-old and +# delta-reuse-new here +# +# - the new history contains an object that is stored on the server as a delta +# against a base that is in the old history +# +# - the base object is not immediately reachable from the tip of the old +# history; finding it would involve digging down through history we know the +# other side has +# +# This should result in a state where fetching from old->new would not +# traditionally reuse the on-disk delta (because we'd have to dig to realize +# that the client has it), but we will do so if bitmaps can tell us cheaply +# that the other side has it. +test_expect_success 'set up thin delta-reuse parent' ' + # This first commit contains the buried base object. + test-tool genrandom delta 16384 >file && + git add file && + git commit -m "delta base" && + base=$(git rev-parse --verify HEAD:file) && + + # These intermediate commits bury the base back in history. + # This becomes the "old" state. + for i in 1 2 3 4 5 + do + echo $i >file && + git commit -am "intermediate $i" || return 1 + done && + git branch delta-reuse-old && + + # And now our new history has a delta against the buried base. Note + # that this must be smaller than the original file, since pack-objects + # prefers to create deltas from smaller objects to larger. + test-tool genrandom delta 16300 >file && + git commit -am "delta result" && + delta=$(git rev-parse --verify HEAD:file) && + git branch delta-reuse-new && + + # Repack with bitmaps and double check that we have the expected delta + # relationship. + git repack -adb && + have_delta $delta $base +' + +# Now we can sanity-check the non-bitmap behavior (that the server is not able +# to reuse the delta). This isn't strictly something we care about, so this +# test could be scrapped in the future. But it makes sure that the next test is +# actually triggering the feature we want. +# +# Note that our tools for working with on-the-wire "thin" packs are limited. So +# we actually perform the fetch, retain the resulting pack, and inspect the +# result. +test_expect_success 'fetch without bitmaps ignores delta against old base' ' + test_config pack.usebitmaps false && + test_when_finished "rm -rf client.git" && + git init --bare client.git && + ( + cd client.git && + git config transfer.unpackLimit 1 && + git fetch .. delta-reuse-old:delta-reuse-old && + git fetch .. delta-reuse-new:delta-reuse-new && + have_delta $delta $ZERO_OID + ) +' + +# And do the same for the bitmap case, where we do expect to find the delta. +test_expect_success 'fetch with bitmaps can reuse old base' ' + test_config pack.usebitmaps true && + test_when_finished "rm -rf client.git" && + git init --bare client.git && + ( + cd client.git && + git config transfer.unpackLimit 1 && + git fetch .. delta-reuse-old:delta-reuse-old && + git fetch .. delta-reuse-new:delta-reuse-new && + have_delta $delta $base + ) +' + test_done diff --git a/t/t5315-pack-objects-compression.sh b/t/t5315-pack-objects-compression.sh index 34c47dae09..df970d7584 100755 --- a/t/t5315-pack-objects-compression.sh +++ b/t/t5315-pack-objects-compression.sh @@ -7,7 +7,7 @@ test_description='pack-object compression configuration' # This should be moved to test-lib.sh together with the # copy in t0021 after both topics have graduated to 'master'. file_size () { - perl -e 'print -s $ARGV[0]' "$1" + test-tool path-utils file-size "$1" } test_expect_success setup ' diff --git a/t/t5317-pack-objects-filter-objects.sh b/t/t5317-pack-objects-filter-objects.sh index 6710c8bc8c..24541ea137 100755 --- a/t/t5317-pack-objects-filter-objects.sh +++ b/t/t5317-pack-objects-filter-objects.sh @@ -21,17 +21,21 @@ test_expect_success 'setup r1' ' 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 && + >ls_files_result && + awk -f print_2.awk ls_files_result | + 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 + + git -C r1 verify-pack -v ../all.pack >verify_result && + grep blob verify_result | + awk -f print_1.awk | + sort >observed && + + test_cmp expected observed ' test_expect_success 'verify blob:none packfile has no blobs' ' @@ -39,24 +43,69 @@ test_expect_success 'verify blob:none packfile has no blobs' ' 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 && + + git -C r1 verify-pack -v ../filter.pack >verify_result && + grep blob verify_result | + 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 + git -C r1 verify-pack -v ../all.pack >verify_result && + grep -E "commit|tree" verify_result | + awk -f print_1.awk | + sort >expected && + + git -C r1 verify-pack -v ../filter.pack >verify_result && + grep -E "commit|tree" verify_result | + awk -f print_1.awk | + sort >observed && + + test_cmp expected observed +' + +test_expect_success 'get an error for missing tree object' ' + git init r5 && + echo foo >r5/foo && + git -C r5 add foo && + git -C r5 commit -m "foo" && + del=$(git -C r5 rev-parse HEAD^{tree} | sed "s|..|&/|") && + rm r5/.git/objects/$del && + test_must_fail git -C r5 pack-objects --rev --stdout 2>bad_tree <<-EOF && + HEAD + EOF + grep "bad tree object" bad_tree +' + +test_expect_success 'setup for tests of tree:0' ' + mkdir r1/subtree && + echo "This is a file in a subtree" >r1/subtree/file && + git -C r1 add subtree/file && + git -C r1 commit -m subtree +' + +test_expect_success 'verify tree:0 packfile has no blobs or trees' ' + git -C r1 pack-objects --rev --stdout --filter=tree:0 >commitsonly.pack <<-EOF && + HEAD + EOF + git -C r1 index-pack ../commitsonly.pack && + git -C r1 verify-pack -v ../commitsonly.pack >objs && + ! grep -E "tree|blob" objs +' + +test_expect_success 'grab tree directly when using tree:0' ' + # We should get the tree specified directly but not its blobs or subtrees. + git -C r1 pack-objects --rev --stdout --filter=tree:0 >commitsonly.pack <<-EOF && + HEAD: + EOF + git -C r1 index-pack ../commitsonly.pack && + git -C r1 verify-pack -v ../commitsonly.pack >objs && + awk "/tree|blob/{print \$1}" objs >trees_and_blobs && + git -C r1 rev-parse HEAD: >expected && + test_cmp expected trees_and_blobs ' # Test blob:limit=<n>[kmg] filter. @@ -75,18 +124,21 @@ test_expect_success 'setup r2' ' ' 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 ls-files -s large.1000 large.10000 >ls_files_result && + awk -f print_2.awk ls_files_result | + 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 + + git -C r2 verify-pack -v ../all.pack >verify_result && + grep blob verify_result | + awk -f print_1.awk | + sort >observed && + + test_cmp expected observed ' test_expect_success 'verify blob:limit=500 omits all blobs' ' @@ -94,10 +146,12 @@ test_expect_success 'verify blob:limit=500 omits all blobs' ' 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 && + + git -C r2 verify-pack -v ../filter.pack >verify_result && + grep blob verify_result | + awk -f print_1.awk | + sort >observed && + nr=$(wc -l <observed) && test 0 -eq $nr ' @@ -107,100 +161,119 @@ test_expect_success 'verify blob:limit=1000' ' 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 && + + git -C r2 verify-pack -v ../filter.pack >verify_result && + grep blob verify_result | + 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 ls-files -s large.1000 >ls_files_result && + awk -f print_2.awk ls_files_result | + 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 + + git -C r2 verify-pack -v ../filter.pack >verify_result && + grep blob verify_result | + awk -f print_1.awk | + sort >observed && + + test_cmp expected observed ' 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 ls-files -s large.1000 large.10000 >ls_files_result && + awk -f print_2.awk ls_files_result | + 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 + + git -C r2 verify-pack -v ../filter.pack >verify_result && + grep blob verify_result | + awk -f print_1.awk | + sort >observed && + + test_cmp expected observed ' 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 ls-files -s large.1000 >ls_files_result && + awk -f print_2.awk ls_files_result | + 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 + + git -C r2 verify-pack -v ../filter.pack >verify_result && + grep blob verify_result | + awk -f print_1.awk | + sort >observed && + + test_cmp expected observed ' test_expect_success 'verify explicitly specifying oversized blob in input' ' - git -C r2 ls-files -s large.1000 large.10000 \ - | awk -f print_2.awk \ - | sort >expected && + git -C r2 ls-files -s large.1000 large.10000 >ls_files_result && + awk -f print_2.awk ls_files_result | + sort >expected && + git -C r2 pack-objects --rev --stdout --filter=blob:limit=1k >filter.pack <<-EOF && HEAD $(git -C r2 rev-parse HEAD:large.10000) 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 + + git -C r2 verify-pack -v ../filter.pack >verify_result && + grep blob verify_result | + awk -f print_1.awk | + sort >observed && + + test_cmp expected observed ' 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 ls-files -s large.1000 large.10000 >ls_files_result && + awk -f print_2.awk ls_files_result | + 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 + + git -C r2 verify-pack -v ../filter.pack >verify_result && + grep blob verify_result | + awk -f print_1.awk | + sort >observed && + + test_cmp expected observed ' 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 + git -C r2 verify-pack -v ../all.pack >verify_result && + grep -E "commit|tree" verify_result | + awk -f print_1.awk | + sort >expected && + + git -C r2 verify-pack -v ../filter.pack >verify_result && + grep -E "commit|tree" verify_result | + awk -f print_1.awk | + sort >observed && + + test_cmp expected observed ' # Test sparse:path=<path> filter. @@ -225,71 +298,85 @@ test_expect_success 'setup r3' ' 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 && + >ls_files_result && + awk -f print_2.awk ls_files_result | + 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 + + git -C r3 verify-pack -v ../all.pack >verify_result && + grep blob verify_result | + awk -f print_1.awk | + sort >observed && + + test_cmp expected observed ' 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 ls-files -s dir1/sparse1 dir1/sparse2 >ls_files_result && + awk -f print_2.awk ls_files_result | + 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 + + git -C r3 verify-pack -v ../filter.pack >verify_result && + grep blob verify_result | + awk -f print_1.awk | + sort >observed && + + test_cmp expected observed ' 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 + git -C r3 verify-pack -v ../all.pack >verify_result && + grep -E "commit|tree" verify_result | + awk -f print_1.awk | + sort >expected && + + git -C r3 verify-pack -v ../filter.pack >verify_result && + grep -E "commit|tree" verify_result | + awk -f print_1.awk | + sort >observed && + + test_cmp expected observed ' 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 ls-files -s sparse1 dir1/sparse1 >ls_files_result && + awk -f print_2.awk ls_files_result | + 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 + + git -C r3 verify-pack -v ../filter.pack >verify_result && + grep blob verify_result | + awk -f print_1.awk | + sort >observed && + + test_cmp expected observed ' 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 + git -C r3 verify-pack -v ../all.pack >verify_result && + grep -E "commit|tree" verify_result | + awk -f print_1.awk | + sort >expected && + + git -C r3 verify-pack -v ../filter.pack >verify_result && + grep -E "commit|tree" verify_result | + awk -f print_1.awk | + sort >observed && + + test_cmp expected observed ' # Test sparse:oid=<oid-ish> filter. @@ -313,48 +400,58 @@ test_expect_success 'setup r4' ' 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 && + >ls_files_result && + awk -f print_2.awk ls_files_result | + 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 + + git -C r4 verify-pack -v ../all.pack >verify_result && + grep blob verify_result | + awk -f print_1.awk | + sort >observed && + + test_cmp expected observed ' test_expect_success 'verify sparse:oid=OID' ' - git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 \ - | awk -f print_2.awk \ - | sort >expected && + git -C r4 ls-files -s dir1/sparse1 dir1/sparse2 >ls_files_result && + awk -f print_2.awk ls_files_result | + 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 + + git -C r4 verify-pack -v ../filter.pack >verify_result && + grep blob verify_result | + awk -f print_1.awk | + sort >observed && + + test_cmp expected observed ' 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 ls-files -s dir1/sparse1 dir1/sparse2 >ls_files_result && + awk -f print_2.awk ls_files_result | + 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 + + git -C r4 verify-pack -v ../filter.pack >verify_result && + grep blob verify_result | + awk -f print_1.awk | + sort >observed && + + test_cmp expected observed ' # Delete some loose objects and use pack-objects, but WITHOUT any filtering. @@ -362,8 +459,10 @@ test_expect_success 'verify sparse:oid=oid-ish' ' 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 && + >ls_files_result && + awk -f print_2.awk ls_files_result | + sort >expected && + for id in `cat expected | sed "s|..|&/|"` do rm r1/.git/objects/$id diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh index 2799969f3e..16d10ebce8 100755 --- a/t/t5318-commit-graph.sh +++ b/t/t5318-commit-graph.sh @@ -8,7 +8,8 @@ test_expect_success 'setup full repo' ' cd "$TRASH_DIRECTORY/full" && git init && git config core.commitGraph true && - objdir=".git/objects" + objdir=".git/objects" && + test_oid_init ' test_expect_success 'verify graph with no graph file' ' @@ -35,7 +36,7 @@ test_expect_success 'create commits and repack' ' graph_git_two_modes() { git -c core.commitGraph=true $1 >output git -c core.commitGraph=false $1 >expect - test_cmp output expect + test_cmp expect output } graph_git_behavior() { @@ -121,7 +122,7 @@ 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_read_expect "10" "extra_edges" ' graph_git_behavior 'merge 1 vs 2' full merge/1 merge/2 @@ -156,7 +157,7 @@ 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_read_expect "11" "extra_edges" ' graph_git_behavior 'full graph, commit 8 vs merge 1' full commits/8 merge/1 @@ -166,7 +167,7 @@ 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_read_expect "11" "extra_edges" ' graph_git_behavior 'cleared graph, commit 8 vs merge 1' full commits/8 merge/1 @@ -176,7 +177,7 @@ 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_read_expect "9" "extra_edges" ' graph_git_behavior 'graph from pack, commit 8 vs merge 1' full commits/8 merge/1 @@ -199,7 +200,7 @@ 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_read_expect "10" "extra_edges" ' graph_git_behavior 'append graph, commit 8 vs merge 1' full commits/8 merge/1 @@ -209,7 +210,7 @@ test_expect_success 'build graph using --reachable' ' cd "$TRASH_DIRECTORY/full" && git commit-graph write --reachable && test_path_is_file $objdir/info/commit-graph && - graph_read_expect "11" "large_edges" + graph_read_expect "11" "extra_edges" ' graph_git_behavior 'append graph, commit 8 vs merge 1' full commits/8 merge/1 @@ -230,7 +231,7 @@ 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_read_expect "11" "extra_edges" ' graph_git_behavior 'bare repo with graph, commit 8 vs merge 1' bare commits/8 merge/1 @@ -333,7 +334,7 @@ test_expect_success 'git commit-graph verify' ' NUM_COMMITS=9 NUM_OCTOPUS_EDGES=2 -HASH_LEN=20 +HASH_LEN="$(test_oid rawsz)" GRAPH_BYTE_VERSION=4 GRAPH_BYTE_HASH=5 GRAPH_BYTE_CHUNK_COUNT=6 @@ -365,9 +366,10 @@ GRAPH_OCTOPUS_DATA_OFFSET=$(($GRAPH_COMMIT_DATA_OFFSET + \ GRAPH_BYTE_OCTOPUS=$(($GRAPH_OCTOPUS_DATA_OFFSET + 4)) GRAPH_BYTE_FOOTER=$(($GRAPH_OCTOPUS_DATA_OFFSET + 4 * $NUM_OCTOPUS_EDGES)) -# usage: corrupt_graph_and_verify <position> <data> <string> +# usage: corrupt_graph_and_verify <position> <data> <string> [<zero_pos>] # Manipulates the commit-graph file at the position -# by inserting the data, then runs 'git commit-graph verify' +# by inserting the data, optionally zeroing the file +# starting at <zero_pos>, then runs 'git commit-graph verify' # and places the output in the file 'err'. Test 'err' for # the given string. corrupt_graph_and_verify() { @@ -375,11 +377,15 @@ corrupt_graph_and_verify() { data="${2:-\0}" grepstr=$3 cd "$TRASH_DIRECTORY/full" && + orig_size=$(wc -c < $objdir/info/commit-graph) && + zero_pos=${4:-${orig_size}} && test_when_finished mv commit-graph-backup $objdir/info/commit-graph && cp $objdir/info/commit-graph commit-graph-backup && printf "$data" | dd of="$objdir/info/commit-graph" bs=1 seek="$pos" conv=notrunc && + dd of="$objdir/info/commit-graph" bs=1 seek="$zero_pos" count=0 && + dd if=/dev/zero of="$objdir/info/commit-graph" bs=1 seek="$zero_pos" count=$(($orig_size - $zero_pos)) && test_must_fail git commit-graph verify 2>test_err && - grep -v "^+" test_err >err + grep -v "^+" test_err >err && test_i18ngrep "$grepstr" err } @@ -483,6 +489,11 @@ test_expect_success 'detect invalid checksum hash' ' "incorrect checksum" ' +test_expect_success 'detect incorrect chunk count' ' + corrupt_graph_and_verify $GRAPH_BYTE_CHUNK_COUNT "\377" \ + "chunk lookup table entry missing" $GRAPH_CHUNK_LOOKUP_OFFSET +' + test_expect_success 'git fsck (checks commit-graph)' ' cd "$TRASH_DIRECTORY/full" && git fsck && diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh new file mode 100755 index 0000000000..70926b5bc0 --- /dev/null +++ b/t/t5319-multi-pack-index.sh @@ -0,0 +1,351 @@ +#!/bin/sh + +test_description='multi-pack-indexes' +. ./test-lib.sh + +objdir=.git/objects + +midx_read_expect () { + NUM_PACKS=$1 + NUM_OBJECTS=$2 + NUM_CHUNKS=$3 + OBJECT_DIR=$4 + EXTRA_CHUNKS="$5" + { + cat <<-EOF && + header: 4d494458 1 $NUM_CHUNKS $NUM_PACKS + chunks: pack-names oid-fanout oid-lookup object-offsets$EXTRA_CHUNKS + num_objects: $NUM_OBJECTS + packs: + EOF + if test $NUM_PACKS -ge 1 + then + ls $OBJECT_DIR/pack/ | grep idx | sort + fi && + printf "object-dir: $OBJECT_DIR\n" + } >expect && + test-tool read-midx $OBJECT_DIR >actual && + test_cmp expect actual +} + +test_expect_success 'write midx with no packs' ' + test_when_finished rm -f pack/multi-pack-index && + git multi-pack-index --object-dir=. write && + midx_read_expect 0 0 4 . +' + +generate_objects () { + i=$1 + iii=$(printf '%03i' $i) + { + test-tool genrandom "bar" 200 && + test-tool genrandom "baz $iii" 50 + } >wide_delta_$iii && + { + test-tool genrandom "foo"$i 100 && + test-tool genrandom "foo"$(( $i + 1 )) 100 && + test-tool genrandom "foo"$(( $i + 2 )) 100 + } >deep_delta_$iii && + { + echo $iii && + test-tool genrandom "$iii" 8192 + } >file_$iii && + git update-index --add file_$iii deep_delta_$iii wide_delta_$iii +} + +commit_and_list_objects () { + { + echo 101 && + test-tool genrandom 100 8192; + } >file_101 && + git update-index --add file_101 && + tree=$(git write-tree) && + commit=$(git commit-tree $tree -p HEAD</dev/null) && + { + echo $tree && + git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\) .*/\\1/" + } >obj-list && + git reset --hard $commit +} + +test_expect_success 'create objects' ' + test_commit initial && + for i in $(test_seq 1 5) + do + generate_objects $i + done && + commit_and_list_objects +' + +test_expect_success 'write midx with one v1 pack' ' + pack=$(git pack-objects --index-version=1 $objdir/pack/test <obj-list) && + test_when_finished rm $objdir/pack/test-$pack.pack \ + $objdir/pack/test-$pack.idx $objdir/pack/multi-pack-index && + git multi-pack-index --object-dir=$objdir write && + midx_read_expect 1 18 4 $objdir +' + +midx_git_two_modes () { + if [ "$2" = "sorted" ] + then + git -c core.multiPackIndex=false $1 | sort >expect && + git -c core.multiPackIndex=true $1 | sort >actual + else + git -c core.multiPackIndex=false $1 >expect && + git -c core.multiPackIndex=true $1 >actual + fi && + test_cmp expect actual +} + +compare_results_with_midx () { + MSG=$1 + test_expect_success "check normal git operations: $MSG" ' + midx_git_two_modes "rev-list --objects --all" && + midx_git_two_modes "log --raw" && + midx_git_two_modes "count-objects --verbose" && + midx_git_two_modes "cat-file --batch-all-objects --buffer --batch-check" && + midx_git_two_modes "cat-file --batch-all-objects --buffer --batch-check --unsorted" sorted + ' +} + +test_expect_success 'write midx with one v2 pack' ' + git pack-objects --index-version=2,0x40 $objdir/pack/test <obj-list && + git multi-pack-index --object-dir=$objdir write && + midx_read_expect 1 18 4 $objdir +' + +compare_results_with_midx "one v2 pack" + +test_expect_success 'add more objects' ' + for i in $(test_seq 6 10) + do + generate_objects $i + done && + commit_and_list_objects +' + +test_expect_success 'write midx with two packs' ' + git pack-objects --index-version=1 $objdir/pack/test-2 <obj-list && + git multi-pack-index --object-dir=$objdir write && + midx_read_expect 2 34 4 $objdir +' + +compare_results_with_midx "two packs" + +test_expect_success 'add more packs' ' + for j in $(test_seq 11 20) + do + generate_objects $j && + commit_and_list_objects && + git pack-objects --index-version=2 $objdir/pack/test-pack <obj-list + done +' + +compare_results_with_midx "mixed mode (two packs + extra)" + +test_expect_success 'write midx with twelve packs' ' + git multi-pack-index --object-dir=$objdir write && + midx_read_expect 12 74 4 $objdir +' + +compare_results_with_midx "twelve packs" + +test_expect_success 'verify multi-pack-index success' ' + git multi-pack-index verify --object-dir=$objdir +' + +# usage: corrupt_midx_and_verify <pos> <data> <objdir> <string> +corrupt_midx_and_verify() { + POS=$1 && + DATA="${2:-\0}" && + OBJDIR=$3 && + GREPSTR="$4" && + COMMAND="$5" && + if test -z "$COMMAND" + then + COMMAND="git multi-pack-index verify --object-dir=$OBJDIR" + fi && + FILE=$OBJDIR/pack/multi-pack-index && + chmod a+w $FILE && + test_when_finished mv midx-backup $FILE && + cp $FILE midx-backup && + printf "$DATA" | dd of="$FILE" bs=1 seek="$POS" conv=notrunc && + test_must_fail $COMMAND 2>test_err && + grep -v "^+" test_err >err && + test_i18ngrep "$GREPSTR" err +} + +test_expect_success 'verify bad signature' ' + corrupt_midx_and_verify 0 "\00" $objdir \ + "multi-pack-index signature" +' + +HASH_LEN=20 +NUM_OBJECTS=74 +MIDX_BYTE_VERSION=4 +MIDX_BYTE_OID_VERSION=5 +MIDX_BYTE_CHUNK_COUNT=6 +MIDX_HEADER_SIZE=12 +MIDX_BYTE_CHUNK_ID=$MIDX_HEADER_SIZE +MIDX_BYTE_CHUNK_OFFSET=$(($MIDX_HEADER_SIZE + 4)) +MIDX_NUM_CHUNKS=5 +MIDX_CHUNK_LOOKUP_WIDTH=12 +MIDX_OFFSET_PACKNAMES=$(($MIDX_HEADER_SIZE + \ + $MIDX_NUM_CHUNKS * $MIDX_CHUNK_LOOKUP_WIDTH)) +MIDX_BYTE_PACKNAME_ORDER=$(($MIDX_OFFSET_PACKNAMES + 2)) +MIDX_OFFSET_OID_FANOUT=$(($MIDX_OFFSET_PACKNAMES + 652)) +MIDX_OID_FANOUT_WIDTH=4 +MIDX_BYTE_OID_FANOUT_ORDER=$((MIDX_OFFSET_OID_FANOUT + 250 * $MIDX_OID_FANOUT_WIDTH + 1)) +MIDX_OFFSET_OID_LOOKUP=$(($MIDX_OFFSET_OID_FANOUT + 256 * $MIDX_OID_FANOUT_WIDTH)) +MIDX_BYTE_OID_LOOKUP=$(($MIDX_OFFSET_OID_LOOKUP + 16 * $HASH_LEN)) +MIDX_OFFSET_OBJECT_OFFSETS=$(($MIDX_OFFSET_OID_LOOKUP + $NUM_OBJECTS * $HASH_LEN)) +MIDX_OFFSET_WIDTH=8 +MIDX_BYTE_PACK_INT_ID=$(($MIDX_OFFSET_OBJECT_OFFSETS + 16 * $MIDX_OFFSET_WIDTH + 2)) +MIDX_BYTE_OFFSET=$(($MIDX_OFFSET_OBJECT_OFFSETS + 16 * $MIDX_OFFSET_WIDTH + 6)) + +test_expect_success 'verify bad version' ' + corrupt_midx_and_verify $MIDX_BYTE_VERSION "\00" $objdir \ + "multi-pack-index version" +' + +test_expect_success 'verify bad OID version' ' + corrupt_midx_and_verify $MIDX_BYTE_OID_VERSION "\02" $objdir \ + "hash version" +' + +test_expect_success 'verify truncated chunk count' ' + corrupt_midx_and_verify $MIDX_BYTE_CHUNK_COUNT "\01" $objdir \ + "missing required" +' + +test_expect_success 'verify extended chunk count' ' + corrupt_midx_and_verify $MIDX_BYTE_CHUNK_COUNT "\07" $objdir \ + "terminating multi-pack-index chunk id appears earlier than expected" +' + +test_expect_success 'verify missing required chunk' ' + corrupt_midx_and_verify $MIDX_BYTE_CHUNK_ID "\01" $objdir \ + "missing required" +' + +test_expect_success 'verify invalid chunk offset' ' + corrupt_midx_and_verify $MIDX_BYTE_CHUNK_OFFSET "\01" $objdir \ + "invalid chunk offset (too large)" +' + +test_expect_success 'verify packnames out of order' ' + corrupt_midx_and_verify $MIDX_BYTE_PACKNAME_ORDER "z" $objdir \ + "pack names out of order" +' + +test_expect_success 'verify packnames out of order' ' + corrupt_midx_and_verify $MIDX_BYTE_PACKNAME_ORDER "a" $objdir \ + "failed to load pack" +' + +test_expect_success 'verify oid fanout out of order' ' + corrupt_midx_and_verify $MIDX_BYTE_OID_FANOUT_ORDER "\01" $objdir \ + "oid fanout out of order" +' + +test_expect_success 'verify oid lookup out of order' ' + corrupt_midx_and_verify $MIDX_BYTE_OID_LOOKUP "\00" $objdir \ + "oid lookup out of order" +' + +test_expect_success 'verify incorrect pack-int-id' ' + corrupt_midx_and_verify $MIDX_BYTE_PACK_INT_ID "\07" $objdir \ + "bad pack-int-id" +' + +test_expect_success 'verify incorrect offset' ' + corrupt_midx_and_verify $MIDX_BYTE_OFFSET "\07" $objdir \ + "incorrect object offset" +' + +test_expect_success 'git-fsck incorrect offset' ' + corrupt_midx_and_verify $MIDX_BYTE_OFFSET "\07" $objdir \ + "incorrect object offset" \ + "git -c core.multipackindex=true fsck" +' + +test_expect_success 'repack removes multi-pack-index' ' + test_path_is_file $objdir/pack/multi-pack-index && + GIT_TEST_MULTI_PACK_INDEX=0 git repack -adf && + test_path_is_missing $objdir/pack/multi-pack-index +' + +compare_results_with_midx "after repack" + +test_expect_success 'multi-pack-index and pack-bitmap' ' + git -c repack.writeBitmaps=true repack -ad && + git multi-pack-index write && + git rev-list --test-bitmap HEAD +' + +test_expect_success 'multi-pack-index and alternates' ' + git init --bare alt.git && + echo $(pwd)/alt.git/objects >.git/objects/info/alternates && + echo content1 >file1 && + altblob=$(GIT_DIR=alt.git git hash-object -w file1) && + git cat-file blob $altblob && + git rev-list --all +' + +compare_results_with_midx "with alternate (local midx)" + +test_expect_success 'multi-pack-index in an alternate' ' + mv .git/objects/pack/* alt.git/objects/pack && + test_commit add_local_objects && + git repack --local && + git multi-pack-index write && + midx_read_expect 1 3 4 $objdir && + git reset --hard HEAD~1 && + rm -f .git/objects/pack/* +' + +compare_results_with_midx "with alternate (remote midx)" + +# usage: corrupt_data <file> <pos> [<data>] +corrupt_data () { + file=$1 + pos=$2 + data="${3:-\0}" + printf "$data" | dd of="$file" bs=1 seek="$pos" conv=notrunc +} + +# Force 64-bit offsets by manipulating the idx file. +# This makes the IDX file _incorrect_ so be careful to clean up after! +test_expect_success 'force some 64-bit offsets with pack-objects' ' + mkdir objects64 && + mkdir objects64/pack && + for i in $(test_seq 1 11) + do + generate_objects 11 + done && + commit_and_list_objects && + pack64=$(git pack-objects --index-version=2,0x40 objects64/pack/test-64 <obj-list) && + idx64=objects64/pack/test-64-$pack64.idx && + chmod u+w $idx64 && + corrupt_data $idx64 2999 "\02" && + midx64=$(git multi-pack-index --object-dir=objects64 write) && + midx_read_expect 1 63 5 objects64 " large-offsets" +' + +test_expect_success 'verify multi-pack-index with 64-bit offsets' ' + git multi-pack-index verify --object-dir=objects64 +' + +NUM_OBJECTS=63 +MIDX_OFFSET_OID_FANOUT=$((MIDX_OFFSET_PACKNAMES + 54)) +MIDX_OFFSET_OID_LOOKUP=$((MIDX_OFFSET_OID_FANOUT + 256 * $MIDX_OID_FANOUT_WIDTH)) +MIDX_OFFSET_OBJECT_OFFSETS=$(($MIDX_OFFSET_OID_LOOKUP + $NUM_OBJECTS * $HASH_LEN)) +MIDX_OFFSET_LARGE_OFFSETS=$(($MIDX_OFFSET_OBJECT_OFFSETS + $NUM_OBJECTS * $MIDX_OFFSET_WIDTH)) +MIDX_BYTE_LARGE_OFFSET=$(($MIDX_OFFSET_LARGE_OFFSETS + 3)) + +test_expect_success 'verify incorrect 64-bit offset' ' + corrupt_midx_and_verify $MIDX_BYTE_LARGE_OFFSET "\07" objects64 \ + "incorrect object offset" +' + +test_done diff --git a/t/t5320-delta-islands.sh b/t/t5320-delta-islands.sh new file mode 100755 index 0000000000..fea92a5777 --- /dev/null +++ b/t/t5320-delta-islands.sh @@ -0,0 +1,143 @@ +#!/bin/sh + +test_description='exercise delta islands' +. ./test-lib.sh + +# returns true iff $1 is a delta based on $2 +is_delta_base () { + delta_base=$(echo "$1" | git cat-file --batch-check='%(deltabase)') && + echo >&2 "$1 has base $delta_base" && + test "$delta_base" = "$2" +} + +# generate a commit on branch $1 with a single file, "file", whose +# content is mostly based on the seed $2, but with a unique bit +# of content $3 appended. This should allow us to see whether +# blobs of different refs delta against each other. +commit() { + blob=$({ test-tool genrandom "$2" 10240 && echo "$3"; } | + git hash-object -w --stdin) && + tree=$(printf '100644 blob %s\tfile\n' "$blob" | git mktree) && + commit=$(echo "$2-$3" | git commit-tree "$tree" ${4:+-p "$4"}) && + git update-ref "refs/heads/$1" "$commit" && + eval "$1"'=$(git rev-parse $1:file)' && + eval "echo >&2 $1=\$$1" +} + +test_expect_success 'setup commits' ' + commit one seed 1 && + commit two seed 12 +' + +# Note: This is heavily dependent on the "prefer larger objects as base" +# heuristic. +test_expect_success 'vanilla repack deltas one against two' ' + git repack -adf && + is_delta_base $one $two +' + +test_expect_success 'island repack with no island definition is vanilla' ' + git repack -adfi && + is_delta_base $one $two +' + +test_expect_success 'island repack with no matches is vanilla' ' + git -c "pack.island=refs/foo" repack -adfi && + is_delta_base $one $two +' + +test_expect_success 'separate islands disallows delta' ' + git -c "pack.island=refs/heads/(.*)" repack -adfi && + ! is_delta_base $one $two && + ! is_delta_base $two $one +' + +test_expect_success 'same island allows delta' ' + git -c "pack.island=refs/heads" repack -adfi && + is_delta_base $one $two +' + +test_expect_success 'coalesce same-named islands' ' + git \ + -c "pack.island=refs/(.*)/one" \ + -c "pack.island=refs/(.*)/two" \ + repack -adfi && + is_delta_base $one $two +' + +test_expect_success 'island restrictions drop reused deltas' ' + git repack -adfi && + is_delta_base $one $two && + git -c "pack.island=refs/heads/(.*)" repack -adi && + ! is_delta_base $one $two && + ! is_delta_base $two $one +' + +test_expect_success 'island regexes are left-anchored' ' + git -c "pack.island=heads/(.*)" repack -adfi && + is_delta_base $one $two +' + +test_expect_success 'island regexes follow last-one-wins scheme' ' + git \ + -c "pack.island=refs/heads/(.*)" \ + -c "pack.island=refs/heads/" \ + repack -adfi && + is_delta_base $one $two +' + +test_expect_success 'setup shared history' ' + commit root shared root && + commit one shared 1 root && + commit two shared 12-long root +' + +# We know that $two will be preferred as a base from $one, +# because we can transform it with a pure deletion. +# +# We also expect $root as a delta against $two by the "longest is base" rule. +test_expect_success 'vanilla delta goes between branches' ' + git repack -adf && + is_delta_base $one $two && + is_delta_base $root $two +' + +# Here we should allow $one to base itself on $root; even though +# they are in different islands, the objects in $root are in a superset +# of islands compared to those in $one. +# +# Similarly, $two can delta against $root by our rules. And unlike $one, +# in which we are just allowing it, the island rules actually put $root +# as a possible base for $two, which it would not otherwise be (due to the size +# sorting). +test_expect_success 'deltas allowed against superset islands' ' + git -c "pack.island=refs/heads/(.*)" repack -adfi && + is_delta_base $one $root && + is_delta_base $two $root +' + +# We are going to test the packfile order here, so we again have to make some +# assumptions. We assume that "$root", as part of our core "one", must come +# before "$two". This should be guaranteed by the island code. However, for +# this test to fail without islands, we are also assuming that it would not +# otherwise do so. This is true by the current write order, which will put +# commits (and their contents) before their parents. +test_expect_success 'island core places core objects first' ' + cat >expect <<-EOF && + $root + $two + EOF + git -c "pack.island=refs/heads/(.*)" \ + -c "pack.islandcore=one" \ + repack -adfi && + git verify-pack -v .git/objects/pack/*.pack | + cut -d" " -f1 | + egrep "$root|$two" >actual && + test_cmp expect actual +' + +test_expect_success 'unmatched island core is not fatal' ' + git -c "pack.islandcore=one" repack -adfi +' + +test_done diff --git a/t/t5322-pack-objects-sparse.sh b/t/t5322-pack-objects-sparse.sh new file mode 100755 index 0000000000..7124b5581a --- /dev/null +++ b/t/t5322-pack-objects-sparse.sh @@ -0,0 +1,136 @@ +#!/bin/sh + +test_description='pack-objects object selection using sparse algorithm' +. ./test-lib.sh + +test_expect_success 'setup repo' ' + test_commit initial && + for i in $(test_seq 1 3) + do + mkdir f$i && + for j in $(test_seq 1 3) + do + mkdir f$i/f$j && + echo $j >f$i/f$j/data.txt + done + done && + git add . && + git commit -m "Initialized trees" && + for i in $(test_seq 1 3) + do + git checkout -b topic$i master && + echo change-$i >f$i/f$i/data.txt && + git commit -a -m "Changed f$i/f$i/data.txt" + done && + cat >packinput.txt <<-EOF && + topic1 + ^topic2 + ^topic3 + EOF + git rev-parse \ + topic1 \ + topic1^{tree} \ + topic1:f1 \ + topic1:f1/f1 \ + topic1:f1/f1/data.txt | sort >expect_objects.txt +' + +test_expect_success 'non-sparse pack-objects' ' + git pack-objects --stdout --revs --no-sparse <packinput.txt >nonsparse.pack && + git index-pack -o nonsparse.idx nonsparse.pack && + git show-index <nonsparse.idx | awk "{print \$2}" >nonsparse_objects.txt && + test_cmp expect_objects.txt nonsparse_objects.txt +' + +test_expect_success 'sparse pack-objects' ' + git pack-objects --stdout --revs --sparse <packinput.txt >sparse.pack && + git index-pack -o sparse.idx sparse.pack && + git show-index <sparse.idx | awk "{print \$2}" >sparse_objects.txt && + test_cmp expect_objects.txt sparse_objects.txt +' + +test_expect_success 'duplicate a folder from f3 and commit to topic1' ' + git checkout topic1 && + echo change-3 >f3/f3/data.txt && + git commit -a -m "Changed f3/f3/data.txt" && + git rev-parse \ + topic1~1 \ + topic1~1^{tree} \ + topic1^{tree} \ + topic1 \ + topic1:f1 \ + topic1:f1/f1 \ + topic1:f1/f1/data.txt | sort >required_objects.txt +' + +test_expect_success 'non-sparse pack-objects' ' + git pack-objects --stdout --revs --no-sparse <packinput.txt >nonsparse.pack && + git index-pack -o nonsparse.idx nonsparse.pack && + git show-index <nonsparse.idx | awk "{print \$2}" >nonsparse_objects.txt && + comm -1 -2 required_objects.txt nonsparse_objects.txt >nonsparse_required_objects.txt && + test_cmp required_objects.txt nonsparse_required_objects.txt +' + +test_expect_success 'sparse pack-objects' ' + git pack-objects --stdout --revs --sparse <packinput.txt >sparse.pack && + git index-pack -o sparse.idx sparse.pack && + git show-index <sparse.idx | awk "{print \$2}" >sparse_objects.txt && + comm -1 -2 required_objects.txt sparse_objects.txt >sparse_required_objects.txt && + test_cmp required_objects.txt sparse_required_objects.txt +' + +# Demonstrate that the algorithms differ when we copy a tree wholesale +# from one folder to another. + +test_expect_success 'duplicate a folder from f1 into f3' ' + mkdir f3/f4 && + cp -r f1/f1/* f3/f4 && + git add f3/f4 && + git commit -m "Copied f1/f1 to f3/f4" && + cat >packinput.txt <<-EOF && + topic1 + ^topic1~1 + EOF + git rev-parse \ + topic1 \ + topic1^{tree} \ + topic1:f3 | sort >required_objects.txt +' + +test_expect_success 'non-sparse pack-objects' ' + git pack-objects --stdout --revs --no-sparse <packinput.txt >nonsparse.pack && + git index-pack -o nonsparse.idx nonsparse.pack && + git show-index <nonsparse.idx | awk "{print \$2}" >nonsparse_objects.txt && + comm -1 -2 required_objects.txt nonsparse_objects.txt >nonsparse_required_objects.txt && + test_cmp required_objects.txt nonsparse_required_objects.txt +' + +test_expect_success 'sparse pack-objects' ' + git rev-parse \ + topic1 \ + topic1^{tree} \ + topic1:f3 \ + topic1:f3/f4 \ + topic1:f3/f4/data.txt | sort >expect_sparse_objects.txt && + git pack-objects --stdout --revs --sparse <packinput.txt >sparse.pack && + git index-pack -o sparse.idx sparse.pack && + git show-index <sparse.idx | awk "{print \$2}" >sparse_objects.txt && + test_cmp expect_sparse_objects.txt sparse_objects.txt +' + +test_expect_success 'pack.useSparse enables algorithm' ' + git config pack.useSparse true && + git pack-objects --stdout --revs <packinput.txt >sparse.pack && + git index-pack -o sparse.idx sparse.pack && + git show-index <sparse.idx | awk "{print \$2}" >sparse_objects.txt && + test_cmp expect_sparse_objects.txt sparse_objects.txt +' + +test_expect_success 'pack.useSparse overridden' ' + git pack-objects --stdout --revs --no-sparse <packinput.txt >sparse.pack && + git index-pack -o sparse.idx sparse.pack && + git show-index <sparse.idx | awk "{print \$2}" >sparse_objects.txt && + test_cmp required_objects.txt sparse_objects.txt +' + +test_done diff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh index fc898c9eac..a539ffc080 100755 --- a/t/t5403-post-checkout-hook.sh +++ b/t/t5403-post-checkout-hook.sh @@ -7,82 +7,70 @@ test_description='Test the post-checkout hook.' . ./test-lib.sh test_expect_success setup ' - echo Data for commit0. >a && - echo Data for commit0. >b && - git update-index --add a && - git update-index --add b && - tree0=$(git write-tree) && - commit0=$(echo setup | git commit-tree $tree0) && - git update-ref refs/heads/master $commit0 && - git clone ./. clone1 && - git clone ./. clone2 && - GIT_DIR=clone2/.git git branch new2 && - echo Data for commit1. >clone2/b && - GIT_DIR=clone2/.git git add clone2/b && - GIT_DIR=clone2/.git git commit -m new2 -' - -for clone in 1 2; do - cat >clone${clone}/.git/hooks/post-checkout <<'EOF' -#!/bin/sh -echo $@ > $GIT_DIR/post-checkout.args -EOF - chmod u+x clone${clone}/.git/hooks/post-checkout -done - -test_expect_success 'post-checkout runs as expected ' ' - GIT_DIR=clone1/.git git checkout master && - test -e clone1/.git/post-checkout.args + mkdir -p .git/hooks && + write_script .git/hooks/post-checkout <<-\EOF && + echo "$@" >.git/post-checkout.args + EOF + test_commit one && + test_commit two && + test_commit rebase-on-me && + git reset --hard HEAD^ && + test_commit three ' test_expect_success 'post-checkout receives the right arguments with HEAD unchanged ' ' - old=$(awk "{print \$1}" clone1/.git/post-checkout.args) && - new=$(awk "{print \$2}" clone1/.git/post-checkout.args) && - flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) && + test_when_finished "rm -f .git/post-checkout.args" && + git checkout master && + read old new flag <.git/post-checkout.args && test $old = $new && test $flag = 1 ' -test_expect_success 'post-checkout runs as expected ' ' - GIT_DIR=clone1/.git git checkout master && - test -e clone1/.git/post-checkout.args -' - test_expect_success 'post-checkout args are correct with git checkout -b ' ' - GIT_DIR=clone1/.git git checkout -b new1 && - old=$(awk "{print \$1}" clone1/.git/post-checkout.args) && - new=$(awk "{print \$2}" clone1/.git/post-checkout.args) && - flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) && + test_when_finished "rm -f .git/post-checkout.args" && + git checkout -b new1 && + read old new flag <.git/post-checkout.args && test $old = $new && test $flag = 1 ' test_expect_success 'post-checkout receives the right args with HEAD changed ' ' - GIT_DIR=clone2/.git git checkout new2 && - old=$(awk "{print \$1}" clone2/.git/post-checkout.args) && - new=$(awk "{print \$2}" clone2/.git/post-checkout.args) && - flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) && + test_when_finished "rm -f .git/post-checkout.args" && + git checkout two && + read old new flag <.git/post-checkout.args && test $old != $new && test $flag = 1 ' test_expect_success 'post-checkout receives the right args when not switching branches ' ' - GIT_DIR=clone2/.git git checkout master b && - old=$(awk "{print \$1}" clone2/.git/post-checkout.args) && - new=$(awk "{print \$2}" clone2/.git/post-checkout.args) && - flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) && + test_when_finished "rm -f .git/post-checkout.args" && + git checkout master -- three.t && + read old new flag <.git/post-checkout.args && test $old = $new && test $flag = 0 ' -if test "$(git config --bool core.filemode)" = true; then -mkdir -p templates/hooks -cat >templates/hooks/post-checkout <<'EOF' -#!/bin/sh -echo $@ > $GIT_DIR/post-checkout.args -EOF -chmod +x templates/hooks/post-checkout +test_expect_success 'post-checkout is triggered on rebase' ' + test_when_finished "rm -f .git/post-checkout.args" && + git checkout -b rebase-test master && + rm -f .git/post-checkout.args && + git rebase rebase-on-me && + read old new flag <.git/post-checkout.args && + test $old != $new && test $flag = 1 +' + +test_expect_success 'post-checkout is triggered on rebase with fast-forward' ' + test_when_finished "rm -f .git/post-checkout.args" && + git checkout -b ff-rebase-test rebase-on-me^ && + rm -f .git/post-checkout.args && + git rebase rebase-on-me && + read old new flag <.git/post-checkout.args && + test $old != $new && test $flag = 1 +' test_expect_success 'post-checkout hook is triggered by clone' ' + mkdir -p templates/hooks && + write_script templates/hooks/post-checkout <<-\EOF && + echo "$@" >$GIT_DIR/post-checkout.args + EOF git clone --template=templates . clone3 && test -f clone3/.git/post-checkout.args ' -fi test_done diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh index 9b2a274c71..a4a5903cba 100755 --- a/t/t5407-post-rewrite-hook.sh +++ b/t/t5407-post-rewrite-hook.sh @@ -78,6 +78,7 @@ test_expect_success 'git rebase --skip' ' git rebase --continue && echo rebase >expected.args && cat >expected.data <<-EOF && + $(git rev-parse C) $(git rev-parse HEAD^) $(git rev-parse D) $(git rev-parse HEAD) EOF verify_hook_input @@ -91,6 +92,7 @@ test_expect_success 'git rebase --skip the last one' ' echo rebase >expected.args && cat >expected.data <<-EOF && $(git rev-parse E) $(git rev-parse HEAD) + $(git rev-parse F) $(git rev-parse HEAD) EOF verify_hook_input ' @@ -120,6 +122,38 @@ test_expect_success 'git rebase -m --skip' ' git rebase --continue && echo rebase >expected.args && cat >expected.data <<-EOF && + $(git rev-parse C) $(git rev-parse HEAD^) + $(git rev-parse D) $(git rev-parse HEAD) + EOF + verify_hook_input +' + +test_expect_success 'git rebase with implicit use of interactive backend' ' + git reset --hard D && + clear_hook_input && + test_must_fail git rebase --keep --onto A B && + echo C > foo && + git add foo && + git rebase --continue && + echo rebase >expected.args && + cat >expected.data <<-EOF && + $(git rev-parse C) $(git rev-parse HEAD^) + $(git rev-parse D) $(git rev-parse HEAD) + EOF + verify_hook_input +' + +test_expect_success 'git rebase --skip with implicit use of interactive backend' ' + git reset --hard D && + clear_hook_input && + test_must_fail git rebase --keep --onto A B && + test_must_fail git rebase --skip && + echo D > foo && + git add foo && + git rebase --continue && + echo rebase >expected.args && + cat >expected.data <<-EOF && + $(git rev-parse C) $(git rev-parse HEAD^) $(git rev-parse D) $(git rev-parse HEAD) EOF verify_hook_input diff --git a/t/t5409-colorize-remote-messages.sh b/t/t5409-colorize-remote-messages.sh index f81b6813c0..2a8c449661 100755 --- a/t/t5409-colorize-remote-messages.sh +++ b/t/t5409-colorize-remote-messages.sh @@ -17,6 +17,7 @@ test_expect_success 'setup' ' echo " " "error: leading space" echo " " echo Err + echo SUCCESS exit 0 EOF echo 1 >file && @@ -35,6 +36,7 @@ test_expect_success 'keywords' ' grep "<BOLD;RED>error<RESET>: error" decoded && grep "<YELLOW>hint<RESET>:" decoded && grep "<BOLD;GREEN>success<RESET>:" decoded && + grep "<BOLD;GREEN>SUCCESS<RESET>" decoded && grep "<BOLD;YELLOW>warning<RESET>:" decoded ' diff --git a/t/t5410-receive-pack-alternates.sh b/t/t5410-receive-pack-alternates.sh new file mode 100755 index 0000000000..f00d0da860 --- /dev/null +++ b/t/t5410-receive-pack-alternates.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +test_description='git receive-pack with alternate ref filtering' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit base && + git clone -s --bare . fork && + git checkout -b public/branch master && + test_commit public && + git checkout -b private/branch master && + test_commit private +' + +extract_haves () { + depacketize | perl -lne '/^(\S+) \.have/ and print $1' +} + +test_expect_success 'with core.alternateRefsCommand' ' + write_script fork/alternate-refs <<-\EOF && + git --git-dir="$1" for-each-ref \ + --format="%(objectname)" \ + refs/heads/public/ + EOF + test_config -C fork core.alternateRefsCommand ./alternate-refs && + git rev-parse public/branch >expect && + printf "0000" | git receive-pack fork >actual && + extract_haves <actual >actual.haves && + test_cmp expect actual.haves +' + +test_expect_success 'with core.alternateRefsPrefixes' ' + test_config -C fork core.alternateRefsPrefixes "refs/heads/private" && + git rev-parse private/branch >expect && + printf "0000" | git receive-pack fork >actual && + extract_haves <actual >actual.haves && + test_cmp expect actual.haves +' + +test_done diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index 1b5a4a6d38..49c540b1e1 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -50,8 +50,11 @@ pull_to_client () { case "$heads" in *B*) git update-ref refs/heads/B "$BTIP";; esac && - git symbolic-ref HEAD refs/heads/$(echo $heads \ - | sed -e "s/^\(.\).*$/\1/") && + + git symbolic-ref HEAD refs/heads/$( + echo $heads | + sed -e "s/^\(.\).*$/\1/" + ) && git fsck --full && @@ -436,15 +439,23 @@ test_expect_success 'setup tests for the --stdin parameter' ' ) >input.dup ' -test_expect_success 'fetch refs from cmdline' ' - ( - cd client && - git fetch-pack --no-progress .. $(cat ../input) - ) >output && - cut -d " " -f 2 <output | sort >actual && - test_cmp expect actual +test_expect_success 'setup fetch refs from cmdline v[12]' ' + cp -r client client1 && + cp -r client client2 ' +for version in '' 1 2 +do + test_expect_success "protocol.version=$version fetch refs from cmdline" " + ( + cd client$version && + GIT_TEST_PROTOCOL_VERSION=$version git fetch-pack --no-progress .. \$(cat ../input) + ) >output && + cut -d ' ' -f 2 <output | sort >actual && + test_cmp expect actual + " +done + test_expect_success 'fetch refs from stdin' ' ( cd client && diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh index 62f3569891..7bc706873c 100755 --- a/t/t5504-fetch-receive-strict.sh +++ b/t/t5504-fetch-receive-strict.sh @@ -133,6 +133,34 @@ committer Bugs Bunny <bugs@bun.ni> 1234567890 +0000 This commit object intentionally broken EOF +test_expect_success 'setup bogus commit' ' + commit="$(git hash-object -t commit -w --stdin <bogus-commit)" +' + +test_expect_success 'fsck with no skipList input' ' + test_must_fail git fsck 2>err && + test_i18ngrep "missingEmail" err +' + +test_expect_success 'setup sorted and unsorted skipLists' ' + cat >SKIP.unsorted <<-EOF && + 0000000000000000000000000000000000000004 + 0000000000000000000000000000000000000002 + $commit + 0000000000000000000000000000000000000001 + 0000000000000000000000000000000000000003 + EOF + sort SKIP.unsorted >SKIP.sorted +' + +test_expect_success 'fsck with sorted skipList' ' + git -c fsck.skipList=SKIP.sorted fsck +' + +test_expect_success 'fsck with unsorted skipList' ' + git -c fsck.skipList=SKIP.unsorted fsck +' + test_expect_success 'fsck with invalid or bogus skipList input' ' git -c fsck.skipList=/dev/null -c fsck.missingEmail=ignore fsck && test_must_fail git -c fsck.skipList=does-not-exist -c fsck.missingEmail=ignore fsck 2>err && @@ -141,8 +169,47 @@ test_expect_success 'fsck with invalid or bogus skipList input' ' test_i18ngrep "Invalid SHA-1: \[core\]" err ' +test_expect_success 'fsck with other accepted skipList input (comments & empty lines)' ' + cat >SKIP.with-comment <<-EOF && + # Some bad commit + 0000000000000000000000000000000000000001 + EOF + test_must_fail git -c fsck.skipList=SKIP.with-comment fsck 2>err-with-comment && + test_i18ngrep "missingEmail" err-with-comment && + cat >SKIP.with-empty-line <<-EOF && + 0000000000000000000000000000000000000001 + + 0000000000000000000000000000000000000002 + EOF + test_must_fail git -c fsck.skipList=SKIP.with-empty-line fsck 2>err-with-empty-line && + test_i18ngrep "missingEmail" err-with-empty-line +' + +test_expect_success 'fsck no garbage output from comments & empty lines errors' ' + test_line_count = 1 err-with-comment && + test_line_count = 1 err-with-empty-line +' + +test_expect_success 'fsck with invalid abbreviated skipList input' ' + echo $commit | test_copy_bytes 20 >SKIP.abbreviated && + test_must_fail git -c fsck.skipList=SKIP.abbreviated fsck 2>err-abbreviated && + test_i18ngrep "^fatal: Invalid SHA-1: " err-abbreviated +' + +test_expect_success 'fsck with exhaustive accepted skipList input (various types of comments etc.)' ' + >SKIP.exhaustive && + echo "# A commented line" >>SKIP.exhaustive && + echo "" >>SKIP.exhaustive && + echo " " >>SKIP.exhaustive && + echo " # Comment after whitespace" >>SKIP.exhaustive && + echo "$commit # Our bad commit (with leading whitespace and trailing comment)" >>SKIP.exhaustive && + echo "# Some bad commit (leading whitespace)" >>SKIP.exhaustive && + echo " 0000000000000000000000000000000000000001" >>SKIP.exhaustive && + git -c fsck.skipList=SKIP.exhaustive fsck 2>err && + test_must_be_empty err +' + test_expect_success 'push with receive.fsck.skipList' ' - commit="$(git hash-object -t commit -w --stdin <bogus-commit)" && git push . $commit:refs/heads/bogus && rm -rf dst && git init dst && @@ -169,7 +236,6 @@ test_expect_success 'push with receive.fsck.skipList' ' ' test_expect_success 'fetch with fetch.fsck.skipList' ' - commit="$(git hash-object -t commit -w --stdin <bogus-commit)" && refspec=refs/heads/bogus:refs/heads/bogus && git push . $commit:refs/heads/bogus && rm -rf dst && @@ -204,7 +270,6 @@ test_expect_success 'fsck.<unknownmsg-id> dies' ' ' test_expect_success 'push with receive.fsck.missingEmail=warn' ' - commit="$(git hash-object -t commit -w --stdin <bogus-commit)" && git push . $commit:refs/heads/bogus && rm -rf dst && git init dst && @@ -232,7 +297,6 @@ test_expect_success 'push with receive.fsck.missingEmail=warn' ' ' test_expect_success 'fetch with fetch.fsck.missingEmail=warn' ' - commit="$(git hash-object -t commit -w --stdin <bogus-commit)" && refspec=refs/heads/bogus:refs/heads/bogus && git push . $commit:refs/heads/bogus && rm -rf dst && diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index d2a2cdd453..883b32efa0 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -1222,4 +1222,59 @@ test_expect_success 'add remote matching the "insteadOf" URL' ' git remote add backup xyz@example.com ' +test_expect_success 'unqualified <dst> refspec DWIM and advice' ' + test_when_finished "(cd test && git tag -d some-tag)" && + ( + cd test && + git tag -a -m "Some tag" some-tag master && + exit_with=true && + for type in commit tag tree blob + do + if test "$type" = "blob" + then + oid=$(git rev-parse some-tag:file) + else + oid=$(git rev-parse some-tag^{$type}) + fi && + test_must_fail git push origin $oid:dst 2>err && + test_i18ngrep "error: The destination you" err && + test_i18ngrep "hint: Did you mean" err && + test_must_fail git -c advice.pushUnqualifiedRefName=false \ + push origin $oid:dst 2>err && + test_i18ngrep "error: The destination you" err && + test_i18ngrep ! "hint: Did you mean" err || + exit_with=false + done && + $exit_with + ) +' + +test_expect_success 'refs/remotes/* <src> refspec and unqualified <dst> DWIM and advice' ' + ( + cd two && + git tag -a -m "Some tag" my-tag master && + git update-ref refs/trees/my-head-tree HEAD^{tree} && + git update-ref refs/blobs/my-file-blob HEAD:file + ) && + ( + cd test && + git config --add remote.two.fetch "+refs/tags/*:refs/remotes/tags-from-two/*" && + git config --add remote.two.fetch "+refs/trees/*:refs/remotes/trees-from-two/*" && + git config --add remote.two.fetch "+refs/blobs/*:refs/remotes/blobs-from-two/*" && + git fetch --no-tags two && + + test_must_fail git push origin refs/remotes/two/another:dst 2>err && + test_i18ngrep "error: The destination you" err && + + test_must_fail git push origin refs/remotes/tags-from-two/my-tag:dst-tag 2>err && + test_i18ngrep "error: The destination you" err && + + test_must_fail git push origin refs/remotes/trees-from-two/my-head-tree:dst-tree 2>err && + test_i18ngrep "error: The destination you" err && + + test_must_fail git push origin refs/remotes/blobs-from-two/my-file-blob:dst-blob 2>err && + test_i18ngrep "error: The destination you" err + ) +' + test_done diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh index bc5703ff9b..ced15ae122 100755 --- a/t/t5512-ls-remote.sh +++ b/t/t5512-ls-remote.sh @@ -204,6 +204,12 @@ test_expect_success 'overrides work between mixed transfer/upload-pack hideRefs' grep refs/tags/magic actual ' +test_expect_success 'protocol v2 supports hiderefs' ' + test_config uploadpack.hiderefs refs/tags && + git -c protocol.version=2 ls-remote . >actual && + ! grep refs/tags actual +' + test_expect_success 'ls-remote --symref' ' git fetch origin && cat >expect <<-EOF && @@ -260,7 +266,7 @@ test_lazy_prereq GIT_DAEMON ' # This test spawns a daemon, so run it only if the user would be OK with # testing with git-daemon. test_expect_success PIPE,JGIT,GIT_DAEMON 'indicate no refs in standards-compliant empty remote' ' - JGIT_DAEMON_PORT=${JGIT_DAEMON_PORT-${this_test#t}} && + test_set_port JGIT_DAEMON_PORT && JGIT_DAEMON_PID= && git init --bare empty.git && >empty.git/git-daemon-export-ok && @@ -302,4 +308,28 @@ test_expect_success 'ls-remote works outside repository' ' nongit git ls-remote dst.git ' +test_expect_success 'ls-remote --sort fails gracefully outside repository' ' + # Use a sort key that requires access to the referenced objects. + nongit test_must_fail git ls-remote --sort=authordate "$TRASH_DIRECTORY" 2>err && + test_i18ngrep "^fatal: not a git repository, but the field '\''authordate'\'' requires access to object data" err +' + +test_expect_success 'ls-remote patterns work with all protocol versions' ' + git for-each-ref --format="%(objectname) %(refname)" \ + refs/heads/master refs/remotes/origin/master >expect && + git -c protocol.version=1 ls-remote . master >actual.v1 && + test_cmp expect actual.v1 && + git -c protocol.version=2 ls-remote . master >actual.v2 && + test_cmp expect actual.v2 +' + +test_expect_success 'ls-remote prefixes work with all protocol versions' ' + git for-each-ref --format="%(objectname) %(refname)" \ + refs/heads/ refs/tags/ >expect && + git -c protocol.version=1 ls-remote --heads --tags . >actual.v1 && + test_cmp expect actual.v1 && + git -c protocol.version=2 ls-remote --heads --tags . >actual.v2 && + test_cmp expect actual.v2 +' + test_done diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index bb0a36535c..37e8e80893 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -95,7 +95,7 @@ mk_child() { check_push_result () { test $# -ge 3 || - error "bug in the test script: check_push_result requires at least 3 parameters" + BUG "check_push_result requires at least 3 parameters" repo_name="$1" shift @@ -969,7 +969,7 @@ test_force_push_tag () { tag_type_description=$1 tag_args=$2 - test_expect_success 'force pushing required to update lightweight tag' " + test_expect_success "force pushing required to update $tag_type_description" " mk_test testrepo heads/master && mk_child testrepo child1 && mk_child testrepo child2 && @@ -1009,7 +1009,32 @@ test_force_push_tag () { } test_force_push_tag "lightweight tag" "-f" -test_force_push_tag "annotated tag" "-f -a -m'msg'" +test_force_push_tag "annotated tag" "-f -a -m'tag message'" + +test_force_fetch_tag () { + tag_type_description=$1 + tag_args=$2 + + test_expect_success "fetch will not clobber an existing $tag_type_description without --force" " + mk_test testrepo heads/master && + mk_child testrepo child1 && + mk_child testrepo child2 && + ( + cd testrepo && + git tag testTag && + git -C ../child1 fetch origin tag testTag && + >file1 && + git add file1 && + git commit -m 'file1' && + git tag $tag_args testTag && + test_must_fail git -C ../child1 fetch origin tag testTag && + git -C ../child1 fetch origin '+refs/tags/*:refs/tags/*' + ) + " +} + +test_force_fetch_tag "lightweight tag" "-f" +test_force_fetch_tag "annotated tag" "-f -a -m'tag message'" test_expect_success 'push --porcelain' ' mk_empty testrepo && diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index 5e501c8b08..cf4cc32fd0 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -461,7 +461,8 @@ test_expect_success 'pull.rebase=1 is treated as true and flattens keep-merge' ' test file3 = "$(git show HEAD:file3.t)" ' -test_expect_success 'pull.rebase=preserve rebases and merges keep-merge' ' +test_expect_success REBASE_P \ + 'pull.rebase=preserve rebases and merges keep-merge' ' git reset --hard before-preserve-rebase && test_config pull.rebase preserve && git pull . copy && @@ -514,7 +515,8 @@ test_expect_success '--rebase=true rebases and flattens keep-merge' ' test file3 = "$(git show HEAD:file3.t)" ' -test_expect_success '--rebase=preserve rebases and merges keep-merge' ' +test_expect_success REBASE_P \ + '--rebase=preserve rebases and merges keep-merge' ' git reset --hard before-preserve-rebase && test_config pull.rebase true && git pull --rebase=preserve . copy && diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh index 6c2f9b2ba2..63205dfdf9 100755 --- a/t/t5526-fetch-submodules.sh +++ b/t/t5526-fetch-submodules.sh @@ -524,6 +524,8 @@ test_expect_success 'fetching submodules respects parallel settings' ' git config fetch.recurseSubmodules true && ( cd downstream && + GIT_TRACE=$(pwd)/trace.out git fetch && + grep "1 tasks" trace.out && GIT_TRACE=$(pwd)/trace.out git fetch --jobs 7 && grep "7 tasks" trace.out && git config submodule.fetchJobs 8 && @@ -600,4 +602,121 @@ test_expect_success "fetch new commits when submodule got renamed" ' test_cmp expect actual ' +test_expect_success "fetch new submodule commits on-demand outside standard refspec" ' + # add a second submodule and ensure it is around in downstream first + git clone submodule sub1 && + git submodule add ./sub1 && + git commit -m "adding a second submodule" && + git -C downstream pull && + git -C downstream submodule update --init --recursive && + + git checkout --detach && + + C=$(git -C submodule commit-tree -m "new change outside refs/heads" HEAD^{tree}) && + git -C submodule update-ref refs/changes/1 $C && + git update-index --cacheinfo 160000 $C submodule && + test_tick && + + D=$(git -C sub1 commit-tree -m "new change outside refs/heads" HEAD^{tree}) && + git -C sub1 update-ref refs/changes/2 $D && + git update-index --cacheinfo 160000 $D sub1 && + + git commit -m "updated submodules outside of refs/heads" && + E=$(git rev-parse HEAD) && + git update-ref refs/changes/3 $E && + ( + cd downstream && + git fetch --recurse-submodules origin refs/changes/3:refs/heads/my_branch && + git -C submodule cat-file -t $C && + git -C sub1 cat-file -t $D && + git checkout --recurse-submodules FETCH_HEAD + ) +' + +test_expect_success 'fetch new submodule commit on-demand in FETCH_HEAD' ' + # depends on the previous test for setup + + C=$(git -C submodule commit-tree -m "another change outside refs/heads" HEAD^{tree}) && + git -C submodule update-ref refs/changes/4 $C && + git update-index --cacheinfo 160000 $C submodule && + test_tick && + + D=$(git -C sub1 commit-tree -m "another change outside refs/heads" HEAD^{tree}) && + git -C sub1 update-ref refs/changes/5 $D && + git update-index --cacheinfo 160000 $D sub1 && + + git commit -m "updated submodules outside of refs/heads" && + E=$(git rev-parse HEAD) && + git update-ref refs/changes/6 $E && + ( + cd downstream && + git fetch --recurse-submodules origin refs/changes/6 && + git -C submodule cat-file -t $C && + git -C sub1 cat-file -t $D && + git checkout --recurse-submodules FETCH_HEAD + ) +' + +test_expect_success 'fetch new submodule commits on-demand without .gitmodules entry' ' + # depends on the previous test for setup + + git config -f .gitmodules --remove-section submodule.sub1 && + git add .gitmodules && + git commit -m "delete gitmodules file" && + git checkout -B master && + git -C downstream fetch && + git -C downstream checkout origin/master && + + C=$(git -C submodule commit-tree -m "yet another change outside refs/heads" HEAD^{tree}) && + git -C submodule update-ref refs/changes/7 $C && + git update-index --cacheinfo 160000 $C submodule && + test_tick && + + D=$(git -C sub1 commit-tree -m "yet another change outside refs/heads" HEAD^{tree}) && + git -C sub1 update-ref refs/changes/8 $D && + git update-index --cacheinfo 160000 $D sub1 && + + git commit -m "updated submodules outside of refs/heads" && + E=$(git rev-parse HEAD) && + git update-ref refs/changes/9 $E && + ( + cd downstream && + git fetch --recurse-submodules origin refs/changes/9 && + git -C submodule cat-file -t $C && + git -C sub1 cat-file -t $D && + git checkout --recurse-submodules FETCH_HEAD + ) +' + +test_expect_success 'fetch new submodule commit intermittently referenced by superproject' ' + # depends on the previous test for setup + + D=$(git -C sub1 commit-tree -m "change 10 outside refs/heads" HEAD^{tree}) && + E=$(git -C sub1 commit-tree -m "change 11 outside refs/heads" HEAD^{tree}) && + F=$(git -C sub1 commit-tree -m "change 12 outside refs/heads" HEAD^{tree}) && + + git -C sub1 update-ref refs/changes/10 $D && + git update-index --cacheinfo 160000 $D sub1 && + git commit -m "updated submodules outside of refs/heads" && + + git -C sub1 update-ref refs/changes/11 $E && + git update-index --cacheinfo 160000 $E sub1 && + git commit -m "updated submodules outside of refs/heads" && + + git -C sub1 update-ref refs/changes/12 $F && + git update-index --cacheinfo 160000 $F sub1 && + git commit -m "updated submodules outside of refs/heads" && + + G=$(git rev-parse HEAD) && + git update-ref refs/changes/13 $G && + ( + cd downstream && + git fetch --recurse-submodules origin refs/changes/13 && + + git -C sub1 cat-file -t $D && + git -C sub1 cat-file -t $E && + git -C sub1 cat-file -t $F + ) +' + test_done diff --git a/t/t5537-fetch-shallow.sh b/t/t5537-fetch-shallow.sh index 6faf17e17a..6caf628efa 100755 --- a/t/t5537-fetch-shallow.sh +++ b/t/t5537-fetch-shallow.sh @@ -243,7 +243,8 @@ test_expect_success 'shallow fetches check connectivity before writing shallow f "$(git -C "$REPO" rev-parse HEAD)" \ "$(git -C "$REPO" rev-parse HEAD^)" \ >"$HTTPD_ROOT_PATH/one-time-sed" && - test_must_fail git -C client fetch --depth=1 "$HTTPD_URL/one_time_sed/repo" \ + test_must_fail env GIT_TEST_SIDEBAND_ALL=0 git -C client \ + fetch --depth=1 "$HTTPD_URL/one_time_sed/repo" \ master:a_branch && # Ensure that the one-time-sed script was used. diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index 3dc8f8ecec..8630b0cc39 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -381,6 +381,21 @@ test_expect_success 'using fetch command in remote-curl updates refs' ' test_cmp expect actual ' +test_expect_success 'fetch by SHA-1 without tag following' ' + SERVER="$HTTPD_DOCUMENT_ROOT_PATH/server" && + rm -rf "$SERVER" client && + + git init "$SERVER" && + test_commit -C "$SERVER" foo && + + git clone $HTTPD_URL/smart/server client && + + test_commit -C "$SERVER" bar && + git -C "$SERVER" rev-parse bar >bar_hash && + git -C client -c protocol.version=0 fetch \ + --no-tags origin $(cat bar_hash) +' + test_expect_success 'GIT_REDACT_COOKIES redacts cookies' ' rm -rf clone && echo "Set-Cookie: Foo=1" >cookies && diff --git a/t/t5562-http-backend-content-length.sh b/t/t5562-http-backend-content-length.sh index f94d01f69e..90d890d02f 100755 --- a/t/t5562-http-backend-content-length.sh +++ b/t/t5562-http-backend-content-length.sh @@ -31,6 +31,7 @@ test_http_env() { PATH_TRANSLATED="$PWD/.git/git-$handler_type-pack" \ GIT_HTTP_EXPORT_ALL=TRUE \ REQUEST_METHOD=POST \ + "$PERL_PATH" \ "$TEST_DIRECTORY"/t5562/invoke-with-content-length.pl \ "$request_body" git http-backend >act.out 2>act.err } @@ -155,8 +156,8 @@ test_expect_success 'CONTENT_LENGTH overflow ssite_t' ' test_expect_success 'empty CONTENT_LENGTH' ' env \ - QUERY_STRING=/repo.git/HEAD \ - PATH_TRANSLATED="$PWD"/.git/HEAD \ + QUERY_STRING="service=git-receive-pack" \ + PATH_TRANSLATED="$PWD"/.git/info/refs \ GIT_HTTP_EXPORT_ALL=TRUE \ REQUEST_METHOD=GET \ CONTENT_LENGTH="" \ diff --git a/t/t5562/invoke-with-content-length.pl b/t/t5562/invoke-with-content-length.pl index 6c2aae7692..0943474af2 100755..100644 --- a/t/t5562/invoke-with-content-length.pl +++ b/t/t5562/invoke-with-content-length.pl @@ -1,4 +1,3 @@ -#!/usr/bin/perl use 5.008; use strict; use warnings; diff --git a/t/t5570-git-daemon.sh b/t/t5570-git-daemon.sh index 7466aad111..58ee787685 100755 --- a/t/t5570-git-daemon.sh +++ b/t/t5570-git-daemon.sh @@ -183,19 +183,6 @@ test_expect_success 'hostname cannot break out of directory' ' 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 diff --git a/t/t5573-pull-verify-signatures.sh b/t/t5573-pull-verify-signatures.sh index 747775c147..3e9876e197 100755 --- a/t/t5573-pull-verify-signatures.sh +++ b/t/t5573-pull-verify-signatures.sh @@ -78,4 +78,11 @@ test_expect_success GPG 'pull commit with bad signature with --no-verify-signatu git pull --ff-only --no-verify-signatures bad 2>pullerror ' +test_expect_success GPG 'pull unsigned commit into unborn branch' ' + git init empty-repo && + test_must_fail \ + git -C empty-repo pull --verify-signatures .. 2>pullerror && + test_i18ngrep "does not have a GPG signature" pullerror +' + test_done diff --git a/t/t5580-clone-push-unc.sh b/t/t5580-clone-push-unc.sh index ba548df4a9..217adf3a63 100755 --- a/t/t5580-clone-push-unc.sh +++ b/t/t5580-clone-push-unc.sh @@ -40,6 +40,11 @@ test_expect_success clone ' git clone "file://$UNCPATH" clone ' +test_expect_success 'clone with backslashed path' ' + BACKSLASHED="$(echo "$UNCPATH" | tr / \\\\)" && + git clone "$BACKSLASHED" backslashed +' + test_expect_success push ' ( cd clone && diff --git a/t/t5581-http-curl-verbose.sh b/t/t5581-http-curl-verbose.sh new file mode 100755 index 0000000000..cd9283eeec --- /dev/null +++ b/t/t5581-http-curl-verbose.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +test_description='test GIT_CURL_VERBOSE' +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +test_expect_success 'setup repository' ' + mkdir "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" --bare init && + git config push.default matching && + echo content >file && + git add file && + git commit -m one && + git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git push public master:master +' + +test_expect_success 'failure in git-upload-pack is shown' ' + test_might_fail env GIT_CURL_VERBOSE=1 \ + git clone "$HTTPD_URL/error_git_upload_pack/smart/repo.git" \ + 2>curl_log && + grep "< HTTP/1.1 500 Intentional Breakage" curl_log +' + +stop_httpd + +test_done diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index ddaa96ac4f..d6948cbdab 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -487,7 +487,7 @@ test_clone_url () { expect_ssh "$@" } -test_expect_success !MINGW 'clone c:temp is ssl' ' +test_expect_success !MINGW,!CYGWIN 'clone c:temp is ssl' ' test_clone_url c:temp c temp ' @@ -624,10 +624,16 @@ test_expect_success 'clone on case-insensitive fs' ' 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 + git clone -b bogus . bogus 2>warning ) ' +test_expect_success CASE_INSENSITIVE_FS 'colliding file detection' ' + grep X icasefs/warning && + grep x icasefs/warning && + test_i18ngrep "the following paths have collided" icasefs/warning +' + partial_clone () { SERVER="$1" && URL="$2" && diff --git a/t/t5607-clone-bundle.sh b/t/t5607-clone-bundle.sh index 348d9b3bc7..cf39e9e243 100755 --- a/t/t5607-clone-bundle.sh +++ b/t/t5607-clone-bundle.sh @@ -71,4 +71,10 @@ test_expect_success 'prerequisites with an empty commit message' ' git bundle verify bundle ' +test_expect_success 'failed bundle creation does not leave cruft' ' + # This fails because the bundle would be empty. + test_must_fail git bundle create fail.bundle master..master && + test_path_is_missing fail.bundle.lock +' + test_done diff --git a/t/t5611-clone-config.sh b/t/t5611-clone-config.sh index 39329eb7a8..60c1ba951b 100755 --- a/t/t5611-clone-config.sh +++ b/t/t5611-clone-config.sh @@ -45,6 +45,53 @@ test_expect_success 'clone -c config is available during clone' ' test_cmp expect child/file ' +test_expect_success 'clone -c remote.origin.fetch=<refspec> works' ' + rm -rf child && + git update-ref refs/grab/it refs/heads/master && + git update-ref refs/leave/out refs/heads/master && + git clone -c "remote.origin.fetch=+refs/grab/*:refs/grab/*" . child && + git -C child for-each-ref --format="%(refname)" >actual && + + cat >expect <<-\EOF && + refs/grab/it + refs/heads/master + refs/remotes/origin/HEAD + refs/remotes/origin/master + EOF + test_cmp expect actual +' + +test_expect_success 'git -c remote.origin.fetch=<refspec> clone works' ' + rm -rf child && + git -c "remote.origin.fetch=+refs/grab/*:refs/grab/*" clone . child && + git -C child for-each-ref --format="%(refname)" >actual && + + cat >expect <<-\EOF && + refs/grab/it + refs/heads/master + refs/remotes/origin/HEAD + refs/remotes/origin/master + EOF + test_cmp expect actual +' + +test_expect_success 'clone -c remote.<remote>.fetch=<refspec> --origin=<name>' ' + rm -rf child && + git clone --origin=upstream \ + -c "remote.upstream.fetch=+refs/grab/*:refs/grab/*" \ + -c "remote.origin.fetch=+refs/leave/*:refs/leave/*" \ + . child && + git -C child for-each-ref --format="%(refname)" >actual && + + cat >expect <<-\EOF && + refs/grab/it + refs/heads/master + refs/remotes/upstream/HEAD + refs/remotes/upstream/master + EOF + test_cmp expect actual +' + # Tests for the hidden file attribute on windows is_hidden () { # Use the output of `attrib`, ignore the absolute path diff --git a/t/t5612-clone-refspec.sh b/t/t5612-clone-refspec.sh index 5582b3d5fd..e36ac01661 100755 --- a/t/t5612-clone-refspec.sh +++ b/t/t5612-clone-refspec.sh @@ -103,7 +103,7 @@ test_expect_success 'clone with --no-tags' ' test_expect_success '--single-branch while HEAD pointing at master' ' ( cd dir_master && - git fetch && + git fetch --force && git for-each-ref refs/remotes/origin | sed -e "/HEAD$/d" \ -e "s|/remotes/origin/|/heads/|" >../actual @@ -114,7 +114,7 @@ test_expect_success '--single-branch while HEAD pointing at master' ' test_cmp expect actual && ( cd dir_master && - git fetch --tags && + git fetch --tags --force && git for-each-ref refs/tags >../actual ) && git for-each-ref refs/tags >expect && diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh index bbbe7537df..9643acb161 100755 --- a/t/t5616-partial-clone.sh +++ b/t/t5616-partial-clone.sh @@ -34,10 +34,12 @@ test_expect_success 'setup bare clone for server' ' # 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 && + + git -C pc1 rev-list --quiet --objects --missing=print HEAD >revs && + awk -f print_1.awk revs | + 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" && @@ -46,10 +48,10 @@ test_expect_success 'do partial clone 1' ' # 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 && + git -C pc1 rev-list --quiet --objects --missing=print HEAD >observed && test_line_count = 4 observed && git -C pc1 checkout master && - git -C pc1 rev-list HEAD --quiet --objects --missing=print >observed && + git -C pc1 rev-list --quiet --objects --missing=print HEAD >observed && test_line_count = 0 observed ' @@ -72,7 +74,8 @@ test_expect_success 'push new commits to server' ' # 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 && + git -C pc1 rev-list --quiet --objects --missing=print \ + master..origin/master >observed && test_line_count = 5 observed ' @@ -80,7 +83,8 @@ test_expect_success 'partial fetch inherits filter settings' ' # 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 && + git -C pc1 rev-list --quiet --objects --missing=print \ + master..origin/master >observed && test_line_count = 4 observed ' @@ -89,7 +93,8 @@ test_expect_success 'verify diff causes dynamic object fetch' ' 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 && + git -C pc1 rev-list --quiet --objects --missing=print \ + master..origin/master >observed && test_line_count = 0 observed ' @@ -109,7 +114,8 @@ test_expect_success 'push new commits to server for file.2.txt' ' # 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 && + git -C pc1 rev-list --quiet --objects --missing=print \ + master..origin/master >observed && test_line_count = 0 observed ' @@ -130,16 +136,22 @@ test_expect_success 'push new commits to server for file.3.txt' ' # 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 && + + git -C pc1 rev-list --quiet --objects --missing=print \ + master..origin/master >revs && + awk -f print_1.awk revs | + 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 && + + git -C pc1 rev-list --quiet --objects --missing=print \ + master..origin/master >revs && + awk -f print_1.awk revs | + sed "s/?//" | + sort >observed.oids && + test_line_count = 0 observed.oids ' @@ -154,6 +166,48 @@ test_expect_success 'partial clone with transfer.fsckobjects=1 uses index-pack - grep "git index-pack.*--fsck-objects" trace ' +test_expect_success 'use fsck before and after manually fetching a missing subtree' ' + # push new commit so server has a subtree + mkdir src/dir && + echo "in dir" >src/dir/file.txt && + git -C src add dir/file.txt && + git -C src commit -m "file in dir" && + git -C src push -u srv master && + SUBTREE=$(git -C src rev-parse HEAD:dir) && + + rm -rf dst && + git clone --no-checkout --filter=tree:0 "file://$(pwd)/srv.bare" dst && + git -C dst fsck && + + # Make sure we only have commits, and all trees and blobs are missing. + git -C dst rev-list --missing=allow-any --objects master \ + >fetched_objects && + awk -f print_1.awk fetched_objects | + xargs -n1 git -C dst cat-file -t >fetched_types && + + sort -u fetched_types >unique_types.observed && + echo commit >unique_types.expected && + test_cmp unique_types.expected unique_types.observed && + + # Auto-fetch a tree with cat-file. + git -C dst cat-file -p $SUBTREE >tree_contents && + grep file.txt tree_contents && + + # fsck still works after an auto-fetch of a tree. + git -C dst fsck && + + # Auto-fetch all remaining trees and blobs with --missing=error + git -C dst rev-list --missing=error --objects master >fetched_objects && + test_line_count = 70 fetched_objects && + + awk -f print_1.awk fetched_objects | + xargs -n1 git -C dst cat-file -t >fetched_types && + + sort -u fetched_types >unique_types.observed && + test_write_lines blob commit tree >unique_types.expected && + test_cmp unique_types.expected unique_types.observed +' + test_expect_success 'partial clone fetches blobs pointed to by refs even if normally filtered out' ' rm -rf src dst && git init src && @@ -170,6 +224,23 @@ test_expect_success 'partial clone fetches blobs pointed to by refs even if norm git -C dst fsck ' +test_expect_success 'fetch what is specified on CLI even if already promised' ' + rm -rf src dst.git && + git init src && + test_commit -C src foo && + test_config -C src uploadpack.allowfilter 1 && + test_config -C src uploadpack.allowanysha1inwant 1 && + + git hash-object --stdin <src/foo.t >blob && + + git clone --bare --filter=blob:none "file://$(pwd)/src" dst.git && + git -C dst.git rev-list --objects --quiet --missing=print HEAD >missing_before && + grep "?$(cat blob)" missing_before && + git -C dst.git fetch origin $(cat blob) && + git -C dst.git rev-list --objects --quiet --missing=print HEAD >missing_after && + ! grep "?$(cat blob)" missing_after +' + . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd @@ -194,7 +265,7 @@ test_expect_success 'upon cloning, check that all refs point to objects' ' # Craft a packfile not including that blob. git -C "$SERVER" rev-parse HEAD | - git -C "$SERVER" pack-objects --stdout >incomplete.pack && + git -C "$SERVER" pack-objects --stdout >incomplete.pack && # Replace the existing packfile with the crafted one. The protocol # requires that the packfile be sent in sideband 1, hence the extra @@ -210,7 +281,7 @@ test_expect_success 'upon cloning, check that all refs point to objects' ' test_must_fail git -c protocol.version=2 clone \ --filter=blob:none $HTTPD_URL/one_time_sed/server repo 2>err && - grep "did not send all necessary objects" err && + test_i18ngrep "did not send all necessary objects" err && # Ensure that the one-time-sed script was used. ! test -e "$HTTPD_ROOT_PATH/one-time-sed" diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh index 75ec79e6cb..fe45bf828d 100755 --- a/t/t5701-git-serve.sh +++ b/t/t5701-git-serve.sh @@ -14,14 +14,14 @@ test_expect_success 'test capability advertisement' ' 0000 EOF - git serve --advertise-capabilities >out && - test-pkt-line unpack <out >actual && - test_cmp actual expect + GIT_TEST_SIDEBAND_ALL=0 git serve --advertise-capabilities >out && + test-tool pkt-line unpack <out >actual && + test_cmp expect actual ' test_expect_success 'stateless-rpc flag does not list capabilities' ' # Empty request - test-pkt-line pack >in <<-EOF && + test-tool pkt-line pack >in <<-EOF && 0000 EOF git serve --stateless-rpc >out <in && @@ -33,7 +33,7 @@ test_expect_success 'stateless-rpc flag does not list capabilities' ' ' test_expect_success 'request invalid capability' ' - test-pkt-line pack >in <<-EOF && + test-tool pkt-line pack >in <<-EOF && foobar 0000 EOF @@ -42,7 +42,7 @@ test_expect_success 'request invalid capability' ' ' test_expect_success 'request with no command' ' - test-pkt-line pack >in <<-EOF && + test-tool pkt-line pack >in <<-EOF && agent=git/test 0000 EOF @@ -51,7 +51,7 @@ test_expect_success 'request with no command' ' ' test_expect_success 'request invalid command' ' - test-pkt-line pack >in <<-EOF && + test-tool pkt-line pack >in <<-EOF && command=foo agent=git/test 0000 @@ -71,7 +71,7 @@ test_expect_success 'setup some refs and tags' ' ' test_expect_success 'basics of ls-refs' ' - test-pkt-line pack >in <<-EOF && + test-tool pkt-line pack >in <<-EOF && command=ls-refs 0000 EOF @@ -88,12 +88,12 @@ test_expect_success 'basics of ls-refs' ' EOF git serve --stateless-rpc <in >out && - test-pkt-line unpack <out >actual && - test_cmp actual expect + test-tool pkt-line unpack <out >actual && + test_cmp expect actual ' test_expect_success 'basic ref-prefixes' ' - test-pkt-line pack >in <<-EOF && + test-tool pkt-line pack >in <<-EOF && command=ls-refs 0001 ref-prefix refs/heads/master @@ -108,12 +108,12 @@ test_expect_success 'basic ref-prefixes' ' EOF git serve --stateless-rpc <in >out && - test-pkt-line unpack <out >actual && - test_cmp actual expect + test-tool pkt-line unpack <out >actual && + test_cmp expect actual ' test_expect_success 'refs/heads prefix' ' - test-pkt-line pack >in <<-EOF && + test-tool pkt-line pack >in <<-EOF && command=ls-refs 0001 ref-prefix refs/heads/ @@ -128,12 +128,12 @@ test_expect_success 'refs/heads prefix' ' EOF git serve --stateless-rpc <in >out && - test-pkt-line unpack <out >actual && - test_cmp actual expect + test-tool pkt-line unpack <out >actual && + test_cmp expect actual ' test_expect_success 'peel parameter' ' - test-pkt-line pack >in <<-EOF && + test-tool pkt-line pack >in <<-EOF && command=ls-refs 0001 peel @@ -149,12 +149,12 @@ test_expect_success 'peel parameter' ' EOF git serve --stateless-rpc <in >out && - test-pkt-line unpack <out >actual && - test_cmp actual expect + test-tool pkt-line unpack <out >actual && + test_cmp expect actual ' test_expect_success 'symrefs parameter' ' - test-pkt-line pack >in <<-EOF && + test-tool pkt-line pack >in <<-EOF && command=ls-refs 0001 symrefs @@ -170,12 +170,12 @@ test_expect_success 'symrefs parameter' ' EOF git serve --stateless-rpc <in >out && - test-pkt-line unpack <out >actual && - test_cmp actual expect + test-tool pkt-line unpack <out >actual && + test_cmp expect actual ' test_expect_success 'sending server-options' ' - test-pkt-line pack >in <<-EOF && + test-tool pkt-line pack >in <<-EOF && command=ls-refs server-option=hello server-option=world @@ -190,14 +190,14 @@ test_expect_success 'sending server-options' ' EOF git serve --stateless-rpc <in >out && - test-pkt-line unpack <out >actual && - test_cmp actual expect + test-tool pkt-line unpack <out >actual && + test_cmp expect actual ' test_expect_success 'unexpected lines are not allowed in fetch request' ' git init server && - test-pkt-line pack >in <<-EOF && + test-tool pkt-line pack >in <<-EOF && command=fetch 0001 this-is-not-a-command diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh index 3beeed4546..db4ae09f2f 100755 --- a/t/t5702-protocol-v2.sh +++ b/t/t5702-protocol-v2.sh @@ -29,7 +29,7 @@ test_expect_success 'list refs with git:// using protocol v2' ' grep "git< version 2" log && git ls-remote --symref "$GIT_DAEMON_URL/parent" >expect && - test_cmp actual expect + test_cmp expect actual ' test_expect_success 'ref advertisment is filtered with ls-remote using protocol v2' ' @@ -42,7 +42,7 @@ test_expect_success 'ref advertisment is filtered with ls-remote using protocol $(git -C "$daemon_parent" rev-parse refs/heads/master)$(printf "\t")refs/heads/master EOF - test_cmp actual expect + test_cmp expect actual ' test_expect_success 'clone with git:// using protocol v2' ' @@ -79,6 +79,19 @@ test_expect_success 'fetch with git:// using protocol v2' ' grep "fetch< version 2" log ' +test_expect_success 'fetch by hash without tag following with protocol v2 does not list refs' ' + test_when_finished "rm -f log" && + + test_commit -C "$daemon_parent" two_a && + git -C "$daemon_parent" rev-parse two_a >two_a_hash && + + GIT_TRACE_PACKET="$(pwd)/log" git -C daemon_child -c protocol.version=2 \ + fetch --no-tags origin $(cat two_a_hash) && + + grep "fetch< version 2" log && + ! grep "fetch> command=ls-refs" log +' + test_expect_success 'pull with git:// using protocol v2' ' test_when_finished "rm -f log" && @@ -138,7 +151,7 @@ test_expect_success 'list refs with file:// using protocol v2' ' grep "git< version 2" log && git ls-remote --symref "file://$(pwd)/file_parent" >expect && - test_cmp actual expect + test_cmp expect actual ' test_expect_success 'ref advertisment is filtered with ls-remote using protocol v2' ' @@ -151,7 +164,7 @@ test_expect_success 'ref advertisment is filtered with ls-remote using protocol $(git -C file_parent rev-parse refs/heads/master)$(printf "\t")refs/heads/master EOF - test_cmp actual expect + test_cmp expect actual ' test_expect_success 'server-options are sent when using ls-remote' ' @@ -164,7 +177,7 @@ test_expect_success 'server-options are sent when using ls-remote' ' $(git -C file_parent rev-parse refs/heads/master)$(printf "\t")refs/heads/master EOF - test_cmp actual expect && + test_cmp expect actual && grep "server-option=hello" log && grep "server-option=world" log ' @@ -271,7 +284,7 @@ test_expect_success 'partial clone' ' grep "version 2" trace && # Ensure that the old version of the file is missing - git -C client rev-list master --quiet --objects --missing=print \ + git -C client rev-list --quiet --objects --missing=print master \ >observed.oids && grep "$(git -C server rev-parse message1:a.txt)" observed.oids && @@ -286,6 +299,10 @@ test_expect_success 'dynamically fetch missing object' ' grep "version 2" trace ' +test_expect_success 'when dynamically fetching missing object, do not list refs' ' + ! grep "git> command=ls-refs" trace +' + test_expect_success 'partial fetch' ' rm -rf client "$(pwd)/trace" && git init client && @@ -297,7 +314,7 @@ test_expect_success 'partial fetch' ' grep "version 2" trace && # Ensure that the old version of the file is missing - git -C client rev-list other --quiet --objects --missing=print \ + git -C client rev-list --quiet --objects --missing=print other \ >observed.oids && grep "$(git -C server rev-parse message1:a.txt)" observed.oids && @@ -334,7 +351,7 @@ test_expect_success 'even with handcrafted request, filter does not work if not 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 && + test-tool pkt-line pack >in <<-EOF && command=fetch 0001 want $(git -C server rev-parse master) @@ -429,6 +446,78 @@ test_expect_success 'fetch supports include-tag and tag following' ' git -C client cat-file -e $(git -C client rev-parse annotated_tag) ' +test_expect_success 'upload-pack respects client shallows' ' + rm -rf server client trace && + + git init server && + test_commit -C server base && + test_commit -C server client_has && + + git clone --depth=1 "file://$(pwd)/server" client && + + # Add extra commits to the client so that the whole fetch takes more + # than 1 request (due to negotiation) + for i in $(test_seq 1 32) + do + test_commit -C client c$i + done && + + git -C server checkout -b newbranch base && + test_commit -C server client_wants && + + GIT_TRACE_PACKET="$(pwd)/trace" git -C client -c protocol.version=2 \ + fetch origin newbranch && + # Ensure that protocol v2 is used + grep "fetch< version 2" trace +' + +test_expect_success 'ensure that multiple fetches in same process from a shallow repo works' ' + rm -rf server client trace && + + test_create_repo server && + test_commit -C server one && + test_commit -C server two && + test_commit -C server three && + git clone --shallow-exclude two "file://$(pwd)/server" client && + + git -C server tag -a -m "an annotated tag" twotag two && + + # Triggers tag following (thus, 2 fetches in one process) + GIT_TRACE_PACKET="$(pwd)/trace" git -C client -c protocol.version=2 \ + fetch --shallow-exclude one origin && + # Ensure that protocol v2 is used + grep "fetch< version 2" trace +' + +test_expect_success 'deepen-relative' ' + rm -rf server client trace && + + test_create_repo server && + test_commit -C server one && + test_commit -C server two && + test_commit -C server three && + git clone --depth 1 "file://$(pwd)/server" client && + test_commit -C server four && + + # Sanity check that only "three" is downloaded + git -C client log --pretty=tformat:%s master >actual && + echo three >expected && + test_cmp expected actual && + + GIT_TRACE_PACKET="$(pwd)/trace" git -C client -c protocol.version=2 \ + fetch --deepen=1 origin && + # Ensure that protocol v2 is used + grep "fetch< version 2" trace && + + git -C client log --pretty=tformat:%s origin/master >actual && + cat >expected <<-\EOF && + four + three + two + EOF + test_cmp expected actual +' + # Test protocol v2 with 'http://' transport # . "$TEST_DIRECTORY"/lib-httpd.sh @@ -472,6 +561,27 @@ test_expect_success 'fetch with http:// using protocol v2' ' grep "git< version 2" log ' +test_expect_success 'fetch from namespaced repo respects namespaces' ' + test_when_finished "rm -f log" && + + git init "$HTTPD_DOCUMENT_ROOT_PATH/nsrepo" && + test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/nsrepo" one && + test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/nsrepo" two && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/nsrepo" \ + update-ref refs/namespaces/ns/refs/heads/master one && + + GIT_TRACE_PACKET="$(pwd)/log" git -C http_child -c protocol.version=2 \ + fetch "$HTTPD_URL/smart_namespace/nsrepo" \ + refs/heads/master:refs/heads/theirs && + + # Server responded using protocol v2 + grep "fetch< version 2" log && + + git -C "$HTTPD_DOCUMENT_ROOT_PATH/nsrepo" rev-parse one >expect && + git -C http_child rev-parse theirs >actual && + test_cmp expect actual +' + 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 @@ -495,6 +605,56 @@ test_expect_success 'push with http:// and a config of v2 does not request v2' ' ! grep "git< version 2" log ' +test_expect_success 'when server sends "ready", expect DELIM' ' + rm -rf "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" http_child && + + git init "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" one && + + git clone "$HTTPD_URL/smart/http_parent" http_child && + + test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" two && + + # After "ready" in the acknowledgments section, pretend that a FLUSH + # (0000) was sent instead of a DELIM (0001). + printf "/ready/,$ s/0001/0000/" \ + >"$HTTPD_ROOT_PATH/one-time-sed" && + + test_must_fail git -C http_child -c protocol.version=2 \ + fetch "$HTTPD_URL/one_time_sed/http_parent" 2> err && + test_i18ngrep "expected packfile to be sent after .ready." err +' + +test_expect_success 'when server does not send "ready", expect FLUSH' ' + rm -rf "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" http_child log && + + git init "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" one && + + git clone "$HTTPD_URL/smart/http_parent" http_child && + + test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" two && + + # Create many commits to extend the negotiation phase across multiple + # requests, so that the server does not send "ready" in the first + # request. + for i in $(test_seq 1 32) + do + test_commit -C http_child c$i + done && + + # After the acknowledgments section, pretend that a DELIM + # (0001) was sent instead of a FLUSH (0000). + printf "/acknowledgments/,$ s/0000/0001/" \ + >"$HTTPD_ROOT_PATH/one-time-sed" && + + test_must_fail env GIT_TRACE_PACKET="$(pwd)/log" git -C http_child \ + -c protocol.version=2 \ + fetch "$HTTPD_URL/one_time_sed/http_parent" 2> err && + grep "fetch< .*acknowledgments" log && + ! grep "fetch< .*ready" log && + test_i18ngrep "expected no other sections to be sent after no .ready." err +' stop_httpd diff --git a/t/t5703-upload-pack-ref-in-want.sh b/t/t5703-upload-pack-ref-in-want.sh index d1ccc22331..f87b2f6df3 100755 --- a/t/t5703-upload-pack-ref-in-want.sh +++ b/t/t5703-upload-pack-ref-in-want.sh @@ -9,14 +9,14 @@ get_actual_refs () { /wanted-refs/d /0001/d p - }' <out | test-pkt-line unpack >actual_refs + }' <out | test-tool pkt-line unpack >actual_refs } get_actual_commits () { sed -n -e '/packfile/,/0000/{ /packfile/d p - }' <out | test-pkt-line unpack-sideband >o.pack && + }' <out | test-tool pkt-line unpack-sideband >o.pack && git index-pack o.pack && git verify-pack -v o.idx | grep commit | cut -c-40 | sort >actual_commits } @@ -61,7 +61,7 @@ test_expect_success 'config controls ref-in-want advertisement' ' ' test_expect_success 'invalid want-ref line' ' - test-pkt-line pack >in <<-EOF && + test-tool pkt-line pack >in <<-EOF && command=fetch 0001 no-progress @@ -80,7 +80,7 @@ test_expect_success 'basic want-ref' ' EOF git rev-parse f | sort >expected_commits && - test-pkt-line pack >in <<-EOF && + test-tool pkt-line pack >in <<-EOF && command=fetch 0001 no-progress @@ -101,7 +101,7 @@ test_expect_success 'multiple want-ref lines' ' EOF git rev-parse c d | sort >expected_commits && - test-pkt-line pack >in <<-EOF && + test-tool pkt-line pack >in <<-EOF && command=fetch 0001 no-progress @@ -122,7 +122,7 @@ test_expect_success 'mix want and want-ref' ' EOF git rev-parse e f | sort >expected_commits && - test-pkt-line pack >in <<-EOF && + test-tool pkt-line pack >in <<-EOF && command=fetch 0001 no-progress @@ -143,7 +143,7 @@ test_expect_success 'want-ref with ref we already have commit for' ' EOF >expected_commits && - test-pkt-line pack >in <<-EOF && + test-tool pkt-line pack >in <<-EOF && command=fetch 0001 no-progress @@ -208,7 +208,7 @@ test_expect_success 'server is initially ahead - no ref in want' ' cp -r "$LOCAL_PRISTINE" local && inconsistency master 1234567890123456789012345678901234567890 && test_must_fail git -C local fetch 2>err && - grep "ERR upload-pack: not our ref" err + test_i18ngrep "fatal: remote error: upload-pack: not our ref" err ' test_expect_success 'server is initially ahead - ref in want' ' @@ -254,7 +254,7 @@ test_expect_success 'server loses a ref - ref in want' ' echo "s/master/raster/" >"$HTTPD_ROOT_PATH/one-time-sed" && test_must_fail git -C local fetch 2>err && - grep "ERR unknown ref refs/heads/raster" err + test_i18ngrep "fatal: remote error: unknown ref refs/heads/raster" err ' stop_httpd diff --git a/t/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh index fb4d295aa0..0507999729 100755 --- a/t/t6000-rev-list-misc.sh +++ b/t/t6000-rev-list-misc.sh @@ -90,11 +90,18 @@ test_expect_success 'rev-list can show index objects' ' 9200b628cf9dc883a85a7abc8d6e6730baee589c two EOF echo only-in-index >only-in-index && + test_when_finished "git reset --hard" && git add only-in-index && git rev-list --objects --indexed-objects >actual && test_cmp expect actual ' +test_expect_success 'rev-list can negate index objects' ' + git rev-parse HEAD >expect && + git rev-list -1 --objects HEAD --not --indexed-objects >actual && + test_cmp expect actual +' + test_expect_success '--bisect and --first-parent can not be combined' ' test_must_fail git rev-list --bisect --first-parent HEAD ' diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index ec42c2f779..da113d975b 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -185,6 +185,10 @@ test_expect_success 'basic colors' ' test_cmp expect actual ' +test_expect_success '%S is not a placeholder for rev-list yet' ' + git rev-list --format="%S" -1 master | grep "%S" +' + test_expect_success 'advanced colors' ' cat >expect <<-EOF && commit $head2 diff --git a/t/t6011-rev-list-with-bad-commit.sh b/t/t6011-rev-list-with-bad-commit.sh index e51eb41f4b..545b461e51 100755 --- a/t/t6011-rev-list-with-bad-commit.sh +++ b/t/t6011-rev-list-with-bad-commit.sh @@ -41,10 +41,9 @@ test_expect_success 'corrupt second commit object' \ test_must_fail git fsck --full ' -test_expect_success 'rev-list should fail' \ - ' - test_must_fail git rev-list --all > /dev/null - ' +test_expect_success 'rev-list should fail' ' + test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git rev-list --all > /dev/null +' test_expect_success 'git repack _MUST_ fail' \ ' diff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh index b5a1190ffe..a10f0df02b 100755 --- a/t/t6012-rev-list-simplify.sh +++ b/t/t6012-rev-list-simplify.sh @@ -12,6 +12,22 @@ unnote () { git name-rev --tags --stdin | sed -e "s|$OID_REGEX (tags/\([^)]*\)) |\1 |g" } +# +# Create a test repo with interesting commit graph: +# +# A--B----------G--H--I--K--L +# \ \ / / +# \ \ / / +# C------E---F J +# \_/ +# +# The commits are laid out from left-to-right starting with +# the root commit A and terminating at the tip commit L. +# +# There are a few places where we adjust the commit date or +# author date to make the --topo-order, --date-order, and +# --author-date-order flags produce different output. + test_expect_success setup ' echo "Hi there" >file && echo "initial" >lost && @@ -21,10 +37,18 @@ test_expect_success setup ' git branch other-branch && + git symbolic-ref HEAD refs/heads/unrelated && + git rm -f "*" && + echo "Unrelated branch" >side && + git add side && + test_tick && git commit -m "Side root" && + note J && + git checkout master && + echo "Hello" >file && echo "second" >lost && git add file lost && - test_tick && git commit -m "Modified file and lost" && + test_tick && GIT_AUTHOR_DATE=$(($test_tick + 120)) git commit -m "Modified file and lost" && note B && git checkout other-branch && @@ -63,13 +87,6 @@ test_expect_success setup ' test_tick && git commit -a -m "Final change" && note I && - git symbolic-ref HEAD refs/heads/unrelated && - git rm -f "*" && - echo "Unrelated branch" >side && - git add side && - test_tick && git commit -m "Side root" && - note J && - git checkout master && test_tick && git merge --allow-unrelated-histories -m "Coolest" unrelated && note K && @@ -103,14 +120,24 @@ check_result () { check_outcome success "$@" } -check_result 'L K J I H G F E D C B A' --full-history +check_result 'L K J I H F E D C G B A' --full-history --topo-order +check_result 'L K I H G F E D C B J A' --full-history +check_result 'L K I H G F E D C B J A' --full-history --date-order +check_result 'L K I H G F E D B C J A' --full-history --author-date-order check_result 'K I H E C B A' --full-history -- file check_result 'K I H E C B A' --full-history --topo-order -- file check_result 'K I H E C B A' --full-history --date-order -- file +check_result 'K I H E B C A' --full-history --author-date-order -- file check_result 'I E C B A' --simplify-merges -- file +check_result 'I E C B A' --simplify-merges --topo-order -- file +check_result 'I E C B A' --simplify-merges --date-order -- file +check_result 'I E B C A' --simplify-merges --author-date-order -- file check_result 'I B A' -- file check_result 'I B A' --topo-order -- file +check_result 'I B A' --date-order -- file +check_result 'I B A' --author-date-order -- file check_result 'H' --first-parent -- another-file +check_result 'H' --first-parent --topo-order -- another-file check_result 'E C B A' --full-history E -- lost test_expect_success 'full history simplification without parent' ' diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh index 0bf10d0686..bb5aeac07f 100755 --- a/t/t6018-rev-list-glob.sh +++ b/t/t6018-rev-list-glob.sh @@ -36,7 +36,13 @@ test_expect_success 'setup' ' git tag foo/bar master && commit master3 && git update-ref refs/remotes/foo/baz master && - commit master4 + commit master4 && + git update-ref refs/remotes/upstream/one subspace/one && + git update-ref refs/remotes/upstream/two subspace/two && + git update-ref refs/remotes/upstream/x subspace-x && + git tag qux/one subspace/one && + git tag qux/two subspace/two && + git tag qux/x subspace-x ' test_expect_success 'rev-parse --glob=refs/heads/subspace/*' ' @@ -141,6 +147,66 @@ test_expect_success 'rev-parse accumulates multiple --exclude' ' compare rev-parse "--exclude=refs/remotes/* --exclude=refs/tags/* --all" --branches ' +test_expect_success 'rev-parse --branches clears --exclude' ' + compare rev-parse "--exclude=* --branches --branches" "--branches" +' + +test_expect_success 'rev-parse --tags clears --exclude' ' + compare rev-parse "--exclude=* --tags --tags" "--tags" +' + +test_expect_success 'rev-parse --all clears --exclude' ' + compare rev-parse "--exclude=* --all --all" "--all" +' + +test_expect_success 'rev-parse --exclude=glob with --branches=glob' ' + compare rev-parse "--exclude=subspace-* --branches=sub*" "subspace/one subspace/two" +' + +test_expect_success 'rev-parse --exclude=glob with --tags=glob' ' + compare rev-parse "--exclude=qux/? --tags=qux/*" "qux/one qux/two" +' + +test_expect_success 'rev-parse --exclude=glob with --remotes=glob' ' + compare rev-parse "--exclude=upstream/? --remotes=upstream/*" "upstream/one upstream/two" +' + +test_expect_success 'rev-parse --exclude=ref with --branches=glob' ' + compare rev-parse "--exclude=subspace-x --branches=sub*" "subspace/one subspace/two" +' + +test_expect_success 'rev-parse --exclude=ref with --tags=glob' ' + compare rev-parse "--exclude=qux/x --tags=qux/*" "qux/one qux/two" +' + +test_expect_success 'rev-parse --exclude=ref with --remotes=glob' ' + compare rev-parse "--exclude=upstream/x --remotes=upstream/*" "upstream/one upstream/two" +' + +test_expect_success 'rev-list --exclude=glob with --branches=glob' ' + compare rev-list "--exclude=subspace-* --branches=sub*" "subspace/one subspace/two" +' + +test_expect_success 'rev-list --exclude=glob with --tags=glob' ' + compare rev-list "--exclude=qux/? --tags=qux/*" "qux/one qux/two" +' + +test_expect_success 'rev-list --exclude=glob with --remotes=glob' ' + compare rev-list "--exclude=upstream/? --remotes=upstream/*" "upstream/one upstream/two" +' + +test_expect_success 'rev-list --exclude=ref with --branches=glob' ' + compare rev-list "--exclude=subspace-x --branches=sub*" "subspace/one subspace/two" +' + +test_expect_success 'rev-list --exclude=ref with --tags=glob' ' + compare rev-list "--exclude=qux/x --tags=qux/*" "qux/one qux/two" +' + +test_expect_success 'rev-list --exclude=ref with --remotes=glob' ' + compare rev-list "--exclude=upstream/x --remotes=upstream/*" "upstream/one upstream/two" +' + test_expect_success 'rev-list --glob=refs/heads/subspace/*' ' compare rev-list "subspace/one subspace/two" "--glob=refs/heads/subspace/*" @@ -233,7 +299,7 @@ test_expect_success 'rev-list --tags=foo' ' test_expect_success 'rev-list --tags' ' - compare rev-list "foo/bar" "--tags" + compare rev-list "foo/bar qux/x qux/two qux/one" "--tags" ' @@ -255,7 +321,7 @@ test_expect_success 'rev-list accumulates multiple --exclude' ' compare rev-list "--exclude=refs/remotes/* --exclude=refs/tags/* --all" --branches ' -test_expect_failure 'rev-list should succeed with empty output on empty stdin' ' +test_expect_success 'rev-list should succeed with empty output on empty stdin' ' git rev-list --stdin </dev/null >actual && test_must_be_empty actual ' @@ -292,7 +358,7 @@ test_expect_success 'shortlog accepts --glob/--tags/--remotes' ' "master other/three someref subspace-x subspace/one subspace/two" \ "--glob=heads/*" && compare shortlog foo/bar --tags=foo && - compare shortlog foo/bar --tags && + compare shortlog "foo/bar qux/one qux/two qux/x" --tags && compare shortlog foo/baz --remotes=foo ' diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh index 20aee43f95..51ee887a77 100755 --- a/t/t6023-merge-file.sh +++ b/t/t6023-merge-file.sh @@ -99,7 +99,7 @@ EOF printf "propter nomen suum." >> expect.txt test_expect_success "merge does not add LF away of change" \ - "test_cmp test3.txt expect.txt" + "test_cmp expect.txt test3.txt" cp test.txt backup.txt test_expect_success "merge with conflicts" \ @@ -122,7 +122,7 @@ non timebo mala, quoniam tu mecum es: virga tua et baculus tuus ipsa me consolata sunt. EOF -test_expect_success "expected conflict markers" "test_cmp test.txt expect.txt" +test_expect_success "expected conflict markers" "test_cmp expect.txt test.txt" cp backup.txt test.txt @@ -138,7 +138,7 @@ non timebo mala, quoniam tu mecum es: virga tua et baculus tuus ipsa me consolata sunt. EOF test_expect_success "merge conflicting with --ours" \ - "git merge-file --ours test.txt orig.txt new3.txt && test_cmp test.txt expect.txt" + "git merge-file --ours test.txt orig.txt new3.txt && test_cmp expect.txt test.txt" cp backup.txt test.txt cat > expect.txt << EOF @@ -154,7 +154,7 @@ non timebo mala, quoniam tu mecum es: virga tua et baculus tuus ipsa me consolata sunt. EOF test_expect_success "merge conflicting with --theirs" \ - "git merge-file --theirs test.txt orig.txt new3.txt && test_cmp test.txt expect.txt" + "git merge-file --theirs test.txt orig.txt new3.txt && test_cmp expect.txt test.txt" cp backup.txt test.txt cat > expect.txt << EOF @@ -171,7 +171,7 @@ non timebo mala, quoniam tu mecum es: virga tua et baculus tuus ipsa me consolata sunt. EOF test_expect_success "merge conflicting with --union" \ - "git merge-file --union test.txt orig.txt new3.txt && test_cmp test.txt expect.txt" + "git merge-file --union test.txt orig.txt new3.txt && test_cmp expect.txt test.txt" cp backup.txt test.txt test_expect_success "merge with conflicts, using -L" \ @@ -195,7 +195,7 @@ virga tua et baculus tuus ipsa me consolata sunt. EOF test_expect_success "expected conflict markers, with -L" \ - "test_cmp test.txt expect.txt" + "test_cmp expect.txt test.txt" sed "s/ tu / TU /" < new1.txt > new5.txt test_expect_success "conflict in removed tail" \ diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh index 3f59e58dfb..27c7de90ce 100755 --- a/t/t6024-recursive-merge.sh +++ b/t/t6024-recursive-merge.sh @@ -60,9 +60,9 @@ git update-index a1 && GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F ' -test_expect_success "combined merge conflicts" " - test_must_fail git merge -m final G -" +test_expect_success 'combined merge conflicts' ' + test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git merge -m final G +' cat > expect << EOF <<<<<<< HEAD diff --git a/t/t6027-merge-binary.sh b/t/t6027-merge-binary.sh index 07735410b9..4e6c7cb77e 100755 --- a/t/t6027-merge-binary.sh +++ b/t/t6027-merge-binary.sh @@ -45,7 +45,7 @@ test_expect_success resolve ' false else git ls-files -s >current - test_cmp current expect + test_cmp expect current fi ' @@ -60,7 +60,7 @@ test_expect_success recursive ' false else git ls-files -s >current - test_cmp current expect + test_cmp expect current fi ' diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index f84ff941c3..55835ee4a4 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -802,7 +802,7 @@ test_expect_success 'bisect terms needs 0 or 1 argument' ' test_must_fail git bisect terms only-one && test_must_fail git bisect terms 1 2 && test_must_fail git bisect terms 2>actual && - echo "no terms defined" >expected && + echo "error: no terms defined" >expected && test_i18ncmp expected actual ' diff --git a/t/t6031-merge-filemode.sh b/t/t6031-merge-filemode.sh index 7d06461f13..87741efad3 100755 --- a/t/t6031-merge-filemode.sh +++ b/t/t6031-merge-filemode.sh @@ -61,7 +61,7 @@ do_both_modes () { git checkout -f a2 && test_must_fail git merge -s $strategy b2 && git ls-files -u >actual && - test_cmp actual expect && + test_cmp expect actual && git ls-files -s file2 | grep ^100755 ' diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh index 59e52c5a09..d23b948f27 100755 --- a/t/t6036-recursive-corner-cases.sh +++ b/t/t6036-recursive-corner-cases.sh @@ -64,15 +64,12 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' ' git ls-files -u >out && test_line_count = 2 out && git ls-files -o >out && - test_line_count = 3 out && + test_line_count = 1 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 ) ' @@ -140,15 +137,12 @@ test_expect_success 'merge criss-cross + rename merges with basic modification' git ls-files -u >out && test_line_count = 2 out && git ls-files -o >out && - test_line_count = 3 out && + test_line_count = 1 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 ) ' @@ -185,7 +179,7 @@ test_expect_success 'setup differently handled merges of rename/add conflict' ' git branch B && git checkout -b C && echo 10 >>a && - echo "other content" >>new_a && + test_write_lines 0 1 2 3 4 5 6 7 foobar >new_a && git add a new_a && test_tick && git commit -m C && @@ -195,14 +189,14 @@ test_expect_success 'setup differently handled merges of rename/add conflict' ' git checkout B^0 && test_must_fail git merge C && - git clean -f && + git show :2:new_a >new_a && + git add new_a && 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 && + test_write_lines 0 1 2 3 4 5 6 7 bad_merge >new_a && git add -u && test_tick && git commit -m E && git tag E @@ -225,21 +219,74 @@ test_expect_success 'git detects differently handled merges conflict' ' test_line_count = 1 out && git rev-parse >expect \ - D:new_a E:new_a && + C:new_a D:new_a E:new_a && git rev-parse >actual \ - :2:new_a :3:new_a && + :1:new_a :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 && + # Test that the two-way merge in new_a is as expected + git cat-file -p D:new_a >ours && + git cat-file -p E:new_a >theirs && >empty && test_must_fail git merge-file \ - -L "Temporary merge branch 2" \ + -L "HEAD" \ -L "" \ - -L "Temporary merge branch 1" \ + -L "E^0" \ ours empty theirs && sed -e "s/^\([<=>]\)/\1\1\1/" ours >expect && - git cat-file -p :1:new_a >actual && + git hash-object new_a >actual && + git hash-object ours >expect && + test_cmp expect actual + ) +' + +# Repeat the above testcase with precisely the same setup, other than with +# the two merge bases having different orderings of commit timestamps so +# that they are reversed in the order they are provided to merge-recursive, +# so that we can improve code coverage. +test_expect_success 'git detects differently handled merges conflict, swapped' ' + ( + cd rename-add && + + # Difference #1: Do cleanup from previous testrun + git reset --hard && + git clean -fdqx && + + # Difference #2: Change commit timestamps + btime=$(git log --no-walk --date=raw --format=%cd B | awk "{print \$1}") && + ctime=$(git log --no-walk --date=raw --format=%cd C | awk "{print \$1}") && + newctime=$(($btime+1)) && + git fast-export --no-data --all | sed -e s/$ctime/$newctime/ | git fast-import --force --quiet && + # End of differences; rest is copy-paste of last test + + 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 \ + C:new_a D:new_a E:new_a && + git rev-parse >actual \ + :1:new_a :2:new_a :3:new_a && + test_cmp expect actual && + + # Test that the two-way merge in new_a is as expected + git cat-file -p D:new_a >ours && + git cat-file -p E:new_a >theirs && + >empty && + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "E^0" \ + ours empty theirs && + sed -e "s/^\([<=>]\)/\1\1\1/" ours >expect && + git hash-object new_a >actual && + git hash-object ours >expect && test_cmp expect actual ) ' @@ -1402,4 +1449,349 @@ test_expect_failure 'check conflicting modes for regular file' ' ) ' +# Setup: +# L1---L2 +# / \ / \ +# master X ? +# \ / \ / +# R1---R2 +# +# Where: +# master has two files, named 'b' and 'a' +# branches L1 and R1 both modify each of the two files in conflicting ways +# +# L2 is a merge of R1 into L1; more on it later. +# R2 is a merge of L1 into R1; more on it later. +# +# X is an auto-generated merge-base used when merging L2 and R2. +# since X is a merge of L1 and R1, it has conflicting versions of each file +# +# More about L2 and R2: +# - both resolve the conflicts in 'b' and 'a' differently +# - L2 renames 'b' to 'm' +# - R2 renames 'a' to 'm' +# +# In the end, in file 'm' we have four different conflicting files (from +# two versions of 'b' and two of 'a'). In addition, if +# merge.conflictstyle is diff3, then the base version also has +# conflict markers of its own, leading to a total of three levels of +# conflict markers. This is a pretty weird corner case, but we just want +# to ensure that we handle it as well as practical. + +test_expect_success 'setup nested conflicts' ' + test_create_repo nested_conflicts && + ( + cd nested_conflicts && + + # Create some related files now + for i in $(test_seq 1 10) + do + echo Random base content line $i + done >initial && + + cp initial b_L1 && + cp initial b_R1 && + cp initial b_L2 && + cp initial b_R2 && + cp initial a_L1 && + cp initial a_R1 && + cp initial a_L2 && + cp initial a_R2 && + + test_write_lines b b_L1 >>b_L1 && + test_write_lines b b_R1 >>b_R1 && + test_write_lines b b_L2 >>b_L2 && + test_write_lines b b_R2 >>b_R2 && + test_write_lines a a_L1 >>a_L1 && + test_write_lines a a_R1 >>a_R1 && + test_write_lines a a_L2 >>a_L2 && + test_write_lines a a_R2 >>a_R2 && + + # Setup original commit (or merge-base), consisting of + # files named "b" and "a" + cp initial b && + cp initial a && + echo b >>b && + echo a >>a && + git add b a && + test_tick && git commit -m initial && + + git branch L && + git branch R && + + # Handle the left side + git checkout L && + mv -f b_L1 b && + mv -f a_L1 a && + git add b a && + test_tick && git commit -m "version L1 of files" && + git tag L1 && + + # Handle the right side + git checkout R && + mv -f b_R1 b && + mv -f a_R1 a && + git add b a && + test_tick && git commit -m "verson R1 of files" && + git tag R1 && + + # Create first merge on left side + git checkout L && + test_must_fail git merge R1 && + mv -f b_L2 b && + mv -f a_L2 a && + git add b a && + git mv b m && + test_tick && git commit -m "left merge, rename b->m" && + git tag L2 && + + # Create first merge on right side + git checkout R && + test_must_fail git merge L1 && + mv -f b_R2 b && + mv -f a_R2 a && + git add b a && + git mv a m && + test_tick && git commit -m "right merge, rename a->m" && + git tag R2 + ) +' + +test_expect_success 'check nested conflicts' ' + ( + cd nested_conflicts && + + git clean -f && + git checkout L2^0 && + + # Merge must fail; there is a conflict + test_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R2^0 && + + # Make sure the index has the right number of entries + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + # Ensure we have the correct number of untracked files + git ls-files -o >out && + test_line_count = 1 out && + + # Create a and b from virtual merge base X + git cat-file -p master:a >base && + git cat-file -p L1:a >ours && + git cat-file -p R1:a >theirs && + test_must_fail git merge-file --diff3 \ + -L "Temporary merge branch 1" \ + -L "merged common ancestors" \ + -L "Temporary merge branch 2" \ + ours \ + base \ + theirs && + sed -e "s/^\([<|=>]\)/\1\1/" ours >vmb_a && + + git cat-file -p master:b >base && + git cat-file -p L1:b >ours && + git cat-file -p R1:b >theirs && + test_must_fail git merge-file --diff3 \ + -L "Temporary merge branch 1" \ + -L "merged common ancestors" \ + -L "Temporary merge branch 2" \ + ours \ + base \ + theirs && + sed -e "s/^\([<|=>]\)/\1\1/" ours >vmb_b && + + # Compare :2:m to expected values + git cat-file -p L2:m >ours && + git cat-file -p R2:b >theirs && + test_must_fail git merge-file --diff3 \ + -L "HEAD:m" \ + -L "merged common ancestors:b" \ + -L "R2^0:b" \ + ours \ + vmb_b \ + theirs && + sed -e "s/^\([<|=>]\)/\1\1/" ours >m_stage_2 && + git cat-file -p :2:m >actual && + test_cmp m_stage_2 actual && + + # Compare :3:m to expected values + git cat-file -p L2:a >ours && + git cat-file -p R2:m >theirs && + test_must_fail git merge-file --diff3 \ + -L "HEAD:a" \ + -L "merged common ancestors:a" \ + -L "R2^0:m" \ + ours \ + vmb_a \ + theirs && + sed -e "s/^\([<|=>]\)/\1\1/" ours >m_stage_3 && + git cat-file -p :3:m >actual && + test_cmp m_stage_3 actual && + + # Compare m to expected contents + >empty && + cp m_stage_2 expected_final_m && + test_must_fail git merge-file --diff3 \ + -L "HEAD" \ + -L "merged common ancestors" \ + -L "R2^0" \ + expected_final_m \ + empty \ + m_stage_3 && + test_cmp expected_final_m m + ) +' + +# Setup: +# L1---L2---L3 +# / \ / \ / \ +# master X1 X2 ? +# \ / \ / \ / +# R1---R2---R3 +# +# Where: +# master has one file named 'content' +# branches L1 and R1 both modify each of the two files in conflicting ways +# +# L<n> (n>1) is a merge of R<n-1> into L<n-1> +# R<n> (n>1) is a merge of L<n-1> into R<n-1> +# L<n> and R<n> resolve the conflicts differently. +# +# X<n> is an auto-generated merge-base used when merging L<n+1> and R<n+1>. +# By construction, X1 has conflict markers due to conflicting versions. +# X2, due to using merge.conflictstyle=3, has nested conflict markers. +# +# So, merging R3 into L3 using merge.conflictstyle=3 should show the +# nested conflict markers from X2 in the base version -- that means we +# have three levels of conflict markers. Can we distinguish all three? + +test_expect_success 'setup virtual merge base with nested conflicts' ' + test_create_repo virtual_merge_base_has_nested_conflicts && + ( + cd virtual_merge_base_has_nested_conflicts && + + # Create some related files now + for i in $(test_seq 1 10) + do + echo Random base content line $i + done >content && + + # Setup original commit + git add content && + test_tick && git commit -m initial && + + git branch L && + git branch R && + + # Create L1 + git checkout L && + echo left >>content && + git add content && + test_tick && git commit -m "version L1 of content" && + git tag L1 && + + # Create R1 + git checkout R && + echo right >>content && + git add content && + test_tick && git commit -m "verson R1 of content" && + git tag R1 && + + # Create L2 + git checkout L && + test_must_fail git -c merge.conflictstyle=diff3 merge R1 && + git checkout L1 content && + test_tick && git commit -m "version L2 of content" && + git tag L2 && + + # Create R2 + git checkout R && + test_must_fail git -c merge.conflictstyle=diff3 merge L1 && + git checkout R1 content && + test_tick && git commit -m "version R2 of content" && + git tag R2 && + + # Create L3 + git checkout L && + test_must_fail git -c merge.conflictstyle=diff3 merge R2 && + git checkout L1 content && + test_tick && git commit -m "version L3 of content" && + git tag L3 && + + # Create R3 + git checkout R && + test_must_fail git -c merge.conflictstyle=diff3 merge L2 && + git checkout R1 content && + test_tick && git commit -m "version R3 of content" && + git tag R3 + ) +' + +test_expect_success 'check virtual merge base with nested conflicts' ' + ( + cd virtual_merge_base_has_nested_conflicts && + + git checkout L3^0 && + + # Merge must fail; there is a conflict + test_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R3^0 && + + # Make sure the index has the right number of entries + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 3 out && + # Ensure we have the correct number of untracked files + git ls-files -o >out && + test_line_count = 1 out && + + # Compare :[23]:content to expected values + git rev-parse L1:content R1:content >expect && + git rev-parse :2:content :3:content >actual && + test_cmp expect actual && + + # Imitate X1 merge base, except without long enough conflict + # markers because a subsequent sed will modify them. Put + # result into vmb. + git cat-file -p master:content >base && + git cat-file -p L:content >left && + git cat-file -p R:content >right && + cp left merged-once && + test_must_fail git merge-file --diff3 \ + -L "Temporary merge branch 1" \ + -L "merged common ancestors" \ + -L "Temporary merge branch 2" \ + merged-once \ + base \ + right && + sed -e "s/^\([<|=>]\)/\1\1\1/" merged-once >vmb && + + # Imitate X2 merge base, overwriting vmb. Note that we + # extend both sets of conflict markers to make them longer + # with the sed command. + cp left merged-twice && + test_must_fail git merge-file --diff3 \ + -L "Temporary merge branch 1" \ + -L "merged common ancestors" \ + -L "Temporary merge branch 2" \ + merged-twice \ + vmb \ + right && + sed -e "s/^\([<|=>]\)/\1\1\1/" merged-twice >vmb && + + # Compare :1:content to expected value + git cat-file -p :1:content >actual && + test_cmp vmb actual && + + # Determine expected content in final outer merge, compare to + # what the merge generated. + cp -f left expect && + test_must_fail git merge-file --diff3 \ + -L "HEAD" -L "merged common ancestors" -L "R3^0" \ + expect vmb right && + test_cmp expect content + ) +' + test_done diff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh index b97aca7fa2..09dfa8bd92 100755 --- a/t/t6042-merge-rename-corner-cases.sh +++ b/t/t6042-merge-rename-corner-cases.sh @@ -464,17 +464,28 @@ test_expect_success 'handle rename/rename (2to1) conflict correctly' ' git ls-files -u c >out && test_line_count = 2 out && git ls-files -o >out && - test_line_count = 3 out && + test_line_count = 1 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 && + git rev-parse >expect \ + C:a B:b && + git rev-parse >actual \ + :2:c :3:c && + test_cmp expect actual && + + # Test that the two-way merge in new_a is as expected + git cat-file -p :2:c >>ours && + git cat-file -p :3:c >>theirs && + >empty && + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "C^0" \ + ours empty theirs && + git hash-object c >actual && + git hash-object ours >expect && test_cmp expect actual ) ' @@ -673,7 +684,7 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting git ls-files -u c >out && test_line_count = 2 out && git ls-files -o >out && - test_line_count = 5 out && + test_line_count = 1 out && git rev-parse >expect \ A:a C:b B:b C:c B:c && @@ -681,14 +692,27 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting :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 && + # Record some contents for re-doing merges + git cat-file -p A:a >stuff && + git cat-file -p C:b >important_info && + git cat-file -p B:c >precious_data && + >empty && - test_path_is_missing b && - test_path_is_missing c + # Test the merge in b + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "B^0" \ + important_info empty stuff && + test_cmp important_info b && + + # Test the merge in c + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "B^0" \ + stuff empty precious_data && + test_cmp stuff c ) ' @@ -937,4 +961,283 @@ test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename ) ' +test_conflicts_with_adds_and_renames() { + sideL=$1 + sideR=$2 + + # Setup: + # L + # / \ + # master ? + # \ / + # R + # + # Where: + # Both L and R have files named 'three' which collide. Each of + # the colliding files could have been involved in a rename, in + # which case there was a file named 'one' or 'two' that was + # modified on the opposite side of history and renamed into the + # collision on this side of history. + # + # Questions: + # 1) The index should contain both a stage 2 and stage 3 entry + # for the colliding file. Does it? + # 2) When renames are involved, the content merges are clean, so + # the index should reflect the content merges, not merely the + # version of the colliding file from the prior commit. Does + # it? + # 3) There should be a file in the worktree named 'three' + # containing the two-way merged contents of the content-merged + # versions of 'three' from each of the two colliding + # files. Is it present? + # 4) There should not be any three~* files in the working + # tree + test_expect_success "setup simple $sideL/$sideR conflict" ' + test_create_repo simple_${sideL}_${sideR} && + ( + cd simple_${sideL}_${sideR} && + + # Create some related files now + for i in $(test_seq 1 10) + do + echo Random base content line $i + done >file_v1 && + cp file_v1 file_v2 && + echo modification >>file_v2 && + + cp file_v1 file_v3 && + echo more stuff >>file_v3 && + cp file_v3 file_v4 && + echo yet more stuff >>file_v4 && + + # Use a tag to record both these files for simple + # access, and clean out these untracked files + git tag file_v1 $(git hash-object -w file_v1) && + git tag file_v2 $(git hash-object -w file_v2) && + git tag file_v3 $(git hash-object -w file_v3) && + git tag file_v4 $(git hash-object -w file_v4) && + git clean -f && + + # Setup original commit (or merge-base), consisting of + # files named "one" and "two" if renames were involved. + touch irrelevant_file && + git add irrelevant_file && + if [ $sideL = "rename" ] + then + git show file_v1 >one && + git add one + fi && + if [ $sideR = "rename" ] + then + git show file_v3 >two && + git add two + fi && + test_tick && git commit -m initial && + + git branch L && + git branch R && + + # Handle the left side + git checkout L && + if [ $sideL = "rename" ] + then + git mv one three + else + git show file_v2 >three && + git add three + fi && + if [ $sideR = "rename" ] + then + git show file_v4 >two && + git add two + fi && + test_tick && git commit -m L && + + # Handle the right side + git checkout R && + if [ $sideL = "rename" ] + then + git show file_v2 >one && + git add one + fi && + if [ $sideR = "rename" ] + then + git mv two three + else + git show file_v4 >three && + git add three + fi && + test_tick && git commit -m R + ) + ' + + test_expect_success "check simple $sideL/$sideR conflict" ' + ( + cd simple_${sideL}_${sideR} && + + git checkout L^0 && + + # Merge must fail; there is a conflict + test_must_fail git merge -s recursive R^0 && + + # Make sure the index has the right number of entries + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 2 out && + # Ensure we have the correct number of untracked files + git ls-files -o >out && + test_line_count = 1 out && + + # Nothing should have touched irrelevant_file + git rev-parse >actual \ + :0:irrelevant_file \ + :2:three \ + :3:three && + git rev-parse >expected \ + master:irrelevant_file \ + file_v2 \ + file_v4 && + test_cmp expected actual && + + # Make sure we have the correct merged contents for + # three + git show file_v1 >expected && + cat <<-\EOF >>expected && + <<<<<<< HEAD + modification + ======= + more stuff + yet more stuff + >>>>>>> R^0 + EOF + + test_cmp expected three + ) + ' +} + +test_conflicts_with_adds_and_renames rename rename +test_conflicts_with_adds_and_renames rename add +test_conflicts_with_adds_and_renames add rename +test_conflicts_with_adds_and_renames add add + +# Setup: +# L +# / \ +# master ? +# \ / +# R +# +# Where: +# master has two files, named 'one' and 'two'. +# branches L and R both modify 'one', in conflicting ways. +# branches L and R both modify 'two', in conflicting ways. +# branch L also renames 'one' to 'three'. +# branch R also renames 'two' to 'three'. +# +# So, we have four different conflicting files that all end up at path +# 'three'. +test_expect_success 'setup nested conflicts from rename/rename(2to1)' ' + test_create_repo nested_conflicts_from_rename_rename && + ( + cd nested_conflicts_from_rename_rename && + + # Create some related files now + for i in $(test_seq 1 10) + do + echo Random base content line $i + done >file_v1 && + + cp file_v1 file_v2 && + cp file_v1 file_v3 && + cp file_v1 file_v4 && + cp file_v1 file_v5 && + cp file_v1 file_v6 && + + echo one >>file_v1 && + echo uno >>file_v2 && + echo eins >>file_v3 && + + echo two >>file_v4 && + echo dos >>file_v5 && + echo zwei >>file_v6 && + + # Setup original commit (or merge-base), consisting of + # files named "one" and "two". + mv file_v1 one && + mv file_v4 two && + git add one two && + test_tick && git commit -m english && + + git branch L && + git branch R && + + # Handle the left side + git checkout L && + git rm one two && + mv -f file_v2 three && + mv -f file_v5 two && + git add two three && + test_tick && git commit -m spanish && + + # Handle the right side + git checkout R && + git rm one two && + mv -f file_v3 one && + mv -f file_v6 three && + git add one three && + test_tick && git commit -m german + ) +' + +test_expect_success 'check nested conflicts from rename/rename(2to1)' ' + ( + cd nested_conflicts_from_rename_rename && + + git checkout L^0 && + + # Merge must fail; there is a conflict + test_must_fail git merge -s recursive R^0 && + + # Make sure the index has the right number of entries + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + # Ensure we have the correct number of untracked files + git ls-files -o >out && + test_line_count = 1 out && + + # Compare :2:three to expected values + git cat-file -p master:one >base && + git cat-file -p L:three >ours && + git cat-file -p R:one >theirs && + test_must_fail git merge-file \ + -L "HEAD:three" -L "" -L "R^0:one" \ + ours base theirs && + sed -e "s/^\([<=>]\)/\1\1/" ours >L-three && + git cat-file -p :2:three >expect && + test_cmp expect L-three && + + # Compare :2:three to expected values + git cat-file -p master:two >base && + git cat-file -p L:two >ours && + git cat-file -p R:three >theirs && + test_must_fail git merge-file \ + -L "HEAD:two" -L "" -L "R^0:three" \ + ours base theirs && + sed -e "s/^\([<=>]\)/\1\1/" ours >R-three && + git cat-file -p :3:three >expect && + test_cmp expect R-three && + + # Compare three to expected contents + >empty && + test_must_fail git merge-file \ + -L "HEAD" -L "" -L "R^0" \ + L-three empty R-three && + test_cmp three L-three + ) +' + test_done diff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh index 4a71f17edd..62c564707b 100755 --- a/t/t6043-merge-rename-directories.sh +++ b/t/t6043-merge-rename-directories.sh @@ -278,7 +278,7 @@ test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) con git ls-files -u >out && test_line_count = 2 out && git ls-files -o >out && - test_line_count = 3 out && + test_line_count = 1 out && git rev-parse >actual \ :0:x/b :0:x/c :0:x/d :0:x/e :0:x/m :0:x/n && @@ -293,15 +293,16 @@ test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) con 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 + # Test that the two-way merge in x/wham is as expected + git cat-file -p :2:x/wham >expect && + git cat-file -p :3:x/wham >other && + >empty && + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "B^0" \ + expect empty other && + test_cmp expect x/wham ) ' @@ -1077,7 +1078,7 @@ test_expect_success '5c-check: Transitive rename would cause rename/rename/renam git ls-files -u >out && test_line_count = 6 out && git ls-files -o >out && - test_line_count = 3 out && + test_line_count = 1 out && git rev-parse >actual \ :0:y/b :0:y/c :0:y/e && @@ -1093,9 +1094,9 @@ test_expect_success '5c-check: Transitive rename would cause rename/rename/renam test_cmp expect actual && git hash-object >actual \ - w/d~HEAD w/d~B^0 z/d && + z/d && git rev-parse >expect \ - O:x/d B:w/d O:x/d && + O:x/d && test_cmp expect actual && test_path_is_missing x/d && test_path_is_file y/d && @@ -1670,7 +1671,7 @@ test_expect_success '7b-check: rename/rename(2to1), but only due to transitive r git ls-files -u >out && test_line_count = 2 out && git ls-files -o >out && - test_line_count = 3 out && + test_line_count = 1 out && git rev-parse >actual \ :0:y/b :0:y/c :2:y/d :3:y/d && @@ -1678,15 +1679,16 @@ test_expect_success '7b-check: rename/rename(2to1), but only due to transitive r 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 + # Test that the two-way merge in y/d is as expected + git cat-file -p :2:y/d >expect && + git cat-file -p :3:y/d >other && + >empty && + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "B^0" \ + expect empty other && + test_cmp expect y/d ) ' @@ -3161,11 +3163,48 @@ test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2) ) ' +test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2), other direction' ' + ( + cd 10c && + + git reset --hard && + git clean -fdqx && + + git checkout B^0 && + mkdir y && + echo important >y/c && + + test_must_fail git merge -s recursive A^0 >out 2>err && + test_i18ngrep "CONFLICT (rename/rename)" out && + test_i18ngrep "Refusing to lose untracked file at y/c; adding as y/c~HEAD 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 :3:w/c :2: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~HEAD >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}+ +# Expected: Failed Merge; y/{a,b,d,e} + untracked y/{wham,wham~merged}+ # CONFLICT(rename/rename) z/c_1 vs x/f_2 -> y/wham # ERROR_MSG(Refusing to lose untracked file at y/wham) @@ -3219,7 +3258,7 @@ test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' ' git ls-files -u >out && test_line_count = 2 out && git ls-files -o >out && - test_line_count = 4 out && + test_line_count = 3 out && git rev-parse >actual \ :0:y/a :0:y/b :0:y/d :0:y/e :2:y/wham :3:y/wham && @@ -3232,11 +3271,16 @@ test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' ' 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 + # Test that the two-way merge in y/wham~merged is as expected + git cat-file -p :2:y/wham >expect && + git cat-file -p :3:y/wham >other && + >empty && + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "B^0" \ + expect empty other && + test_cmp expect y/wham~merged ) ' @@ -3665,7 +3709,7 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena git ls-files -u >out && test_line_count = 4 out && git ls-files -o >out && - test_line_count = 4 out && + test_line_count = 3 out && echo different >expected && echo mods >>expected && @@ -3677,11 +3721,17 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena 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 + # See if y/c~merged has expected contents; requires manually + # doing the expected file merge + git cat-file -p A:y/c >c1 && + git cat-file -p B:z/c >c2 && + >empty && + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "B^0" \ + c1 empty c2 && + test_cmp c1 y/c~merged ) ' @@ -3689,7 +3739,7 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena # 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} + +# Expected: Failed Merge; y/{a,b} + untracked y/{wham~merged} + # 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) @@ -3741,24 +3791,30 @@ test_expect_success '11f-check: Avoid deleting not-uptodate with dir rename/rena git ls-files -u >out && test_line_count = 2 out && git ls-files -o >out && - test_line_count = 4 out && + test_line_count = 3 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 + test_cmp expect actual && + + # Test that the two-way merge in y/wham~merged is as expected + git cat-file -p :2:y/wham >expect && + git cat-file -p :3:y/wham >other && + >empty && + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "B^0" \ + expect empty other && + test_cmp expect y/wham~merged ) ' diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh index 86374a9c52..d638119750 100755 --- a/t/t6050-replace.sh +++ b/t/t6050-replace.sh @@ -133,8 +133,8 @@ test_expect_success 'tag replaced commit' ' test_expect_success '"git fsck" works' ' git fsck master >fsck_master.out && - grep "dangling commit $R" fsck_master.out && - grep "dangling tag $(cat .git/refs/tags/mytag)" fsck_master.out && + test_i18ngrep "dangling commit $R" fsck_master.out && + test_i18ngrep "dangling tag $(cat .git/refs/tags/mytag)" fsck_master.out && test -z "$(git fsck)" ' @@ -461,7 +461,10 @@ test_expect_success '--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 && + git status 2>stderr && + test_i18ngrep "hint:.*grafts is deprecated" stderr && + git replace --convert-graft-file 2>stderr && + test_i18ngrep ! "hint:.*grafts is deprecated" stderr && test_path_is_missing .git/info/grafts && : verify that the history is now "grafted" && diff --git a/t/t6112-rev-list-filters-objects.sh b/t/t6112-rev-list-filters-objects.sh index d4ff0b3bef..9c11427719 100755 --- a/t/t6112-rev-list-filters-objects.sh +++ b/t/t6112-rev-list-filters-objects.sh @@ -21,24 +21,43 @@ test_expect_success 'setup r1' ' 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 + >ls_files_result && + awk -f print_2.awk ls_files_result | + sort >expected && + + git -C r1 rev-list --quiet --objects --filter-print-omitted \ + --filter=blob:none HEAD >revs && + awk -f print_1.awk revs | + sed "s/~//" | + sort >observed && + + test_cmp expected observed +' + +test_expect_success 'specify blob explicitly prevents filtering' ' + file_3=$(git -C r1 ls-files -s file.3 | + awk -f print_2.awk) && + + file_4=$(git -C r1 ls-files -s file.4 | + awk -f print_2.awk) && + + git -C r1 rev-list --objects --filter=blob:none HEAD $file_3 >observed && + grep "$file_3" observed && + ! grep "$file_4" observed ' 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 + git -C r1 rev-list --objects HEAD >revs && + awk -f print_1.awk revs | + sort >expected && + + git -C r1 rev-list --objects --filter-print-omitted --filter=blob:none \ + HEAD >revs && + awk -f print_1.awk revs | + sed "s/~//" | + sort >observed && + + test_cmp expected observed ' @@ -58,65 +77,82 @@ test_expect_success 'setup r2' ' ' 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 + git -C r2 ls-files -s large.1000 large.10000 >ls_files_result && + awk -f print_2.awk ls_files_result | + sort >expected && + + git -C r2 rev-list --quiet --objects --filter-print-omitted \ + --filter=blob:limit=500 HEAD >revs && + awk -f print_1.awk revs | + sed "s/~//" | + sort >observed && + + test_cmp expected observed ' 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 + git -C r2 rev-list --objects HEAD >revs && + awk -f print_1.awk revs | + sort >expected && + + git -C r2 rev-list --objects --filter-print-omitted \ + --filter=blob:limit=500 HEAD >revs && + awk -f print_1.awk revs | + sed "s/~//" | + sort >observed && + + test_cmp expected observed ' 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 + git -C r2 ls-files -s large.1000 large.10000 >ls_files_result && + awk -f print_2.awk ls_files_result | + sort >expected && + + git -C r2 rev-list --quiet --objects --filter-print-omitted \ + --filter=blob:limit=1000 HEAD >revs && + awk -f print_1.awk revs | + sed "s/~//" | + sort >observed && + + test_cmp expected observed ' 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 + git -C r2 ls-files -s large.10000 >ls_files_result && + awk -f print_2.awk ls_files_result | + sort >expected && + + git -C r2 rev-list --quiet --objects --filter-print-omitted \ + --filter=blob:limit=1001 HEAD >revs && + awk -f print_1.awk revs | + sed "s/~//" | + sort >observed && + + test_cmp expected observed ' 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 + git -C r2 ls-files -s large.10000 >ls_files_result && + awk -f print_2.awk ls_files_result | + sort >expected && + + git -C r2 rev-list --quiet --objects --filter-print-omitted \ + --filter=blob:limit=1k HEAD >revs && + awk -f print_1.awk revs | + sed "s/~//" | + sort >observed && + + test_cmp expected observed ' test_expect_success 'verify blob:limit=1m' ' - git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1m \ - | awk -f print_1.awk \ - | sed "s/~//" \ - | sort >observed && + git -C r2 rev-list --quiet --objects --filter-print-omitted \ + --filter=blob:limit=1m HEAD >revs && + awk -f print_1.awk revs | + sed "s/~//" | + sort >observed && + test_must_be_empty observed ' @@ -141,25 +177,31 @@ test_expect_success 'setup r3' ' ' 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 + git -C r3 ls-files -s sparse1 sparse2 >ls_files_result && + awk -f print_2.awk ls_files_result | + sort >expected && + + git -C r3 rev-list --quiet --objects --filter-print-omitted \ + --filter=sparse:path=../pattern1 HEAD >revs && + awk -f print_1.awk revs | + sed "s/~//" | + sort >observed && + + test_cmp expected observed ' 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 + git -C r3 ls-files -s sparse2 dir1/sparse2 >ls_files_result && + awk -f print_2.awk ls_files_result | + sort >expected && + + git -C r3 rev-list --quiet --objects --filter-print-omitted \ + --filter=sparse:path=../pattern2 HEAD >revs && + awk -f print_1.awk revs | + sed "s/~//" | + sort >observed && + + test_cmp expected observed ' # Test sparse:oid=<oid-ish> filter. @@ -173,26 +215,203 @@ test_expect_success 'setup r3 part 2' ' ' 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 && + git -C r3 ls-files -s pattern sparse1 sparse2 >ls_files_result && + awk -f print_2.awk ls_files_result | + 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 + + git -C r3 rev-list --quiet --objects --filter-print-omitted \ + --filter=sparse:oid=$oid HEAD >revs && + awk -f print_1.awk revs | + sed "s/~//" | + sort >observed && + + test_cmp expected observed ' 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 + git -C r3 ls-files -s pattern sparse1 sparse2 >ls_files_result && + awk -f print_2.awk ls_files_result | + sort >expected && + + git -C r3 rev-list --quiet --objects --filter-print-omitted \ + --filter=sparse:oid=master:pattern HEAD >revs && + awk -f print_1.awk revs | + sed "s/~//" | + sort >observed && + + test_cmp expected observed +' + +test_expect_success 'rev-list W/ --missing=print and --missing=allow-any for trees' ' + TREE=$(git -C r3 rev-parse HEAD:dir1) && + + # Create a spare repo because we will be deleting objects from this one. + git clone r3 r3.b && + + rm r3.b/.git/objects/$(echo $TREE | sed "s|^..|&/|") && + + git -C r3.b rev-list --quiet --missing=print --objects HEAD \ + >missing_objs 2>rev_list_err && + echo "?$TREE" >expected && + test_cmp expected missing_objs && + + # do not complain when a missing tree cannot be parsed + test_must_be_empty rev_list_err && + + git -C r3.b rev-list --missing=allow-any --objects HEAD \ + >objs 2>rev_list_err && + ! grep $TREE objs && + test_must_be_empty rev_list_err +' + +# Test tree:0 filter. + +test_expect_success 'verify tree:0 includes trees in "filtered" output' ' + git -C r3 rev-list --quiet --objects --filter-print-omitted \ + --filter=tree:0 HEAD >revs && + + awk -f print_1.awk revs | + sed s/~// | + xargs -n1 git -C r3 cat-file -t >unsorted_filtered_types && + + sort -u unsorted_filtered_types >filtered_types && + test_write_lines blob tree >expected && + test_cmp expected filtered_types +' + +# Make sure tree:0 does not iterate through any trees. + +test_expect_success 'verify skipping tree iteration when not collecting omits' ' + GIT_TRACE=1 git -C r3 rev-list \ + --objects --filter=tree:0 HEAD 2>filter_trace && + grep "Skipping contents of tree [.][.][.]" filter_trace >actual && + # One line for each commit traversed. + test_line_count = 2 actual && + + # Make sure no other trees were considered besides the root. + ! grep "Skipping contents of tree [^.]" filter_trace +' + +# Test tree:# filters. + +expect_has () { + commit=$1 && + name=$2 && + + hash=$(git -C r3 rev-parse $commit:$name) && + grep "^$hash $name$" actual +} + +test_expect_success 'verify tree:1 includes root trees' ' + git -C r3 rev-list --objects --filter=tree:1 HEAD >actual && + + # We should get two root directories and two commits. + expect_has HEAD "" && + expect_has HEAD~1 "" && + test_line_count = 4 actual +' + +test_expect_success 'verify tree:2 includes root trees and immediate children' ' + git -C r3 rev-list --objects --filter=tree:2 HEAD >actual && + + expect_has HEAD "" && + expect_has HEAD~1 "" && + expect_has HEAD dir1 && + expect_has HEAD pattern && + expect_has HEAD sparse1 && + expect_has HEAD sparse2 && + + # There are also 2 commit objects + test_line_count = 8 actual +' + +test_expect_success 'verify tree:3 includes everything expected' ' + git -C r3 rev-list --objects --filter=tree:3 HEAD >actual && + + expect_has HEAD "" && + expect_has HEAD~1 "" && + expect_has HEAD dir1 && + expect_has HEAD dir1/sparse1 && + expect_has HEAD dir1/sparse2 && + expect_has HEAD pattern && + expect_has HEAD sparse1 && + expect_has HEAD sparse2 && + + # There are also 2 commit objects + test_line_count = 10 actual +' + +# Test provisional omit collection logic with a repo that has objects appearing +# at multiple depths - first deeper than the filter's threshold, then shallow. + +test_expect_success 'setup r4' ' + git init r4 && + + echo foo > r4/foo && + mkdir r4/subdir && + echo bar > r4/subdir/bar && + + mkdir r4/filt && + cp -r r4/foo r4/subdir r4/filt && + + git -C r4 add foo subdir filt && + git -C r4 commit -m "commit msg" +' + +expect_has_with_different_name () { + repo=$1 && + name=$2 && + + hash=$(git -C $repo rev-parse HEAD:$name) && + ! grep "^$hash $name$" actual && + grep "^$hash " actual && + ! grep "~$hash" actual +} + +test_expect_success 'test tree:# filter provisional omit for blob and tree' ' + git -C r4 rev-list --objects --filter-print-omitted --filter=tree:2 \ + HEAD >actual && + expect_has_with_different_name r4 filt/foo && + expect_has_with_different_name r4 filt/subdir +' + +test_expect_success 'verify skipping tree iteration when collecting omits' ' + GIT_TRACE=1 git -C r4 rev-list --filter-print-omitted \ + --objects --filter=tree:0 HEAD 2>filter_trace && + grep "^Skipping contents of tree " filter_trace >actual && + + echo "Skipping contents of tree subdir/..." >expect && + test_cmp expect actual +' + +# Test tree:<depth> where a tree is iterated to twice - once where a subentry is +# too deep to be included, and again where the blob inside it is shallow enough +# to be included. This makes sure we don't use LOFR_MARK_SEEN incorrectly (we +# can't use it because a tree can be iterated over again at a lower depth). + +test_expect_success 'tree:<depth> where we iterate over tree at two levels' ' + git init r5 && + + mkdir -p r5/a/subdir/b && + echo foo > r5/a/subdir/b/foo && + + mkdir -p r5/subdir/b && + echo foo > r5/subdir/b/foo && + + git -C r5 add a subdir && + git -C r5 commit -m "commit msg" && + + git -C r5 rev-list --objects --filter=tree:4 HEAD >actual && + expect_has_with_different_name r5 a/subdir/b/foo +' + +test_expect_success 'tree:<depth> which filters out blob but given as arg' ' + blob_hash=$(git -C r4 rev-parse HEAD:subdir/bar) && + + git -C r4 rev-list --objects --filter=tree:1 HEAD $blob_hash >actual && + grep ^$blob_hash actual ' # Delete some loose objects and use rev-list, but WITHOUT any filtering. @@ -200,17 +419,21 @@ test_expect_success 'verify sparse:oid=oid-ish omits top-level files' ' 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 && + >ls_files_result && + awk -f print_2.awk ls_files_result | + 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 + + git -C r1 rev-list --quiet --missing=print --objects HEAD >revs && + awk -f print_1.awk revs | + sed "s/?//" | + sort >observed && + + test_cmp expected observed ' test_expect_success 'rev-list W/O --missing fails' ' @@ -221,4 +444,21 @@ test_expect_success 'rev-list W/ missing=allow-any' ' git -C r1 rev-list --quiet --missing=allow-any --objects HEAD ' +# Test expansion of filter specs. + +test_expect_success 'expand blob limit in protocol' ' + git -C r2 config --local uploadpack.allowfilter 1 && + GIT_TRACE_PACKET="$(pwd)/trace" git -c protocol.version=2 clone \ + --filter=blob:limit=1k "file://$(pwd)/r2" limit && + ! grep "blob:limit=1k" trace && + grep "blob:limit=1024" trace +' + +test_expect_success 'expand tree depth limit in protocol' ' + GIT_TRACE_PACKET="$(pwd)/tree_trace" git -c protocol.version=2 clone \ + --filter=tree:0k "file://$(pwd)/r2" tree && + ! grep "tree:0k" tree_trace && + grep "tree:0" tree_trace +' + test_done diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index d639d94696..ee5b03ee18 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -143,16 +143,46 @@ test_expect_success 'rename tag Q back to A' ' test_expect_success 'pack tag refs' 'git pack-refs' check_describe A-* HEAD +test_expect_success 'describe works from outside repo using --git-dir' ' + git clone --bare "$TRASH_DIRECTORY" "$TRASH_DIRECTORY/bare" && + git --git-dir "$TRASH_DIRECTORY/bare" describe >out && + grep "^A-[1-9][0-9]\?-g[0-9a-f]\+$" out +' + check_describe "A-*[0-9a-f]" --dirty +test_expect_success 'describe --dirty with --work-tree' ' + ( + cd "$TEST_DIRECTORY" && + git --git-dir "$TRASH_DIRECTORY/.git" --work-tree "$TRASH_DIRECTORY" describe --dirty >"$TRASH_DIRECTORY/out" + ) && + grep "^A-[1-9][0-9]\?-g[0-9a-f]\+$" out +' + test_expect_success 'set-up dirty work tree' ' echo >>file ' check_describe "A-*[0-9a-f]-dirty" --dirty +test_expect_success 'describe --dirty with --work-tree (dirty)' ' + ( + cd "$TEST_DIRECTORY" && + git --git-dir "$TRASH_DIRECTORY/.git" --work-tree "$TRASH_DIRECTORY" describe --dirty >"$TRASH_DIRECTORY/out" + ) && + grep "^A-[1-9][0-9]\?-g[0-9a-f]\+-dirty$" out +' + check_describe "A-*[0-9a-f].mod" --dirty=.mod +test_expect_success 'describe --dirty=.mod with --work-tree (dirty)' ' + ( + cd "$TEST_DIRECTORY" && + git --git-dir "$TRASH_DIRECTORY/.git" --work-tree "$TRASH_DIRECTORY" describe --dirty=.mod >"$TRASH_DIRECTORY/out" + ) && + grep "^A-[1-9][0-9]\?-g[0-9a-f]\+.mod$" out +' + test_expect_success 'describe --dirty HEAD' ' test_must_fail git describe --dirty HEAD ' @@ -303,8 +333,17 @@ 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 && + grep broken out +' + +test_expect_success 'describe with --work-tree ignoring a broken submodule' ' + ( + cd "$TEST_DIRECTORY" && + git --git-dir "$TRASH_DIRECTORY/.git" --work-tree "$TRASH_DIRECTORY" describe --broken >"$TRASH_DIRECTORY/out" + ) && test_when_finished "mv .git/modules/sub_moved .git/modules/sub1" && grep broken out ' diff --git a/t/t6132-pathspec-exclude.sh b/t/t6132-pathspec-exclude.sh index eb829fce97..2462b19ddd 100755 --- a/t/t6132-pathspec-exclude.sh +++ b/t/t6132-pathspec-exclude.sh @@ -194,4 +194,21 @@ test_expect_success 'multiple exclusions' ' test_cmp expect actual ' +test_expect_success 't_e_i() exclude case #8' ' + git init case8 && + ( + cd case8 && + echo file >file1 && + echo file >file2 && + git add file1 file2 && + git commit -m twofiles && + git grep -l file HEAD :^file2 >actual && + echo HEAD:file1 >expected && + test_cmp expected actual && + git grep -l file HEAD :^file1 >actual && + echo HEAD:file2 >expected && + test_cmp expected actual + ) +' + test_done diff --git a/t/t6135-pathspec-with-attrs.sh b/t/t6135-pathspec-with-attrs.sh index e436a73962..457cc167c7 100755 --- a/t/t6135-pathspec-with-attrs.sh +++ b/t/t6135-pathspec-with-attrs.sh @@ -31,7 +31,7 @@ test_expect_success 'setup a tree' ' mkdir sub && while read path do - : >$path && + echo content >$path && git add $path || return 1 done <expect && git commit -m "initial commit" && @@ -48,6 +48,10 @@ test_expect_success 'pathspec with labels and non existent .gitattributes' ' test_must_be_empty actual ' +test_expect_success 'pathspec with labels and non existent .gitattributes (2)' ' + test_must_fail git grep content HEAD -- ":(attr:label)" +' + test_expect_success 'setup .gitattributes' ' cat <<-\EOF >.gitattributes && fileA labelA @@ -74,6 +78,15 @@ test_expect_success 'check specific set attr' ' test_cmp expect actual ' +test_expect_success 'check specific set attr (2)' ' + cat <<-\EOF >expect && + HEAD:fileSetLabel + HEAD:sub/fileSetLabel + EOF + git grep -l content HEAD ":(attr:label)" >actual && + test_cmp expect actual +' + test_expect_success 'check specific unset attr' ' cat <<-\EOF >expect && fileUnsetLabel @@ -83,6 +96,15 @@ test_expect_success 'check specific unset attr' ' test_cmp expect actual ' +test_expect_success 'check specific unset attr (2)' ' + cat <<-\EOF >expect && + HEAD:fileUnsetLabel + HEAD:sub/fileUnsetLabel + EOF + git grep -l content HEAD ":(attr:-label)" >actual && + test_cmp expect actual +' + test_expect_success 'check specific value attr' ' cat <<-\EOF >expect && fileValue @@ -94,6 +116,16 @@ test_expect_success 'check specific value attr' ' test_must_be_empty actual ' +test_expect_success 'check specific value attr (2)' ' + cat <<-\EOF >expect && + HEAD:fileValue + HEAD:sub/fileValue + EOF + git grep -l content HEAD ":(attr:label=foo)" >actual && + test_cmp expect actual && + test_must_fail git grep -l content HEAD ":(attr:label=bar)" +' + test_expect_success 'check unspecified attr' ' cat <<-\EOF >expect && .gitattributes @@ -118,6 +150,30 @@ test_expect_success 'check unspecified attr' ' test_cmp expect actual ' +test_expect_success 'check unspecified attr (2)' ' + cat <<-\EOF >expect && + HEAD:.gitattributes + HEAD:fileA + HEAD:fileAB + HEAD:fileAC + HEAD:fileB + HEAD:fileBC + HEAD:fileC + HEAD:fileNoLabel + HEAD:fileWrongLabel + HEAD:sub/fileA + HEAD:sub/fileAB + HEAD:sub/fileAC + HEAD:sub/fileB + HEAD:sub/fileBC + HEAD:sub/fileC + HEAD:sub/fileNoLabel + HEAD:sub/fileWrongLabel + EOF + git grep -l ^ HEAD ":(attr:!label)" >actual && + test_cmp expect actual +' + test_expect_success 'check multiple unspecified attr' ' cat <<-\EOF >expect && .gitattributes diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 97bfbee6e8..0ffd630713 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -83,6 +83,8 @@ 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 objectsize:disk 138 +test_atom head deltabase 0000000000000000000000000000000000000000 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) @@ -124,6 +126,10 @@ test_atom tag upstream '' test_atom tag push '' test_atom tag objecttype tag test_atom tag objectsize 154 +test_atom tag objectsize:disk 138 +test_atom tag '*objectsize:disk' 138 +test_atom tag deltabase 0000000000000000000000000000000000000000 +test_atom tag '*deltabase' 0000000000000000000000000000000000000000 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) diff --git a/t/t6500-gc.sh b/t/t6500-gc.sh index 818435f04e..4684d06552 100755 --- a/t/t6500-gc.sh +++ b/t/t6500-gc.sh @@ -4,6 +4,7 @@ test_description='basic git gc tests ' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-terminal.sh test_expect_success 'setup' ' # do not let the amount of physical memory affects gc @@ -99,6 +100,26 @@ 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 ' +test_expect_success 'gc --no-quiet' ' + git -c gc.writeCommitGraph=true gc --no-quiet >stdout 2>stderr && + test_must_be_empty stdout && + test_line_count = 1 stderr && + test_i18ngrep "Computing commit graph generation numbers" stderr +' + +test_expect_success TTY 'with TTY: gc --no-quiet' ' + test_terminal git -c gc.writeCommitGraph=true gc --no-quiet >stdout 2>stderr && + test_must_be_empty stdout && + test_i18ngrep "Enumerating objects" stderr && + test_i18ngrep "Computing commit graph generation numbers" stderr +' + +test_expect_success 'gc --quiet' ' + git -c gc.writeCommitGraph=true gc --quiet >stdout 2>stderr && + test_must_be_empty stdout && + test_must_be_empty stderr +' + 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 @@ -116,11 +137,11 @@ test_expect_success 'background auto gc does not run if gc.log is present and re 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 && + git gc --auto 2>err && + test_i18ngrep "^warning:" err && test_config gc.logexpiry 5.days && test-tool chmtime =-345600 .git/gc.log && - test_must_fail git gc --auto && + git gc --auto && test_config gc.logexpiry 2.days && run_and_wait_for_auto_gc && ls .git/objects/pack/pack-*.pack >packs && diff --git a/t/t6600-test-reach.sh b/t/t6600-test-reach.sh new file mode 100755 index 0000000000..b24d850036 --- /dev/null +++ b/t/t6600-test-reach.sh @@ -0,0 +1,408 @@ +#!/bin/sh + +test_description='basic commit reachability tests' + +. ./test-lib.sh + +# Construct a grid-like commit graph with points (x,y) +# with 1 <= x <= 10, 1 <= y <= 10, where (x,y) has +# parents (x-1, y) and (x, y-1), keeping in mind that +# we drop a parent if a coordinate is nonpositive. +# +# (10,10) +# / \ +# (10,9) (9,10) +# / \ / \ +# (10,8) (9,9) (8,10) +# / \ / \ / \ +# ( continued...) +# \ / \ / \ / +# (3,1) (2,2) (1,3) +# \ / \ / +# (2,1) (2,1) +# \ / +# (1,1) +# +# We use branch 'commit-x-y' to refer to (x,y). +# This grid allows interesting reachability and +# non-reachability queries: (x,y) can reach (x',y') +# if and only if x' <= x and y' <= y. +test_expect_success 'setup' ' + for i in $(test_seq 1 10) + do + test_commit "1-$i" && + git branch -f commit-1-$i && + git tag -a -m "1-$i" tag-1-$i commit-1-$i + done && + for j in $(test_seq 1 9) + do + git reset --hard commit-$j-1 && + x=$(($j + 1)) && + test_commit "$x-1" && + git branch -f commit-$x-1 && + git tag -a -m "$x-1" tag-$x-1 commit-$x-1 && + + for i in $(test_seq 2 10) + do + git merge commit-$j-$i -m "$x-$i" && + git branch -f commit-$x-$i && + git tag -a -m "$x-$i" tag-$x-$i commit-$x-$i + done + done && + git commit-graph write --reachable && + mv .git/objects/info/commit-graph commit-graph-full && + git show-ref -s commit-5-5 | git commit-graph write --stdin-commits && + mv .git/objects/info/commit-graph commit-graph-half && + git config core.commitGraph true +' + +run_three_modes () { + test_when_finished rm -rf .git/objects/info/commit-graph && + "$@" <input >actual && + test_cmp expect actual && + cp commit-graph-full .git/objects/info/commit-graph && + "$@" <input >actual && + test_cmp expect actual && + cp commit-graph-half .git/objects/info/commit-graph && + "$@" <input >actual && + test_cmp expect actual +} + +test_three_modes () { + run_three_modes test-tool reach "$@" +} + +test_expect_success 'ref_newer:miss' ' + cat >input <<-\EOF && + A:commit-5-7 + B:commit-4-9 + EOF + echo "ref_newer(A,B):0" >expect && + test_three_modes ref_newer +' + +test_expect_success 'ref_newer:hit' ' + cat >input <<-\EOF && + A:commit-5-7 + B:commit-2-3 + EOF + echo "ref_newer(A,B):1" >expect && + test_three_modes ref_newer +' + +test_expect_success 'in_merge_bases:hit' ' + cat >input <<-\EOF && + A:commit-5-7 + B:commit-8-8 + EOF + echo "in_merge_bases(A,B):1" >expect && + test_three_modes in_merge_bases +' + +test_expect_success 'in_merge_bases:miss' ' + cat >input <<-\EOF && + A:commit-6-8 + B:commit-5-9 + EOF + echo "in_merge_bases(A,B):0" >expect && + test_three_modes in_merge_bases +' + +test_expect_success 'is_descendant_of:hit' ' + cat >input <<-\EOF && + A:commit-5-7 + X:commit-4-8 + X:commit-6-6 + X:commit-1-1 + EOF + echo "is_descendant_of(A,X):1" >expect && + test_three_modes is_descendant_of +' + +test_expect_success 'is_descendant_of:miss' ' + cat >input <<-\EOF && + A:commit-6-8 + X:commit-5-9 + X:commit-4-10 + X:commit-7-6 + EOF + echo "is_descendant_of(A,X):0" >expect && + test_three_modes is_descendant_of +' + +test_expect_success 'get_merge_bases_many' ' + cat >input <<-\EOF && + A:commit-5-7 + X:commit-4-8 + X:commit-6-6 + X:commit-8-3 + EOF + { + echo "get_merge_bases_many(A,X):" && + git rev-parse commit-5-6 \ + commit-4-7 | sort + } >expect && + test_three_modes get_merge_bases_many +' + +test_expect_success 'reduce_heads' ' + cat >input <<-\EOF && + X:commit-1-10 + X:commit-2-8 + X:commit-3-6 + X:commit-4-4 + X:commit-1-7 + X:commit-2-5 + X:commit-3-3 + X:commit-5-1 + EOF + { + echo "reduce_heads(X):" && + git rev-parse commit-5-1 \ + commit-4-4 \ + commit-3-6 \ + commit-2-8 \ + commit-1-10 | sort + } >expect && + test_three_modes reduce_heads +' + +test_expect_success 'can_all_from_reach:hit' ' + cat >input <<-\EOF && + X:commit-2-10 + X:commit-3-9 + X:commit-4-8 + X:commit-5-7 + X:commit-6-6 + X:commit-7-5 + X:commit-8-4 + X:commit-9-3 + Y:commit-1-9 + Y:commit-2-8 + Y:commit-3-7 + Y:commit-4-6 + Y:commit-5-5 + Y:commit-6-4 + Y:commit-7-3 + Y:commit-8-1 + EOF + echo "can_all_from_reach(X,Y):1" >expect && + test_three_modes can_all_from_reach +' + +test_expect_success 'can_all_from_reach:miss' ' + cat >input <<-\EOF && + X:commit-2-10 + X:commit-3-9 + X:commit-4-8 + X:commit-5-7 + X:commit-6-6 + X:commit-7-5 + X:commit-8-4 + X:commit-9-3 + Y:commit-1-9 + Y:commit-2-8 + Y:commit-3-7 + Y:commit-4-6 + Y:commit-5-5 + Y:commit-6-4 + Y:commit-8-5 + EOF + echo "can_all_from_reach(X,Y):0" >expect && + test_three_modes can_all_from_reach +' + +test_expect_success 'can_all_from_reach_with_flag: tags case' ' + cat >input <<-\EOF && + X:tag-2-10 + X:tag-3-9 + X:tag-4-8 + X:commit-5-7 + X:commit-6-6 + X:commit-7-5 + X:commit-8-4 + X:commit-9-3 + Y:tag-1-9 + Y:tag-2-8 + Y:tag-3-7 + Y:commit-4-6 + Y:commit-5-5 + Y:commit-6-4 + Y:commit-7-3 + Y:commit-8-1 + EOF + echo "can_all_from_reach_with_flag(X,_,_,0,0):1" >expect && + test_three_modes can_all_from_reach_with_flag +' + +test_expect_success 'commit_contains:hit' ' + cat >input <<-\EOF && + A:commit-7-7 + X:commit-2-10 + X:commit-3-9 + X:commit-4-8 + X:commit-5-7 + X:commit-6-6 + X:commit-7-5 + X:commit-8-4 + X:commit-9-3 + EOF + echo "commit_contains(_,A,X,_):1" >expect && + test_three_modes commit_contains && + test_three_modes commit_contains --tag +' + +test_expect_success 'commit_contains:miss' ' + cat >input <<-\EOF && + A:commit-6-5 + X:commit-2-10 + X:commit-3-9 + X:commit-4-8 + X:commit-5-7 + X:commit-6-6 + X:commit-7-5 + X:commit-8-4 + X:commit-9-3 + EOF + echo "commit_contains(_,A,X,_):0" >expect && + test_three_modes commit_contains && + test_three_modes commit_contains --tag +' + +test_expect_success 'rev-list: basic topo-order' ' + git rev-parse \ + commit-6-6 commit-5-6 commit-4-6 commit-3-6 commit-2-6 commit-1-6 \ + commit-6-5 commit-5-5 commit-4-5 commit-3-5 commit-2-5 commit-1-5 \ + commit-6-4 commit-5-4 commit-4-4 commit-3-4 commit-2-4 commit-1-4 \ + commit-6-3 commit-5-3 commit-4-3 commit-3-3 commit-2-3 commit-1-3 \ + commit-6-2 commit-5-2 commit-4-2 commit-3-2 commit-2-2 commit-1-2 \ + commit-6-1 commit-5-1 commit-4-1 commit-3-1 commit-2-1 commit-1-1 \ + >expect && + run_three_modes git rev-list --topo-order commit-6-6 +' + +test_expect_success 'rev-list: first-parent topo-order' ' + git rev-parse \ + commit-6-6 \ + commit-6-5 \ + commit-6-4 \ + commit-6-3 \ + commit-6-2 \ + commit-6-1 commit-5-1 commit-4-1 commit-3-1 commit-2-1 commit-1-1 \ + >expect && + run_three_modes git rev-list --first-parent --topo-order commit-6-6 +' + +test_expect_success 'rev-list: range topo-order' ' + git rev-parse \ + commit-6-6 commit-5-6 commit-4-6 commit-3-6 commit-2-6 commit-1-6 \ + commit-6-5 commit-5-5 commit-4-5 commit-3-5 commit-2-5 commit-1-5 \ + commit-6-4 commit-5-4 commit-4-4 commit-3-4 commit-2-4 commit-1-4 \ + commit-6-3 commit-5-3 commit-4-3 \ + commit-6-2 commit-5-2 commit-4-2 \ + commit-6-1 commit-5-1 commit-4-1 \ + >expect && + run_three_modes git rev-list --topo-order commit-3-3..commit-6-6 +' + +test_expect_success 'rev-list: range topo-order' ' + git rev-parse \ + commit-6-6 commit-5-6 commit-4-6 \ + commit-6-5 commit-5-5 commit-4-5 \ + commit-6-4 commit-5-4 commit-4-4 \ + commit-6-3 commit-5-3 commit-4-3 \ + commit-6-2 commit-5-2 commit-4-2 \ + commit-6-1 commit-5-1 commit-4-1 \ + >expect && + run_three_modes git rev-list --topo-order commit-3-8..commit-6-6 +' + +test_expect_success 'rev-list: first-parent range topo-order' ' + git rev-parse \ + commit-6-6 \ + commit-6-5 \ + commit-6-4 \ + commit-6-3 \ + commit-6-2 \ + commit-6-1 commit-5-1 commit-4-1 \ + >expect && + run_three_modes git rev-list --first-parent --topo-order commit-3-8..commit-6-6 +' + +test_expect_success 'rev-list: ancestry-path topo-order' ' + git rev-parse \ + commit-6-6 commit-5-6 commit-4-6 commit-3-6 \ + commit-6-5 commit-5-5 commit-4-5 commit-3-5 \ + commit-6-4 commit-5-4 commit-4-4 commit-3-4 \ + commit-6-3 commit-5-3 commit-4-3 \ + >expect && + run_three_modes git rev-list --topo-order --ancestry-path commit-3-3..commit-6-6 +' + +test_expect_success 'rev-list: symmetric difference topo-order' ' + git rev-parse \ + commit-6-6 commit-5-6 commit-4-6 \ + commit-6-5 commit-5-5 commit-4-5 \ + commit-6-4 commit-5-4 commit-4-4 \ + commit-6-3 commit-5-3 commit-4-3 \ + commit-6-2 commit-5-2 commit-4-2 \ + commit-6-1 commit-5-1 commit-4-1 \ + commit-3-8 commit-2-8 commit-1-8 \ + commit-3-7 commit-2-7 commit-1-7 \ + >expect && + run_three_modes git rev-list --topo-order commit-3-8...commit-6-6 +' + +test_expect_success 'get_reachable_subset:all' ' + cat >input <<-\EOF && + X:commit-9-1 + X:commit-8-3 + X:commit-7-5 + X:commit-6-6 + X:commit-1-7 + Y:commit-3-3 + Y:commit-1-7 + Y:commit-5-6 + EOF + ( + echo "get_reachable_subset(X,Y)" && + git rev-parse commit-3-3 \ + commit-1-7 \ + commit-5-6 | sort + ) >expect && + test_three_modes get_reachable_subset +' + +test_expect_success 'get_reachable_subset:some' ' + cat >input <<-\EOF && + X:commit-9-1 + X:commit-8-3 + X:commit-7-5 + X:commit-1-7 + Y:commit-3-3 + Y:commit-1-7 + Y:commit-5-6 + EOF + ( + echo "get_reachable_subset(X,Y)" && + git rev-parse commit-3-3 \ + commit-1-7 | sort + ) >expect && + test_three_modes get_reachable_subset +' + +test_expect_success 'get_reachable_subset:none' ' + cat >input <<-\EOF && + X:commit-9-1 + X:commit-8-3 + X:commit-7-5 + X:commit-1-7 + Y:commit-9-3 + Y:commit-7-6 + Y:commit-2-8 + EOF + echo "get_reachable_subset(X,Y)" >expect && + test_three_modes get_reachable_subset +' + +test_done diff --git a/t/t7063-status-untracked-cache.sh b/t/t7063-status-untracked-cache.sh index 2da57fce7b..190ae149cf 100755 --- a/t/t7063-status-untracked-cache.sh +++ b/t/t7063-status-untracked-cache.sh @@ -55,7 +55,7 @@ test_expect_success 'setup' ' ' test_expect_success 'untracked cache is empty' ' - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && cat >../expect-empty <<EOF && info/exclude 0000000000000000000000000000000000000000 core.excludesfile 0000000000000000000000000000000000000000 @@ -106,7 +106,7 @@ EOF ' test_expect_success 'untracked cache after first status' ' - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && test_cmp ../dump.expect ../actual ' @@ -126,7 +126,7 @@ EOF ' test_expect_success 'untracked cache after second status' ' - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && test_cmp ../dump.expect ../actual ' @@ -157,7 +157,7 @@ EOF ' test_expect_success 'verify untracked cache dump' ' - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && cat >../expect <<EOF && info/exclude $EMPTY_BLOB core.excludesfile 0000000000000000000000000000000000000000 @@ -204,7 +204,7 @@ EOF ' test_expect_success 'verify untracked cache dump' ' - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && cat >../expect <<EOF && info/exclude $EMPTY_BLOB core.excludesfile 0000000000000000000000000000000000000000 @@ -248,7 +248,7 @@ EOF ' test_expect_success 'verify untracked cache dump' ' - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && cat >../expect <<EOF && info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0 core.excludesfile 0000000000000000000000000000000000000000 @@ -267,7 +267,7 @@ EOF test_expect_success 'move two from tracked to untracked' ' git rm --cached two && - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && cat >../expect <<EOF && info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0 core.excludesfile 0000000000000000000000000000000000000000 @@ -304,7 +304,7 @@ EOF ' test_expect_success 'verify untracked cache dump' ' - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && cat >../expect <<EOF && info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0 core.excludesfile 0000000000000000000000000000000000000000 @@ -324,7 +324,7 @@ EOF test_expect_success 'move two from untracked to tracked' ' git add two && - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && cat >../expect <<EOF && info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0 core.excludesfile 0000000000000000000000000000000000000000 @@ -361,7 +361,7 @@ EOF ' test_expect_success 'verify untracked cache dump' ' - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && cat >../expect <<EOF && info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0 core.excludesfile 0000000000000000000000000000000000000000 @@ -405,7 +405,7 @@ EOF ' test_expect_success 'untracked cache correct after commit' ' - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && cat >../expect <<EOF && info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0 core.excludesfile 0000000000000000000000000000000000000000 @@ -464,7 +464,7 @@ EOF ' test_expect_success 'untracked cache correct after status' ' - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && cat >../expect <<EOF && info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0 core.excludesfile 0000000000000000000000000000000000000000 @@ -532,7 +532,7 @@ EOF ' test_expect_success 'verify untracked cache dump (sparse/subdirs)' ' - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && cat >../expect-from-test-dump <<EOF && info/exclude 13263c0978fb9fad16b2d580fb800b6d811c3ff0 core.excludesfile 0000000000000000000000000000000000000000 @@ -598,66 +598,66 @@ EOF test_expect_success '--no-untracked-cache removes the cache' ' git update-index --no-untracked-cache && - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && echo "no untracked cache" >../expect-no-uc && test_cmp ../expect-no-uc ../actual ' test_expect_success 'git status does not change anything' ' git status && - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && test_cmp ../expect-no-uc ../actual ' test_expect_success 'setting core.untrackedCache to true and using git status creates the cache' ' git config core.untrackedCache true && - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && test_cmp ../expect-no-uc ../actual && git status && - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && test_cmp ../expect-from-test-dump ../actual ' test_expect_success 'using --no-untracked-cache does not fail when core.untrackedCache is true' ' git update-index --no-untracked-cache && - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && test_cmp ../expect-no-uc ../actual && git update-index --untracked-cache && - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && test_cmp ../expect-empty ../actual ' test_expect_success 'setting core.untrackedCache to false and using git status removes the cache' ' git config core.untrackedCache false && - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && test_cmp ../expect-empty ../actual && git status && - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && test_cmp ../expect-no-uc ../actual ' test_expect_success 'using --untracked-cache does not fail when core.untrackedCache is false' ' git update-index --untracked-cache && - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && test_cmp ../expect-empty ../actual ' test_expect_success 'setting core.untrackedCache to keep' ' git config core.untrackedCache keep && git update-index --untracked-cache && - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && test_cmp ../expect-empty ../actual && git status && - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && test_cmp ../expect-from-test-dump ../actual && git update-index --no-untracked-cache && - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && test_cmp ../expect-no-uc ../actual && git update-index --force-untracked-cache && - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && test_cmp ../expect-empty ../actual && git status && - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && test_cmp ../expect-from-test-dump ../actual ' @@ -671,23 +671,23 @@ test_expect_success 'test ident field is working' ' test_expect_success 'untracked cache survives a checkout' ' git commit --allow-empty -m empty && - test-dump-untracked-cache >../before && + test-tool dump-untracked-cache >../before && test_when_finished "git checkout master" && git checkout -b other_branch && - test-dump-untracked-cache >../after && + test-tool dump-untracked-cache >../after && test_cmp ../before ../after && test_commit test && - test-dump-untracked-cache >../before && + test-tool dump-untracked-cache >../before && git checkout master && - test-dump-untracked-cache >../after && + test-tool dump-untracked-cache >../after && test_cmp ../before ../after ' test_expect_success 'untracked cache survives a commit' ' - test-dump-untracked-cache >../before && + test-tool dump-untracked-cache >../before && git add done/two && git commit -m commit && - test-dump-untracked-cache >../after && + test-tool dump-untracked-cache >../after && test_cmp ../before ../after ' @@ -751,7 +751,7 @@ test_expect_success '"status" after file replacement should be clean with UC=tru git checkout master && avoid_racy && status_is_clean && - test-dump-untracked-cache >../actual && + test-tool dump-untracked-cache >../actual && grep -F "recurse valid" ../actual >../actual.grep && cat >../expect.grep <<EOF && / 0000000000000000000000000000000000000000 recurse valid diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 324933acfe..72b9b375ba 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -160,7 +160,7 @@ test_expect_success 'checkout -m with merge conflict' ' git diff master:one :3:uno | sed -e "1,/^@@/d" -e "/^ /d" -e "s/^-/d/" -e "s/^+/a/" >current && fill d2 aT d7 aS >expect && - test_cmp current expect && + test_cmp expect current && git diff --cached two >current && test_must_be_empty current ' @@ -174,7 +174,7 @@ test_expect_success 'format of merge conflict from checkout -m' ' git ls-files >current && fill same two two two >expect && - test_cmp current expect && + test_cmp expect current && cat <<-EOF >expect && <<<<<<< simple @@ -254,9 +254,9 @@ test_expect_success 'checkout to detach HEAD (with advice declined)' ' test_expect_success 'checkout to detach HEAD' ' git config advice.detachedHead true && git checkout -f renamer && git clean -f && - git checkout renamer^ 2>messages && - test_i18ngrep "HEAD is now at 7329388" messages && - (test_line_count -gt 1 messages || test -n "$GETTEXT_POISON") && + GIT_TEST_GETTEXT_POISON= git checkout renamer^ 2>messages && + grep "HEAD is now at 7329388" messages && + test_line_count -gt 1 messages && H=$(git rev-parse --verify HEAD) && M=$(git show-ref -s --verify refs/heads/master) && test "z$H" = "z$M" && diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index c0ffc1022a..aba2d4d6ee 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -984,6 +984,11 @@ test_expect_success 'submodule deinit should remove the whole submodule section rmdir init ' +test_expect_success 'submodule deinit should unset core.worktree' ' + test_path_is_file .git/modules/example/config && + test_must_fail git config -f .git/modules/example/config core.worktree +' + test_expect_success 'submodule deinit from subdirectory' ' git submodule update --init && git config submodule.example.foo bar && @@ -1224,6 +1229,30 @@ test_expect_success 'submodule update and setting submodule.<name>.active' ' test_cmp expect actual ' +test_expect_success 'clone active submodule without submodule url set' ' + test_when_finished "rm -rf test/test" && + mkdir test && + # another dir breaks accidental relative paths still being correct + git clone file://"$pwd"/multisuper test/test && + ( + cd test/test && + git config submodule.active "." && + + # do not pass --init flag, as the submodule is already active: + git submodule update && + git submodule status >actual_raw && + + cut -c 1,43- actual_raw >actual && + cat >expect <<-\EOF && + sub0 (test2) + sub1 (test2) + sub2 (test2) + sub3 (test2) + EOF + test_cmp expect actual + ) +' + test_expect_success 'clone --recurse-submodules with a pathspec works' ' test_when_finished "rm -rf multisuper_clone" && cat >expected <<-\EOF && diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index 10dc91620a..e87164aa8f 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -789,7 +789,7 @@ test_expect_success 'submodule add places git-dir in superprojects git-dir' ' (cd .git/modules/deeper/submodule && git log > ../../../../actual ) && - test_cmp actual expected + test_cmp expected actual ) ' @@ -807,7 +807,7 @@ test_expect_success 'submodule update places git-dir in superprojects git-dir' ' (cd .git/modules/deeper/submodule && git log > ../../../../actual ) && - test_cmp actual expected + test_cmp expected actual ) ' @@ -827,7 +827,7 @@ test_expect_success 'submodule add places git-dir in superprojects git-dir recur git add deeper/submodule && git commit -m "update submodule" && git push origin : && - test_cmp actual expected + test_cmp expected actual ) ' @@ -874,7 +874,7 @@ test_expect_success 'submodule update places git-dir in superprojects git-dir re (cd .git/modules/submodule/modules/subsubmodule && git log > ../../../../../actual ) && - test_cmp actual expected + test_cmp expected actual ) ' diff --git a/t/t7411-submodule-config.sh b/t/t7411-submodule-config.sh index 0bde5850ac..89690b7adb 100755 --- a/t/t7411-submodule-config.sh +++ b/t/t7411-submodule-config.sh @@ -82,29 +82,23 @@ Submodule name: 'a' for path 'b' Submodule name: 'submodule' for path 'submodule' EOF -test_expect_success 'error in one submodule config lets continue' ' +test_expect_success 'error in history of one submodule config lets continue, stderr message contains blob ref' ' + ORIG=$(git -C super rev-parse HEAD) && + test_when_finished "git -C super reset --hard $ORIG" && (cd super && cp .gitmodules .gitmodules.bak && echo " value = \"" >>.gitmodules && git add .gitmodules && mv .gitmodules.bak .gitmodules && git commit -m "add error" && - test-tool submodule-config \ - HEAD b \ - HEAD submodule \ - >actual && - test_cmp expect_error actual - ) -' - -test_expect_success 'error message contains blob reference' ' - (cd super && sha1=$(git rev-parse HEAD) && test-tool submodule-config \ HEAD b \ HEAD submodule \ - 2>actual_err && - test_i18ngrep "submodule-blob $sha1:.gitmodules" actual_err >/dev/null + >actual \ + 2>actual_stderr && + test_cmp expect_error actual && + test_i18ngrep "submodule-blob $sha1:.gitmodules" actual_stderr >/dev/null ) ' @@ -123,6 +117,8 @@ test_expect_success 'using different treeishs works' ' ' test_expect_success 'error in history in fetchrecursesubmodule lets continue' ' + ORIG=$(git -C super rev-parse HEAD) && + test_when_finished "git -C super reset --hard $ORIG" && (cd super && git config -f .gitmodules \ submodule.submodule.fetchrecursesubmodules blabla && @@ -134,8 +130,123 @@ test_expect_success 'error in history in fetchrecursesubmodule lets continue' ' HEAD b \ HEAD submodule \ >actual && - test_cmp expect_error actual && - git reset --hard HEAD^ + test_cmp expect_error actual + ) +' + +test_expect_success 'reading submodules config from the working tree with "submodule--helper config"' ' + (cd super && + echo "../submodule" >expect && + git submodule--helper config submodule.submodule.url >actual && + test_cmp expect actual + ) +' + +test_expect_success 'writing submodules config with "submodule--helper config"' ' + (cd super && + echo "new_url" >expect && + git submodule--helper config submodule.submodule.url "new_url" && + git submodule--helper config submodule.submodule.url >actual && + test_cmp expect actual + ) +' + +test_expect_success 'overwriting unstaged submodules config with "submodule--helper config"' ' + test_when_finished "git -C super checkout .gitmodules" && + (cd super && + echo "newer_url" >expect && + git submodule--helper config submodule.submodule.url "newer_url" && + git submodule--helper config submodule.submodule.url >actual && + test_cmp expect actual + ) +' + +test_expect_success 'writeable .gitmodules when it is in the working tree' ' + git -C super submodule--helper config --check-writeable +' + +test_expect_success 'writeable .gitmodules when it is nowhere in the repository' ' + ORIG=$(git -C super rev-parse HEAD) && + test_when_finished "git -C super reset --hard $ORIG" && + (cd super && + git rm .gitmodules && + git commit -m "remove .gitmodules from the current branch" && + git submodule--helper config --check-writeable + ) +' + +test_expect_success 'non-writeable .gitmodules when it is in the index but not in the working tree' ' + test_when_finished "git -C super checkout .gitmodules" && + (cd super && + rm -f .gitmodules && + test_must_fail git submodule--helper config --check-writeable + ) +' + +test_expect_success 'non-writeable .gitmodules when it is in the current branch but not in the index' ' + ORIG=$(git -C super rev-parse HEAD) && + test_when_finished "git -C super reset --hard $ORIG" && + (cd super && + git rm .gitmodules && + test_must_fail git submodule--helper config --check-writeable + ) +' + +test_expect_success 'reading submodules config from the index when .gitmodules is not in the working tree' ' + ORIG=$(git -C super rev-parse HEAD) && + test_when_finished "git -C super reset --hard $ORIG" && + (cd super && + git submodule--helper config submodule.submodule.url "staged_url" && + git add .gitmodules && + rm -f .gitmodules && + echo "staged_url" >expect && + git submodule--helper config submodule.submodule.url >actual && + test_cmp expect actual + ) +' + +test_expect_success 'reading submodules config from the current branch when .gitmodules is not in the index' ' + ORIG=$(git -C super rev-parse HEAD) && + test_when_finished "git -C super reset --hard $ORIG" && + (cd super && + git rm .gitmodules && + echo "../submodule" >expect && + git submodule--helper config submodule.submodule.url >actual && + test_cmp expect actual + ) +' + +test_expect_success 'reading nested submodules config' ' + (cd super && + git init submodule/nested_submodule && + echo "a" >submodule/nested_submodule/a && + git -C submodule/nested_submodule add a && + git -C submodule/nested_submodule commit -m "add a" && + git -C submodule submodule add ./nested_submodule && + git -C submodule add nested_submodule && + git -C submodule commit -m "added nested_submodule" && + git add submodule && + git commit -m "updated submodule" && + echo "./nested_submodule" >expect && + test-tool submodule-nested-repo-config \ + submodule submodule.nested_submodule.url >actual && + test_cmp expect actual + ) +' + +# When this test eventually passes, before turning it into +# test_expect_success, remember to replace the test_i18ngrep below with +# a "test_must_be_empty warning" to be sure that the warning is actually +# removed from the code. +test_expect_failure 'reading nested submodules config when .gitmodules is not in the working tree' ' + test_when_finished "git -C super/submodule checkout .gitmodules" && + (cd super && + echo "./nested_submodule" >expect && + rm submodule/.gitmodules && + test-tool submodule-nested-repo-config \ + submodule submodule.nested_submodule.url >actual 2>warning && + test_i18ngrep "nested submodules without %s in the working tree are not supported yet" warning && + test_cmp expect actual ) ' diff --git a/t/t7412-submodule-absorbgitdirs.sh b/t/t7412-submodule-absorbgitdirs.sh index ce74c12da2..1cfa150768 100755 --- a/t/t7412-submodule-absorbgitdirs.sh +++ b/t/t7412-submodule-absorbgitdirs.sh @@ -75,7 +75,12 @@ test_expect_success 're-setup nested submodule' ' 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 + git status --ignore-submodules=none && + + # also make sure this old setup does not regress + git submodule update --init --recursive >out 2>err && + test_must_be_empty out && + test_must_be_empty err ' test_expect_success 'absorb the git dir in a nested submodule' ' diff --git a/t/t7415-submodule-names.sh b/t/t7415-submodule-names.sh index 293e2e1963..49a37efe9c 100755 --- a/t/t7415-submodule-names.sh +++ b/t/t7415-submodule-names.sh @@ -154,7 +154,7 @@ test_expect_success 'fsck detects symlinked .gitmodules file' ' # 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_i18ngrep gitmodulesSymlink output ) ' @@ -172,7 +172,7 @@ test_expect_success 'fsck detects non-blob .gitmodules' ' git ls-tree HEAD | sed s/subdir/.gitmodules/ | git mktree && test_must_fail git fsck 2>output && - grep gitmodulesBlob output + test_i18ngrep gitmodulesBlob output ) ' @@ -186,7 +186,7 @@ test_expect_success 'fsck detects corrupt .gitmodules' ' git commit -m "broken gitmodules" && git fsck 2>output && - grep gitmodulesParse output && + test_i18ngrep gitmodulesParse output && test_i18ngrep ! "bad config" output ) ' diff --git a/t/t7418-submodule-sparse-gitmodules.sh b/t/t7418-submodule-sparse-gitmodules.sh new file mode 100755 index 0000000000..3f7f271883 --- /dev/null +++ b/t/t7418-submodule-sparse-gitmodules.sh @@ -0,0 +1,122 @@ +#!/bin/sh +# +# Copyright (C) 2018 Antonio Ospite <ao2@ao2.it> +# + +test_description='Test reading/writing .gitmodules when not in the working tree + +This test verifies that, when .gitmodules is in the current branch but is not +in the working tree reading from it still works but writing to it does not. + +The test setup uses a sparse checkout, however the same scenario can be set up +also by committing .gitmodules and then just removing it from the filesystem. +' + +. ./test-lib.sh + +test_expect_success 'sparse checkout setup which hides .gitmodules' ' + git init upstream && + git init submodule && + (cd submodule && + echo file >file && + git add file && + test_tick && + git commit -m "Add file" + ) && + (cd upstream && + git submodule add ../submodule && + test_tick && + git commit -m "Add submodule" + ) && + git clone upstream super && + (cd super && + cat >.git/info/sparse-checkout <<-\EOF && + /* + !/.gitmodules + EOF + git config core.sparsecheckout true && + git read-tree -m -u HEAD && + test_path_is_missing .gitmodules + ) +' + +test_expect_success 'reading gitmodules config file when it is not checked out' ' + echo "../submodule" >expect && + git -C super submodule--helper config submodule.submodule.url >actual && + test_cmp expect actual +' + +test_expect_success 'not writing gitmodules config file when it is not checked out' ' + test_must_fail git -C super submodule--helper config submodule.submodule.url newurl && + test_path_is_missing super/.gitmodules +' + +test_expect_success 'initialising submodule when the gitmodules config is not checked out' ' + test_must_fail git -C super config submodule.submodule.url && + git -C super submodule init && + git -C super config submodule.submodule.url >actual && + echo "$(pwd)/submodule" >expect && + test_cmp expect actual +' + +test_expect_success 'updating submodule when the gitmodules config is not checked out' ' + test_path_is_missing super/submodule/file && + git -C super submodule update && + test_cmp submodule/file super/submodule/file +' + +ORIG_SUBMODULE=$(git -C submodule rev-parse HEAD) +ORIG_UPSTREAM=$(git -C upstream rev-parse HEAD) +ORIG_SUPER=$(git -C super rev-parse HEAD) + +test_expect_success 're-updating submodule when the gitmodules config is not checked out' ' + test_when_finished "git -C submodule reset --hard $ORIG_SUBMODULE; + git -C upstream reset --hard $ORIG_UPSTREAM; + git -C super reset --hard $ORIG_SUPER; + git -C upstream submodule update --remote; + git -C super pull; + git -C super submodule update --remote" && + (cd submodule && + echo file2 >file2 && + git add file2 && + test_tick && + git commit -m "Add file2 to submodule" + ) && + (cd upstream && + git submodule update --remote && + git add submodule && + test_tick && + git commit -m "Update submodule" + ) && + git -C super pull && + # The --for-status options reads the gitmodules config + git -C super submodule summary --for-status >actual && + rev1=$(git -C submodule rev-parse --short HEAD) && + rev2=$(git -C submodule rev-parse --short HEAD^) && + cat >expect <<-EOF && + * submodule ${rev1}...${rev2} (1): + < Add file2 to submodule + + EOF + test_cmp expect actual && + # Test that the update actually succeeds + test_path_is_missing super/submodule/file2 && + git -C super submodule update && + test_cmp submodule/file2 super/submodule/file2 && + git -C super status --short >output && + test_must_be_empty output +' + +test_expect_success 'not adding submodules when the gitmodules config is not checked out' ' + git clone submodule new_submodule && + test_must_fail git -C super submodule add ../new_submodule && + test_path_is_missing .gitmodules +' + +# This test checks that the previous "git submodule add" did not leave the +# repository in a spurious state when it failed. +test_expect_success 'init submodule still works even after the previous add failed' ' + git -C super submodule init +' + +test_done diff --git a/t/t7500-commit.sh b/t/t7500-commit-template-squash-signoff.sh index 31ab608b67..46a5cd4b73 100755 --- a/t/t7500-commit.sh +++ b/t/t7500-commit-template-squash-signoff.sh @@ -5,7 +5,7 @@ test_description='git commit -Tests for selected commit options.' +Tests for template, signoff, squash and -F functions.' . ./test-lib.sh diff --git a/t/t7501-commit.sh b/t/t7501-commit-basic-functionality.sh index 1a6773ee68..f1349af56e 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit-basic-functionality.sh @@ -99,12 +99,12 @@ test_expect_success '--dry-run with stuff to commit returns ok' ' git commit -m next -a --dry-run ' -test_expect_failure '--short with stuff to commit returns ok' ' +test_expect_success '--short with stuff to commit returns ok' ' echo bongo bongo bongo >>file && git commit -m next -a --short ' -test_expect_failure '--porcelain with stuff to commit returns ok' ' +test_expect_success '--porcelain with stuff to commit returns ok' ' echo bongo bongo bongo >>file && git commit -m next -a --porcelain ' @@ -698,4 +698,10 @@ test_expect_success '--dry-run with conflicts fixed from a merge' ' git commit -m "conflicts fixed from merge." ' +test_expect_success '--dry-run --short' ' + >test-file && + git add test-file && + git commit --dry-run --short +' + test_done diff --git a/t/t7502-commit.sh b/t/t7502-commit-porcelain.sh index ca4a740da0..ca4a740da0 100755 --- a/t/t7502-commit.sh +++ b/t/t7502-commit-porcelain.sh diff --git a/t/t7505-prepare-commit-msg-hook.sh b/t/t7505-prepare-commit-msg-hook.sh index 1f43b3cd4c..ba8bd1b514 100755 --- a/t/t7505-prepare-commit-msg-hook.sh +++ b/t/t7505-prepare-commit-msg-hook.sh @@ -215,7 +215,7 @@ test_expect_success 'with hook and editor (merge)' ' test_rebase () { expect=$1 && mode=$2 && - test_expect_$expect C_LOCALE_OUTPUT "with hook (rebase $mode)" ' + test_expect_$expect C_LOCALE_OUTPUT "with hook (rebase ${mode:--i})" ' test_when_finished "\ git rebase --abort git checkout -f master @@ -225,7 +225,7 @@ test_rebase () { GIT_EDITOR="\"$FAKE_EDITOR\"" && ( export GIT_SEQUENCE_EDITOR GIT_EDITOR && - test_must_fail git rebase $mode b && + test_must_fail git rebase -i $mode b && echo x >a && git add a && test_must_fail git rebase --continue && @@ -241,19 +241,19 @@ test_rebase () { git add b && git rebase --continue ) && - if test $mode = -p # reword amended after pick + 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_cmp "$TEST_DIRECTORY/t7505/expected-rebase${mode:--i}" actual ' } -test_rebase success -i -test_rebase success -p +test_rebase success +test_have_prereq !REBASE_P || test_rebase success -p test_expect_success 'with hook (cherry-pick)' ' test_when_finished "git checkout -f master" && diff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh index 943708fb04..08629a6e70 100755 --- a/t/t7506-status-submodule.sh +++ b/t/t7506-status-submodule.sh @@ -325,7 +325,8 @@ test_expect_success 'setup superproject with untracked file in nested submodule' ( cd super && git clean -dfx && - rm .gitmodules && + git rm .gitmodules && + git commit -m "remove .gitmodules" && git submodule add -f ./sub1 && git submodule add -f ./sub2 && git submodule add -f ./sub1 sub3 && diff --git a/t/t7509-commit.sh b/t/t7509-commit-authorship.sh index ddef7ea6b0..500ab2fe72 100755 --- a/t/t7509-commit.sh +++ b/t/t7509-commit-authorship.sh @@ -3,7 +3,7 @@ # Copyright (c) 2009 Erick Mattos # -test_description='git commit --reset-author' +test_description='commit tests of various authorhip options. ' . ./test-lib.sh diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh index 4e37ff8f16..682b23a068 100755 --- a/t/t7510-signed-commit.sh +++ b/t/t7510-signed-commit.sh @@ -49,15 +49,28 @@ test_expect_success GPG 'create signed commits' ' git tag eighth-signed-alt && # commit.gpgsign is still on but this must not be signed - git tag ninth-unsigned $(echo 9 | git commit-tree HEAD^{tree}) && + echo 9 | git commit-tree HEAD^{tree} >oid && + test_line_count = 1 oid && + git tag ninth-unsigned $(cat oid) && # explicit -S of course must sign. - git tag tenth-signed $(echo 9 | git commit-tree -S HEAD^{tree}) + echo 10 | git commit-tree -S HEAD^{tree} >oid && + test_line_count = 1 oid && + git tag tenth-signed $(cat oid) && + + # --gpg-sign[=<key-id>] must sign. + echo 11 | git commit-tree --gpg-sign HEAD^{tree} >oid && + test_line_count = 1 oid && + git tag eleventh-signed $(cat oid) && + echo 12 | git commit-tree --gpg-sign=B7227189 HEAD^{tree} >oid && + test_line_count = 1 oid && + git tag twelfth-signed-alt $(cat oid) ' test_expect_success GPG 'verify and show signatures' ' ( for commit in initial second merge fourth-signed \ - fifth-signed sixth-signed seventh-signed tenth-signed + fifth-signed sixth-signed seventh-signed tenth-signed \ + eleventh-signed do git verify-commit $commit && git show --pretty=short --show-signature $commit >actual && @@ -78,7 +91,7 @@ test_expect_success GPG 'verify and show signatures' ' done ) && ( - for commit in eighth-signed-alt + for commit in eighth-signed-alt twelfth-signed-alt do git show --pretty=short --show-signature $commit >actual && grep "Good signature from" actual && @@ -175,8 +188,10 @@ test_expect_success GPG 'show good signature with custom format' ' G 13B6F51ECDDE430D C O Mitter <committer@example.com> + 73D758744BE721698EC54E8713B6F51ECDDE430D + 73D758744BE721698EC54E8713B6F51ECDDE430D EOF - git log -1 --format="%G?%n%GK%n%GS" sixth-signed >actual && + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" sixth-signed >actual && test_cmp expect actual ' @@ -185,28 +200,34 @@ test_expect_success GPG 'show bad signature with custom format' ' B 13B6F51ECDDE430D C O Mitter <committer@example.com> + + EOF - git log -1 --format="%G?%n%GK%n%GS" $(cat forged1.commit) >actual && + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" $(cat forged1.commit) >actual && test_cmp expect actual ' test_expect_success GPG 'show untrusted signature with custom format' ' cat >expect <<-\EOF && U - 61092E85B7227189 + 65A0EEA02E30CAD7 Eris Discordia <discord@example.net> + F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7 + D4BE22311AD3131E5EDA29A461092E85B7227189 EOF - git log -1 --format="%G?%n%GK%n%GS" eighth-signed-alt >actual && + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" eighth-signed-alt >actual && test_cmp expect actual ' test_expect_success GPG 'show unknown signature with custom format' ' cat >expect <<-\EOF && E - 61092E85B7227189 + 65A0EEA02E30CAD7 + + EOF - GNUPGHOME="$GNUPGHOME_NOT_USED" git log -1 --format="%G?%n%GK%n%GS" eighth-signed-alt >actual && + GNUPGHOME="$GNUPGHOME_NOT_USED" git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" eighth-signed-alt >actual && test_cmp expect actual ' @@ -215,8 +236,10 @@ test_expect_success GPG 'show lack of signature with custom format' ' N + + EOF - git log -1 --format="%G?%n%GK%n%GS" seventh-unsigned >actual && + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" seventh-unsigned >actual && test_cmp expect actual ' @@ -234,4 +257,32 @@ test_expect_success GPG 'check config gpg.format values' ' test_must_fail git commit -S --amend -m "fail" ' +test_expect_success GPG 'detect fudged commit with double signature' ' + sed -e "/gpgsig/,/END PGP/d" forged1 >double-base && + sed -n -e "/gpgsig/,/END PGP/p" forged1 | \ + sed -e "s/^gpgsig//;s/^ //" | gpg --dearmor >double-sig1.sig && + gpg -o double-sig2.sig -u 29472784 --detach-sign double-base && + cat double-sig1.sig double-sig2.sig | gpg --enarmor >double-combined.asc && + sed -e "s/^\(-.*\)ARMORED FILE/\1SIGNATURE/;1s/^/gpgsig /;2,\$s/^/ /" \ + double-combined.asc > double-gpgsig && + sed -e "/committer/r double-gpgsig" double-base >double-commit && + git hash-object -w -t commit double-commit >double-commit.commit && + test_must_fail git verify-commit $(cat double-commit.commit) && + git show --pretty=short --show-signature $(cat double-commit.commit) >double-actual && + grep "BAD signature from" double-actual && + grep "Good signature from" double-actual +' + +test_expect_success GPG 'show double signature with custom format' ' + cat >expect <<-\EOF && + E + + + + + EOF + git log -1 --format="%G?%n%GK%n%GS%n%GF%n%GP" $(cat double-commit.commit) >actual && + test_cmp expect actual +' + test_done diff --git a/t/t7517-per-repo-email.sh b/t/t7517-per-repo-email.sh index 2a22fa7588..231b8cc19d 100755 --- a/t/t7517-per-repo-email.sh +++ b/t/t7517-per-repo-email.sh @@ -72,12 +72,14 @@ test_expect_success 'noop interactive rebase does not care about ident' ' git rebase -i HEAD^ ' -test_expect_success 'fast-forward rebase does not care about ident (preserve)' ' +test_expect_success REBASE_P \ + 'fast-forward rebase does not care about ident (preserve)' ' git checkout -B tmp side-without-commit && git rebase -p master ' -test_expect_success 'non-fast-forward rebase refuses to write commits (preserve)' ' +test_expect_success REBASE_P \ + 'non-fast-forward rebase refuses to write commits (preserve)' ' test_when_finished "git rebase --abort || true" && git checkout -B tmp side-with-commit && test_must_fail git rebase -p master diff --git a/t/t7519-status-fsmonitor.sh b/t/t7519-status-fsmonitor.sh index 756beb0d8e..3e0a61db23 100755 --- a/t/t7519-status-fsmonitor.sh +++ b/t/t7519-status-fsmonitor.sh @@ -4,13 +4,6 @@ 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. @@ -84,21 +77,21 @@ test_expect_success 'setup' ' # test that the fsmonitor extension is off by default test_expect_success 'fsmonitor extension is off by default' ' - test-dump-fsmonitor >actual && + test-tool 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 && + test-tool 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 && + test-tool dump-fsmonitor >actual && grep "^no fsmonitor" actual ' @@ -245,9 +238,9 @@ do git config core.preloadIndex $preload_val && if test $preload_val = true then - GIT_FORCE_PRELOAD_TEST=$preload_val; export GIT_FORCE_PRELOAD_TEST + GIT_TEST_PRELOAD_INDEX=$preload_val; export GIT_TEST_PRELOAD_INDEX else - unset GIT_FORCE_PRELOAD_TEST + sane_unset GIT_TEST_PRELOAD_INDEX fi ' @@ -307,9 +300,9 @@ test_expect_success 'splitting the index results in the same state' ' dirty_repo && git update-index --fsmonitor && git ls-files -f >expect && - test-dump-fsmonitor >&2 && echo && + test-tool dump-fsmonitor >&2 && echo && git update-index --fsmonitor --split-index && - test-dump-fsmonitor >&2 && echo && + test-tool dump-fsmonitor >&2 && echo && git ls-files -f >actual && test_cmp expect actual ' @@ -333,7 +326,7 @@ test_expect_success UNTRACKED_CACHE 'ignore .git changes when invalidating UNTR' git update-index --fsmonitor && GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace-before" \ git status && - test-dump-untracked-cache >../before + test-tool dump-untracked-cache >../before ) && cat >>dot-git/.git/hooks/fsmonitor-test <<-\EOF && printf ".git\0" @@ -345,7 +338,7 @@ test_expect_success UNTRACKED_CACHE 'ignore .git changes when invalidating UNTR' cd dot-git && GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace-after" \ git status && - test-dump-untracked-cache >../after + test-tool dump-untracked-cache >../after ) && grep "directory invalidation" trace-before >>before && grep "directory invalidation" trace-after >>after && diff --git a/t/t7612-merge-verify-signatures.sh b/t/t7612-merge-verify-signatures.sh index e2b1df817a..d99218a725 100755 --- a/t/t7612-merge-verify-signatures.sh +++ b/t/t7612-merge-verify-signatures.sh @@ -103,4 +103,11 @@ test_expect_success GPG 'merge commit with bad signature with merge.verifySignat git merge --no-verify-signatures $(cat forged.commit) ' +test_expect_success GPG 'merge unsigned commit into unborn branch' ' + test_when_finished "git checkout initial" && + git checkout --orphan unborn && + test_must_fail git merge --verify-signatures side-unsigned 2>mergeerror && + test_i18ngrep "does not have a GPG signature" mergeerror +' + test_done diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 668bbee73c..22b9199d59 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -332,7 +332,7 @@ test_expect_success 'difftool --extcmd cat arg1' ' test_expect_success 'difftool --extcmd cat arg2' ' echo branch >expect && git difftool --no-prompt \ - --extcmd sh\ -c\ \"cat\ \$2\" branch >actual && + --extcmd sh\ -c\ \"cat\ \\\"\$2\\\"\" branch >actual && test_cmp expect actual ' @@ -557,7 +557,7 @@ test_expect_success SYMLINKS 'difftool --dir-diff --symlink without unstaged cha EOF git difftool --dir-diff --symlink \ --extcmd "./.git/CHECK_SYMLINKS" branch HEAD && - test_cmp actual expect + test_cmp expect actual ' write_script modify-right-file <<\EOF diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh index be5c1bd553..43aa4161cf 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -309,6 +309,8 @@ do echo ${HC}v:1:vvv } >expected && git grep --max-depth -1 -n -e vvv $H >actual && + test_cmp expected actual && + git grep --recursive -n -e vvv $H >actual && test_cmp expected actual ' @@ -317,6 +319,8 @@ do echo ${HC}v:1:vvv } >expected && git grep --max-depth 0 -n -e vvv $H >actual && + test_cmp expected actual && + git grep --no-recursive -n -e vvv $H >actual && test_cmp expected actual ' @@ -327,6 +331,8 @@ do echo ${HC}v:1:vvv } >expected && git grep --max-depth 0 -n -e vvv $H -- "*" >actual && + test_cmp expected actual && + git grep --no-recursive -n -e vvv $H -- "*" >actual && test_cmp expected actual ' @@ -344,6 +350,8 @@ do echo ${HC}t/v:1:vvv } >expected && git grep --max-depth 0 -n -e vvv $H -- t >actual && + test_cmp expected actual && + git grep --no-recursive -n -e vvv $H -- t >actual && test_cmp expected actual ' @@ -353,6 +361,8 @@ do echo ${HC}v:1:vvv } >expected && git grep --max-depth 0 -n -e vvv $H -- . t >actual && + test_cmp expected actual && + git grep --no-recursive -n -e vvv $H -- . t >actual && test_cmp expected actual ' @@ -362,6 +372,8 @@ do echo ${HC}v:1:vvv } >expected && git grep --max-depth 0 -n -e vvv $H -- t . >actual && + test_cmp expected actual && + git grep --no-recursive -n -e vvv $H -- t . >actual && test_cmp expected actual ' test_expect_success "grep $L with grep.extendedRegexp=false" ' diff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh index 7184113b9b..fa475d52fa 100755 --- a/t/t7814-grep-recurse-submodules.sh +++ b/t/t7814-grep-recurse-submodules.sh @@ -380,4 +380,20 @@ test_expect_success 'grep --recurse-submodules should pass the pattern type alon fi ' +# Recursing down into nested submodules which do not have .gitmodules in their +# working tree does not work yet. This is because config_from_gitmodules() +# uses get_oid() and the latter is still not able to get objects from an +# arbitrary repository (the nested submodule, in this case). +test_expect_failure 'grep --recurse-submodules with submodules without .gitmodules in the working tree' ' + test_when_finished "git -C submodule checkout .gitmodules" && + rm submodule/.gitmodules && + git grep --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 +' + test_done diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index 1ef1a19003..ee1efcc59d 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -492,6 +492,21 @@ do --validate \ $patches longline.patch ' + +done + +for enc in 7bit 8bit quoted-printable base64 +do + test_expect_success $PREREQ "--transfer-encoding=$enc produces correct header" ' + clean_fake_sendmail && + git send-email \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + --transfer-encoding=$enc \ + $patches && + grep "Content-Transfer-Encoding: $enc" msgtxt1 + ' done test_expect_success $PREREQ 'Invalid In-Reply-To' ' diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 9af6078844..2c309a57d9 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -221,7 +221,7 @@ tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e tree 8f51f74cf0163afc9ad68a4b1537288c4558b5a4 EOF -test_expect_success POSIXPERM,SYMLINKS "$name" "test_cmp a expected" +test_expect_success POSIXPERM,SYMLINKS "$name" "test_cmp expected a" test_expect_success 'exit if remote refs are ambigious' ' git config --add svn-remote.svn.fetch \ diff --git a/t/t9101-git-svn-props.sh b/t/t9101-git-svn-props.sh index 8a5c8dc1aa..c26c4b0927 100755 --- a/t/t9101-git-svn-props.sh +++ b/t/t9101-git-svn-props.sh @@ -174,7 +174,8 @@ test_expect_success 'test create-ignore' " cmp ./deeply/.gitignore create-ignore.expect && cmp ./deeply/nested/.gitignore create-ignore.expect && cmp ./deeply/nested/directory/.gitignore create-ignore.expect && - git ls-files -s | grep gitignore | cmp - create-ignore-index.expect + git ls-files -s >ls_files_result && + grep gitignore ls_files_result | cmp - create-ignore-index.expect " cat >prop.expect <<\EOF @@ -189,17 +190,21 @@ EOF # This test can be improved: since all the svn:ignore contain the same # pattern, it can pass even though the propget did not execute on the # right directory. -test_expect_success 'test propget' " - git svn propget svn:ignore . | cmp - prop.expect && +test_expect_success 'test propget' ' + test_propget () { + git svn propget $1 $2 >actual && + cmp $3 actual + } && + test_propget svn:ignore . prop.expect && cd deeply && - git svn propget svn:ignore . | cmp - ../prop.expect && - git svn propget svn:entry:committed-rev nested/directory/.keep \ - | cmp - ../prop2.expect && - git svn propget svn:ignore .. | cmp - ../prop.expect && - git svn propget svn:ignore nested/ | cmp - ../prop.expect && - git svn propget svn:ignore ./nested | cmp - ../prop.expect && - git svn propget svn:ignore .././deeply/nested | cmp - ../prop.expect - " + test_propget svn:ignore . ../prop.expect && + test_propget svn:entry:committed-rev nested/directory/.keep \ + ../prop2.expect && + test_propget svn:ignore .. ../prop.expect && + test_propget svn:ignore nested/ ../prop.expect && + test_propget svn:ignore ./nested ../prop.expect && + test_propget svn:ignore .././deeply/nested ../prop.expect + ' cat >prop.expect <<\EOF Properties on '.': @@ -218,8 +223,11 @@ Properties on 'nested/directory/.keep': EOF test_expect_success 'test proplist' " - git svn proplist . | cmp - prop.expect && - git svn proplist nested/directory/.keep | cmp - prop2.expect + git svn proplist . >actual && + cmp prop.expect actual && + + git svn proplist nested/directory/.keep >actual && + cmp prop2.expect actual " test_done diff --git a/t/t9133-git-svn-nested-git-repo.sh b/t/t9133-git-svn-nested-git-repo.sh index f3c30e63b7..f894860867 100755 --- a/t/t9133-git-svn-nested-git-repo.sh +++ b/t/t9133-git-svn-nested-git-repo.sh @@ -45,7 +45,7 @@ test_expect_success 'update git svn-cloned repo' ' git svn rebase && echo a > expect && echo b >> expect && - test_cmp a expect && + test_cmp expect a && rm expect ) ' @@ -69,7 +69,7 @@ test_expect_success 'update git svn-cloned repo' ' git svn rebase && echo a > expect && echo b >> expect && - test_cmp a expect && + test_cmp expect a && rm expect ) ' @@ -93,7 +93,7 @@ test_expect_success 'update git svn-cloned repo again' ' echo a > expect && echo b >> expect && echo c >> expect && - test_cmp a expect && + test_cmp expect a && rm expect ) ' diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 40fe7e4976..59a13b6a77 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -1558,7 +1558,7 @@ test_expect_success 'O: blank lines not necessary after other commands' ' INPUT_END git fast-import <input && - test 8 = $(find .git/objects/pack -type f | wc -l) && + test 8 = $(find .git/objects/pack -type f | grep -v multi-pack-index | wc -l) && test $(git rev-parse refs/tags/O3-2nd) = $(git rev-parse O3^) && git log --reverse --pretty=oneline O3 | sed s/^.*z// >actual && test_cmp expect actual diff --git a/t/t9303-fast-import-compression.sh b/t/t9303-fast-import-compression.sh index 856219f46a..5045f02a53 100755 --- a/t/t9303-fast-import-compression.sh +++ b/t/t9303-fast-import-compression.sh @@ -6,7 +6,7 @@ test_description='compression setting of fast-import utility' # This should be moved to test-lib.sh together with the # copy in t0021 after both topics have graduated to 'master'. file_size () { - perl -e 'print -s $ARGV[0]' "$1" + test-tool path-utils file-size "$1" } import_large () { diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index 6a392e87bc..5690fe2810 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -66,6 +66,34 @@ test_expect_success 'fast-export master~2..master' ' ' +test_expect_success 'fast-export --reference-excluded-parents master~2..master' ' + + git fast-export --reference-excluded-parents master~2..master >actual && + grep commit.refs/heads/master actual >commit-count && + test_line_count = 2 commit-count && + sed "s/master/rewrite/" actual | + (cd new && + git fast-import && + test $MASTER = $(git rev-parse --verify refs/heads/rewrite)) +' + +test_expect_success 'fast-export --show-original-ids' ' + + git fast-export --show-original-ids master >output && + grep ^original-oid output| sed -e s/^original-oid.// | sort >actual && + git rev-list --objects master muss >objects-and-names && + awk "{print \$1}" objects-and-names | sort >commits-trees-blobs && + comm -23 actual commits-trees-blobs >unfound && + test_must_be_empty unfound +' + +test_expect_success 'fast-export --show-original-ids | git fast-import' ' + + git fast-export --show-original-ids master muss | git fast-import --quiet && + test $MASTER = $(git rev-parse --verify refs/heads/master) && + test $MUSS = $(git rev-parse --verify refs/tags/muss) +' + test_expect_success 'iso-8859-1' ' git config i18n.commitencoding ISO8859-1 && @@ -325,6 +353,22 @@ test_expect_success 'rewriting tag of filtered out object' ' ) ' +test_expect_success 'rewrite tag predating pathspecs to nothing' ' + test_create_repo rewrite_tag_predating_pathspecs && + ( + cd rewrite_tag_predating_pathspecs && + + test_commit initial && + + git tag -a -m "Some old tag" v0.0.0.0.0.0.1 && + + test_commit bar && + + git fast-export --tag-of-filtered-object=rewrite --all -- bar.t >output && + grep from.$ZERO_OID output + ) +' + cat > limit-by-paths/expected << EOF blob mark :1 @@ -366,6 +410,26 @@ test_expect_success 'path limiting with import-marks does not lose unmodified fi grep file0 actual ' +test_expect_success 'avoid corrupt stream with non-existent mark' ' + test_create_repo avoid_non_existent_mark && + ( + cd avoid_non_existent_mark && + + test_commit important-path && + + test_commit ignored && + + git branch A && + git branch B && + + echo foo >>important-path.t && + git add important-path.t && + test_commit more changes && + + git fast-export --all -- important-path.t | git fast-import --force + ) +' + test_expect_success 'full-tree re-shows unmodified files' ' git checkout -f simple && git fast-export --full-tree simple >actual && @@ -508,10 +572,20 @@ test_expect_success 'use refspec' ' test_cmp expected actual ' -test_expect_success 'delete refspec' ' +test_expect_success 'delete ref because entire history excluded' ' git branch to-delete && - git fast-export --refspec :refs/heads/to-delete to-delete ^to-delete > actual && - cat > expected <<-EOF && + git fast-export to-delete ^to-delete >actual && + cat >expected <<-EOF && + reset refs/heads/to-delete + from 0000000000000000000000000000000000000000 + + EOF + test_cmp expected actual +' + +test_expect_success 'delete refspec' ' + git fast-export --refspec :refs/heads/to-delete >actual && + cat >expected <<-EOF && reset refs/heads/to-delete from 0000000000000000000000000000000000000000 diff --git a/t/t9600-cvsimport.sh b/t/t9600-cvsimport.sh index 5dfee07d9a..251fdd66c4 100755 --- a/t/t9600-cvsimport.sh +++ b/t/t9600-cvsimport.sh @@ -148,7 +148,7 @@ test_expect_success PERL 'import from a CVS working tree' ' git cvsimport -a -z0 && echo 1 >expect && git log -1 --pretty=format:%s%n >actual && - test_cmp actual expect + test_cmp expect actual ) ' diff --git a/t/t9603-cvsimport-patchsets.sh b/t/t9603-cvsimport-patchsets.sh index c4c3c49546..3e64b11eac 100755 --- a/t/t9603-cvsimport-patchsets.sh +++ b/t/t9603-cvsimport-patchsets.sh @@ -29,11 +29,11 @@ test_expect_failure PERL 'import with criss cross times on revisions' ' Rev 3 Rev 2 Rev 1" > expect-master && - test_cmp actual-master expect-master && + test_cmp expect-master actual-master && echo "Rev 5 Branch A Wed Mar 11 19:09:10 2009 +0000 Rev 4 Branch A Wed Mar 11 19:03:52 2009 +0000" > expect-A && - test_cmp actual-A expect-A + test_cmp expect-A actual-A ' test_done diff --git a/t/t9604-cvsimport-timestamps.sh b/t/t9604-cvsimport-timestamps.sh index a4b3db24bd..2ff4aa932d 100755 --- a/t/t9604-cvsimport-timestamps.sh +++ b/t/t9604-cvsimport-timestamps.sh @@ -31,7 +31,7 @@ test_expect_success PERL 'check timestamps are UTC (TZ=CST6CDT)' ' Rev 2 2005-02-01 00:00:00 +0000 Rev 1 2005-01-01 00:00:00 +0000 EOF - test_cmp actual-1 expect-1 + test_cmp expect-1 actual-1 ' test_expect_success PERL 'check timestamps with author-specific timezones' ' @@ -65,7 +65,7 @@ test_expect_success PERL 'check timestamps with author-specific timezones' ' Rev 2 2005-01-31 18:00:00 -0600 User Two Rev 1 2005-01-01 00:00:00 +0000 User One EOF - test_cmp actual-2 expect-2 + test_cmp expect-2 actual-2 ' test_done diff --git a/t/t9807-git-p4-submit.sh b/t/t9807-git-p4-submit.sh index 2325599ee6..850d979119 100755 --- a/t/t9807-git-p4-submit.sh +++ b/t/t9807-git-p4-submit.sh @@ -500,6 +500,10 @@ test_expect_success 'submit --shelve' ' ) ' +last_shelve () { + p4 -G changes -s shelved -m 1 //depot/... | marshal_dump change +} + make_shelved_cl() { test_commit "$1" >/dev/null && git p4 submit --origin HEAD^ --shelve >/dev/null && @@ -533,12 +537,59 @@ test_expect_success 'submit --update-shelve' ' ) && ( cd "$cli" && - change=$(p4 -G changes -s shelved -m 1 //depot/... | \ - marshal_dump change) && + change=$(last_shelve) && 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_path_is_missing shelved-change-1.t && + p4 revert ... + ) +' + +test_expect_success 'update a shelve involving moved and copied files' ' + test_when_finished cleanup_git && + ( + cd "$cli" && + : >file_to_move && + p4 add file_to_move && + p4 submit -d "change1" && + p4 edit file_to_move && + echo change >>file_to_move && + p4 submit -d "change2" && + p4 opened + ) && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + git config git-p4.detectCopies true && + git config git-p4.detectRenames true && + git config git-p4.skipSubmitEdit true && + mkdir moved && + cp file_to_move copy_of_file && + git add copy_of_file && + git mv file_to_move moved/ && + git commit -m "rename a file" && + git p4 submit -M --shelve --origin HEAD^ && + : >new_file && + git add new_file && + git commit --amend && + git show --stat HEAD && + change=$(last_shelve) && + git p4 submit -M --update-shelve $change --commit HEAD + ) && + ( + cd "$cli" && + change=$(last_shelve) && + echo change=$change && + p4 unshelve -s $change && + p4 submit -d "Testing update-shelve" && + test_path_is_file copy_of_file && + test_path_is_file moved/file_to_move && + test_path_is_missing file_to_move && + test_path_is_file new_file && + echo "unshelved and submitted change $change" && + p4 changes moved/file_to_move | grep "Testing update-shelve" && + p4 changes copy_of_file | grep "Testing update-shelve" ) ' diff --git a/t/t9832-unshelve.sh b/t/t9832-unshelve.sh index 48ec7679b8..41c09f11f4 100755 --- a/t/t9832-unshelve.sh +++ b/t/t9832-unshelve.sh @@ -19,8 +19,10 @@ test_expect_success 'init depot' ' p4 add file1 && p4 submit -d "change 1" && : >file_to_delete && + : >file_to_move && p4 add file_to_delete && - p4 submit -d "file to delete" + p4 add file_to_move && + p4 submit -d "add files to delete" ) ' @@ -36,6 +38,8 @@ test_expect_success 'create shelved changelist' ' echo "new file" >file2 && p4 add file2 && p4 delete file_to_delete && + p4 edit file_to_move && + p4 move file_to_move moved_file && p4 opened && p4 shelve -i <<EOF Change: new @@ -47,6 +51,8 @@ Files: //depot/file1 //depot/file2 //depot/file_to_delete + //depot/file_to_move + //depot/moved_file EOF ) && @@ -54,12 +60,14 @@ 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 && + 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_path_is_missing file_to_delete && + test_path_is_missing file_to_move && + test_path_is_file moved_file ) ' @@ -88,10 +96,22 @@ EOF 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 + git diff refs/remotes/p4-unshelved/$change.0 refs/remotes/p4-unshelved/$change | grep -q file3 ) ' +shelve_one_file () { + description="Change to be unshelved" && + file="$1" && + p4 shelve -i <<EOF +Change: new +Description: + $description +Files: + $file +EOF +} + # This is the tricky case where the shelved changelist base revision doesn't # match git-p4's idea of the base revision # @@ -108,29 +128,52 @@ test_expect_success 'create shelved changelist based on p4 change ahead of p4/ma 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 + shelve_one_file //depot/file1 && change=$(last_shelved_change) && - p4 describe -S $change | grep -q "Change to be unshelved" + p4 describe -S $change >out.txt && + grep -q "Change to be unshelved" out.txt ) ' -# Now try to unshelve it. git-p4 should refuse to do so. +# Now try to unshelve it. 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 + git p4 unshelve $change >out.txt && + grep -q "unshelved changelist $change" out.txt ) ' +# Specify the origin. Create 2 unrelated files, and check that +# we only get the one in HEAD~, not the one in HEAD. + +test_expect_success 'unshelve specifying the origin' ' + ( + cd "$cli" && + : >unrelated_file0 && + p4 add unrelated_file0 && + p4 submit -d "unrelated" && + : >unrelated_file1 && + p4 add unrelated_file1 && + p4 submit -d "unrelated" && + : >file_to_shelve && + p4 add file_to_shelve && + shelve_one_file //depot/file_to_shelve + ) && + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot/@all && + ( + cd "$git" && + change=$(last_shelved_change) && + git p4 unshelve --origin HEAD~ $change && + git checkout refs/remotes/p4-unshelved/$change && + test_path_is_file unrelated_file0 && + test_path_is_missing unrelated_file1 && + test_path_is_file file_to_shelve + ) +' test_expect_success 'kill p4d' ' kill_p4d ' diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 175f83d704..3a2c6326d8 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -1249,7 +1249,7 @@ test_expect_success 'teardown after ref completion' ' test_path_completion () { - test $# = 2 || error "bug in the test script: not 2 parameters to test_path_completion" + test $# = 2 || BUG "not 2 parameters to test_path_completion" local cur="$1" expected="$2" echo "$expected" >expected && @@ -1434,7 +1434,8 @@ test_expect_success 'double dash "git checkout"' ' --ignore-other-worktrees Z --recurse-submodules Z --progress Z - --no-quiet Z + --guess Z + --no-guess Z --no-... Z EOF ' @@ -1515,8 +1516,8 @@ test_expect_success 'show completes all refs' ' test_expect_success '<ref>: completes paths' ' test_completion "git show mytag:f" <<-\EOF - file1 Z - file2 Z + file1Z + file2Z EOF ' @@ -1525,7 +1526,7 @@ test_expect_success 'complete tree filename with spaces' ' git add "name with spaces" && git commit -m spaces && test_completion "git show HEAD:nam" <<-\EOF - name with spaces Z + name with spacesZ EOF ' @@ -1534,12 +1535,12 @@ test_expect_success 'complete tree filename with metacharacters' ' git add "name with \${meta}" && git commit -m meta && test_completion "git show HEAD:nam" <<-\EOF - name with ${meta} Z - name with spaces Z + name with ${meta}Z + name with spacesZ EOF ' -test_expect_success 'send-email' ' +test_expect_success PERL 'send-email' ' test_completion "git send-email --cov" "--cover-letter " && test_completion "git send-email ma" "master " ' @@ -1697,7 +1698,8 @@ test_expect_success 'sourcing the completion script clears cached commands' ' verbose test -z "$__git_all_commands" ' -test_expect_success !GETTEXT_POISON 'sourcing the completion script clears cached merge strategies' ' +test_expect_success 'sourcing the completion script clears cached merge strategies' ' + GIT_TEST_GETTEXT_POISON= && __git_compute_merge_strategies && verbose test -n "$__git_merge_strategies" && . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh index 81a5179e28..5cadedb2a9 100755 --- a/t/t9903-bash-prompt.sh +++ b/t/t9903-bash-prompt.sh @@ -180,7 +180,7 @@ test_expect_success 'prompt - interactive rebase' ' ' test_expect_success 'prompt - rebase merge' ' - printf " (b2|REBASE-m 1/3)" >expected && + printf " (b2|REBASE-i 1/3)" >expected && git checkout b2 && test_when_finished "git checkout master" && test_must_fail git rebase --merge b1 b2 && diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 4207af4077..92cf8f812c 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -42,6 +42,8 @@ test_decode_color () { function name(n) { if (n == 0) return "RESET"; if (n == 1) return "BOLD"; + if (n == 2) return "FAINT"; + if (n == 3) return "ITALIC"; if (n == 7) return "REVERSE"; if (n == 30) return "BLACK"; if (n == 31) return "RED"; @@ -416,14 +418,14 @@ test_declared_prereq () { test_verify_prereq () { test -z "$test_prereq" || expr >/dev/null "$test_prereq" : '[A-Z0-9_,!]*$' || - error "bug in the test script: '$test_prereq' does not look like a prereq" + BUG "'$test_prereq' does not look like a prereq" } test_expect_failure () { test_start_ test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq= test "$#" = 2 || - error "bug in the test script: not 2 or 3 parameters to test-expect-failure" + BUG "not 2 or 3 parameters to test-expect-failure" test_verify_prereq export test_prereq if ! test_skip "$@" @@ -443,7 +445,7 @@ test_expect_success () { test_start_ test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq= test "$#" = 2 || - error "bug in the test script: not 2 or 3 parameters to test-expect-success" + BUG "not 2 or 3 parameters to test-expect-success" test_verify_prereq export test_prereq if ! test_skip "$@" @@ -470,7 +472,7 @@ test_expect_success () { test_external () { test "$#" = 4 && { test_prereq=$1; shift; } || test_prereq= test "$#" = 3 || - error >&5 "bug in the test script: not 3 or 4 parameters to test_external" + BUG "not 3 or 4 parameters to test_external" descr="$1" shift test_verify_prereq @@ -611,7 +613,7 @@ test_path_is_missing () { test_line_count () { if test $# != 3 then - error "bug in the test script: not 3 parameters to test_line_count" + BUG "not 3 parameters to test_line_count" elif ! test $(wc -l <"$3") "$1" "$2" then echo "test_line_count: line count for $3 !$1 $2" @@ -745,6 +747,29 @@ test_cmp() { $GIT_TEST_CMP "$@" } +# Check that the given config key has the expected value. +# +# test_cmp_config [-C <dir>] <expected-value> +# [<git-config-options>...] <config-key> +# +# for example to check that the value of core.bar is foo +# +# test_cmp_config foo core.bar +# +test_cmp_config() { + local GD && + if test "$1" = "-C" + then + shift && + GD="-C $1" && + shift + fi && + printf "%s\n" "$1" >expect.config && + shift && + git $GD config "$@" >actual.config && + test_cmp expect.config actual.config +} + # test_cmp_bin - helper to compare binary files test_cmp_bin() { @@ -753,31 +778,30 @@ test_cmp_bin() { # 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 +# under GIT_TEST_GETTEXT_POISON this pretends that the command produced expected # results. test_i18ncmp () { - test -n "$GETTEXT_POISON" || test_cmp "$@" + ! test_have_prereq C_LOCALE_OUTPUT || 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 +# under GIT_TEST_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" + BUG "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" + BUG "too few parameters to test_i18ngrep" fi - if test -n "$GETTEXT_POISON" + if test_have_prereq !C_LOCALE_OUTPUT then # pretend success return 0 @@ -829,9 +853,23 @@ test_must_be_empty () { # Tests that its two parameters refer to the same revision test_cmp_rev () { - git rev-parse --verify "$1" >expect.rev && - git rev-parse --verify "$2" >actual.rev && - test_cmp expect.rev actual.rev + if test $# != 2 + then + error "bug in the test script: test_cmp_rev requires two revisions, but got $#" + else + local r1 r2 + r1=$(git rev-parse --verify "$1") && + r2=$(git rev-parse --verify "$2") && + if test "$r1" != "$r2" + then + cat >&4 <<-EOF + error: two revisions point to different objects: + '$1': $r1 + '$2': $r2 + EOF + return 1 + fi + fi } # Print a sequence of integers in increasing order, either with @@ -846,7 +884,7 @@ test_seq () { case $# in 1) set 1 "$@" ;; 2) ;; - *) error "bug in the test script: not 1 or 2 parameters to test_seq" ;; + *) BUG "not 1 or 2 parameters to test_seq" ;; esac test_seq_counter__=$1 while test "$test_seq_counter__" -le "$2" @@ -884,7 +922,7 @@ test_when_finished () { # doing so on Bash is better than nothing (the test will # silently pass on other shells). test "${BASH_SUBSHELL-0}" = 0 || - error "bug in test script: test_when_finished does nothing in a subshell" + BUG "test_when_finished does nothing in a subshell" test_cleanup="{ $* } && (exit \"\$eval_ret\"); eval_ret=\$?; $test_cleanup" } @@ -893,12 +931,13 @@ test_when_finished () { # Usage: test_create_repo <directory> test_create_repo () { test "$#" = 1 || - error "bug in the test script: not 1 parameter to test-create-repo" + BUG "not 1 parameter to test-create-repo" repo="$1" mkdir -p "$repo" ( cd "$repo" || error "Cannot setup test environment" - "$GIT_EXEC_PATH/git-init" "--template=$GIT_BUILD_DIR/templates/blt/" >&3 2>&4 || + "${GIT_TEST_INSTALLED:-$GIT_EXEC_PATH}/git$X" init \ + "--template=$GIT_BUILD_DIR/templates/blt/" >&3 2>&4 || error "cannot run git init -- have you built things yet?" mv .git/hooks .git/hooks-disabled ) || exit @@ -1155,3 +1194,111 @@ depacketize () { } ' } + +# Set the hash algorithm in use to $1. Only useful when testing the testsuite. +test_set_hash () { + test_hash_algo="$1" +} + +# Detect the hash algorithm in use. +test_detect_hash () { + # Currently we only support SHA-1, but in the future this function will + # actually detect the algorithm in use. + test_hash_algo='sha1' +} + +# Load common hash metadata and common placeholder object IDs for use with +# test_oid. +test_oid_init () { + test -n "$test_hash_algo" || test_detect_hash && + test_oid_cache <"$TEST_DIRECTORY/oid-info/hash-info" && + test_oid_cache <"$TEST_DIRECTORY/oid-info/oid" +} + +# Load key-value pairs from stdin suitable for use with test_oid. Blank lines +# and lines starting with "#" are ignored. Keys must be shell identifier +# characters. +# +# Examples: +# rawsz sha1:20 +# rawsz sha256:32 +test_oid_cache () { + local tag rest k v && + + { test -n "$test_hash_algo" || test_detect_hash; } && + while read tag rest + do + case $tag in + \#*) + continue;; + ?*) + # non-empty + ;; + *) + # blank line + continue;; + esac && + + k="${rest%:*}" && + v="${rest#*:}" && + + if ! expr "$k" : '[a-z0-9][a-z0-9]*$' >/dev/null + then + BUG 'bad hash algorithm' + fi && + eval "test_oid_${k}_$tag=\"\$v\"" + done +} + +# Look up a per-hash value based on a key ($1). The value must have been loaded +# by test_oid_init or test_oid_cache. +test_oid () { + local var="test_oid_${test_hash_algo}_$1" && + + # If the variable is unset, we must be missing an entry for this + # key-hash pair, so exit with an error. + if eval "test -z \"\${$var+set}\"" + then + BUG "undefined key '$1'" + fi && + eval "printf '%s' \"\${$var}\"" +} + +# Choose a port number based on the test script's number and store it in +# the given variable name, unless that variable already contains a number. +test_set_port () { + local var=$1 port + + if test $# -ne 1 || test -z "$var" + then + BUG "test_set_port requires a variable name" + fi + + eval port=\$$var + case "$port" in + "") + # No port is set in the given env var, use the test + # number as port number instead. + # Remove not only the leading 't', but all leading zeros + # as well, so the arithmetic below won't (mis)interpret + # a test number like '0123' as an octal value. + port=${this_test#${this_test%%[1-9]*}} + if test "${port:-0}" -lt 1024 + then + # root-only port, use a larger one instead. + port=$(($port + 10000)) + fi + ;; + *[^0-9]*|0*) + error >&7 "invalid port number: $port" + ;; + *) + # The user has specified the port. + ;; + esac + + # Make sure that parallel '--stress' test jobs get different + # ports. + port=$(($port + ${GIT_TEST_STRESS_JOB_NR:-0})) + eval $var=$port +} diff --git a/t/test-lib.sh b/t/test-lib.sh index 1373fd4592..42b1a0aa7f 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -49,31 +49,249 @@ export ASAN_OPTIONS : ${LSAN_OPTIONS=abort_on_error=1} export LSAN_OPTIONS +if test ! -f "$GIT_BUILD_DIR"/GIT-BUILD-OPTIONS +then + echo >&2 'error: GIT-BUILD-OPTIONS missing (has Git been built?).' + exit 1 +fi +. "$GIT_BUILD_DIR"/GIT-BUILD-OPTIONS +export PERL_PATH SHELL_PATH + ################################################################ # It appears that people try to run tests without building... -"$GIT_BUILD_DIR/git" >/dev/null +"${GIT_TEST_INSTALLED:-$GIT_BUILD_DIR}/git$X" >/dev/null if test $? != 1 then - echo >&2 'error: you do not seem to have built git yet.' + if test -n "$GIT_TEST_INSTALLED" + then + echo >&2 "error: there is no working Git at '$GIT_TEST_INSTALLED'" + else + echo >&2 'error: you do not seem to have built git yet.' + fi exit 1 fi -. "$GIT_BUILD_DIR"/GIT-BUILD-OPTIONS -export PERL_PATH SHELL_PATH +# Parse options while taking care to leave $@ intact, so we will still +# have all the original command line options when executing the test +# script again for '--tee' and '--verbose-log' below. +store_arg_to= +prev_opt= +for opt +do + if test -n "$store_arg_to" + then + eval $store_arg_to=\$opt + store_arg_to= + prev_opt= + continue + fi + + case "$opt" in + -d|--d|--de|--deb|--debu|--debug) + debug=t ;; + -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate) + immediate=t ;; + -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests) + GIT_TEST_LONG=t; export GIT_TEST_LONG ;; + -r) + store_arg_to=run_list + ;; + --run=*) + run_list=${opt#--*=} ;; + -h|--h|--he|--hel|--help) + help=t ;; + -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) + verbose=t ;; + --verbose-only=*) + verbose_only=${opt#--*=} + ;; + -q|--q|--qu|--qui|--quie|--quiet) + # Ignore --quiet under a TAP::Harness. Saying how many tests + # passed without the ok/not ok details is always an error. + test -z "$HARNESS_ACTIVE" && quiet=t ;; + --with-dashes) + with_dashes=t ;; + --no-bin-wrappers) + no_bin_wrappers=t ;; + --no-color) + color= ;; + --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) + valgrind=memcheck + tee=t + ;; + --valgrind=*) + valgrind=${opt#--*=} + tee=t + ;; + --valgrind-only=*) + valgrind_only=${opt#--*=} + tee=t + ;; + --tee) + tee=t ;; + --root=*) + root=${opt#--*=} ;; + --chain-lint) + GIT_TEST_CHAIN_LINT=1 ;; + --no-chain-lint) + GIT_TEST_CHAIN_LINT=0 ;; + -x) + trace=t ;; + -V|--verbose-log) + verbose_log=t + tee=t + ;; + --write-junit-xml) + write_junit_xml=t + ;; + --stress) + stress=t ;; + --stress=*) + stress=${opt#--*=} + case "$stress" in + *[^0-9]*|0*|"") + echo "error: --stress=<N> requires the number of jobs to run" >&2 + exit 1 + ;; + *) # Good. + ;; + esac + ;; + *) + echo "error: unknown test option '$opt'" >&2; exit 1 ;; + esac + + prev_opt=$opt +done +if test -n "$store_arg_to" +then + echo "error: $prev_opt requires an argument" >&2 + exit 1 +fi + +if test -n "$valgrind_only" +then + test -z "$valgrind" && valgrind=memcheck + test -z "$verbose" && verbose_only="$valgrind_only" +elif test -n "$valgrind" +then + test -z "$verbose_log" && verbose=t +fi + +if test -n "$stress" +then + verbose=t + trace=t + immediate=t +fi + +TEST_STRESS_JOB_SFX="${GIT_TEST_STRESS_JOB_NR:+.stress-$GIT_TEST_STRESS_JOB_NR}" +TEST_NAME="$(basename "$0" .sh)" +TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/test-results" +TEST_RESULTS_BASE="$TEST_RESULTS_DIR/$TEST_NAME$TEST_STRESS_JOB_SFX" +TRASH_DIRECTORY="trash directory.$TEST_NAME$TEST_STRESS_JOB_SFX" +test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY" +case "$TRASH_DIRECTORY" in +/*) ;; # absolute path is good + *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;; +esac + +# If --stress was passed, run this test repeatedly in several parallel loops. +if test "$GIT_TEST_STRESS_STARTED" = "done" +then + : # Don't stress test again. +elif test -n "$stress" +then + if test "$stress" != t + then + job_count=$stress + elif test -n "$GIT_TEST_STRESS_LOAD" + then + job_count="$GIT_TEST_STRESS_LOAD" + elif job_count=$(getconf _NPROCESSORS_ONLN 2>/dev/null) && + test -n "$job_count" + then + job_count=$((2 * $job_count)) + else + job_count=8 + fi + + mkdir -p "$TEST_RESULTS_DIR" + stressfail="$TEST_RESULTS_BASE.stress-failed" + rm -f "$stressfail" + + stress_exit=0 + trap ' + kill $job_pids 2>/dev/null + wait + stress_exit=1 + ' TERM INT HUP + + job_pids= + job_nr=0 + while test $job_nr -lt "$job_count" + do + ( + GIT_TEST_STRESS_STARTED=done + GIT_TEST_STRESS_JOB_NR=$job_nr + export GIT_TEST_STRESS_STARTED GIT_TEST_STRESS_JOB_NR + + trap ' + kill $test_pid 2>/dev/null + wait + exit 1 + ' TERM INT + + cnt=0 + while ! test -e "$stressfail" + do + $TEST_SHELL_PATH "$0" "$@" >"$TEST_RESULTS_BASE.stress-$job_nr.out" 2>&1 & + test_pid=$! + + if wait $test_pid + then + printf "OK %2d.%d\n" $GIT_TEST_STRESS_JOB_NR $cnt + else + echo $GIT_TEST_STRESS_JOB_NR >>"$stressfail" + printf "FAIL %2d.%d\n" $GIT_TEST_STRESS_JOB_NR $cnt + fi + cnt=$(($cnt + 1)) + done + ) & + job_pids="$job_pids $!" + job_nr=$(($job_nr + 1)) + done + + wait + + if test -f "$stressfail" + then + echo "Log(s) of failed test run(s):" + for failed_job_nr in $(sort -n "$stressfail") + do + echo "Contents of '$TEST_RESULTS_BASE.stress-$failed_job_nr.out':" + cat "$TEST_RESULTS_BASE.stress-$failed_job_nr.out" + done + rm -rf "$TRASH_DIRECTORY.stress-failed" + # Move the last one. + mv "$TRASH_DIRECTORY.stress-$failed_job_nr" "$TRASH_DIRECTORY.stress-failed" + fi + + exit $stress_exit +fi # if --tee was passed, write the output not only to the terminal, but # additionally to the file test-results/$BASENAME.out, too. -case "$GIT_TEST_TEE_STARTED, $* " in -done,*) - # do not redirect again - ;; -*' --tee '*|*' --va'*|*' -V '*|*' --verbose-log '*) - mkdir -p "$TEST_OUTPUT_DIRECTORY/test-results" - BASE="$TEST_OUTPUT_DIRECTORY/test-results/$(basename "$0" .sh)" +if test "$GIT_TEST_TEE_STARTED" = "done" +then + : # do not redirect again +elif test -n "$tee" +then + mkdir -p "$TEST_RESULTS_DIR" # Make this filename available to the sub-process in case it is using # --verbose-log. - GIT_TEST_TEE_OUTPUT_FILE=$BASE.out + GIT_TEST_TEE_OUTPUT_FILE=$TEST_RESULTS_BASE.out export GIT_TEST_TEE_OUTPUT_FILE # Truncate before calling "tee -a" to get rid of the results @@ -81,11 +299,38 @@ done,*) >"$GIT_TEST_TEE_OUTPUT_FILE" (GIT_TEST_TEE_STARTED=done ${TEST_SHELL_PATH} "$0" "$@" 2>&1; - echo $? >"$BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE" - test "$(cat "$BASE.exit")" = 0 + echo $? >"$TEST_RESULTS_BASE.exit") | tee -a "$GIT_TEST_TEE_OUTPUT_FILE" + test "$(cat "$TEST_RESULTS_BASE.exit")" = 0 exit - ;; -esac +fi + +if test -n "$trace" && test -n "$test_untraceable" +then + # '-x' tracing requested, but this test script can't be reliably + # traced, unless it is run with a Bash version supporting + # BASH_XTRACEFD (introduced in Bash v4.1). + # + # Perform this version check _after_ the test script was + # potentially re-executed with $TEST_SHELL_PATH for '--tee' or + # '--verbose-log', so the right shell is checked and the + # warning is issued only once. + if test -n "$BASH_VERSION" && eval ' + test ${BASH_VERSINFO[0]} -gt 4 || { + test ${BASH_VERSINFO[0]} -eq 4 && + test ${BASH_VERSINFO[1]} -ge 1 + } + ' + then + : Executed by a Bash version supporting BASH_XTRACEFD. Good. + else + echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" + trace= + fi +fi +if test -n "$trace" && test -z "$verbose_log" +then + verbose=t +fi # For repeatability, reset the environment to known value. # TERM is sanitized below, after saving color control sequences. @@ -95,6 +340,16 @@ PAGER=cat TZ=UTC export LANG LC_ALL PAGER TZ EDITOR=: + +# GIT_TEST_GETTEXT_POISON should not influence git commands executed +# during initialization of test-lib and the test repo. Back it up, +# unset and then restore after initialization is finished. +if test -n "$GIT_TEST_GETTEXT_POISON" +then + GIT_TEST_GETTEXT_POISON_ORIG=$GIT_TEST_GETTEXT_POISON + unset GIT_TEST_GETTEXT_POISON +fi + # A call to "unset" with no arguments causes at least Solaris 10 # /usr/xpg4/bin/sh and /bin/ksh to bail out. So keep the unsets # deriving from the command substitution clustered with the other @@ -134,15 +389,46 @@ export EDITOR GIT_TRACE_BARE=1 export GIT_TRACE_BARE -if test -n "${TEST_GIT_INDEX_VERSION:+isset}" +check_var_migration () { + # the warnings and hints given from this helper depends + # on end-user settings, which will disrupt the self-test + # done on the test framework itself. + case "$GIT_TEST_FRAMEWORK_SELFTEST" in + t) return ;; + esac + + old_name=$1 new_name=$2 + eval "old_isset=\${${old_name}:+isset}" + eval "new_isset=\${${new_name}:+isset}" + + case "$old_isset,$new_isset" in + isset,) + echo >&2 "warning: $old_name is now $new_name" + echo >&2 "hint: set $new_name too during the transition period" + eval "$new_name=\$$old_name" + ;; + isset,isset) + # do this later + # echo >&2 "warning: $old_name is now $new_name" + # echo >&2 "hint: remove $old_name" + ;; + esac +} + +check_var_migration GIT_FSMONITOR_TEST GIT_TEST_FSMONITOR +check_var_migration TEST_GIT_INDEX_VERSION GIT_TEST_INDEX_VERSION +check_var_migration GIT_FORCE_PRELOAD_TEST GIT_TEST_PRELOAD_INDEX + +# Use specific version of the index file format +if test -n "${GIT_TEST_INDEX_VERSION:+isset}" then - GIT_INDEX_VERSION="$TEST_GIT_INDEX_VERSION" + GIT_INDEX_VERSION="$GIT_TEST_INDEX_VERSION" export GIT_INDEX_VERSION fi # Add libc MALLOC and MALLOC_PERTURB test # only if we are not executing the test with valgrind -if expr " $GIT_TEST_OPTS " : ".* --valgrind " >/dev/null || +if test -n "$valgrind" || test -n "$TEST_NO_MALLOC_CHECK" then setup_malloc_check () { @@ -213,100 +499,6 @@ test "x$TERM" != "xdumb" && ( ) && color=t -while test "$#" -ne 0 -do - case "$1" in - -d|--d|--de|--deb|--debu|--debug) - debug=t; shift ;; - -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate) - immediate=t; shift ;; - -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests) - GIT_TEST_LONG=t; export GIT_TEST_LONG; shift ;; - -r) - shift; test "$#" -ne 0 || { - echo 'error: -r requires an argument' >&2; - exit 1; - } - run_list=$1; shift ;; - --run=*) - run_list=${1#--*=}; shift ;; - -h|--h|--he|--hel|--help) - help=t; shift ;; - -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) - verbose=t; shift ;; - --verbose-only=*) - verbose_only=${1#--*=} - shift ;; - -q|--q|--qu|--qui|--quie|--quiet) - # Ignore --quiet under a TAP::Harness. Saying how many tests - # passed without the ok/not ok details is always an error. - test -z "$HARNESS_ACTIVE" && quiet=t; shift ;; - --with-dashes) - with_dashes=t; shift ;; - --no-color) - color=; shift ;; - --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) - valgrind=memcheck - shift ;; - --valgrind=*) - valgrind=${1#--*=} - shift ;; - --valgrind-only=*) - valgrind_only=${1#--*=} - shift ;; - --tee) - shift ;; # was handled already - --root=*) - root=${1#--*=} - shift ;; - --chain-lint) - GIT_TEST_CHAIN_LINT=1 - shift ;; - --no-chain-lint) - GIT_TEST_CHAIN_LINT=0 - shift ;; - -x) - # 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 ;; - -V|--verbose-log) - verbose_log=t - shift ;; - *) - echo "error: unknown test option '$1'" >&2; exit 1 ;; - esac -done - -if test -n "$valgrind_only" -then - test -z "$valgrind" && valgrind=memcheck - test -z "$verbose" && verbose_only="$valgrind_only" -elif test -n "$valgrind" -then - test -z "$verbose_log" && verbose=t -fi - -if test -n "$trace" && test -z "$verbose_log" -then - verbose=t -fi - if test -n "$color" then # Save the color control sequences now rather than run tput @@ -351,6 +543,10 @@ error () { exit 1 } +BUG () { + error >&7 "bug in the test script: $*" +} + say () { say_color info "$*" } @@ -421,7 +617,7 @@ die () { GIT_EXIT_OK= trap 'die' EXIT -trap 'exit $?' INT +trap 'exit $?' INT TERM HUP # The user-facing functions are loaded from a separate file so that # test_perf subshells can have them too @@ -431,11 +627,35 @@ trap 'exit $?' INT # the test_expect_* functions instead. test_ok_ () { + if test -n "$write_junit_xml" + then + write_junit_xml_testcase "$*" + fi test_success=$(($test_success + 1)) say_color "" "ok $test_count - $@" } test_failure_ () { + if test -n "$write_junit_xml" + then + junit_insert="<failure message=\"not ok $test_count -" + junit_insert="$junit_insert $(xml_attr_encode "$1")\">" + junit_insert="$junit_insert $(xml_attr_encode \ + "$(if test -n "$GIT_TEST_TEE_OUTPUT_FILE" + then + test-tool path-utils skip-n-bytes \ + "$GIT_TEST_TEE_OUTPUT_FILE" $GIT_TEST_TEE_OFFSET + else + printf '%s\n' "$@" | sed 1d + fi)")" + junit_insert="$junit_insert</failure>" + if test -n "$GIT_TEST_TEE_OUTPUT_FILE" + then + junit_insert="$junit_insert<system-err>$(xml_attr_encode \ + "$(cat "$GIT_TEST_TEE_OUTPUT_FILE")")</system-err>" + fi + write_junit_xml_testcase "$1" " $junit_insert" + fi test_failure=$(($test_failure + 1)) say_color error "not ok $test_count - $1" shift @@ -444,11 +664,19 @@ test_failure_ () { } test_known_broken_ok_ () { + if test -n "$write_junit_xml" + then + write_junit_xml_testcase "$* (breakage fixed)" + fi test_fixed=$(($test_fixed+1)) say_color error "ok $test_count - $@ # TODO known breakage vanished" } test_known_broken_failure_ () { + if test -n "$write_junit_xml" + then + write_junit_xml_testcase "$* (known breakage)" + fi test_broken=$(($test_broken+1)) say_color warn "not ok $test_count - $@ # TODO known breakage" } @@ -678,7 +906,7 @@ test_run_ () { if $(printf '%s\n' "$1" | sed -f "$GIT_BUILD_DIR/t/chainlint.sed" | grep -q '?![A-Z][A-Z]*?!') || 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" + BUG "broken &&-chain or run-away HERE-DOC: $1" fi trace=$trace_tmp fi @@ -706,12 +934,21 @@ test_start_ () { test_count=$(($test_count+1)) maybe_setup_verbose maybe_setup_valgrind + if test -n "$write_junit_xml" + then + junit_start=$(test-tool date getnanos) + fi } test_finish_ () { echo >&3 "" maybe_teardown_valgrind maybe_teardown_verbose + if test -n "$GIT_TEST_TEE_OFFSET" + then + GIT_TEST_TEE_OFFSET=$(test-tool path-utils file-size \ + "$GIT_TEST_TEE_OUTPUT_FILE") + fi } test_skip () { @@ -743,6 +980,13 @@ test_skip () { case "$to_skip" in t) + if test -n "$write_junit_xml" + then + message="$(xml_attr_encode "$skipped_reason")" + write_junit_xml_testcase "$1" \ + " <skipped message=\"$message\" />" + fi + say_color skip >&3 "skipping test: $@" say_color skip "ok $test_count # skip $1 ($skipped_reason)" : true @@ -758,17 +1002,56 @@ test_at_end_hook_ () { : } +write_junit_xml () { + case "$1" in + --truncate) + >"$junit_xml_path" + junit_have_testcase= + shift + ;; + esac + printf '%s\n' "$@" >>"$junit_xml_path" +} + +xml_attr_encode () { + printf '%s\n' "$@" | test-tool xml-encode +} + +write_junit_xml_testcase () { + junit_attrs="name=\"$(xml_attr_encode "$this_test.$test_count $1")\"" + shift + junit_attrs="$junit_attrs classname=\"$this_test\"" + junit_attrs="$junit_attrs time=\"$(test-tool \ + date getnanos $junit_start)\"" + write_junit_xml "$(printf '%s\n' \ + " <testcase $junit_attrs>" "$@" " </testcase>")" + junit_have_testcase=t +} + test_done () { GIT_EXIT_OK=t + if test -n "$write_junit_xml" && test -n "$junit_xml_path" + then + test -n "$junit_have_testcase" || { + junit_start=$(test-tool date getnanos) + write_junit_xml_testcase "all tests skipped" + } + + # adjust the overall time + junit_time=$(test-tool date getnanos $junit_suite_start) + sed "s/<testsuite [^>]*/& time=\"$junit_time\"/" \ + <"$junit_xml_path" >"$junit_xml_path.new" + mv "$junit_xml_path.new" "$junit_xml_path" + + write_junit_xml " </testsuite>" "</testsuites>" + fi + if test -z "$HARNESS_ACTIVE" then - test_results_dir="$TEST_OUTPUT_DIRECTORY/test-results" - mkdir -p "$test_results_dir" - base=${0##*/} - test_results_path="$test_results_dir/${base%.sh}.counts" + mkdir -p "$TEST_RESULTS_DIR" - cat >"$test_results_path" <<-EOF + cat >"$TEST_RESULTS_BASE.counts" <<-EOF total $test_count success $test_success fixed $test_fixed @@ -823,7 +1106,11 @@ test_done () { error "Tests passed but trash directory already removed before test cleanup; aborting" cd "$TRASH_DIRECTORY/.." && - rm -fr "$TRASH_DIRECTORY" || + rm -fr "$TRASH_DIRECTORY" || { + # try again in a bit + sleep 5; + rm -fr "$TRASH_DIRECTORY" + } || error "Tests passed but test cleanup failed; aborting" fi test_at_end_hook_ @@ -926,23 +1213,28 @@ elif test -n "$GIT_TEST_INSTALLED" then GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path) || error "Cannot run git from $GIT_TEST_INSTALLED." - PATH=$GIT_TEST_INSTALLED:$GIT_BUILD_DIR:$PATH + PATH=$GIT_TEST_INSTALLED:$GIT_BUILD_DIR/t/helper:$PATH GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH} else # normal case, use ../bin-wrappers only unless $with_dashes: - git_bin_dir="$GIT_BUILD_DIR/bin-wrappers" - if ! test -x "$git_bin_dir/git" + if test -n "$no_bin_wrappers" then - if test -z "$with_dashes" + with_dashes=t + else + git_bin_dir="$GIT_BUILD_DIR/bin-wrappers" + if ! test -x "$git_bin_dir/git" then - say "$git_bin_dir/git is not executable; using GIT_EXEC_PATH" + if test -z "$with_dashes" + then + say "$git_bin_dir/git is not executable; using GIT_EXEC_PATH" + fi + with_dashes=t fi - with_dashes=t + PATH="$git_bin_dir:$PATH" fi - PATH="$git_bin_dir:$PATH" GIT_EXEC_PATH=$GIT_BUILD_DIR if test -n "$with_dashes" then - PATH="$GIT_BUILD_DIR:$PATH" + PATH="$GIT_BUILD_DIR:$GIT_BUILD_DIR/t/helper:$PATH" fi fi GIT_TEMPLATE_DIR="$GIT_BUILD_DIR"/templates/blt @@ -966,7 +1258,7 @@ 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-tool +if ! test -x "$GIT_BUILD_DIR"/t/helper/test-tool$X then echo >&2 'You need to build test-tool:' echo >&2 'Run "make t/helper/test-tool" in the source (toplevel) directory' @@ -974,12 +1266,6 @@ then fi # Test repository -TRASH_DIRECTORY="trash directory.$(basename "$0" .sh)" -test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY" -case "$TRASH_DIRECTORY" in -/*) ;; # absolute path is good - *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;; -esac rm -fr "$TRASH_DIRECTORY" || { GIT_EXIT_OK=t echo >&5 "FATAL: Cannot prepare test area" @@ -996,6 +1282,7 @@ then else mkdir -p "$TRASH_DIRECTORY" fi + # Use -P to resolve symlinks in our working directory so that the cwd # in subprocesses like git equals our $PWD (for pathname comparisons). cd -P "$TRASH_DIRECTORY" || exit 1 @@ -1009,6 +1296,23 @@ then test_done fi +if test -n "$write_junit_xml" +then + junit_xml_dir="$TEST_OUTPUT_DIRECTORY/out" + mkdir -p "$junit_xml_dir" + junit_xml_base=${0##*/} + junit_xml_path="$junit_xml_dir/TEST-${junit_xml_base%.sh}.xml" + junit_attrs="name=\"${junit_xml_base%.sh}\"" + junit_attrs="$junit_attrs timestamp=\"$(TZ=UTC \ + date +%Y-%m-%dT%H:%M:%S)\"" + write_junit_xml --truncate "<testsuites>" " <testsuite $junit_attrs>" + junit_suite_start=$(test-tool date getnanos) + if test -n "$GIT_TEST_TEE_OUTPUT_FILE" + then + GIT_TEST_TEE_OFFSET=0 + fi +fi + # Provide an implementation of the 'yes' utility yes () { if test $# = 0 @@ -1073,16 +1377,24 @@ test -n "$USE_LIBPCRE1" && test_set_prereq LIBPCRE1 test -n "$USE_LIBPCRE2" && test_set_prereq LIBPCRE2 test -z "$NO_GETTEXT" && test_set_prereq GETTEXT +if test -n "$GIT_TEST_GETTEXT_POISON_ORIG" +then + GIT_TEST_GETTEXT_POISON=$GIT_TEST_GETTEXT_POISON_ORIG + unset GIT_TEST_GETTEXT_POISON_ORIG +fi + # Can we rely on git's output in the C locale? -if test -n "$GETTEXT_POISON" +if test -z "$GIT_TEST_GETTEXT_POISON" then - GIT_GETTEXT_POISON=YesPlease - export GIT_GETTEXT_POISON - test_set_prereq GETTEXT_POISON -else test_set_prereq C_LOCALE_OUTPUT fi +if test -z "$GIT_TEST_CHECK_CACHE_TREE" +then + GIT_TEST_CHECK_CACHE_TREE=true + export GIT_TEST_CHECK_CACHE_TREE +fi + test_lazy_prereq PIPE ' # test whether the filesystem supports FIFOs test_have_prereq !MINGW,!CYGWIN && @@ -1172,7 +1484,7 @@ test_lazy_prereq SANITY ' chmod -w SANETESTD.1 && chmod -r SANETESTD.1/x && chmod -rx SANETESTD.2 || - error "bug in test sript: cannot prepare SANETESTD" + BUG "cannot prepare SANETESTD" ! test -r SANETESTD.1/x && ! rm SANETESTD.1/x && ! test -f SANETESTD.2/x @@ -1180,7 +1492,7 @@ test_lazy_prereq SANITY ' chmod +rwx SANETESTD.1 SANETESTD.2 && rm -rf SANETESTD.1 SANETESTD.2 || - error "bug in test sript: cannot clean SANETESTD" + BUG "cannot clean SANETESTD" return $status ' @@ -1231,3 +1543,7 @@ test_lazy_prereq CURL ' test_lazy_prereq SHA1 ' test $(git hash-object /dev/null) = e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 ' + +test_lazy_prereq REBASE_P ' + test -z "$GIT_TEST_SKIP_REBASE_P" +' |